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 2016/05/12 14:52:10 UTC

[1/2] isis git commit: ISIS-1370: refining the API for PublisherService.

Repository: isis
Updated Branches:
  refs/heads/ISIS-1291 2a8918287 -> b8d19e67d


ISIS-1370: refining the API for PublisherService.


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

Branch: refs/heads/ISIS-1291
Commit: ea7c1904429e327647def9a3f5cd7d95a804c9ab
Parents: 2a89182
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu May 12 15:50:43 2016 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu May 12 15:50:43 2016 +0100

----------------------------------------------------------------------
 .../applib/services/changes/ChangedObjects.java |  38 --
 .../isis/applib/services/iactn/Interaction.java |  36 +-
 .../services/publish/PublishedObjects.java      |  47 +++
 .../services/publish/PublisherService.java      |   3 +-
 .../publish/PublisherServiceLogging.java        |   5 +-
 .../applib/service/DomainChangeJdoAbstract.java |  19 +-
 ...onInvocationFacetForDomainEventAbstract.java |   6 +-
 .../publishedobject/PublishedObjectFacet.java   |  17 +
 ...etterOrClearFacetForDomainEventAbstract.java |   6 +-
 .../services/changes/ChangedObjectsDefault.java | 178 ---------
 .../changes/ChangedObjectsServiceInternal.java  |  13 +-
 .../publish/PublishedObjectsDefault.java        | 216 ++++++++++
 .../PublishingServiceInternalDefault.java       | 397 +++++++++++++++++++
 .../PublishingServiceInternalDefault.java       | 394 ------------------
 14 files changed, 741 insertions(+), 634 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/applib/src/main/java/org/apache/isis/applib/services/changes/ChangedObjects.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/changes/ChangedObjects.java b/core/applib/src/main/java/org/apache/isis/applib/services/changes/ChangedObjects.java
