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/01 10:17:09 UTC

[32/35] isis git commit: ISIS-1370: move responsibility for publishing actions into the ActionInvocationFacet (instead of in IsisTransaction and DomainObjectInvocationHandler)

ISIS-1370: move responsibility for publishing actions into the ActionInvocationFacet (instead of in IsisTransaction and DomainObjectInvocationHandler)

To this end, extract interface for PublishingServiceInternal, move to metamodel module (out from runtime).
Also minor refactoring wihin PublishingServiceInternal(Default) itself.


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

Branch: refs/heads/ISIS-1291
Commit: ce533253f8540cf990746260acdd99dc2e574805
Parents: 02dd0ee
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Sun May 1 08:56:04 2016 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Sun May 1 08:56:04 2016 +0100

----------------------------------------------------------------------
 ...onInvocationFacetForDomainEventAbstract.java |  44 ++-
 .../publishing/PublishingServiceInternal.java   |  37 ++
 .../specimpl/ObjectActionContributee.java       |   3 +-
 .../transaction/PublishingServiceInternal.java  | 341 -------------------
 .../PublishingServiceInternalDefault.java       | 331 ++++++++++++++++++
 .../system/transaction/IsisTransaction.java     |   8 +-
 .../handlers/DomainObjectInvocationHandler.java |   8 +-
 7 files changed, 396 insertions(+), 376 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/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 1102356..573e815 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
@@ -70,6 +70,7 @@ import org.apache.isis.core.metamodel.facets.actions.semantics.ActionSemanticsFa
 import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.services.publishing.PublishingServiceInternal;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.specloader.ReflectiveActionException;
@@ -230,7 +231,7 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
 
     }
 
-    protected InvocationResult invoke(
+    private InvocationResult invoke(
             final ObjectAction owningAction,
             final ObjectAdapter targetAdapter,
             final ObjectAdapter[] arguments) {
@@ -316,10 +317,9 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
             // persist command so can be this command can be in the 'background'
             final CommandService commandService = getCommandService();
             if (!commandService.persistIfPossible(command)) {
-                throw new IsisException(
-                        "Unable to schedule action '"
-                                + owningAction.getIdentifier().toClassAndNameIdentityString() + "' to run in background: "
-                                + "CommandService does not support persistent commands " );
+                throw new IsisException(String.format(
+                        "Unable to schedule action '%s' to run in background; CommandService does not support persistent commands ",
+                                owningAction.getIdentifier().toClassAndNameIdentityString()));
             }
             resultAdapter = getAdapterManager().adapterFor(command);
 
@@ -367,7 +367,17 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
             setCommandResultIfEntity(command, resultAdapter);
 
             // TODO: use InteractionContext instead
-            captureCurrentInvocationForPublishing(owningAction, targetAdapter, argumentAdapters, command, resultAdapter);
+
+            final PublishedActionFacet publishedActionFacet = getIdentified().getFacet(PublishedActionFacet.class);
+            if (publishedActionFacet != null && currentInvocation.get() == null) {
+                final CurrentInvocation currentInvocation1 = new CurrentInvocation(
+                        targetAdapter, owningAction, getIdentified(),
+                        argumentAdapters, resultAdapter, command);
+                currentInvocation.set(currentInvocation1);
+            }
+
+            getPublishingServiceInternal().publishAction();
+
 
         }
         return resultAdapter;
@@ -482,24 +492,6 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
         return lookupServiceIfAny(RepositoryService.class);
     }
 
-    protected void captureCurrentInvocationForPublishing(
-            final ObjectAction owningAction,
-            final ObjectAdapter targetAdapter,
-            final ObjectAdapter[] arguments,
-            final Command command,
-            final ObjectAdapter resultAdapter) {
-
-        // TODO: should instead be using the top-level ActionInvocationMemento associated with command.
-
-        final PublishedActionFacet publishedActionFacet = getIdentified().getFacet(PublishedActionFacet.class);
-        if (publishedActionFacet != null && currentInvocation.get() == null) {
-            final CurrentInvocation currentInvocation = new CurrentInvocation(
-                    targetAdapter, owningAction, getIdentified(),
-                    arguments, resultAdapter, command);
-            ActionInvocationFacet.currentInvocation.set(currentInvocation);
-        }
-    }
-
     protected ObjectAdapter filteredIfRequired(
             final ObjectAdapter resultAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
@@ -589,6 +581,10 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
         return lookupService(ClockService.class);
     }
 
