You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/01/18 17:03:42 UTC

git commit: ISIS-304: fixing bulk actions, introduced new @Bulk annotation.

Updated Branches:
  refs/heads/master b1aa11f86 -> 38f71bf9f


ISIS-304: fixing bulk actions, introduced new @Bulk annotation.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/38f71bf9
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/38f71bf9
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/38f71bf9

Branch: refs/heads/master
Commit: 38f71bf9fd76cd7a3f9da917d7a8c15e3c64a9ea
Parents: b1aa11f
Author: Dan Haywood <da...@apache.org>
Authored: Fri Jan 18 15:54:52 2013 +0000
Committer: Dan Haywood <da...@apache.org>
Committed: Fri Jan 18 16:02:52 2013 +0000

----------------------------------------------------------------------
 .../ajaxtable/BulkActionsLinkFactory.java          |   85 +++++++
 .../ajaxtable/BulkCollectionsLinkFactory.java      |   66 ------
 .../CollectionContentsAsAjaxTablePanel.css         |   13 +-
 .../CollectionContentsAsAjaxTablePanel.java        |   74 ++++---
 .../components/entity/EntityActionLinkFactory.java |    1 -
 .../components/widgets/cssmenu/CssMenuBuilder.java |   75 ++++---
 .../ui/components/widgets/cssmenu/CssMenuItem.java |   33 ++-
 .../ui/components/widgets/cssmenu/CssMenuPanel.css |   13 +-
 core/applib/src/docbkx/guide/isis-applib.xml       |  174 ++++++++++-----
 .../org/apache/isis/applib/annotation/Bulk.java    |   50 ++++
 .../isis/applib/annotation/NotInServiceMenu.java   |    4 +-
 .../spec/feature/ObjectActionContainer.java        |    1 -
 .../spec/feature/ObjectActionFilters.java          |   16 +-
 .../specloader/ObjectReflectorDefault.java         |    2 +-
 .../isis/core/progmodel/app/IsisMetaModel.java     |    7 +-
 .../progmodel/facets/actions/bulk/BulkFacet.java   |   34 +++
 .../facets/actions/bulk/BulkFacetAbstract.java     |   36 +++
 .../annotation/BulkAnnotationFacetFactory.java     |   49 ++++
 .../bulk/annotation/BulkFacetAnnotation.java       |   31 +++
 .../method/NotInServiceMenuMethodFacetFactory.java |   15 +-
 .../dflt/ProgrammingModelFacetsJava5.java          |    2 +
 .../dom/src/main/java/dom/todo/ToDoItems.java      |    2 +-
 .../dom/src/main/java/dom/todo/ToDoItem.java       |   16 +-
 .../dom/src/main/java/dom/todo/ToDoItems.java      |    3 +-
 24 files changed, 578 insertions(+), 224 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