deleted file mode 100644
index 6a194a2..0000000
--- a/core/applib/src/main/java/org/apache/isis/applib/services/changes/ChangedObjects.java
+++ /dev/null
@@ -1,38 +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.applib.services.changes;
-
-import java.sql.Timestamp;
-
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.services.HasTransactionId;
-import org.apache.isis.applib.services.HasUsername;
-import org.apache.isis.schema.chg.v1.ChangesDto;
-
-public interface ChangedObjects extends HasTransactionId, HasUsername {
-
-    @Programmatic
-    Timestamp getCompletedAt();
-
-    @Programmatic
-    String getUser();
-
-    @Programmatic
-    ChangesDto getDto();
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java b/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
index 584bbb1..9281bae 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
@@ -316,6 +316,8 @@ public class Interaction implements HasTransactionId {
 
         private final String memberIdentifier;
         private final Object target;
+        private final String targetMember;
+        private final String targetClass;
         private final Interaction interaction;
         private final InteractionType interactionType;
 
@@ -323,15 +325,19 @@ public class Interaction implements HasTransactionId {
                 final Interaction interaction,
                 final InteractionType interactionType,
                 final String memberIdentifier,
-                final Object target) {
+                final Object target,
+                final String targetMember,
+                final String targetClass) {
             this.interaction = interaction;
             this.interactionType = interactionType;
             this.memberIdentifier = memberIdentifier;
             this.target = target;
+            this.targetMember = targetMember;
+            this.targetClass = targetClass;
         }
         //endregion
 
-        //region > via constructor: interaction, interactionType, memberId, target
+        //region > via constructor: interaction, interactionType, memberId, target, targetMember, targetClass
 
         public Interaction getInteraction() {
             return interaction;
@@ -349,6 +355,20 @@ public class Interaction implements HasTransactionId {
             return target;
         }
 
+        /**
+         * A human-friendly description of the class of the target object.
+         */
+        public String getTargetClass() {
+            return targetClass;
+        }
+
+        /**
+         * The human-friendly name of the action invoked/property edited on the target object.
+         */
+        public String getTargetMember() {
+            return targetMember;
+        }
+
         //endregion
 
         //region > parent, children
@@ -609,8 +629,10 @@ public class Interaction implements HasTransactionId {
                 final Interaction interaction,
                 final String memberId,
                 final Object target,
-                final List<Object> args) {
-            super(interaction, InteractionType.ACTION_INVOCATION, memberId, target);
+                final List<Object> args,
+                final String targetMember,
+                final String targetClass) {
+            super(interaction, InteractionType.ACTION_INVOCATION, memberId, target, targetMember, targetClass);
             this.args = args;
         }
 
@@ -627,8 +649,10 @@ public class Interaction implements HasTransactionId {
                 final Interaction interaction,
                 final String memberId,
                 final Object target,
-                final Object newValue) {
-            super(interaction, InteractionType.PROPERTY_EDIT, memberId, target);
+                final Object newValue,
+                final String targetMember,
+                final String targetClass) {
+            super(interaction, InteractionType.PROPERTY_EDIT, memberId, target, targetMember, targetClass);
             this.newValue = newValue;
         }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublishedObjects.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublishedObjects.java b/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublishedObjects.java
new file mode 100644
index 0000000..35a1f6f
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublishedObjects.java
@@ -0,0 +1,47 @@
+/*
+ *  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.services.publish;
+
+import java.sql.Timestamp;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.HasTransactionId;
+import org.apache.isis.applib.services.HasUsername;
+import org.apache.isis.schema.chg.v1.ChangesDto;
+
+public interface PublishedObjects extends HasTransactionId, HasUsername {
+
+    @Programmatic
+    Timestamp getCompletedAt();
+
+    @Programmatic
+    String getUser();
+
+    @Programmatic
+    ChangesDto getDto();
+
+    @Programmatic
+    int numberCreated();
+
+    @Programmatic
+    int numberUpdated();
+
+    @Programmatic
+    int numberDeleted();
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherService.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherService.java b/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherService.java
index d1b4ff9..9c1d7ca 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherService.java
@@ -20,7 +20,6 @@ package org.apache.isis.applib.services.publish;
 
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.services.iactn.Interaction;
-import org.apache.isis.applib.services.changes.ChangedObjects;
 
 /**
  * Replaces {@link PublishingService}.
@@ -40,7 +39,7 @@ public interface PublisherService {
     void publish(final Interaction.Execution<?, ?> execution);
 
     @Programmatic
-    void publish(final ChangedObjects changedObjects);
+    void publish(final PublishedObjects publishedObjects);
 }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherServiceLogging.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherServiceLogging.java b/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherServiceLogging.java
index d3bf438..b928d37 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherServiceLogging.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/publish/PublisherServiceLogging.java
@@ -27,7 +27,6 @@ import org.slf4j.LoggerFactory;
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.services.command.CommandContext;
-import org.apache.isis.applib.services.changes.ChangedObjects;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.user.UserService;
 import org.apache.isis.schema.chg.v1.ChangesDto;
@@ -60,13 +59,13 @@ public class PublisherServiceLogging implements PublisherService {
     }
 
     @Override
-    public void publish(final ChangedObjects changedObjects) {
+    public void publish(final PublishedObjects publishedObjects) {
 
         if(!LOG.isDebugEnabled()) {
             return;
         }
 
-        final ChangesDto changesDto = changedObjects.getDto();
+        final ChangesDto changesDto = publishedObjects.getDto();
 
         LOG.debug(ChangesDtoUtils.toXml(changesDto));
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/DomainChangeJdoAbstract.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/DomainChangeJdoAbstract.java b/core/applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/DomainChangeJdoAbstract.java
index 6dfa7da..5c8f74a 100644
--- a/core/applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/DomainChangeJdoAbstract.java
+++ b/core/applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/DomainChangeJdoAbstract.java
@@ -181,20 +181,29 @@ public abstract class DomainChangeJdoAbstract {
     private String targetAction;
     
     /**
-     * The action invoked which caused the domain object to be changed..
-     * 
+     * The member interaction (ie action invocation or property edit) which caused the domain object to be changed.
+     *
      * <p>
-     * Populated only for <tt>CommandJdo</tt>s.
-     * 
+     *     Populated for commands and for published events that represent action invocations or property edits.
+     * </p>
+     *
      * <p>
      * This dummy implementation is a trick so that Isis will render the property in a standalone table.  Each of the
      * subclasses override with the &quot;real&quot; implementation.
+     * </p>
+     *
+     * <p>
+     *     NB: commands and published events applied only to actions, hence the name of this field.  In a future release
+     *     the name of this field may change to &quot;TargetMember&quot;.  Note that the {@link PropertyLayout} already uses
+     *     &quot;Member&quot; this as a name hint.
+     * </p>
+     *
      */
     @Property(
             optionality = OPTIONAL
     )
     @PropertyLayout(
-            named="Action",
+            named="Member",
             hidden = Where.ALL_EXCEPT_STANDALONE_TABLES
     )
     @MemberOrder(name="Target", sequence = "20")

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index c900f17..1222033 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -196,8 +196,12 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
             final List<ObjectAdapter> argumentAdapterList = Arrays.asList(argumentAdapters);
             final List<Object> argumentPojos = ObjectAdapter.Util.unwrap(argumentAdapterList);
 
+            final String targetMember = CommandUtil.targetMemberNameFor(owningAction);
+            final String targetClass = CommandUtil.targetClassNameFor(targetAdapter);
+
             final Interaction.ActionInvocation execution =
-                    new Interaction.ActionInvocation(interaction, actionId, targetPojo, argumentPojos);
+                    new Interaction.ActionInvocation(interaction, actionId, targetPojo, argumentPojos, targetMember,
+                            targetClass);
             final Interaction.MemberExecutor<Interaction.ActionInvocation> callable =
                     new Interaction.MemberExecutor<Interaction.ActionInvocation>() {
 

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/publishedobject/PublishedObjectFacet.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/publishedobject/PublishedObjectFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/publishedobject/PublishedObjectFacet.java
index e7736bb..f344c00 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/publishedobject/PublishedObjectFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/publishedobject/PublishedObjectFacet.java
@@ -19,7 +19,10 @@
 
 package org.apache.isis.core.metamodel.facets.object.publishedobject;
 
+import com.google.common.base.Predicate;
+
 import org.apache.isis.applib.annotation.PublishedObject;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facets.SingleValueFacet;
 
 /**
@@ -28,4 +31,18 @@ import org.apache.isis.core.metamodel.facets.SingleValueFacet;
  */
 public interface PublishedObjectFacet extends SingleValueFacet<PublishedObject.PayloadFactory> {
 
+    class Predicates {
+        private Predicates(){}
+
+        public static Predicate<ObjectAdapter> isPublished() {
+            return new Predicate<ObjectAdapter>() {
+                @Override
+                public boolean apply(final ObjectAdapter objectAdapter) {
+                    final PublishedObjectFacet publishedObjectFacet =
+                            objectAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
+                    return publishedObjectFacet != null;
+                }
+            };
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
index c4899f8..e7af2d6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
@@ -38,6 +38,7 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.DomainEventHelper;
 import org.apache.isis.core.metamodel.facets.SingleValueFacetAbstract;
+import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
 import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
 import org.apache.isis.core.metamodel.facets.properties.publish.PublishedPropertyFacet;
 import org.apache.isis.core.metamodel.facets.properties.update.clear.PropertyClearFacet;
@@ -188,8 +189,11 @@ public abstract class PropertySetterOrClearFacetForDomainEventAbstract
             final Object target = ObjectAdapter.Util.unwrap(targetAdapter);
             final Object argValue = ObjectAdapter.Util.unwrap(newValueAdapter);
 
+            final String targetMember = CommandUtil.targetMemberNameFor(owningProperty);
+            final String targetClass = CommandUtil.targetClassNameFor(targetAdapter);
+
             final Interaction.PropertyEdit execution =
-                    new Interaction.PropertyEdit(interaction, propertyId, target, argValue);
+                    new Interaction.PropertyEdit(interaction, propertyId, target, argValue, targetMember, targetClass);
             final Interaction.MemberExecutor<Interaction.PropertyEdit> executor =
                     new Interaction.MemberExecutor<Interaction.PropertyEdit>() {
                         @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsDefault.java
deleted file mode 100644
index f2d203a..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsDefault.java
+++ /dev/null
@@ -1,178 +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.core.runtime.services.changes;
-
-import java.sql.Timestamp;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.annotation.PublishedObject;
-import org.apache.isis.applib.services.changes.ChangedObjects;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.adapter.oid.RootOid;
-import org.apache.isis.core.metamodel.facets.object.publishedobject.PublishedObjectFacet;
-import org.apache.isis.schema.chg.v1.ChangesDto;
-import org.apache.isis.schema.chg.v1.ObjectsDto;
-import org.apache.isis.schema.common.v1.OidDto;
-import org.apache.isis.schema.common.v1.OidsDto;
-import org.apache.isis.schema.utils.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarAdapter;
-
-/**
- * Captures which objects were created, updated or deleted in the course of a transaction.
- */
-public class ChangedObjectsDefault implements ChangedObjects {
-
-    private UUID transactionUuid;
-    private final String userName;
-    private final Timestamp completedAt;
-    private final Map<ObjectAdapter, PublishedObject.ChangeKind> changesByAdapter;
-
-    public ChangedObjectsDefault(
-            final UUID transactionUuid,
-            final String userName,
-            final Timestamp completedAt,
-            final Map<ObjectAdapter, PublishedObject.ChangeKind> changesByAdapter) {
-        this.transactionUuid = transactionUuid;
-        this.userName = userName;
-        this.completedAt = completedAt;
-        this.changesByAdapter = changesByAdapter;
-    }
-
-    //region > transactionId, completedAt, user
-    @Programmatic
-    @Override
-    public UUID getTransactionId() {
-        return transactionUuid;
-    }
-
-    /**
-     * Unused; the {@link #getTransactionId()} is set in the constructor.
-     */
-    @Override
-    public void setTransactionId(final UUID transactionId) {
-        this.transactionUuid = transactionId;
-    }
-
-    /**
-     * The date/time at which this set of enlisted objects was created (approx the completion time of the transaction).
-     */
-    @Override
-    public Timestamp getCompletedAt() {
-        return completedAt;
-    }
-
-    @Override
-    public String getUser() {
-        return userName;
-    }
-
-    @Programmatic
-    @Override
-    public String getUsername() {
-        return getUser();
-    }
-    //endregion
-
-    private ChangesDto dto;
-
-    @Override
-    public ChangesDto getDto() {
-        return dto != null ? dto : (dto = newDto());
-    }
-
-
-    //region > newDto, newObjectsDto, newChangesDto
-
-    private ChangesDto newDto() {
-        final ObjectsDto objectsDto = newObjectsDto(changesByAdapter);
-        return newChangesDto(objectsDto, transactionUuid, userName, completedAt);
-    }
-
-    protected ObjectsDto newObjectsDto(
-            final Map<ObjectAdapter, PublishedObject.ChangeKind> changeKindByEnlistedAdapter) {
-        final ObjectsDto objectsDto = new ObjectsDto();
-
-        final OidsDto createdOids = new OidsDto();
-        final OidsDto updatedOids = new OidsDto();
-        final OidsDto deletedOids = new OidsDto();
-
-        for (final Map.Entry<ObjectAdapter, PublishedObject.ChangeKind> adapterAndChange : changeKindByEnlistedAdapter.entrySet()) {
-            final ObjectAdapter enlistedAdapter = adapterAndChange.getKey();
-
-            final PublishedObjectFacet publishedObjectFacet =
-                    enlistedAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
-
-            if(publishedObjectFacet == null) {
-                continue;
-            }
-
-            final RootOid rootOid = (RootOid) enlistedAdapter.getOid();
-            final OidDto oidDto = rootOid.asOidDto();
-
-            final PublishedObject.ChangeKind changeKind = adapterAndChange.getValue();
-            switch (changeKind) {
-            case CREATE:
-                createdOids.getOid().add(oidDto);
-                break;
-            case UPDATE:
-                updatedOids.getOid().add(oidDto);
-                break;
-            case DELETE:
-                deletedOids.getOid().add(oidDto);
-                break;
-            default:
-                // shouldn't happen
-                throw new RuntimeException("ChangeKind '" + changeKind + "' not recognized");
-            }
-        }
-
-        objectsDto.setCreated(createdOids);
-        objectsDto.setUpdated(updatedOids);
-        objectsDto.setDeleted(deletedOids);
-        return objectsDto;
-    }
-
-    protected ChangesDto newChangesDto(
-            final ObjectsDto objectsDto,
-            final UUID transactionUuid,
-            final String userName,
-            final Timestamp timestamp) {
-        final String transactionId = transactionUuid.toString();
-        final ChangesDto changesDto = new ChangesDto();
-
-        changesDto.setMajorVersion("1");
-        changesDto.setMinorVersion("0");
-
-        changesDto.setTransactionId(transactionId);
-        changesDto.setUser(userName);
-        changesDto.setCompletedAt(
-                JavaSqlTimestampXmlGregorianCalendarAdapter.print(timestamp));
-        changesDto.setObjects(objectsDto);
-        return changesDto;
-    }
-
-
-
-    //endregion
-
-
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsServiceInternal.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsServiceInternal.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsServiceInternal.java
index d907c0a..9981059 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsServiceInternal.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsServiceInternal.java
@@ -246,18 +246,19 @@ public class ChangedObjectsServiceInternal {
         return changeKindByEnlistedAdapter;
     }
 
-
-    static String asString(Object object) {
-        return object != null? object.toString(): null;
-    }
-
     @Programmatic
     public int numberObjectsDirtied() {
-        return changedObjectProperties.size();
+        return changeKindByEnlistedAdapter.size();
     }
 
     @Programmatic
     public int numberObjectPropertiesModified() {
         return changedObjectProperties.size();
     }
+
+
+    static String asString(Object object) {
+        return object != null? object.toString(): null;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishedObjectsDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishedObjectsDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishedObjectsDefault.java
new file mode 100644
index 0000000..da24a80
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishedObjectsDefault.java
@@ -0,0 +1,216 @@
+/*
+ *  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.runtime.services.publish;
+
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimaps;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.annotation.PublishedObject;
+import org.apache.isis.applib.services.publish.PublishedObjects;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.oid.RootOid;
+import org.apache.isis.schema.chg.v1.ChangesDto;
+import org.apache.isis.schema.chg.v1.ObjectsDto;
+import org.apache.isis.schema.common.v1.OidDto;
+import org.apache.isis.schema.common.v1.OidsDto;
+import org.apache.isis.schema.utils.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarAdapter;
+
+/**
+ * Captures which objects were created, updated or deleted in the course of a transaction.
+ */
+public class PublishedObjectsDefault implements PublishedObjects {
+
+    //region > constructor, fields
+    private UUID transactionUuid;
+    private final String userName;
+    private final Timestamp completedAt;
+    private final Map<ObjectAdapter, PublishedObject.ChangeKind> changesByAdapter;
+
+    public PublishedObjectsDefault(
+            final UUID transactionUuid,
+            final String userName,
+            final Timestamp completedAt,
+            final Map<ObjectAdapter, PublishedObject.ChangeKind> changesByAdapter) {
+        this.transactionUuid = transactionUuid;
+        this.userName = userName;
+        this.completedAt = completedAt;
+        this.changesByAdapter = changesByAdapter;
+    }
+    //endregion
+
+
+    //region > transactionId, completedAt, user
+    @Programmatic
+    @Override
+    public UUID getTransactionId() {
+        return transactionUuid;
+    }
+
+    /**
+     * Unused; the {@link #getTransactionId()} is set in the constructor.
+     */
+    @Override
+    public void setTransactionId(final UUID transactionId) {
+        this.transactionUuid = transactionId;
+    }
+
+    /**
+     * The date/time at which this set of enlisted objects was created (approx the completion time of the transaction).
+     */
+    @Override
+    public Timestamp getCompletedAt() {
+        return completedAt;
+    }
+
+    @Override
+    public String getUser() {
+        return userName;
+    }
+
+    @Programmatic
+    @Override
+    public String getUsername() {
+        return getUser();
+    }
+    //endregion
+
+    //region > dto
+    /**
+     * lazily computed
+     */
+    private ChangesDto dto;
+
+    @Override
+    public ChangesDto getDto() {
+        return dto != null ? dto : (dto = newDto());
+    }
+    //endregion
+
+    //region > numberCreated, numberUpdated, numberDeleted
+
+    @Override
+    public int numberCreated() {
+        return numAdaptersOfKind(PublishedObject.ChangeKind.CREATE);
+    }
+
+    @Override
+    public int numberUpdated() {
+        return numAdaptersOfKind(PublishedObject.ChangeKind.UPDATE);
+    }
+
+    @Override
+    public int numberDeleted() {
+        return numAdaptersOfKind(PublishedObject.ChangeKind.DELETE);
+    }
+
+    private int numAdaptersOfKind(final PublishedObject.ChangeKind kind) {
+        final Collection<ObjectAdapter> objectAdapters = adaptersByChange().get(kind);
+        return objectAdapters != null ? objectAdapters.size() : 0;
+    }
+
+
+    /**
+     * Lazily populated
+     */
+    private Map<PublishedObject.ChangeKind, Collection<ObjectAdapter>> adaptersByChange;
+
+    private Map<PublishedObject.ChangeKind, Collection<ObjectAdapter>> adaptersByChange() {
+        return adaptersByChange != null? adaptersByChange : (adaptersByChange = invert(changesByAdapter));
+    }
+
+    private static <T, S> Map<T, Collection<S>> invert(final Map<S, T> valueByKey) {
+        return new TreeMap<>(
+                Multimaps.invertFrom(
+                        Multimaps.forMap(valueByKey),
+                        ArrayListMultimap.<T, S>create()
+                ).asMap()
+        );
+    }
+
+    //endregion
+
+
+    //region > newDto, newObjectsDto, newChangesDto
+
+    private ChangesDto newDto() {
+        final ObjectsDto objectsDto = newObjectsDto();
+        return newChangesDto(objectsDto);
+    }
+
+    protected ObjectsDto newObjectsDto() {
+
+        final ObjectsDto objectsDto = new ObjectsDto();
+
+        objectsDto.setCreated(oidsDtoFor(PublishedObject.ChangeKind.CREATE));
+        objectsDto.setUpdated(oidsDtoFor(PublishedObject.ChangeKind.UPDATE));
+        objectsDto.setDeleted(oidsDtoFor(PublishedObject.ChangeKind.DELETE));
+
+        return objectsDto;
+    }
+
+    private OidsDto oidsDtoFor(final PublishedObject.ChangeKind kind) {
+        final OidsDto oidsDto = new OidsDto();
+
+        final Map<PublishedObject.ChangeKind, Collection<ObjectAdapter>> adaptersByChange = adaptersByChange();
+
+        final Collection<ObjectAdapter> adapters = adaptersByChange.get(kind);
+        if(adapters != null) {
+            final ImmutableList<OidDto> oidDtos = FluentIterable.from(adapters)
+                    .transform(new Function<ObjectAdapter, OidDto>() {
+                        @Override
+                        public OidDto apply(final ObjectAdapter objectAdapter) {
+                            final RootOid rootOid = (RootOid) objectAdapter.getOid();
+                            return rootOid.asOidDto();
+                        }
+                    })
+                    .toList();
+            oidsDto.getOid().addAll(oidDtos);
+        }
+        return oidsDto;
+    }
+
+    protected ChangesDto newChangesDto(final ObjectsDto objectsDto) {
+        final String transactionId = transactionUuid.toString();
+        final ChangesDto changesDto = new ChangesDto();
+
+        changesDto.setMajorVersion("1");
+        changesDto.setMinorVersion("0");
+
+        changesDto.setTransactionId(transactionId);
+        changesDto.setUser(userName);
+        changesDto.setCompletedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(completedAt));
+        changesDto.setObjects(objectsDto);
+        return changesDto;
+    }
+
+
+    //endregion
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishingServiceInternalDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishingServiceInternalDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishingServiceInternalDefault.java
new file mode 100644
index 0000000..e15a978
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publish/PublishingServiceInternalDefault.java
@@ -0,0 +1,397 @@
+/*
+ *  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.runtime.services.publish;
+
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.annotation.PublishedAction;
+import org.apache.isis.applib.annotation.PublishedObject;
+import org.apache.isis.applib.annotation.PublishedObject.ChangeKind;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.clock.ClockService;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.command.CommandContext;
+import org.apache.isis.applib.services.publish.PublishedObjects;
+import org.apache.isis.applib.services.iactn.Interaction;
+import org.apache.isis.applib.services.iactn.InteractionContext;
+import org.apache.isis.applib.services.publish.EventMetadata;
+import org.apache.isis.applib.services.publish.EventPayload;
+import org.apache.isis.applib.services.publish.EventType;
+import org.apache.isis.applib.services.publish.ObjectStringifier;
+import org.apache.isis.applib.services.publish.PublisherService;
+import org.apache.isis.applib.services.publish.PublishingService;
+import org.apache.isis.applib.services.user.UserService;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.oid.Oid;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.adapter.oid.RootOid;
+import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
+import org.apache.isis.core.metamodel.facets.FacetedMethod;
+import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
+import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
+import org.apache.isis.core.metamodel.facets.actions.publish.PublishedActionFacet;
+import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
+import org.apache.isis.core.metamodel.facets.object.publishedobject.PublishedObjectFacet;
+import org.apache.isis.core.metamodel.services.ixn.InteractionDtoServiceInternal;
+import org.apache.isis.core.metamodel.services.publishing.PublishingServiceInternal;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.runtime.services.changes.ChangedObjectsServiceInternal;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
+
+/**
+ * Wrapper around {@link PublishingService}.  Is a no-op if there is no injected service.
+ */
+@DomainService(nature = NatureOfService.DOMAIN)
+public class PublishingServiceInternalDefault implements PublishingServiceInternal {
+
+    private final static Function<ObjectAdapter, ObjectAdapter> NOT_DESTROYED_ELSE_EMPTY = new Function<ObjectAdapter, ObjectAdapter>() {
+        public ObjectAdapter apply(ObjectAdapter adapter) {
+            if(adapter == null) {
+                return null;
+            }
+            if (!adapter.isDestroyed()) {
+                return adapter;
+            }
+            // objectstores such as JDO prevent the underlying pojo from being touched once it has been deleted.
+            // we therefore replace that pojo with an 'empty' one.
+
+            Object replacementObject = getPersistenceSession().instantiateAndInjectServices(adapter.getSpecification());
+            getPersistenceSession().remapRecreatedPojo(adapter, replacementObject);
+            return adapter;
+        }
+        protected PersistenceSession getPersistenceSession() {
+            return IsisContext.getPersistenceSession();
+        }
+
+    };
+
+    @Programmatic
+    public static EventType eventTypeFor(ChangeKind changeKind) {
+        if(changeKind == ChangeKind.UPDATE) {
+            return EventType.OBJECT_UPDATED;
+        }
+        if(changeKind == ChangeKind.CREATE) {
+            return EventType.OBJECT_CREATED;
+        }
+        if(changeKind == ChangeKind.DELETE) {
+            return EventType.OBJECT_DELETED;
+        }
+        throw new IllegalArgumentException("unknown ChangeKind '" + changeKind + "'");
+    }
+
+    @Override
+    @Programmatic
+    public void publishObjects() {
+        // take a copy of enlisted adapters ... the JDO implementation of the PublishingService
+        // creates further entities which would be enlisted; taking copy of the map avoids ConcurrentModificationException
+        final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter = Maps.newHashMap();
+        changeKindByEnlistedAdapter.putAll(changedObjectsServiceInternal.getChangeKindByEnlistedAdapter());
+
+        publishObjectsToPublishingService(changeKindByEnlistedAdapter);
+        publishObjectsToPublisherServices(changeKindByEnlistedAdapter);
+    }
+
+    private void publishObjectsToPublishingService(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
+
+        if(publishingServiceIfAny == null) {
+            return;
+        }
+
+        final String currentUser = userService.getUser().getName();
+        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
+        final ObjectStringifier stringifier = objectStringifier();
+
+        for (final Map.Entry<ObjectAdapter, ChangeKind> adapterAndChange : changeKindByEnlistedAdapter.entrySet()) {
+            final ObjectAdapter enlistedAdapter = adapterAndChange.getKey();
+            final ChangeKind changeKind = adapterAndChange.getValue();
+
+            publishObjectToPublishingService(
+                    enlistedAdapter, changeKind, currentUser, timestamp, stringifier);
+        }
+    }
+
+    private void publishObjectToPublishingService(
+            final ObjectAdapter enlistedAdapter,
+            final ChangeKind changeKind,
+            final String currentUser,
+            final Timestamp timestamp,
+            final ObjectStringifier stringifier) {
+
+        final PublishedObjectFacet publishedObjectFacet =
+                enlistedAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
+        if(publishedObjectFacet == null) {
+            return;
+        }
+
+        final PublishedObject.PayloadFactory payloadFactory = publishedObjectFacet.value();
+
+        final RootOid enlistedAdapterOid = (RootOid) enlistedAdapter.getOid();
+        final String enlistedAdapterClass = CommandUtil.targetClassNameFor(enlistedAdapter);
+        final Bookmark enlistedTarget = enlistedAdapterOid.asBookmark();
+
+        final EventMetadata metadata = newEventMetadata(
+                currentUser, timestamp, changeKind, enlistedAdapterClass, enlistedTarget);
+
+        final Object pojo = ObjectAdapter.Util.unwrap(undeletedElseEmpty(enlistedAdapter));
+        final EventPayload payload = payloadFactory.payloadFor(pojo, changeKind);
+
+        payload.withStringifier(stringifier);
+        publishingServiceIfAny.publish(metadata, payload);
+    }
+
+    private void publishObjectsToPublisherServices(
+            final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
+
+        final Map<ObjectAdapter, ChangeKind> changeKindByPublishedAdapter =
+                Maps.filterKeys(
+                        changeKindByEnlistedAdapter,
+                        PublishedObjectFacet.Predicates.isPublished());
+
+        if(changeKindByPublishedAdapter.isEmpty()) {
+            return;
+        }
+
+        final PublishedObjects publishedObjects = newPublishedObjects(changeKindByPublishedAdapter);
+
+        for (PublisherService publisherService : publisherServices) {
+            publisherService.publish(publishedObjects);
+        }
+    }
+
+    private PublishedObjects newPublishedObjects(final Map<ObjectAdapter, ChangeKind> changeKindByPublishedAdapter) {
+
+        final Command command = commandContext.getCommand();
+        final UUID transactionUuid = command.getTransactionId();
+
+        final String userName = userService.getUser().getName();
+        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
+
+        return new PublishedObjectsDefault(transactionUuid, userName, timestamp, changeKindByPublishedAdapter);
+    }
+
+
+
+    @Programmatic
+    public void publishAction(
+            final Interaction.Execution execution,
+            final ObjectAction objectAction,
+            final IdentifiedHolder identifiedHolder,
+            final ObjectAdapter targetAdapter,
+            final List<ObjectAdapter> parameterAdapters,
+            final ObjectAdapter resultAdapter) {
+
+        publishActionToPublishingService(
+                objectAction, identifiedHolder, targetAdapter, parameterAdapters, resultAdapter
+        );
+
+        publishToPublisherServices(execution);
+    }
+
+    @Override
+    public void publishProperty(
+            final Interaction.Execution execution) {
+
+        publishToPublisherServices(execution);
+    }
+
+    private void publishActionToPublishingService(
+            final ObjectAction objectAction,
+            final IdentifiedHolder identifiedHolder,
+            final ObjectAdapter targetAdapter,
+            final List<ObjectAdapter> parameterAdapters,
+            final ObjectAdapter resultAdapter) {
+        if(publishingServiceIfAny == null) {
+            return;
+        }
+        final String currentUser = userService.getUser().getName();
+        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
+
+        final PublishedActionFacet publishedActionFacet =
+                identifiedHolder.getFacet(PublishedActionFacet.class);
+        if(publishedActionFacet == null) {
+            return;
+        }
+
+        final RootOid adapterOid = (RootOid) targetAdapter.getOid();
+        final String oidStr = getOidMarshaller().marshal(adapterOid);
+        final Identifier actionIdentifier = objectAction.getIdentifier();
+        final String title = oidStr + ": " + actionIdentifier.toNameParmsIdentityString();
+
+        final String actionTargetClass = CommandUtil.targetClassNameFor(targetAdapter);
+        final String actionTargetAction = CommandUtil.targetMemberNameFor(objectAction);
+        final Bookmark actionTarget = CommandUtil.bookmarkFor(targetAdapter);
+        final String actionMemberIdentifier = CommandUtil.memberIdentifierFor(objectAction);
+
+        final List<String> parameterNames;
+        final List<Class<?>> parameterTypes;
+        final Class<?> returnType;
+
+        if(identifiedHolder instanceof FacetedMethod) {
+            // should always be the case
+
+            final FacetedMethod facetedMethod = (FacetedMethod) identifiedHolder;
+            returnType = facetedMethod.getType();
+
+            final List<FacetedMethodParameter> parameters = facetedMethod.getParameters();
+            parameterNames = immutableList(Iterables.transform(parameters, FacetedMethodParameter.Functions.GET_NAME));
+            parameterTypes = immutableList(Iterables.transform(parameters, FacetedMethodParameter.Functions.GET_TYPE));
+        } else {
+            parameterNames = null;
+            parameterTypes = null;
+            returnType = null;
+        }
+
+        final Interaction interaction = interactionContext.getInteraction();
+
+        final int nextEventSequence = interaction.next(Interaction.Sequence.PUBLISHED_EVENT.id());
+        final UUID transactionId = interaction.getTransactionId();
+        final EventMetadata metadata = new EventMetadata(
+                transactionId, nextEventSequence, EventType.ACTION_INVOCATION, currentUser, timestamp, title,
+                actionTargetClass, actionTargetAction, actionTarget, actionMemberIdentifier, parameterNames,
+                parameterTypes, returnType);
+
+        final PublishedAction.PayloadFactory payloadFactory = publishedActionFacet.value();
+
+        final ObjectStringifier stringifier = objectStringifier();
+
+        final EventPayload payload = payloadFactory.payloadFor(
+                identifiedHolder.getIdentifier(),
+                ObjectAdapter.Util.unwrap(undeletedElseEmpty(targetAdapter)),
+                ObjectAdapter.Util.unwrap(undeletedElseEmpty(parameterAdapters)),
+                ObjectAdapter.Util.unwrap(undeletedElseEmpty(resultAdapter)));
+        payload.withStringifier(stringifier);
+        publishingServiceIfAny.publish(metadata, payload);
+    }
+
+    private static <T> List<T> immutableList(final Iterable<T> iterable) {
+        return Collections.unmodifiableList(Lists.newArrayList(iterable));
+    }
+
+    private ObjectStringifier objectStringifier() {
+        return new ObjectStringifier() {
+                @Override
+                public String toString(Object object) {
+                    if(object == null) {
+                        return null;
+                    }
+                    final ObjectAdapter adapter = IsisContext.getPersistenceSession().adapterFor(object);
+                    Oid oid = adapter.getOid();
+                    return oid != null? oid.enString(getOidMarshaller()): encodedValueOf(adapter);
+                }
+                private String encodedValueOf(ObjectAdapter adapter) {
+                    EncodableFacet facet = adapter.getSpecification().getFacet(EncodableFacet.class);
+                    return facet != null? facet.toEncodedString(adapter): adapter.toString();
+                }
+                @Override
+                public String classNameOf(Object object) {
+                    final ObjectAdapter adapter = getPersistenceSession().adapterFor(object);
+                    final String className = adapter.getSpecification().getFullIdentifier();
+                    return className;
+                }
+            };
+    }
+
+    private static List<ObjectAdapter> undeletedElseEmpty(List<ObjectAdapter> parameters) {
+        return Lists.newArrayList(Iterables.transform(parameters, NOT_DESTROYED_ELSE_EMPTY));
+    }
+
+    private static ObjectAdapter undeletedElseEmpty(ObjectAdapter adapter) {
+        return NOT_DESTROYED_ELSE_EMPTY.apply(adapter);
+    }
+
+    protected OidMarshaller getOidMarshaller() {
+        return IsisContext.getOidMarshaller();
+    }
+
+    private EventMetadata newEventMetadata(
+            final String currentUser,
+            final Timestamp timestamp,
+            final ChangeKind changeKind,
+            final String enlistedAdapterClass,
+            final Bookmark enlistedTarget) {
+        final EventType eventType = PublishingServiceInternalDefault.eventTypeFor(changeKind);
+
+        final Interaction interaction = interactionContext.getInteraction();
+
+        final int nextEventSequence = interaction.next(Interaction.Sequence.PUBLISHED_EVENT.id());
+        final UUID transactionId = interaction.getTransactionId();
+        return new EventMetadata(
+                transactionId, nextEventSequence, eventType, currentUser, timestamp, enlistedTarget.toString(),
+                enlistedAdapterClass, null, enlistedTarget, null, null, null, null);
+    }
+
+    private void publishToPublisherServices(final Interaction.Execution<?,?> execution) {
+
+        if(publisherServices == null || publisherServices.isEmpty()) {
+            return;
+        }
+
+        for (final PublisherService publisherService : publisherServices) {
+            publisherService.publish(execution);
+        }
+    }
+
+    @Inject
+    private List<PublisherService> publisherServices;
+
+    @Inject
+    private PublishingService publishingServiceIfAny;
+
+    @Inject
+    private ChangedObjectsServiceInternal changedObjectsServiceInternal;
+
+    @Inject
+    private InteractionDtoServiceInternal interactionDtoServiceInternal;
+
+    @Inject
+    private CommandContext commandContext;
+
+    @Inject
+    private InteractionContext interactionContext;
+
+    @Inject
+    private ClockService clockService;
+
+    @Inject
+    private UserService userService;
+
+
+    private IsisTransactionManager.PersistenceSessionTransactionManagement getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/ea7c1904/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publishing/PublishingServiceInternalDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publishing/PublishingServiceInternalDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publishing/PublishingServiceInternalDefault.java
deleted file mode 100644
index 870cd21..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/publishing/PublishingServiceInternalDefault.java
+++ /dev/null
@@ -1,394 +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.core.runtime.services.publishing;
-
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.inject.Inject;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.NatureOfService;
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.annotation.PublishedAction;
-import org.apache.isis.applib.annotation.PublishedObject;
-import org.apache.isis.applib.annotation.PublishedObject.ChangeKind;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.clock.ClockService;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.CommandContext;
-import org.apache.isis.applib.services.changes.ChangedObjects;
-import org.apache.isis.applib.services.iactn.Interaction;
-import org.apache.isis.applib.services.iactn.InteractionContext;
-import org.apache.isis.applib.services.publish.EventMetadata;
-import org.apache.isis.applib.services.publish.EventPayload;
-import org.apache.isis.applib.services.publish.EventType;
-import org.apache.isis.applib.services.publish.ObjectStringifier;
-import org.apache.isis.applib.services.publish.PublisherService;
-import org.apache.isis.applib.services.publish.PublishingService;
-import org.apache.isis.applib.services.user.UserService;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.adapter.oid.Oid;
-import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
-import org.apache.isis.core.metamodel.adapter.oid.RootOid;
-import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
-import org.apache.isis.core.metamodel.facets.FacetedMethod;
-import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
-import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
-import org.apache.isis.core.metamodel.facets.actions.publish.PublishedActionFacet;
-import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
-import org.apache.isis.core.metamodel.facets.object.publishedobject.PublishedObjectFacet;
-import org.apache.isis.core.metamodel.services.ixn.InteractionDtoServiceInternal;
-import org.apache.isis.core.metamodel.services.publishing.PublishingServiceInternal;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.core.runtime.services.changes.ChangedObjectsDefault;
-import org.apache.isis.core.runtime.services.changes.ChangedObjectsServiceInternal;
-import org.apache.isis.core.runtime.system.context.IsisContext;
-import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
-import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
-
-/**
- * Wrapper around {@link PublishingService}.  Is a no-op if there is no injected service.
- */
-@DomainService(nature = NatureOfService.DOMAIN)
-public class PublishingServiceInternalDefault implements PublishingServiceInternal {
-
-    private final static Function<ObjectAdapter, ObjectAdapter> NOT_DESTROYED_ELSE_EMPTY = new Function<ObjectAdapter, ObjectAdapter>() {
-        public ObjectAdapter apply(ObjectAdapter adapter) {
-            if(adapter == null) {
-                return null;
-            }
-            if (!adapter.isDestroyed()) {
-                return adapter;
-            }
-            // objectstores such as JDO prevent the underlying pojo from being touched once it has been deleted.
-            // we therefore replace that pojo with an 'empty' one.
-
-            Object replacementObject = getPersistenceSession().instantiateAndInjectServices(adapter.getSpecification());
-            getPersistenceSession().remapRecreatedPojo(adapter, replacementObject);
-            return adapter;
-        }
-        protected PersistenceSession getPersistenceSession() {
-            return IsisContext.getPersistenceSession();
-        }
-
-    };
-
-    @Programmatic
-    public static EventType eventTypeFor(ChangeKind changeKind) {
-        if(changeKind == ChangeKind.UPDATE) {
-            return EventType.OBJECT_UPDATED;
-        }
-        if(changeKind == ChangeKind.CREATE) {
-            return EventType.OBJECT_CREATED;
-        }
-        if(changeKind == ChangeKind.DELETE) {
-            return EventType.OBJECT_DELETED;
-        }
-        throw new IllegalArgumentException("unknown ChangeKind '" + changeKind + "'");
-    }
-
-    @Override
-    @Programmatic
-    public void publishObjects() {
-
-        // take a copy of enlisted adapters ... the JDO implementation of the PublishingService
-        // creates further entities which would be enlisted; taking copy of the map avoids ConcurrentModificationException
-        final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter = Maps.newHashMap();
-        changeKindByEnlistedAdapter.putAll(changedObjectsServiceInternal.getChangeKindByEnlistedAdapter());
-
-        publishObjectsToPublishingService(changeKindByEnlistedAdapter);
-        publishObjectsToPublisherServices(changeKindByEnlistedAdapter);
-    }
-
-    private void publishObjectsToPublishingService(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
-
-        if(publishingServiceIfAny == null) {
-            return;
-        }
-
-        final String currentUser = userService.getUser().getName();
-        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
-        final ObjectStringifier stringifier = objectStringifier();
-
-        for (final Map.Entry<ObjectAdapter, ChangeKind> adapterAndChange : changeKindByEnlistedAdapter.entrySet()) {
-            final ObjectAdapter enlistedAdapter = adapterAndChange.getKey();
-            final ChangeKind changeKind = adapterAndChange.getValue();
-
-            publishObjectToPublishingService(
-                    enlistedAdapter, changeKind, currentUser, timestamp, stringifier);
-        }
-    }
-
-    private void publishObjectToPublishingService(
-            final ObjectAdapter enlistedAdapter,
-            final ChangeKind changeKind,
-            final String currentUser,
-            final Timestamp timestamp,
-            final ObjectStringifier stringifier) {
-
-        final PublishedObjectFacet publishedObjectFacet =
-                enlistedAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
-        if(publishedObjectFacet == null) {
-            return;
-        }
-
-        final PublishedObject.PayloadFactory payloadFactory = publishedObjectFacet.value();
-
-        final RootOid enlistedAdapterOid = (RootOid) enlistedAdapter.getOid();
-        final String enlistedAdapterClass = CommandUtil.targetClassNameFor(enlistedAdapter);
-        final Bookmark enlistedTarget = enlistedAdapterOid.asBookmark();
-
-        final EventMetadata metadata = newEventMetadata(
-                currentUser, timestamp, changeKind, enlistedAdapterClass, enlistedTarget);
-
-        final Object pojo = ObjectAdapter.Util.unwrap(undeletedElseEmpty(enlistedAdapter));
-        final EventPayload payload = payloadFactory.payloadFor(pojo, changeKind);
-
-        payload.withStringifier(stringifier);
-        publishingServiceIfAny.publish(metadata, payload);
-    }
-
-    private void publishObjectsToPublisherServices(
-            final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
-
-        if(changeKindByEnlistedAdapter.isEmpty()) {
-            return;
-        }
-
-        final ChangedObjects changedObjects = newEnlistedObjects(changeKindByEnlistedAdapter);
-
-        for (PublisherService publisherService : publisherServices) {
-            publisherService.publish(changedObjects);
-        }
-    }
-
-    private ChangedObjects newEnlistedObjects(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
-
-        final Command command = commandContext.getCommand();
-        final UUID transactionUuid = command.getTransactionId();
-
-        final String userName = userService.getUser().getName();
-        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
-
-        return new ChangedObjectsDefault(transactionUuid, userName, timestamp, changeKindByEnlistedAdapter);
-    }
-
-
-
-    @Programmatic
-    public void publishAction(
-            final Interaction.Execution execution,
-            final ObjectAction objectAction,
-            final IdentifiedHolder identifiedHolder,
-            final ObjectAdapter targetAdapter,
-            final List<ObjectAdapter> parameterAdapters,
-            final ObjectAdapter resultAdapter) {
-
-        publishActionToPublishingService(
-                objectAction, identifiedHolder, targetAdapter, parameterAdapters, resultAdapter
-        );
-
-        publishToPublisherServices(execution);
-    }
-
-    @Override
-    public void publishProperty(
-            final Interaction.Execution execution) {
-
-        publishToPublisherServices(execution);
-    }
-
-    private void publishActionToPublishingService(
-            final ObjectAction objectAction,
-            final IdentifiedHolder identifiedHolder,
-            final ObjectAdapter targetAdapter,
-            final List<ObjectAdapter> parameterAdapters,
-            final ObjectAdapter resultAdapter) {
-        if(publishingServiceIfAny == null) {
-            return;
-        }
-        final String currentUser = userService.getUser().getName();
-        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
-
-        final PublishedActionFacet publishedActionFacet =
-                identifiedHolder.getFacet(PublishedActionFacet.class);
-        if(publishedActionFacet == null) {
-            return;
-        }
-
-        final RootOid adapterOid = (RootOid) targetAdapter.getOid();
-        final String oidStr = getOidMarshaller().marshal(adapterOid);
-        final Identifier actionIdentifier = objectAction.getIdentifier();
-        final String title = oidStr + ": " + actionIdentifier.toNameParmsIdentityString();
-
-        final String actionTargetClass = CommandUtil.targetClassNameFor(targetAdapter);
-        final String actionTargetAction = CommandUtil.targetMemberNameFor(objectAction);
-        final Bookmark actionTarget = CommandUtil.bookmarkFor(targetAdapter);
-        final String actionMemberIdentifier = CommandUtil.memberIdentifierFor(objectAction);
-
-        final List<String> parameterNames;
-        final List<Class<?>> parameterTypes;
-        final Class<?> returnType;
-
-        if(identifiedHolder instanceof FacetedMethod) {
-            // should always be the case
-
-            final FacetedMethod facetedMethod = (FacetedMethod) identifiedHolder;
-            returnType = facetedMethod.getType();
-
-            final List<FacetedMethodParameter> parameters = facetedMethod.getParameters();
-            parameterNames = immutableList(Iterables.transform(parameters, FacetedMethodParameter.Functions.GET_NAME));
-            parameterTypes = immutableList(Iterables.transform(parameters, FacetedMethodParameter.Functions.GET_TYPE));
-        } else {
-            parameterNames = null;
-            parameterTypes = null;
-            returnType = null;
-        }
-
-        final Interaction interaction = interactionContext.getInteraction();
-
-        final int nextEventSequence = interaction.next(Interaction.Sequence.PUBLISHED_EVENT.id());
-        final UUID transactionId = interaction.getTransactionId();
-        final EventMetadata metadata = new EventMetadata(
-                transactionId, nextEventSequence, EventType.ACTION_INVOCATION, currentUser, timestamp, title,
-                actionTargetClass, actionTargetAction, actionTarget, actionMemberIdentifier, parameterNames,
-                parameterTypes, returnType);
-
-        final PublishedAction.PayloadFactory payloadFactory = publishedActionFacet.value();
-
-        final ObjectStringifier stringifier = objectStringifier();
-
-        final EventPayload payload = payloadFactory.payloadFor(
-                identifiedHolder.getIdentifier(),
-                ObjectAdapter.Util.unwrap(undeletedElseEmpty(targetAdapter)),
-                ObjectAdapter.Util.unwrap(undeletedElseEmpty(parameterAdapters)),
-                ObjectAdapter.Util.unwrap(undeletedElseEmpty(resultAdapter)));
-        payload.withStringifier(stringifier);
-        publishingServiceIfAny.publish(metadata, payload);
-    }
-
-    private static <T> List<T> immutableList(final Iterable<T> iterable) {
-        return Collections.unmodifiableList(Lists.newArrayList(iterable));
-    }
-
-    private ObjectStringifier objectStringifier() {
-        return new ObjectStringifier() {
-                @Override
-                public String toString(Object object) {
-                    if(object == null) {
-                        return null;
-                    }
-                    final ObjectAdapter adapter = IsisContext.getPersistenceSession().adapterFor(object);
-                    Oid oid = adapter.getOid();
-                    return oid != null? oid.enString(getOidMarshaller()): encodedValueOf(adapter);
-                }
-                private String encodedValueOf(ObjectAdapter adapter) {
-                    EncodableFacet facet = adapter.getSpecification().getFacet(EncodableFacet.class);
-                    return facet != null? facet.toEncodedString(adapter): adapter.toString();
-                }
-                @Override
-                public String classNameOf(Object object) {
-                    final ObjectAdapter adapter = getPersistenceSession().adapterFor(object);
-                    final String className = adapter.getSpecification().getFullIdentifier();
-                    return className;
-                }
-            };
-    }
-
-    private static List<ObjectAdapter> undeletedElseEmpty(List<ObjectAdapter> parameters) {
-        return Lists.newArrayList(Iterables.transform(parameters, NOT_DESTROYED_ELSE_EMPTY));
-    }
-
-    private static ObjectAdapter undeletedElseEmpty(ObjectAdapter adapter) {
-        return NOT_DESTROYED_ELSE_EMPTY.apply(adapter);
-    }
-
-    protected OidMarshaller getOidMarshaller() {
-        return IsisContext.getOidMarshaller();
-    }
-
-    private EventMetadata newEventMetadata(
-            final String currentUser,
-            final Timestamp timestamp,
-            final ChangeKind changeKind,
-            final String enlistedAdapterClass,
-            final Bookmark enlistedTarget) {
-        final EventType eventType = PublishingServiceInternalDefault.eventTypeFor(changeKind);
-
-        final Interaction interaction = interactionContext.getInteraction();
-
-        final int nextEventSequence = interaction.next(Interaction.Sequence.PUBLISHED_EVENT.id());
-        final UUID transactionId = interaction.getTransactionId();
-        return new EventMetadata(
-                transactionId, nextEventSequence, eventType, currentUser, timestamp, enlistedTarget.toString(),
-                enlistedAdapterClass, null, enlistedTarget, null, null, null, null);
-    }
-
-    private void publishToPublisherServices(final Interaction.Execution<?,?> execution) {
-
-        if(publisherServices == null || publisherServices.isEmpty()) {
-            return;
-        }
-
-        for (final PublisherService publisherService : publisherServices) {
-            publisherService.publish(execution);
-        }
-    }
-
-    @Inject
-    private List<PublisherService> publisherServices;
-
-    @Inject
-    private PublishingService publishingServiceIfAny;
-
-    @Inject
-    private ChangedObjectsServiceInternal changedObjectsServiceInternal;
-
-    @Inject
-    private InteractionDtoServiceInternal interactionDtoServiceInternal;
-
-    @Inject
-    private CommandContext commandContext;
-
-    @Inject
-    private InteractionContext interactionContext;
-
-    @Inject
-    private ClockService clockService;
-
-    @Inject
-    private UserService userService;
-
-
-    private IsisTransactionManager.PersistenceSessionTransactionManagement getPersistenceSession() {
-        return IsisContext.getPersistenceSession();
-    }
-
-}


[2/2] isis git commit: ISIS-1400: filter out any bulk-only actions.

Posted by da...@apache.org.
ISIS-1400: filter out any bulk-only actions.


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

Branch: refs/heads/ISIS-1291
Commit: b8d19e67d7f24c4a08683d1f77b9c80160d1dec8
Parents: ea7c190
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu May 12 15:51:12 2016 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu May 12 15:51:12 2016 +0100

----------------------------------------------------------------------
 .../services/grid/bootstrap3/GridSystemServiceBS3.java       | 8 ++++++--
 .../actionmenu/entityactions/EntityActionLinkFactory.java    | 3 ++-
 2 files changed, 8 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/b8d19e67/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
index 8e1eafa..1d51fff 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
@@ -40,6 +40,7 @@ import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.MemberGroupLayout;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.filter.Filters;
 import org.apache.isis.applib.layout.component.ActionLayoutData;
 import org.apache.isis.applib.layout.component.ActionLayoutDataOwner;
 import org.apache.isis.applib.layout.component.CollectionLayoutData;
@@ -173,8 +174,11 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
         final Map<String, OneToManyAssociation> oneToManyAssociationById =
                 ObjectMember.Util.mapById(getOneToManyAssociations(objectSpec));
         final Map<String, ObjectAction> objectActionById =
-                ObjectMember.Util.mapById(objectSpec.getObjectActions(Contributed.INCLUDED));
-
+                ObjectMember.Util.mapById(
+                        FluentIterable
+                            .from(objectSpec.getObjectActions(Contributed.INCLUDED))
+                            .filter(Filters.asPredicate(ObjectAction.Filters.notBulkOnly()))
+                            .toList());
 
         final BS3Grid bs3Grid = (BS3Grid) grid;
 

http://git-wip-us.apache.org/repos/asf/isis/blob/b8d19e67/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/entityactions/EntityActionLinkFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/entityactions/EntityActionLinkFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/entityactions/EntityActionLinkFactory.java
index 698dcf9..336f27e 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/entityactions/EntityActionLinkFactory.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/entityactions/EntityActionLinkFactory.java
@@ -56,7 +56,8 @@ public final class EntityActionLinkFactory extends ActionLinkFactoryAbstract {
         
         final Boolean persistent = objectAdapter.representsPersistent();
         if (!persistent) {
-            throw new IllegalArgumentException("Object '" + objectAdapter.titleString(null) + "' is not persistent.");
+            throw new IllegalArgumentException(String.format(
+                    "Object '%s' is not persistent.", objectAdapter.titleString(null)));
         }
 
         // check visibility and whether enabled