+    private PublishingServiceInternal getPublishingServiceInternal() {
+        return lookupService(PublishingServiceInternal.class);
+    }
+
     private <T> T lookupService(final Class<T> serviceClass) {
         T service = lookupServiceIfAny(serviceClass);
         if(service == null) {

http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/PublishingServiceInternal.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/PublishingServiceInternal.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/PublishingServiceInternal.java
new file mode 100644
index 0000000..397d952
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/PublishingServiceInternal.java
@@ -0,0 +1,37 @@
+/*
+ *  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.metamodel.services.publishing;
+
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.annotation.PublishedObject;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+
+public interface PublishingServiceInternal {
+    @Programmatic
+    boolean canPublish();
+
+    @Programmatic
+    void publishObjects(
+            final Map<ObjectAdapter, PublishedObject.ChangeKind> changeKindByEnlistedAdapter);
+
+    @Programmatic
+    void publishAction();
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
index b41f21d..ce336cb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
@@ -193,7 +193,8 @@ public class ObjectActionContributee extends ObjectActionDefault implements Cont
         setupActionInvocationContext(targetAdapter);
         setupCommandTarget(targetAdapter, arguments);
 
-        return serviceAction.execute(getServiceAdapter(), mixedInAdapter, argsPlusContributee(targetAdapter, arguments),
+        return serviceAction.execute(
+                getServiceAdapter(), mixedInAdapter, argsPlusContributee(targetAdapter, arguments),
                 interactionInitiatedBy);
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternal.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternal.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternal.java
deleted file mode 100644
index 365760d..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternal.java
+++ /dev/null
@@ -1,341 +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.persistence.objectstore.transaction;
-
-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 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.iactn.Interaction;
-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.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.ActionInvocationFacet;
-import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacet.CurrentInvocation;
-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.spec.feature.ObjectAction;
-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 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 + "'");
-    }
-
-    @Programmatic
-    public boolean canPublish() {
-        return publishingServiceIfAny != null;
-    }
-
-    @Programmatic
-    public void publishObjects(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
-
-        if(!canPublish()) {
-            return;
-        }
-
-        final String currentUser = userService.getUser().getName();
-        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
-
-        // take a copy of enlisted adapters ... the JDO implementation of the PublishingService
-        // creates further entities which would be enlisted; taking copy of the keys avoids ConcurrentModificationException
-        List<ObjectAdapter> enlistedAdapters = Lists.newArrayList(changeKindByEnlistedAdapter.keySet());
-        for (final ObjectAdapter enlistedAdapter : enlistedAdapters) {
-            final ChangeKind changeKind = changeKindByEnlistedAdapter.get(enlistedAdapter);
-            publishObject(enlistedAdapter, changeKind, currentUser,
-                    timestamp);
-        }
-    }
-
-
-    private void publishObject(
-            final ObjectAdapter enlistedAdapter,
-            final ChangeKind changeKind,
-            final String currentUser,
-            final Timestamp timestamp) {
-
-        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);
-
-        publishObject(payloadFactory, metadata, enlistedAdapter, changeKind);
-    }
-
-    @Programmatic
-    public void publishObject(
-            final PublishedObject.PayloadFactory payloadFactory,
-            final EventMetadata metadata,
-            final ObjectAdapter changedAdapter,
-            final ChangeKind changeKind) {
-
-        if(!canPublish()) {
-            return;
-        }
-
-        final ObjectStringifier stringifier = objectStringifier();
-
-        final EventPayload payload = payloadFactory.payloadFor(
-                ObjectAdapter.Util.unwrap(undeletedElseEmpty(changedAdapter)), changeKind);
-        payload.withStringifier(stringifier);
-        publishingServiceIfAny.publish(metadata, payload);
-    }
-
-
-    @Programmatic
-    public void publishAction() {
-
-        if(!canPublish()) {
-            return;
-        }
-
-        final String currentUser = userService.getUser().getName();
-        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
-
-        try {
-            final CurrentInvocation currentInvocation = ActionInvocationFacet.currentInvocation.get();
-            if(currentInvocation == null) {
-                return;
-            }
-            ObjectAction currentAction = currentInvocation.getAction();
-            IdentifiedHolder currentInvocationHolder = currentInvocation.getIdentifiedHolder();
-
-            final PublishedActionFacet publishedActionFacet = currentInvocationHolder.getFacet(PublishedActionFacet.class);
-            if(publishedActionFacet == null) {
-                return;
-            }
-
-            final ObjectAdapter targetAdapter = currentInvocation.getTarget();
-
-            final RootOid adapterOid = (RootOid) targetAdapter.getOid();
-            final String oidStr = getOidMarshaller().marshal(adapterOid);
-            final Identifier actionIdentifier = currentAction.getIdentifier();
-            final String title = oidStr + ": " + actionIdentifier.toNameParmsIdentityString();
-
-            final String actionTargetClass = CommandUtil.targetClassNameFor(targetAdapter);
-            final String actionTargetAction = CommandUtil.targetActionNameFor(currentAction);
-            final Bookmark actionTarget = CommandUtil.bookmarkFor(targetAdapter);
-            final String actionMemberIdentifier = CommandUtil.actionIdentifierFor(currentAction);
-
-            final List<String> parameterNames;
-            final List<Class<?>> parameterTypes;
-            final Class<?> returnType;
-
-            if(currentInvocationHolder instanceof FacetedMethod) {
-                // should always be the case
-
-                final FacetedMethod facetedMethod = (FacetedMethod) currentInvocationHolder;
-                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 Command command = commandContext.getCommand();
-
-            final Command command1 = commandContext.getCommand();
-
-            final Interaction.SequenceName sequenceName = Interaction.SequenceName.PUBLISHED_EVENT;
-            final int nextEventSequence = command1.next(sequenceName.abbr());
-            final UUID transactionId = command1.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 ObjectAdapter target = currentInvocation.getTarget();
-            final ObjectAdapter result = currentInvocation.getResult();
-            final List<ObjectAdapter> parameters = currentInvocation.getParameters();
-            final EventPayload payload = payloadFactory.payloadFor(
-                    currentInvocation.getIdentifiedHolder().getIdentifier(),
-                    ObjectAdapter.Util.unwrap(undeletedElseEmpty(target)),
-                    ObjectAdapter.Util.unwrap(undeletedElseEmpty(parameters)),
-                    ObjectAdapter.Util.unwrap(undeletedElseEmpty(result)));
-            payload.withStringifier(stringifier);
-            publishingServiceIfAny.publish(metadata, payload);
-        } finally {
-            // ensures that cannot publish this action more than once
-            ActionInvocationFacet.currentInvocation.set(null);
-        }
-    }
-
-    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 = PublishingServiceInternal.eventTypeFor(changeKind);
-
-        final Command command = commandContext.getCommand();
-
-        final Interaction.SequenceName sequenceName = Interaction.SequenceName.PUBLISHED_EVENT;
-        final int nextEventSequence = command.next(sequenceName.abbr());
-        final UUID transactionId = command.getTransactionId();
-        return new EventMetadata(
-                transactionId, nextEventSequence, eventType, currentUser, timestamp, enlistedTarget.toString(),
-                enlistedAdapterClass, null, enlistedTarget, null, null, null, null);
-    }
-
-    private IsisTransactionManager.PersistenceSessionTransactionManagement getPersistenceSession() {
-        return IsisContext.getPersistenceSession();
-    }
-
-
-    @Inject
-    private PublishingService publishingServiceIfAny;
-
-    @Inject
-    private CommandContext commandContext;
-
-    @Inject
-    private ClockService clockService;
-
-    @Inject
-    private UserService userService;
-
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternalDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternalDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternalDefault.java
new file mode 100644
index 0000000..91213bd
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceInternalDefault.java
@@ -0,0 +1,331 @@
+/*
+ *  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.persistence.objectstore.transaction;
+
+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 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.iactn.Interaction;
+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.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.ActionInvocationFacet;
+import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacet.CurrentInvocation;
+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.publishing.PublishingServiceInternal;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+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 boolean canPublish() {
+        return publishingServiceIfAny != null;
+    }
+
+    @Override @Programmatic
+    public void publishObjects(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
+
+        if(!canPublish()) {
+            return;
+        }
+
+        final String currentUser = userService.getUser().getName();
+        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
+        final ObjectStringifier stringifier = objectStringifier();
+
+        // take a copy of enlisted adapters ... the JDO implementation of the PublishingService
+        // creates further entities which would be enlisted; taking copy of the keys avoids ConcurrentModificationException
+        final List<ObjectAdapter> enlistedAdapters =
+                Lists.newArrayList(changeKindByEnlistedAdapter.keySet());
+
+        for (final ObjectAdapter enlistedAdapter : enlistedAdapters) {
+            final ChangeKind changeKind = changeKindByEnlistedAdapter.get(enlistedAdapter);
+
+            publishObject(enlistedAdapter, changeKind, currentUser, timestamp, stringifier);
+        }
+    }
+
+    private void publishObject(
+            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);
+    }
+
+    @Override @Programmatic
+    public void publishAction() {
+
+        if(!canPublish()) {
+            return;
+        }
+
+        final String currentUser = userService.getUser().getName();
+        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
+
+        try {
+            final CurrentInvocation currentInvocation = ActionInvocationFacet.currentInvocation.get();
+            if(currentInvocation == null) {
+                return;
+            }
+            ObjectAction currentAction = currentInvocation.getAction();
+            IdentifiedHolder currentInvocationHolder = currentInvocation.getIdentifiedHolder();
+
+            final PublishedActionFacet publishedActionFacet =
+                    currentInvocationHolder.getFacet(PublishedActionFacet.class);
+            if(publishedActionFacet == null) {
+                return;
+            }
+
+            final ObjectAdapter targetAdapter = currentInvocation.getTarget();
+
+            final RootOid adapterOid = (RootOid) targetAdapter.getOid();
+            final String oidStr = getOidMarshaller().marshal(adapterOid);
+            final Identifier actionIdentifier = currentAction.getIdentifier();
+            final String title = oidStr + ": " + actionIdentifier.toNameParmsIdentityString();
+
+            final String actionTargetClass = CommandUtil.targetClassNameFor(targetAdapter);
+            final String actionTargetAction = CommandUtil.targetActionNameFor(currentAction);
+            final Bookmark actionTarget = CommandUtil.bookmarkFor(targetAdapter);
+            final String actionMemberIdentifier = CommandUtil.actionIdentifierFor(currentAction);
+
+            final List<String> parameterNames;
+            final List<Class<?>> parameterTypes;
+            final Class<?> returnType;
+
+            if(currentInvocationHolder instanceof FacetedMethod) {
+                // should always be the case
+
+                final FacetedMethod facetedMethod = (FacetedMethod) currentInvocationHolder;
+                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 Command command = commandContext.getCommand();
+
+            final Command command1 = commandContext.getCommand();
+
+            final Interaction.SequenceName sequenceName = Interaction.SequenceName.PUBLISHED_EVENT;
+            final int nextEventSequence = command1.next(sequenceName.abbr());
+            final UUID transactionId = command1.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 ObjectAdapter target = currentInvocation.getTarget();
+            final ObjectAdapter result = currentInvocation.getResult();
+            final List<ObjectAdapter> parameters = currentInvocation.getParameters();
+            final EventPayload payload = payloadFactory.payloadFor(
+                    currentInvocation.getIdentifiedHolder().getIdentifier(),
+                    ObjectAdapter.Util.unwrap(undeletedElseEmpty(target)),
+                    ObjectAdapter.Util.unwrap(undeletedElseEmpty(parameters)),
+                    ObjectAdapter.Util.unwrap(undeletedElseEmpty(result)));
+            payload.withStringifier(stringifier);
+            publishingServiceIfAny.publish(metadata, payload);
+        } finally {
+            // ensures that cannot publish this action more than once
+            ActionInvocationFacet.currentInvocation.set(null);
+        }
+    }
+
+    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 Command command = commandContext.getCommand();
+
+        final Interaction.SequenceName sequenceName = Interaction.SequenceName.PUBLISHED_EVENT;
+        final int nextEventSequence = command.next(sequenceName.abbr());
+        final UUID transactionId = command.getTransactionId();
+        return new EventMetadata(
+                transactionId, nextEventSequence, eventType, currentUser, timestamp, enlistedTarget.toString(),
+                enlistedAdapterClass, null, enlistedTarget, null, null, null, null);
+    }
+
+    private IsisTransactionManager.PersistenceSessionTransactionManagement getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+
+    @Inject
+    private PublishingService publishingServiceIfAny;
+
+    @Inject
+    private CommandContext commandContext;
+
+    @Inject
+    private ClockService clockService;
+
+    @Inject
+    private UserService userService;
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransaction.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransaction.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransaction.java
index 0f29521..5fc4934 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransaction.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransaction.java
@@ -63,13 +63,14 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
 import org.apache.isis.core.metamodel.facets.object.audit.AuditableFacet;
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.services.publishing.PublishingServiceInternal;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.core.metamodel.transactions.TransactionState;
 import org.apache.isis.core.runtime.persistence.objectstore.transaction.CreateObjectCommand;
 import org.apache.isis.core.runtime.persistence.objectstore.transaction.DestroyObjectCommand;
 import org.apache.isis.core.runtime.persistence.objectstore.transaction.PersistenceCommand;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.PublishingServiceInternal;
+import org.apache.isis.core.runtime.persistence.objectstore.transaction.PublishingServiceInternalDefault;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 
 import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
@@ -253,7 +254,7 @@ public class IsisTransaction implements TransactionScopedComponent {
         this.interactionContext = lookupService(InteractionContext.class);
 
         this.auditingServiceIfAny = lookupServiceIfAny(AuditingService3.class);
-        this.publishingServiceInternal = lookupService(PublishingServiceInternal.class);
+        this.publishingServiceInternal = lookupService(PublishingServiceInternalDefault.class);
 
 
         this.transactionId = transactionId;
@@ -563,9 +564,6 @@ public class IsisTransaction implements TransactionScopedComponent {
 
         doAudit(changedObjectProperties);
 
-        publishingServiceInternal.publishAction();
-        doFlush();
-
         publishingServiceInternal.publishObjects(this.changeKindByEnlistedAdapter);
         doFlush();
 

http://git-wip-us.apache.org/repos/asf/isis/blob/ce533253/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
index c9f6bc9..90382f2 100644
--- a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
@@ -61,6 +61,7 @@ import org.apache.isis.core.metamodel.facets.ImperativeFacet;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet.Intent;
 import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
 import org.apache.isis.core.metamodel.runtimecontext.PersistenceSessionService;
+import org.apache.isis.core.metamodel.services.publishing.PublishingServiceInternal;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.SpecificationLoader;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
@@ -72,7 +73,7 @@ import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember;
 import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionContributee;
 import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.PublishingServiceInternal;
+import org.apache.isis.core.runtime.persistence.objectstore.transaction.PublishingServiceInternalDefault;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 
 public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandlerDefault<T> {
@@ -629,9 +630,6 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
                             targetAdapter, mixedInAdapter, argAdapters,
                             interactionInitiatedBy);
 
-            final PublishingServiceInternal publishingServiceInternal = getPublishingServiceInternal();
-            publishingServiceInternal.publishAction();
-
             return ObjectAdapter.Util.unwrap(returnedAdapter);
         }
 
@@ -808,7 +806,7 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
 
     private PublishingServiceInternal getPublishingServiceInternal() {
         return IsisContext.getPersistenceSession()
-                .getServicesInjector().lookupService(PublishingServiceInternal.class);
+                .getServicesInjector().lookupService(PublishingServiceInternalDefault.class);
     }