new file mode 100644
index 0000000..34ca6ca
--- /dev/null
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
@@ -0,0 +1,85 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
+import org.apache.wicket.markup.html.link.AbstractLink;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
+import org.apache.isis.viewer.wicket.model.mementos.ActionMemento;
+import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+import org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu.CssMenuLinkFactory;
+
+final class BulkActionsLinkFactory implements CssMenuLinkFactory {
+    private static final long serialVersionUID = 1L;
+    private final EntityCollectionModel model;
+    private final DataTable<ObjectAdapter,String> dataTable;
+
+    BulkActionsLinkFactory(EntityCollectionModel model, DataTable<ObjectAdapter,String> dataTable) {
+        this.model = model;
+        this.dataTable = dataTable;
+    }
+
+    @Override
+    public LinkAndLabel newLink(final ObjectAdapterMemento serviceAdapterMemento, final ObjectAction objectAction, final String linkId) {
+        final ActionMemento actionMemento = new ActionMemento(objectAction);
+        AbstractLink link = new AjaxLink<Void>(linkId) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void onClick(AjaxRequestTarget target) {
+                final ObjectAction objectAction = actionMemento.getAction();
+                
+                for(ObjectAdapterMemento entityAdapterMemento: model.getToggleMementosList()) {
+                    final ObjectAdapter entityAdapter = entityAdapterMemento.getObjectAdapter(ConcurrencyChecking.CHECK);
+
+                    int numParameters = objectAction.getParameterCount();
+                    if(objectAction.isContributed()) {
+                        // a contributed action
+                        if(numParameters != 1) {
+                            return;
+                        }
+                        if(serviceAdapterMemento == null) {
+                            // not expected
+                            return;
+                        }
+                        final ObjectAdapter serviceAdapter = serviceAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK);
+                        objectAction.execute(serviceAdapter, new ObjectAdapter[]{entityAdapter});
+                    } else {
+                        // an entity action
+                        if(numParameters != 0) {
+                            return;
+                        }
+                        objectAction.execute(entityAdapter, new ObjectAdapter[]{});
+                    }                        
+                }
+                model.clearToggleMementosList();
+                target.add(dataTable);
+            }
+        };
+        return new LinkAndLabel(link, objectAction.getName());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkCollectionsLinkFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkCollectionsLinkFactory.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkCollectionsLinkFactory.java
deleted file mode 100644
index 93ac411..0000000
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkCollectionsLinkFactory.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable;
-
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.markup.html.AjaxLink;
-import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
-import org.apache.wicket.markup.html.link.AbstractLink;
-
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
-import org.apache.isis.viewer.wicket.model.mementos.ActionMemento;
-import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
-import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
-import org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu.CssMenuLinkFactory;
-
-final class BulkCollectionsLinkFactory implements CssMenuLinkFactory {
-    private static final long serialVersionUID = 1L;
-    private final EntityCollectionModel model;
-    private final DataTable<ObjectAdapter,String> dataTable;
-
-    BulkCollectionsLinkFactory(EntityCollectionModel model, DataTable<ObjectAdapter,String> dataTable) {
-        this.model = model;
-        this.dataTable = dataTable;
-    }
-
-    @Override
-    public LinkAndLabel newLink(final ObjectAdapterMemento contributorAdapterMemento, final ObjectAction objectAction, final String linkId) {
-        final ActionMemento actionMemento = new ActionMemento(objectAction);
-        AbstractLink link = new AjaxLink<Void>(linkId) {
-
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            public void onClick(AjaxRequestTarget target) {
-                final ObjectAction objectAction = actionMemento.getAction();
-                for(ObjectAdapterMemento contributeeAdapterMemento: model.getToggleMementosList()) {
-                    final ObjectAdapter contributorAdapter = contributorAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK);
-                    final ObjectAdapter contributeeAdapter = contributeeAdapterMemento.getObjectAdapter(ConcurrencyChecking.CHECK);
-                    objectAction.execute(contributorAdapter, new ObjectAdapter[]{contributeeAdapter});
-                }
-                model.clearToggleMementosList();
-                target.add(dataTable);
-            }
-        };
-        return new LinkAndLabel(link, objectAction.getName());
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.css
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.css b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.css
index 40270c5..ece5d60 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.css
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.css
@@ -147,4 +147,15 @@
 .collectionContentsAsAjaxTablePanel table tfoot tr td {
 	padding:20px 4px 0px;
 	background-color:#FFFFFF !important;
-}
\ No newline at end of file
+}
+
+.collectionContentsAsAjaxTablePanel .actions {
+    float: right;
+    padding:0em 0em;
+}
+
+.collectionContentsAsAjaxTablePanel .entityActions .cssMenuPanel .menuh a:hover
+    {
+    color: #000;
+    background-color:#E3E3D9;
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
index 498a500..b13c2c9 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
@@ -19,8 +19,12 @@
 
 package org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 import org.apache.wicket.Component;
@@ -39,6 +43,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionFilters;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActions;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
 import org.apache.isis.viewer.wicket.model.common.SelectionHandler;
@@ -59,6 +64,8 @@ import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
  */
 public class CollectionContentsAsAjaxTablePanel extends PanelAbstract<EntityCollectionModel> {
 
+    private static final Predicate<ObjectAction> BULK = Filters.asPredicate(ObjectActionFilters.bulk());
+
     private static final long serialVersionUID = 1L;
 
     private static final String ID_TABLE = "table";
@@ -75,57 +82,52 @@ public class CollectionContentsAsAjaxTablePanel extends PanelAbstract<EntityColl
     private void buildGui() {
         final EntityCollectionModel model = getModel();
 
-        buildEntityActionsGui();
-        
         final List<IColumn<ObjectAdapter,String>> columns = Lists.newArrayList();
 
-        addToggleboxColumnIfRequired(columns);
+        List<ObjectAction> bulkActions = determineBulkActions();
+
+        addToggleboxColumnIfRequired(columns, bulkActions);
         addTitleColumn(columns);
         addPropertyColumnsIfRequired(columns);
         addSelectedButtonIfRequired(columns);
 
         final SortableDataProvider<ObjectAdapter,String> dataProvider = new CollectionContentsSortableDataProvider(model);
         dataTable = new MyAjaxFallbackDefaultDataTable<ObjectAdapter,String>(ID_TABLE, columns, dataProvider, model.getPageSize());
-        
+
+        buildEntityActionsGui(bulkActions);
+
         add(dataTable);
     }
     
-    private void addToggleboxColumnIfRequired(final List<IColumn<ObjectAdapter,String>> columns) {
+    private void addToggleboxColumnIfRequired(final List<IColumn<ObjectAdapter,String>> columns, List<ObjectAction> bulkActions) {
         final EntityCollectionModel model = getModel();
-        
-        if(model.isStandalone()) {
-            columns.add(new ObjectAdapterToggleboxColumn(new SelectionHandler() {
-                
-                private static final long serialVersionUID = 1L;
-
-                @Override
-                public void onSelected(final Component context, final ObjectAdapter selectedAdapter) {
-                    model.toggleSelectionOn(selectedAdapter);
-                }
-            }));
+        if(bulkActions.isEmpty() || model.isParented()) {
+            return;
         }
+        
+        columns.add(new ObjectAdapterToggleboxColumn(new SelectionHandler() {
+            
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void onSelected(final Component context, final ObjectAdapter selectedAdapter) {
+                model.toggleSelectionOn(selectedAdapter);
+            }
+        }));
     }
 
-    private void buildEntityActionsGui() {
+    private void buildEntityActionsGui(List<ObjectAction> bulkActions) {
         final EntityCollectionModel model = getModel();
         
-        if(model.isParented()) {
+        if(bulkActions.isEmpty() || model.isParented()) {
             permanentlyHide(ID_ENTITY_ACTIONS);
             return;
         }
         
-        final ObjectSpecification typeSpec = model.getTypeOfSpecification();
-        
-        @SuppressWarnings("unchecked")
-        final List<ObjectAction> userActions = typeSpec.getObjectActions(ActionType.USER, Contributed.INCLUDED, 
-                Filters.and(
-                        ObjectActionFilters.withNoBusinessRules(), ObjectActionFilters.contributedAnd1ParamAndVoid()
-                ));
-
-        if(!userActions.isEmpty()) {
-            final CssMenuLinkFactory linkFactory = new BulkCollectionsLinkFactory(model, dataTable);
+        if(!bulkActions.isEmpty()) {
+            final CssMenuLinkFactory linkFactory = new BulkActionsLinkFactory(model, dataTable);
 
-            final CssMenuBuilder cssMenuBuilder = new CssMenuBuilder(null, getServiceAdapters(), userActions, linkFactory);
+            final CssMenuBuilder cssMenuBuilder = new CssMenuBuilder(null, getServiceAdapters(), bulkActions, linkFactory);
             // TODO: i18n
             final CssMenuPanel cssMenuPanel = cssMenuBuilder.buildPanel(ID_ENTITY_ACTIONS, "Actions");
 
@@ -135,6 +137,20 @@ public class CollectionContentsAsAjaxTablePanel extends PanelAbstract<EntityColl
         }
     }
 
+    private List<ObjectAction> determineBulkActions() {
+        final EntityCollectionModel model = getModel();
+        
+        if(model.isParented()) {
+            return Collections.emptyList();
+        }
+        
+        final ObjectSpecification typeSpec = model.getTypeOfSpecification();
+        
+        List<ObjectAction> objectActions = typeSpec.getObjectActions(ActionType.USER, Contributed.INCLUDED, Filters.<ObjectAction>any());
+        List<ObjectAction> flattenedActions = ObjectActions.flattenedActions(objectActions);
+        return Lists.newArrayList(Iterables.filter(flattenedActions, BULK));
+    }
+
 
     private static void addTitleColumn(final List<IColumn<ObjectAdapter,String>> columns) {
         columns.add(new ObjectAdapterTitleColumn());

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityActionLinkFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityActionLinkFactory.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityActionLinkFactory.java
index 3c51a3e..6f5c4b1 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityActionLinkFactory.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityActionLinkFactory.java
@@ -61,7 +61,6 @@ public final class EntityActionLinkFactory implements CssMenuLinkFactory {
         final ObjectAdapter adapter = adapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK);
 
         final AbstractLink link = createLink(adapterMemento, action, linkId, adapter);
-        final ObjectAdapter contextAdapter = entityModel.getObject();
         final String label = ObjectActions.nameFor(action);
 
         return new LinkAndLabel(link, label);

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
index 5773e0a..534cf74 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
@@ -19,28 +19,25 @@
 
 package org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu;
 
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import com.google.common.collect.Collections2;
-
-import org.apache.wicket.Application;
-
 import org.apache.isis.applib.filter.Filters;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.spec.ActionType;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionFilters;
-import org.apache.isis.core.metamodel.spec.feature.ObjectActions;
+import org.apache.isis.core.progmodel.facets.actions.bulk.BulkFacet;
 import org.apache.isis.core.progmodel.facets.actions.notcontributed.NotContributedFacet;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
 import org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu.CssMenuItem.Builder;
 import org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu.CssMenuPanel.Style;
 
+import com.google.common.collect.Collections2;
+
 /**
  * Used to build a {@link CssMenuItem} hierarchy from a
  * {@link ObjectAdapterMemento object adapter}'s actions and any contributed
@@ -48,19 +45,31 @@ import org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu.CssMenuPanel.
  */
 public class CssMenuBuilder {
 
+    /**
+     * The target to invoke upon; may be null in case of bulk actions; not used if a contributed action. 
+     */
     private final ObjectAdapterMemento adapterMemento;
+    /**
+     * Used to determine wire up against contributed actions.
+     */
     private final List<ObjectAdapter> serviceAdapters;
+    
     private final List<ObjectAction> actions;
 
     private final CssMenuLinkFactory cssMenuLinkFactory;
-
+    
     public CssMenuBuilder(final ObjectAdapterMemento adapterMemento, final List<ObjectAdapter> serviceAdapters, final List<ObjectAction> actions, final CssMenuLinkFactory cssMenuLinkFactory) {
-        this.adapterMemento = adapterMemento;
+        this.adapterMemento = adapterMemento; // may be null
         this.serviceAdapters = serviceAdapters;
         this.actions = actions;
         this.cssMenuLinkFactory = cssMenuLinkFactory;
     }
 
+    public CssMenuBuilder(final List<ObjectAction> actions, final CssMenuLinkFactory cssMenuLinkFactory) {
+        this(null, null, actions, cssMenuLinkFactory);
+    }
+
+
     public CssMenuPanel buildPanel(final String wicketId, final String rootName) {
         final CssMenuItem findUsing = CssMenuItem.newMenuItem(rootName).build();
         addMenuItems(findUsing, actions);
@@ -100,10 +109,6 @@ public class CssMenuBuilder {
         }
     }
 
-    private void addMenuItems(final CssMenuItem parent, final ObjectAction[] actions) {
-        addMenuItems(parent, Arrays.asList(actions));
-    }
-
     private void addMenuItem(final CssMenuItem parent, final ObjectAction action) {
         if (action.getType() == ActionType.SET) {
             addMenuItemForActionSet(parent, action);
@@ -121,21 +126,29 @@ public class CssMenuBuilder {
         }
     }
 
-    private void addMenuItemForAction(final CssMenuItem parent, final ObjectAction contributedAction) {
-
-        // skip if annotated to not be contributed
-        if (contributedAction.getFacet(NotContributedFacet.class) != null) {
+    private void addMenuItemForAction(final CssMenuItem parent, final ObjectAction action) {
+        
+        if (action.getFacet(NotContributedFacet.class) != null) {
+            // skip if is an action that has been annotated to not be contributed
             return;
         }
 
-        final ObjectAdapterMemento serviceAdapterMemento = determineAdapterFor(contributedAction);
-        if(serviceAdapterMemento == null) {
-            return;
+        Builder subMenuItemBuilder = null;
+        
+        final ObjectAdapterMemento targetAdapterMemento = determineAdapterFor(action);
+        if(targetAdapterMemento != null) {
+            // against an entity or a service (if a contributed action)
+            subMenuItemBuilder = parent.newSubMenuItem(targetAdapterMemento, action, cssMenuLinkFactory);
+        } else {
+            if (action.containsDoOpFacet(BulkFacet.class)) {
+                // ignore fact have no target action; 
+                // we expect that the link factory is able to handle this
+                // (ie will iterate through all objects from a list and invoke in bulk)
+                subMenuItemBuilder = parent.newSubMenuItem(action, cssMenuLinkFactory);
+            }
         }
-
-        final Builder subMenuItemBuilder = parent.newSubMenuItem(serviceAdapterMemento, contributedAction, cssMenuLinkFactory);
+        
         if (subMenuItemBuilder != null) {
-            // could be null if invisible
             subMenuItemBuilder.build();
         }
     }
@@ -148,14 +161,20 @@ public class CssMenuBuilder {
      * adapters also.
      */
     private ObjectAdapterMemento determineAdapterFor(final ObjectAction action) {
-        // search through service adapters first
-        final ObjectSpecification onType = action.getOnType();
-        for (final ObjectAdapter serviceAdapter : getServiceAdapters()) {
-            if (serviceAdapter.getSpecification() == onType) {
-                return ObjectAdapterMemento.createOrNull(serviceAdapter);
+        
+        if(getServiceAdapters() != null) {
+            // null check required because could be null.
+            
+            // search through service adapters first
+            final ObjectSpecification onType = action.getOnType();
+            for (final ObjectAdapter serviceAdapter : getServiceAdapters()) {
+                if (serviceAdapter.getSpecification() == onType) {
+                    return ObjectAdapterMemento.createOrNull(serviceAdapter);
+                }
             }
         }
-        // otherwise, specified adapter
+
+        // otherwise, specified adapter (could be null)
         return adapterMemento;
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
index 64f65b6..541c5f9 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
@@ -45,6 +45,7 @@ import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
 import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.progmodel.facets.actions.bulk.BulkFacet;
 import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
@@ -194,30 +195,44 @@ public class CssMenuItem implements Serializable {
     private final Where where = Where.ANYWHERE;
 
     /**
+     * Creates a {@link Builder} for a submenu item invoking an action on the provided
+     * {@link ObjectAdapterMemento target adapter}.
+     * 
      * @return the builder, else <tt>null</tt> if the action is not visible for
      *         the current user.
      */
-    public Builder newSubMenuItem(final ObjectAdapterMemento adapterMemento, final ObjectAction objectAction, final CssMenuLinkFactory cssMenuLinkFactory) {
+    public Builder newSubMenuItem(final ObjectAdapterMemento targetAdapterMemento, final ObjectAction objectAction, final CssMenuLinkFactory cssMenuLinkFactory) {
 
-        final AuthenticationSession session = getAuthenticationSession();
+        final LinkAndLabel linkAndLabel = cssMenuLinkFactory.newLink(targetAdapterMemento, objectAction, PageAbstract.ID_MENU_LINK);
+        final AbstractLink link = linkAndLabel.getLink();
+        final String actionLabel = linkAndLabel.getLabel();
 
-        final CssMenuItem parentMenuItem = this;
+        // check visibility and whether enabled
+        final AuthenticationSession session = getAuthenticationSession();
 
-        final ObjectAdapter adapter = adapterMemento.getObjectAdapter(ConcurrencyChecking.CHECK);
+        final ObjectAdapter adapter = targetAdapterMemento.getObjectAdapter(ConcurrencyChecking.CHECK);
         final Consent visibility = objectAction.isVisible(session, adapter, where);
         if (visibility.isVetoed()) {
             return null;
         }
 
-        final LinkAndLabel linkAndLabel = cssMenuLinkFactory.newLink(adapterMemento, objectAction, PageAbstract.ID_MENU_LINK);
+        final Consent usability = objectAction.isUsable(session, adapter, where);
+        final String reasonDisabledIfAny = usability.getReason();
+
+        return newSubMenuItem(actionLabel).link(link).enabled(reasonDisabledIfAny);
+    }
+
+    /**
+     * Creates a {@link Builder} for a submenu item where the provided {@link CssMenuLinkFactory} is able to provide the target adapter. 
+     */
+    public Builder newSubMenuItem(final ObjectAction objectAction, final CssMenuLinkFactory cssMenuLinkFactory) {
+
+        final LinkAndLabel linkAndLabel = cssMenuLinkFactory.newLink(null, objectAction, PageAbstract.ID_MENU_LINK);
 
         final AbstractLink link = linkAndLabel.getLink();
         final String actionLabel = linkAndLabel.getLabel();
 
-        final Consent usability = objectAction.isUsable(session, adapter, where);
-        final String reasonDisabledIfAny = usability.getReason();
-
-        return parentMenuItem.newSubMenuItem(actionLabel).link(link).enabled(reasonDisabledIfAny);
+        return this.newSubMenuItem(actionLabel).link(link);
     }
 
     // //////////////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.css
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.css b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.css
index b021b4a..94c3a67 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.css
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.css
@@ -139,6 +139,10 @@
 	color: gray;
 }
 
+.cssMenuPanel li.cssSubMenuItem {
+    margin-left:0px;
+}
+
 /******** overrides for grouped actions and entity actions ***************/
 .groupedActions .cssMenuPanel li.cssMenuItemPanel>p.top-parent {
 	display: none;
@@ -220,13 +224,18 @@
 	padding: 0px;
 }
 
-.entityActions .cssMenuPanel .menuh li,
-.entityActions .cssMenuPanel .menuh li p {
+.entityActions .cssMenuPanel .menuh li {
 	float:left;
 	display:block;
 	margin-left:10px;
 }
 
+.entityActions .cssMenuPanel .menuh li p {
+    float:left;
+    display:block;
+    margin-left:0px;
+}
+
 .entityActions .cssMenuPanel .menuh a,
 .entityActions .cssMenuPanel .menuh p {
 	display: inline-block;

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/applib/src/docbkx/guide/isis-applib.xml
----------------------------------------------------------------------
diff --git a/core/applib/src/docbkx/guide/isis-applib.xml b/core/applib/src/docbkx/guide/isis-applib.xml
index e4ddeb1..8d52f72 100644
--- a/core/applib/src/docbkx/guide/isis-applib.xml
+++ b/core/applib/src/docbkx/guide/isis-applib.xml
@@ -240,6 +240,63 @@ public void setPropertyName(PropertyType param)</programlisting></para>
       </sect1>
 
       <sect1>
+        <title condition="java">How to specify a title for a domain
+        entity</title>
+
+        <para>A title is used to identify an object to the user in the user
+        interface. For example, a <classname>Customer</classname>'s title
+        might be the organization's customer reference, or perhaps (more
+        informally) their first and last names.</para>
+
+        <para>By default, the framework will use the object's <literal
+        moreinfo="none">toString()</literal> method as the title. Most titles
+        tend to be made up of the same set of elements: for example a
+        Customer's name might be the concatenation of their customer first
+        name and their ;ast name. For these the <classname>@Title</classname>
+        annotation can be used:</para>
+
+        <para><programlisting>public class Customer {
+  @Title
+  public String getFirstName() { ... }
+  @Title
+  public String getLastName() { ... }
+  ...
+}</programlisting></para>
+
+        <para>For more control, the order of the title components can be
+        specified using a sequence number (specified in Dewey decimal
+        format):</para>
+
+        <programlisting>public class Customer {
+  @Title("1.0")
+  public String getFirstName() { ... }
+  @Title("1.1")
+  public String getLastName() { ... }
+  ...
+}</programlisting>
+
+        <para>For more control the title can be declared imperately using the
+        <literal moreinfo="none">title()</literal> method (returning a
+        <literal moreinfo="none">String</literal>). This leaves the programmer
+        needs to make use of the <literal moreinfo="none">toString()</literal>
+        method for other purposes, such as for debugging. For example, to
+        return the title for a customer which is their last name and then
+        first initial of their first name, we could use:</para>
+
+        <para><programlisting>public class Customer {
+  public String title() {
+    return getLastName() + ", " + getFirstName().substring(0,1);
+  } 
+  ...
+}</programlisting></para>
+
+        <para>The applib contains a class,
+        <classname>org.apache.isis.applib.util.TitleBuffer</classname>, which
+        you can use to help create title strings if you so wish. See <xref
+        linkend="apx.UtilityClasses" /> for more details.</para>
+      </sect1>
+
+      <sect1>
         <title>How to add a collection to a domain entity</title>
 
         <para>A collection is a multi-valued attribute/field of a entity, in
@@ -305,7 +362,8 @@ public class Department {
       </sect1>
 
       <sect1>
-        <title>How to add an action to a domain entity or service</title>
+        <title>How to add an action (or bulk action) to a domain entity or
+        service</title>
 
         <para>An 'action' is a method that we expect the user to be able to
         invoke on a domain entity via the user interface, though it may also
@@ -358,63 +416,36 @@ public class Department {
         are ignored: such functionality should reside in a service, such as a
         repository or factory (see <xref
         linkend="chp.DomainServices" />).</para>
-      </sect1>
 
-      <sect1>
-        <title condition="java">How to specify a title for a domain
-        entity</title>
+        <para>If the action is a bulk action - meaning that it should only be
+        applied to a collection of instances of the entity - then annotate
+        using <classname>@Bulk</classname>:</para>
 
-        <para>A title is used to identify an object to the user in the user
-        interface. For example, a <classname>Customer</classname>'s title
-        might be the organization's customer reference, or perhaps (more
-        informally) their first and last names.</para>
-
-        <para>By default, the framework will use the object's <literal
-        moreinfo="none">toString()</literal> method as the title. Most titles
-        tend to be made up of the same set of elements: for example a
-        Customer's name might be the concatenation of their customer first
-        name and their ;ast name. For these the <classname>@Title</classname>
-        annotation can be used:</para>
+        <programlisting>@Bulk
+public void actionName() { ... }</programlisting>
 
-        <para><programlisting>public class Customer {
-  @Title
-  public String getFirstName() { ... }
-  @Title
-  public String getLastName() { ... }
-  ...
-}</programlisting></para>
+        <para>Note that bulk actions have a couple of important
+        restrictions.</para>
 
-        <para>For more control, the order of the title components can be
-        specified using a sequence number (specified in Dewey decimal
-        format):</para>
+        <itemizedlist>
+          <listitem>
+            <para>entity actions cannot take any arguments, while contributed
+            actions can take only a single parameter (the contributee)</para>
 
-        <programlisting>public class Customer {
-  @Title("1.0")
-  public String getFirstName() { ... }
-  @Title("1.1")
-  public String getLastName() { ... }
-  ...
-}</programlisting>
+            <para>This restriction might be lifted in the future;</para>
+          </listitem>
 
-        <para>For more control the title can be declared imperately using the
-        <literal moreinfo="none">title()</literal> method (returning a
-        <literal moreinfo="none">String</literal>). This leaves the programmer
-        needs to make use of the <literal moreinfo="none">toString()</literal>
-        method for other purposes, such as for debugging. For example, to
-        return the title for a customer which is their last name and then
-        first initial of their first name, we could use:</para>
+          <listitem>
+            <para>any business rules for hiding, disabling or validating the
+            action are ignored.</para>
+          </listitem>
+        </itemizedlist>
 
-        <para><programlisting>public class Customer {
-  public String title() {
-    return getLastName() + ", " + getFirstName().substring(0,1);
-  } 
-  ...
-}</programlisting></para>
+        <para>See <xref linkend="chp.BusinessRules" /> for more details on
+        writing business rules.</para>
 
-        <para>The applib contains a class,
-        <classname>org.apache.isis.applib.util.TitleBuffer</classname>, which
-        you can use to help create title strings if you so wish. See <xref
-        linkend="apx.UtilityClasses" /> for more details.</para>
+        <para>At the time of writing, only the Wicket viewer recognizes bulk
+        actions; other viewers treat the action as a regular action.</para>
       </sect1>
 
       <sect1 id="sec.HowToSpecifyTheIconForAnObjectsClass">
@@ -508,11 +539,9 @@ public class Department {
     ...
 }</programlisting>
 
-        <para></para>
-
-        <para>***</para>
-
-        <para></para>
+        <para>The syntax for the <classname>@MemberOrder</classname> is dewey
+        decimal notation, so "3.5" and "3.6" come between "3" and "4"; "3.5.1"
+        comes between "3.5" and "3.6".</para>
       </sect1>
 
       <sect1>
@@ -844,13 +873,13 @@ persist(newCust);</programlisting>
       </sect1>
     </chapter>
 
-    <chapter id="chp.Properties">
+    <chapter id="chp.BusinessRules">
       <title>How to add business rules</title>
 
       <abstract>
         <para>How-to add business rules to domain entities and services,
         controlling whether a domain entity or service's class members are
-        visible, if they are enabled, and to validate arguments. </para>
+        visible, if they are enabled, and to validate arguments.</para>
       </abstract>
 
       <para>Business rules can be added to domain objects in a number of ways.
@@ -1989,7 +2018,7 @@ public class Customer {
 
       <para>The Isis viewers will automatically render the state of properties
       and collections, but the values of such need not be persisted; they can
-      be derived from other information available to the object. </para>
+      be derived from other information available to the object.</para>
 
       <para>For collections</para>
 
@@ -3144,6 +3173,14 @@ isis.services = employee.EmployeeRepositoryDefault, claim.ClaimRepositoryDefault
           If an action should neither be contributed nor appear in service
           menu items, then simply annotate it as
           <classname>@Hidden</classname>.</para>
+
+          <para>Alternatively, this can be performed using a supporting
+          method:</para>
+
+          <programlisting>public class LibraryImpl implements Library {
+    public Loan borrow(Loanable l, Borrower b) { ... }
+    public boolean notInServiceMenuBorrow() { ... }
+}</programlisting>
         </sect2>
 
         <sect2>
@@ -5282,6 +5319,27 @@ public class County {
         </note>
       </sect1>
 
+      <sect1>
+        <title>@Bulk</title>
+
+        <para>For actions that should only be applied to a collection of
+        objects of the same type, annotate using
+        @<classname>Bulk</classname>.</para>
+
+        <para>For example:</para>
+
+        <programlisting format="linespecific">public class ToDoItem {
+    ...
+    @Bulk
+    public void markAsCompleted() {
+        setCompleted(true);
+    }
+}</programlisting>
+
+        <para>Bulk actions cannot take arguments, nor can they be hidden,
+        disabled or have validation rules.</para>
+      </sect1>
+
       <sect1 id="sec.DebugAnnotation">
         <title>@Debug</title>
 

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/applib/src/main/java/org/apache/isis/applib/annotation/Bulk.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Bulk.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Bulk.java
new file mode 100644
index 0000000..c9af9cd
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Bulk.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.applib.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates the (entity) action should be used only against many objects
+ * in a collection.
+ * 
+ * <p>
+ * Bulk actions have a number of constraints:
+ * <ul>
+ * <li>It must take no arguments
+ * <li>It must return <tt>void</tt>
+ * <li>It cannot be hidden (any annotations or supporting methods to that effect will be
+ *     ignored).
+ * <li>It cannot be disabled (any annotations or supporting methods to that effect will be
+ *     ignored).
+ * </ul>
+ * 
+ * <p>
+ * Has no meaning if annotated on an action of a domain service.
+ */
+@Inherited
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Bulk {
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/applib/src/main/java/org/apache/isis/applib/annotation/NotInServiceMenu.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/NotInServiceMenu.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/NotInServiceMenu.java
index 2bcece5..9797fd1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/NotInServiceMenu.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/NotInServiceMenu.java
@@ -26,7 +26,7 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * Indicates the a (service) action should be not be displayed in the service
+ * Indicates the (service) action should be not be displayed in the service
  * menu.
  * 
  * <p>
@@ -35,7 +35,7 @@ import java.lang.annotation.Target;
  * should not appear in the service menu.
  * 
  * <p>
- * Has no meanings for actions on regular entities.
+ * Has no meaning if annotated on an action of a regular entity.
  */
 @Inherited
 @Target({ ElementType.METHOD })

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionContainer.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionContainer.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionContainer.java
index 1cc82a9..b089c97 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionContainer.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionContainer.java
@@ -25,7 +25,6 @@ import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.filter.Filter;
 import org.apache.isis.core.metamodel.spec.ActionType;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer.Contributed;
 
 public interface ObjectActionContainer {
 

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionFilters.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionFilters.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionFilters.java
index 9501e18..32ef9e9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionFilters.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectActionFilters.java
@@ -32,6 +32,7 @@ import org.apache.isis.core.metamodel.interactions.HidingInteractionAdvisor;
 import org.apache.isis.core.metamodel.interactions.ValidatingInteractionAdvisor;
 import org.apache.isis.core.metamodel.spec.ActionType;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.progmodel.facets.actions.bulk.BulkFacet;
 
 public class ObjectActionFilters {
 
@@ -54,14 +55,12 @@ public class ObjectActionFilters {
         };
     }
 
-    public static Filter<ObjectAction> withNoBusinessRules() {
+    public static Filter<ObjectAction> withNoValidationRules() {
         return new Filter<ObjectAction>(){
             @Override
             public boolean accept(final ObjectAction objectAction) {
-                final List<Facet> hidingFacets = objectAction.getFacets(FacetFilters.isA(HidingInteractionAdvisor.class));
-                final List<Facet> disablingFacets = objectAction.getFacets(FacetFilters.isA(DisablingInteractionAdvisor.class));
                 final List<Facet> validatingFacets = objectAction.getFacets(FacetFilters.isA(ValidatingInteractionAdvisor.class));
-                return hidingFacets.isEmpty() && disablingFacets.isEmpty() && validatingFacets.isEmpty();
+                return validatingFacets.isEmpty();
             }};
     }
 
@@ -85,4 +84,13 @@ public class ObjectActionFilters {
             }
         };
     }
+
+    public static Filter<ObjectAction> bulk() {
+        return new Filter<ObjectAction>(){
+
+            @Override
+            public boolean accept(ObjectAction oa) {
+                return oa.containsDoOpFacet(BulkFacet.class);
+            }};
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
index b2ce2ef..6e6583d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
@@ -231,6 +231,7 @@ public final class ObjectReflectorDefault implements SpecificationLoaderSpi, App
      */
     @Override
     public void init() {
+
         ValidationFailures validationFailures = initAndValidate();
         
         validationFailures.assertNone();
@@ -267,7 +268,6 @@ public final class ObjectReflectorDefault implements SpecificationLoaderSpi, App
         facetProcessor.init();
         metaModelValidator.init();
 
-        // prime cache and validate
         primeCache();
         
         ValidationFailures validationFailures = new ValidationFailures();

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/progmodel/app/IsisMetaModel.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/app/IsisMetaModel.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/app/IsisMetaModel.java
index 2ccaa4c..47ba2f3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/app/IsisMetaModel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/app/IsisMetaModel.java
@@ -180,13 +180,14 @@ public class IsisMetaModel implements ApplicationScopedComponent {
         runtimeContext.injectInto(reflector);
         reflector.injectInto(runtimeContext);
 
-        validationFailures = reflector.initAndValidate();
-        runtimeContext.init();
-
         for (final Object service : services) {
             final ObjectSpecification serviceSpec = reflector.loadSpecification(service.getClass());
             serviceSpec.markAsService();
         }
+
+        validationFailures = reflector.initAndValidate();
+        runtimeContext.init();
+
         state = State.INITIALIZED;
     }
     

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacet.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacet.java
new file mode 100644
index 0000000..fd3d284
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacet.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.progmodel.facets.actions.bulk;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+
+/**
+ * Indicates that the action (entity or service) can also be used as a bulk
+ * action against collections of objects.
+ * 
+ * <p>
+ * In the standard Apache Isis Programming Model, corresponds to annotating the
+ * action method using <tt>@Bulk</tt>.
+ */
+public interface BulkFacet extends Facet {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacetAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacetAbstract.java
new file mode 100644
index 0000000..3cd7452
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/BulkFacetAbstract.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.progmodel.facets.actions.bulk;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.MarkerFacetAbstract;
+
+public abstract class BulkFacetAbstract extends MarkerFacetAbstract implements BulkFacet {
+
+    public static Class<? extends Facet> type() {
+        return BulkFacet.class;
+    }
+
+    public BulkFacetAbstract(final FacetHolder holder) {
+        super(type(), holder);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkAnnotationFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkAnnotationFacetFactory.java
new file mode 100644
index 0000000..906dfba
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkAnnotationFacetFactory.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.progmodel.facets.actions.bulk.annotation;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.applib.annotation.Bulk;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.progmodel.facets.actions.bulk.BulkFacet;
+
+public class BulkAnnotationFacetFactory extends FacetFactoryAbstract {
+
+    public BulkAnnotationFacetFactory() {
+        super(FeatureType.ACTIONS_ONLY);
+    }
+
+    @Override
+    public void process(final ProcessMethodContext processMethodContext) {
+        Method method = processMethodContext.getMethod();
+        final Bulk annotation = Annotations.getAnnotation(method, Bulk.class);
+        FacetUtil.addFacet(create(annotation, processMethodContext.getFacetHolder()));
+    }
+
+    private BulkFacet create(final Bulk annotation, final FacetHolder holder) {
+        return annotation == null ? null : new BulkFacetAnnotation(holder);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkFacetAnnotation.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkFacetAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkFacetAnnotation.java
new file mode 100644
index 0000000..3e4b3ee
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/bulk/annotation/BulkFacetAnnotation.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.progmodel.facets.actions.bulk.annotation;
+
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.progmodel.facets.actions.bulk.BulkFacetAbstract;
+
+public class BulkFacetAnnotation extends BulkFacetAbstract {
+
+    public BulkFacetAnnotation(final FacetHolder holder) {
+        super(holder);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/notinservicemenu/method/NotInServiceMenuMethodFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/notinservicemenu/method/NotInServiceMenuMethodFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/notinservicemenu/method/NotInServiceMenuMethodFacetFactory.java
index 4fcd17d..fdc8cb1 100755
--- a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/notinservicemenu/method/NotInServiceMenuMethodFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/notinservicemenu/method/NotInServiceMenuMethodFacetFactory.java
@@ -41,23 +41,16 @@ public class NotInServiceMenuMethodFacetFactory extends MethodPrefixBasedFacetFa
         final String capitalizedName = NameUtils.javaBaseNameStripAccessorPrefixIfRequired(getMethod.getName());
 
         final Class<?> cls = processMethodContext.getCls();
-        final Method hideMethod = MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, /*
-                                                                                         * MethodPrefixConstants
-                                                                                         * .
-                                                                                         * HIDE_PREFIX
-                                                                                         */
+        final Method notInServiceMenuMethod = MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, 
                 "notInServiceMenu" + capitalizedName, boolean.class, new Class[] {});
-        if (hideMethod == null) {
+        if (notInServiceMenuMethod == null) {
             return;
         }
 
-        processMethodContext.removeMethod(hideMethod);
+        processMethodContext.removeMethod(notInServiceMenuMethod);
 
         final FacetHolder facetedMethod = processMethodContext.getFacetHolder();
-        // FacetUtil.addFacet(new
-        // NotInServiceMenuFacetAnnotation(facetedMethod));
-        FacetUtil.addFacet(new NotInServiceMenuFacetMethod(hideMethod, facetedMethod));
-
+        FacetUtil.addFacet(new NotInServiceMenuFacetMethod(notInServiceMenuMethod, facetedMethod));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
index dd5fded..5bb4242 100644
--- a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
+++ b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
@@ -20,6 +20,7 @@
 package org.apache.isis.progmodels.dflt;
 
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModelAbstract;
+import org.apache.isis.core.progmodel.facets.actions.bulk.annotation.BulkAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.debug.annotation.DebugAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.defaults.method.ActionDefaultsFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.exploration.annotation.ExplorationAnnotationFacetFactory;
@@ -309,6 +310,7 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         addFactory(NotContributedAnnotationFacetFactory.class);
         addFactory(NotInServiceMenuAnnotationFacetFactory.class);
         addFactory(NotInServiceMenuMethodFacetFactory.class);
+        addFactory(BulkAnnotationFacetFactory.class);
 
         addFactory(HiddenAnnotationForTypeFacetFactory.class);
         // must come after the TitleAnnotationFacetFactory, because can act as an override

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/example/application/quickstart_dnd_junit_bdd/dom/src/main/java/dom/todo/ToDoItems.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_dnd_junit_bdd/dom/src/main/java/dom/todo/ToDoItems.java b/example/application/quickstart_dnd_junit_bdd/dom/src/main/java/dom/todo/ToDoItems.java
index d13e71e..cd120eb 100644
--- a/example/application/quickstart_dnd_junit_bdd/dom/src/main/java/dom/todo/ToDoItems.java
+++ b/example/application/quickstart_dnd_junit_bdd/dom/src/main/java/dom/todo/ToDoItems.java
@@ -112,7 +112,7 @@ public class ToDoItems extends AbstractFactoryAndRepository {
         });
     }
     // }}
-    
+
     // {{ autoComplete (hidden)
     @Hidden
     public List<ToDoItem> autoComplete(final String description) {

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
index 4bb1a19..641aff2 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
@@ -28,7 +28,9 @@ import javax.jdo.annotations.VersionStrategy;
 import javax.jdo.spi.PersistenceCapable;
 
 import org.apache.isis.applib.DomainObjectContainer;
+import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.AutoComplete;
+import org.apache.isis.applib.annotation.Bulk;
 import org.apache.isis.applib.annotation.Disabled;
 import org.apache.isis.applib.annotation.Hidden;
 import org.apache.isis.applib.annotation.MemberGroups;
@@ -41,6 +43,7 @@ import org.apache.isis.applib.annotation.Optional;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.annotation.RegEx;
 import org.apache.isis.applib.annotation.Resolve;
+import org.apache.isis.applib.annotation.ActionSemantics.Of;
 import org.apache.isis.applib.annotation.Resolve.Type;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.clock.Clock;
@@ -48,7 +51,6 @@ import org.apache.isis.applib.filter.Filter;
 import org.apache.isis.applib.filter.Filters;
 import org.apache.isis.applib.util.TitleBuffer;
 import org.apache.isis.applib.value.Blob;
-import org.apache.isis.applib.value.Clob;
 import org.apache.isis.core.objectstore.jdo.applib.annotations.Auditable;
 import org.joda.time.LocalDate;
 
@@ -130,6 +132,9 @@ public class ToDoItem implements Comparable<ToDoItem> {
     public void setDueBy(final LocalDate dueBy) {
         this.dueBy = dueBy;
     }
+    public void clearDueBy() {
+        setDueBy(null);
+    }
     // proposed new value is validated before setting
     public String validateDueBy(final LocalDate dueBy) {
         if (dueBy == null) {
@@ -236,6 +241,7 @@ public class ToDoItem implements Comparable<ToDoItem> {
     // }}
 
     // {{ completed (action)
+    @Bulk
     @MemberOrder(sequence = "1")
     public ToDoItem completed() {
         setComplete(true);
@@ -246,7 +252,6 @@ public class ToDoItem implements Comparable<ToDoItem> {
     public String disableCompleted() {
         return complete ? "Already completed" : null;
     }
-
     // }}
 
     // {{ notYetCompleted (action)
@@ -262,6 +267,9 @@ public class ToDoItem implements Comparable<ToDoItem> {
         return !complete ? "Not yet completed" : null;
     }
     // }}
+
+    
+    
     
     // {{ dependencies (Collection)
     private List<ToDoItem> dependencies = new ArrayList<ToDoItem>();
@@ -357,7 +365,6 @@ public class ToDoItem implements Comparable<ToDoItem> {
     /**
      * by complete flag, then due by date, then description
      */
-    @Programmatic
     // exclude from the framework's metamodel
     @Override
     public int compareTo(final ToDoItem other) {
@@ -378,7 +385,6 @@ public class ToDoItem implements Comparable<ToDoItem> {
         }
         return getDueBy().compareTo(getDueBy());
     }
-
     // }}
 
     // {{ helpers
@@ -429,8 +435,6 @@ public class ToDoItem implements Comparable<ToDoItem> {
 
         };
     }
-
-
     // }}
 
     // {{ injected: DomainObjectContainer

http://git-wip-us.apache.org/repos/asf/isis/blob/38f71bf9/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
index bbb1034..1c94c32 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.isis.applib.AbstractFactoryAndRepository;
 import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.ActionSemantics.Of;
+import org.apache.isis.applib.annotation.Bulk;
 import org.apache.isis.applib.annotation.Hidden;
 import org.apache.isis.applib.annotation.MemberOrder;
 import org.apache.isis.applib.annotation.Named;
@@ -132,7 +133,6 @@ public class ToDoItems extends AbstractFactoryAndRepository {
     // }}
     
 
-    
     // {{ autoComplete (hidden)
     @Hidden
     public List<ToDoItem> autoComplete(final String description) {
@@ -155,4 +155,5 @@ public class ToDoItems extends AbstractFactoryAndRepository {
     }
     // }}
 
+    
 }