You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/04/20 10:32:44 UTC

git commit: ISIS-387: publish create and delete objects as well as updates

Updated Branches:
  refs/heads/master f6386b443 -> 7e6fbb337


ISIS-387: publish create and delete objects as well as updates

Changes to:
- applib (EventType enum)
- core (IsisTransaction, adding guards to prevent 'touching' deleted objects)
- jdo objectstore (enlisting changes)
- and restful (event serializer)


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

Branch: refs/heads/master
Commit: 7e6fbb33773b1e218fa9bd0e04a581f78acc6fe8
Parents: f6386b4
Author: Dan Haywood <da...@apache.org>
Authored: Sat Apr 20 09:32:28 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Sat Apr 20 09:32:28 2013 +0100

----------------------------------------------------------------------
 .../applib/service/audit/AuditingServiceJdo.java   |    5 +
 .../service/publish/PublishingServiceJdo.java      |    3 -
 .../persistence/FrameworkSynchronizer.java         |   37 +++-
 .../persistence/IsisLifecycleListener.java         |    9 +-
 .../isis/applib/annotation/PublishedObject.java    |   10 +-
 .../publish/EventPayloadForActionInvocation.java   |    2 +-
 .../publish/EventPayloadForObjectChanged.java      |    2 +-
 .../isis/applib/services/publish/EventType.java    |    4 +-
 ...blishingServiceWithDefaultPayloadFactories.java |   47 ++++-
 .../system/transaction/IsisTransaction.java        |  169 ++++++++++-----
 .../system/transaction/IsisTransactionManager.java |    3 +-
 .../dom/todo/ToDoItemChangedPayloadFactory.java    |    3 +-
 .../src/main/webapp/WEB-INF/isis.properties        |    7 +-
 13 files changed, 227 insertions(+), 74 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/audit/AuditingServiceJdo.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/audit/AuditingServiceJdo.java b/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/audit/AuditingServiceJdo.java
index ccd1511..43b2af0 100644
--- a/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/audit/AuditingServiceJdo.java
+++ b/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/audit/AuditingServiceJdo.java
@@ -21,11 +21,16 @@ package org.apache.isis.objectstore.jdo.applib.service.audit;
 import java.util.List;
 
 import org.apache.isis.applib.AbstractFactoryAndRepository;
+import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.Hidden;
+import org.apache.isis.applib.annotation.Named;
+import org.apache.isis.applib.annotation.ActionSemantics.Of;
 import org.apache.isis.objectstore.jdo.applib.AuditService;
 
+@Named("Auditing")
 public class AuditingServiceJdo extends AbstractFactoryAndRepository  implements AuditService {
     
+    @ActionSemantics(Of.SAFE)
     public List<AuditEntry> list() {
         return allInstances(AuditEntry.class);
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/publish/PublishingServiceJdo.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/publish/PublishingServiceJdo.java b/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/publish/PublishingServiceJdo.java
index e8ba8a8..06f6522 100644
--- a/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/publish/PublishingServiceJdo.java
+++ b/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/publish/PublishingServiceJdo.java
@@ -5,17 +5,14 @@ import java.util.List;
 import org.apache.isis.applib.AbstractService;
 import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.ActionSemantics.Of;
-import org.apache.isis.applib.annotation.Bulk;
 import org.apache.isis.applib.annotation.Hidden;
 import org.apache.isis.applib.annotation.MemberOrder;
 import org.apache.isis.applib.annotation.Named;
-import org.apache.isis.applib.annotation.NotInServiceMenu;
 import org.apache.isis.applib.query.QueryDefault;
 import org.apache.isis.applib.services.publish.EventMetadata;
 import org.apache.isis.applib.services.publish.EventPayload;
 import org.apache.isis.applib.services.publish.EventSerializer;
 import org.apache.isis.applib.services.publish.PublishingService;
-import org.apache.isis.objectstore.jdo.applib.service.publish.PublishedEvent.State;
 
 /**
  * An implementation of {@link PublishingService} that persists events as

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
index 4703c05..35e6099 100644
--- a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
+++ b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
@@ -50,6 +50,7 @@ import org.apache.isis.core.runtime.system.persistence.OidGenerator;
 import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
 import org.apache.isis.objectstore.jdo.datanucleus.DataNucleusObjectStore;
+import org.apache.isis.objectstore.jdo.datanucleus.persistence.FrameworkSynchronizer.CalledFrom;
 
 public class FrameworkSynchronizer {
 
@@ -62,7 +63,7 @@ public class FrameworkSynchronizer {
      * Just used for logging.
      */
     public enum CalledFrom {
-        EVENT_LOAD, EVENT_STORE, EVENT_PREDIRTY, OS_QUERY, OS_RESOLVE, OS_LAZILYLOADED
+        EVENT_LOAD, EVENT_STORE, EVENT_PREDIRTY, OS_QUERY, OS_RESOLVE, OS_LAZILYLOADED, EVENT_PREDELETE
     }
 
 
@@ -131,13 +132,21 @@ public class FrameworkSynchronizer {
                 
                 Class<? extends CallbackFacet> callbackFacetClass;
                 if (isisOid.isTransient()) {
+                    // persisting
                     final RootOid persistentOid = getOidGenerator().createPersistent(pojo, isisOid);
                     
                     getPersistenceSession().remapAsPersistent(adapter, persistentOid);
 
                     callbackFacetClass = PersistedCallbackFacet.class;
+                    
+                    final IsisTransaction transaction = getCurrentTransaction();
+                    transaction.enlistCreated(adapter);
                 } else {
+                    // updating
                     callbackFacetClass = UpdatedCallbackFacet.class;
+                    
+                    // no need to call transaction.enlist(..); 
+                    // already called in preDirty and the post value is captured lazily
                 }
                 
                 Utils.clearDirtyFor(adapter);
@@ -146,6 +155,7 @@ public class FrameworkSynchronizer {
                 adapter.setVersion(versionIfAny);
                 CallbackUtils.callCallback(adapter, callbackFacetClass);
 
+                
                 ensureFrameworksInAgreement(pojo);
             }
         }, calledFrom);
@@ -155,9 +165,7 @@ public class FrameworkSynchronizer {
         withLogging(pojo, new Runnable() {
             @Override
             public void run() {
-                final IsisTransaction transaction = getCurrentTransaction();
                 ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
-                
                 if (adapter == null) {
                     // seen this happen in the case when a parent entity (LeaseItem) has a collection of children
                     // objects (LeaseTerm) for which we haven't had a loaded callback fired and so are not yet
@@ -180,7 +188,8 @@ public class FrameworkSynchronizer {
                     // hasn't yet executed, so thinks that the adapter is still transient. 
                     return;
                 }
-                transaction.auditDirty(adapter);
+                final IsisTransaction transaction = getCurrentTransaction();
+                transaction.enlistUpdating(adapter);
 
                 ensureRootObject(pojo);
                 ensureFrameworksInAgreement(pojo);
@@ -204,6 +213,22 @@ public class FrameworkSynchronizer {
         }, calledFrom);
     }
 
+    
+    public void preDeleteProcessingFor(final PersistenceCapable pojo, final CalledFrom calledFrom) {
+        withLogging(pojo, new Runnable() {
+            @Override
+            public void run() {
+                ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
+                
+                final IsisTransaction transaction = getCurrentTransaction();
+                transaction.enlistDeleting(adapter);
+
+                ensureFrameworksInAgreement(pojo);
+            }
+        }, calledFrom);
+        
+    }
+
     // /////////////////////////////////////////////////////////
     // Helpers
     // /////////////////////////////////////////////////////////
@@ -244,7 +269,7 @@ public class FrameworkSynchronizer {
 
 
     // /////////////////////////////////////////////////////////
-    // Helpers
+    // More Helpers...
     // /////////////////////////////////////////////////////////
 
     void ensureFrameworksInAgreement(final PersistenceCapable pojo) {
@@ -350,4 +375,6 @@ public class FrameworkSynchronizer {
     }
 
 
+
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/IsisLifecycleListener.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/IsisLifecycleListener.java b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/IsisLifecycleListener.java
index 41266c6..c9118a8 100644
--- a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/IsisLifecycleListener.java
+++ b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/IsisLifecycleListener.java
@@ -119,7 +119,14 @@ public class IsisLifecycleListener implements AttachLifecycleListener, ClearLife
 
     @Override
     public void preDelete(InstanceLifecycleEvent event) {
-        withLogging(Phase.PRE, event, new RunnableEnsureFrameworksInAgreement(event));
+
+        withLogging(Phase.PRE, event, new RunnableAbstract(event){
+            @Override
+            protected void doRun() {
+                final PersistenceCapable pojo = Utils.persistenceCapableFor(event);
+                synchronizer.preDeleteProcessingFor(pojo, CalledFrom.EVENT_PREDELETE);
+            }
+        });
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/core/applib/src/main/java/org/apache/isis/applib/annotation/PublishedObject.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/PublishedObject.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/PublishedObject.java
index f67c9bb..2d493b5 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/PublishedObject.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/PublishedObject.java
@@ -29,7 +29,7 @@ import org.apache.isis.applib.services.publish.EventPayload;
 import org.apache.isis.applib.services.publish.PublishingService;
 
 /**
- * Indicates that changes to the object's (properties) should be published.
+ * Indicates that changes to an object should be published.
  * 
  * <p>
  * Requires that an implementation of the {@link PublishingService} is registered with the framework.
@@ -39,9 +39,15 @@ import org.apache.isis.applib.services.publish.PublishingService;
 @Retention(RetentionPolicy.RUNTIME)
 public @interface PublishedObject {
     
+    public enum ChangeKind {
+        CREATE,
+        UPDATE,
+        DELETE
+    }
+    
     public interface PayloadFactory {
         @Programmatic
-        public EventPayload payloadFor(Object changedObject);
+        public EventPayload payloadFor(Object changedObject, ChangeKind changeKind);
     }
     
     Class<? extends PayloadFactory> value() default PayloadFactory.class;

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForActionInvocation.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForActionInvocation.java b/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForActionInvocation.java
index df7b63b..4ea80bb 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForActionInvocation.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForActionInvocation.java
@@ -135,7 +135,7 @@ public class EventPayloadForActionInvocation<T> implements EventPayload {
         }
 
         final StringBuilder buf = new StringBuilder();
-        buf.append(EventType.ACTION_INVOCATION + ":").append(getActionName());
+        buf.append(getActionName());
         buf.append("\n    target=").append(stringifier.toString(target));
         buf.append("\n      args=[");
         for (Object arg : arguments) {

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForObjectChanged.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForObjectChanged.java b/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForObjectChanged.java
index c52e0a6..395b935 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForObjectChanged.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventPayloadForObjectChanged.java
@@ -44,6 +44,6 @@ public class EventPayloadForObjectChanged<T> implements EventPayload {
         if(stringifier == null) {
             throw new IllegalStateException("ObjectStringifier has not been injected");
         }
-        return EventType.OBJECT_CHANGED + ":"+ stringifier.toString(changed);
+        return stringifier.toString(changed);
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventType.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventType.java b/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventType.java
index 31f4166..26d9e64 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventType.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/publish/EventType.java
@@ -2,5 +2,7 @@ package org.apache.isis.applib.services.publish;
 
 public enum EventType {
     ACTION_INVOCATION,
-    OBJECT_CHANGED
+    OBJECT_CREATED,
+    OBJECT_UPDATED,
+    OBJECT_DELETED,
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceWithDefaultPayloadFactories.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceWithDefaultPayloadFactories.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceWithDefaultPayloadFactories.java
index aecbb38..fad9eba 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceWithDefaultPayloadFactories.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/objectstore/transaction/PublishingServiceWithDefaultPayloadFactories.java
@@ -1,7 +1,14 @@
 package org.apache.isis.core.runtime.persistence.objectstore.transaction;
 
+import java.util.List;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
 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.publish.EventMetadata;
 import org.apache.isis.applib.services.publish.EventPayload;
 import org.apache.isis.applib.services.publish.ObjectStringifier;
@@ -9,6 +16,8 @@ import org.apache.isis.applib.services.publish.PublishingService;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facets.actions.invoke.ActionInvocationFacet.CurrentInvocation;
 import org.apache.isis.core.metamodel.spec.ObjectAdapterUtils;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 
 /**
  * Wrapper around {@link PublishingService} that also includes the
@@ -19,6 +28,23 @@ public class PublishingServiceWithDefaultPayloadFactories {
     private final PublishingService publishingService;
     private final PublishedObject.PayloadFactory defaultObjectPayloadFactory;
     private final PublishedAction.PayloadFactory defaultActionPayloadFactory;
+
+    private final static Function<ObjectAdapter, ObjectAdapter> NOT_DESTROYED_ELSE_EMPTY = new Function<ObjectAdapter, ObjectAdapter>() {
+        public ObjectAdapter apply(ObjectAdapter adapter) {
+            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 = adapter.getSpecification().createObject();
+            getPersistenceSession().remapRecreatedPojo(adapter, replacementObject);
+            return adapter;
+        }
+        protected PersistenceSession getPersistenceSession() {
+            return IsisContext.getPersistenceSession();
+        }
+
+    };
     
     public PublishingServiceWithDefaultPayloadFactories (
             final PublishingService publishingService, 
@@ -33,12 +59,14 @@ public class PublishingServiceWithDefaultPayloadFactories {
             final PublishedObject.PayloadFactory payloadFactoryIfAny, 
             final EventMetadata metadata, 
             final ObjectAdapter changedAdapter, 
+            final ChangeKind changeKind, 
             final ObjectStringifier stringifier) {
         final PublishedObject.PayloadFactory payloadFactoryToUse = 
                 payloadFactoryIfAny != null
                 ? payloadFactoryIfAny
                 : defaultObjectPayloadFactory;
-        final EventPayload payload = payloadFactoryToUse.payloadFor(ObjectAdapterUtils.unwrapObject(changedAdapter));
+        final EventPayload payload = payloadFactoryToUse.payloadFor(
+                ObjectAdapterUtils.unwrapObject(undeletedElseEmpty(changedAdapter)), changeKind);
         payload.withStringifier(stringifier);
         publishingService.publish(metadata, payload);
     }
@@ -52,12 +80,23 @@ public class PublishingServiceWithDefaultPayloadFactories {
                 payloadFactoryIfAny != null
                 ? payloadFactoryIfAny
                 : defaultActionPayloadFactory;
+        ObjectAdapter target = currentInvocation.getTarget();
+        ObjectAdapter result = currentInvocation.getResult();
+        List<ObjectAdapter> parameters = currentInvocation.getParameters();
         final EventPayload payload = payloadFactoryToUse.payloadFor(
                 currentInvocation.getAction().getIdentifier(),
-                ObjectAdapterUtils.unwrapObject(currentInvocation.getTarget()), 
-                ObjectAdapterUtils.unwrapObjects(currentInvocation.getParameters()), 
-                ObjectAdapterUtils.unwrapObject(currentInvocation.getResult()));
+                ObjectAdapterUtils.unwrapObject(undeletedElseEmpty(target)), 
+                ObjectAdapterUtils.unwrapObjects(undeletedElseEmpty(parameters)), 
+                ObjectAdapterUtils.unwrapObject(undeletedElseEmpty(result)));
         payload.withStringifier(stringifier);
         publishingService.publish(metadata, payload);
     }
+
+    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);
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/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 0e77422..162a526 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
@@ -32,17 +32,19 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.UUID;
 
+import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-import com.google.common.net.MediaType;
 
 import org.apache.log4j.Logger;
 
 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.clock.Clock;
 import org.apache.isis.applib.services.audit.AuditingService;
 import org.apache.isis.applib.services.publish.EventMetadata;
@@ -74,6 +76,8 @@ import org.apache.isis.core.runtime.persistence.objectstore.transaction.Publishi
 import org.apache.isis.core.runtime.persistence.objectstore.transaction.SaveObjectCommand;
 import org.apache.isis.core.runtime.persistence.objectstore.transaction.TransactionalResource;
 import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.transaction.IsisTransaction.AdapterAndProperty;
+import org.apache.isis.core.runtime.system.transaction.IsisTransaction.PreAndPostValues;
 
 /**
  * Used by the {@link IsisTransactionManager} to captures a set of changes to be
@@ -175,7 +179,6 @@ public class IsisTransaction implements TransactionScopedComponent {
 
     private static final Logger LOG = Logger.getLogger(IsisTransaction.class);
 
-
     private final TransactionalResource objectStore;
     private final List<PersistenceCommand> commands = Lists.newArrayList();
     private final IsisTransactionManager transactionManager;
@@ -196,6 +199,7 @@ public class IsisTransaction implements TransactionScopedComponent {
 
     private final UUID guid;
 
+
     private int eventSequence;
 
     public IsisTransaction(final IsisTransactionManager transactionManager, final org.apache.isis.core.commons.authentication.MessageBroker messageBroker, final UpdateNotifier updateNotifier, final TransactionalResource objectStore, final AuditingService auditingService, PublishingServiceWithDefaultPayloadFactories publishingService) {
@@ -209,7 +213,8 @@ public class IsisTransaction implements TransactionScopedComponent {
         this.updateNotifier = updateNotifier;
         this.auditingService = auditingService;
         this.publishingService = publishingService;
-        
+
+
         this.guid = UUID.randomUUID();
         this.eventSequence = 0;
 
@@ -386,28 +391,19 @@ public class IsisTransaction implements TransactionScopedComponent {
         
         // else
         final String currentUser = getTransactionManager().getAuthenticationSession().getUserName();
-        final long currentTimestampEpoch = currentTimestampEpoch();
+        final long currentTimestampEpoch = currentTimestamp();
         for (Entry<AdapterAndProperty, PreAndPostValues> auditEntry : changedObjectProperties) {
             auditChangedProperty(currentUser, currentTimestampEpoch, auditEntry);
         }
     }
 
-    protected void doPublish(final Set<ObjectAdapter> changedAdapters) {
+
+    protected void publishActionIfRequired(final String currentUser, final long currentTimestampEpoch) {
+
         if(publishingService == null) {
             return;
         }
 
-        // else
-        final String currentUser = getTransactionManager().getAuthenticationSession().getUserName();
-        final long currentTimestampEpoch = currentTimestampEpoch();
-        
-        publishActionIfRequired(currentUser, currentTimestampEpoch);
-        publishedChangedObjects(changedAdapters, currentUser, currentTimestampEpoch);
-    }
-
-    protected void publishActionIfRequired(final String currentUser, final long currentTimestampEpoch) {
-        // TODO: need some transaction handling here
-        
         try {
             final CurrentInvocation currentInvocation = ActionInvocationFacet.currentInvocation.get();
             if(currentInvocation == null) {
@@ -426,26 +422,45 @@ public class IsisTransaction implements TransactionScopedComponent {
             final EventMetadata metadata = newEventMetadata(EventType.ACTION_INVOCATION, currentUser, currentTimestampEpoch, title);
             publishingService.publishAction(payloadFactory, metadata, currentInvocation, objectStringifier());
         } finally {
+            // ensures that cannot publish this action more than once
             ActionInvocationFacet.currentInvocation.set(null);
         }
     }
 
-    protected void publishedChangedObjects(final Set<ObjectAdapter> changedAdapters, final String currentUser, final long currentTimestampEpoch) {
-        for (final ObjectAdapter changedAdapter : changedAdapters) {
-            final PublishedObjectFacet publishedObjectFacet = changedAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
+    protected void publishedChangedObjectsIfRequired(final String currentUser, final long currentTimestampEpoch) {
+        if(publishingService == null) {
+            return;
+        }
+        
+        for (final ObjectAdapter enlistedAdapter : changeKindByEnlistedAdapter.keySet()) {
+            final ChangeKind changeKind = changeKindByEnlistedAdapter.get(enlistedAdapter);
+            final PublishedObjectFacet publishedObjectFacet = enlistedAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
             if(publishedObjectFacet == null) {
                 continue;
             }
             final PublishedObject.PayloadFactory payloadFactory = publishedObjectFacet.value();
-
-            final RootOid adapterOid = (RootOid) changedAdapter.getOid();
+        
+            final RootOid adapterOid = (RootOid) enlistedAdapter.getOid();
             final String oidStr = getOidMarshaller().marshal(adapterOid);
             final String title = oidStr;
+        
+            final EventMetadata metadata = newEventMetadata(eventTypeFor(changeKind), currentUser, currentTimestampEpoch, title);
+        
+            publishingService.publishObject(payloadFactory, metadata, enlistedAdapter, changeKind, objectStringifier());
+        }
+    }
 
-            final EventMetadata metadata = newEventMetadata(EventType.OBJECT_CHANGED, currentUser, currentTimestampEpoch, title);
-
-            publishingService.publishObject(payloadFactory, metadata, changedAdapter, objectStringifier());
+    private 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 + "'");
     }
 
     protected ObjectStringifier objectStringifier() {
@@ -476,7 +491,7 @@ public class IsisTransaction implements TransactionScopedComponent {
         return objectStringifier;
     }
 
-    private static long currentTimestampEpoch() {
+    private static long currentTimestamp() {
         return Clock.getTime();
     }
 
@@ -534,11 +549,19 @@ public class IsisTransaction implements TransactionScopedComponent {
             return;
         }
         
+
         try {
             doAudit(getChangedObjectProperties());
+            
+            final String currentUser = getTransactionManager().getAuthenticationSession().getUserName();
+            final long endTimestamp = currentTimestamp();
+            
+            publishActionIfRequired(currentUser, endTimestamp);
             doFlush();
-            doPublish(getChangedObjects());
+            
+            publishedChangedObjectsIfRequired(currentUser, endTimestamp);
             doFlush();
+            
             setState(State.COMMITTED);
         } catch (final RuntimeException ex) {
             setAbortCause(new IsisTransactionManagerException(ex));
@@ -766,7 +789,7 @@ public class IsisTransaction implements TransactionScopedComponent {
             return referencedAdapter == null ? null : referencedAdapter.getObject();
         }
     }
-   
+
     
     ////////////////////////////////////////////////////////////////////////
     // Auditing/Publishing object tracking
@@ -815,51 +838,92 @@ public class IsisTransaction implements TransactionScopedComponent {
     }
     
    
+    private final Map<ObjectAdapter,ChangeKind> changeKindByEnlistedAdapter = Maps.newLinkedHashMap();
     private final Map<AdapterAndProperty, PreAndPostValues> changedObjectProperties = Maps.newLinkedHashMap();
-    private final Set<ObjectAdapter> changedObjects = Sets.newLinkedHashSet();
-
 
     private ObjectStringifier objectStringifier;
-    
+
+
 
     /**
-     * For object stores to record the current values of an {@link ObjectAdapter} that has enlisted
-     * into the transaction, prior to updating its values.
+     * Auditing and publishing support: for object stores to enlist an object that has just been created, 
+     * capturing a dummy value <tt>'[NEW]'</tt> for the pre-modification value. 
      * 
      * <p>
-     * The values of the {@link ObjectAdapter} after being updated are captured when the
-     * audit entries are requested, in {@link #getChangedObjectProperties()}.
+     * The post-modification values are captured in as a side-effect of calling {@link #getChangedObjectProperties()},
+     * which returns the pre- and post- values for each {@link ObjectAdapter} in a map. 
      * 
      * <p>
      * Supported by the JDO object store; check documentation for support in other objectstores.
      */
-    public void auditDirty(ObjectAdapter adapter) {
+    public void enlistCreated(ObjectAdapter adapter) {
+        enlist(adapter, ChangeKind.CREATE);
         for (ObjectAssociation property : adapter.getSpecification().getAssociations(ObjectAssociationFilters.PROPERTIES)) {
-            changedObjectProperty(adapter, property);
+            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+            PreAndPostValues papv = PreAndPostValues.pre("[NEW]");
+            changedObjectProperties.put(aap, papv);
         }
     }
-    
-    private void changedObjectProperty(ObjectAdapter adapter, ObjectAssociation property) {
-        final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
-        PreAndPostValues papv = PreAndPostValues.pre(aap.getPropertyValue());
-        changedObjectProperties.put(aap, papv);
-        changedObjects.add(adapter);
+
+    /**
+     * Auditing and publishing support: for object stores to enlist an object that is about to be updated, 
+     * capturing the pre-modification values of the properties of the {@link ObjectAdapter}.
+     * 
+     * <p>
+     * The post-modification values are captured in as a side-effect of calling {@link #getChangedObjectProperties()},
+     * which returns the pre- and post- values for each {@link ObjectAdapter} in a map. 
+     * 
+     * <p>
+     * Supported by the JDO object store; check documentation for support in other objectstores.
+     */
+    public void enlistUpdating(ObjectAdapter adapter) {
+        enlist(adapter, ChangeKind.UPDATE);
+        for (ObjectAssociation property : adapter.getSpecification().getAssociations(ObjectAssociationFilters.PROPERTIES)) {
+            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+            PreAndPostValues papv = PreAndPostValues.pre(aap.getPropertyValue());
+            changedObjectProperties.put(aap, papv);
+        }
+    }
+
+    /**
+     * Auditing and publishing support: for object stores to enlist an object that is about to be deleted, 
+     * capturing the pre-deletion value of the properties of the {@link ObjectAdapter}. 
+     * 
+     * <p>
+     * The post-modification values are captured in as a side-effect of calling {@link #getChangedObjectProperties()}.
+     * In the case of deleted objects, a dummy value <tt>'[DELETED]'</tt> is used as the post-modification value. 
+     * 
+     * <p>
+     * Supported by the JDO object store; check documentation for support in other objectstores.
+     */
+    public void enlistDeleting(ObjectAdapter adapter) {
+        enlist(adapter, ChangeKind.DELETE);
+        for (ObjectAssociation property : adapter.getSpecification().getAssociations(ObjectAssociationFilters.PROPERTIES)) {
+            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+            PreAndPostValues papv = PreAndPostValues.pre(aap.getPropertyValue());
+            changedObjectProperties.put(aap, papv);
+        }
     }
 
 
+    private void enlist(ObjectAdapter adapter, ChangeKind changeKind) {
+        changeKindByEnlistedAdapter.put(adapter, changeKind);
+    }
+    
+    
     /**
      * Returns the pre- and post-values of all {@link ObjectAdapter}s that were enlisted and dirtied
      * in this transaction.
      * 
      * <p>
-     * This requires that the object store called {@link #auditDirty(ObjectAdapter)} for each object being
+     * This requires that the object store called {@link #enlistUpdating(ObjectAdapter)} for each object being
      * enlisted.
      * 
      * <p>
-     * Supported by the JDO object store (since it calls {@link #auditDirty(ObjectAdapter)}); 
+     * Supported by the JDO object store (since it calls {@link #enlistUpdating(ObjectAdapter)}); 
      * check documentation for support in other object stores.
      */
-    public Set<Entry<AdapterAndProperty, PreAndPostValues>> getChangedObjectProperties() {
+    private Set<Entry<AdapterAndProperty, PreAndPostValues>> getChangedObjectProperties() {
         updatePostValues(changedObjectProperties.entrySet());
 
         return Collections.unmodifiableSet(Sets.filter(changedObjectProperties.entrySet(), PreAndPostValues.CHANGED));
@@ -869,15 +933,17 @@ public class IsisTransaction implements TransactionScopedComponent {
         for (Entry<AdapterAndProperty, PreAndPostValues> entry : entrySet) {
             final AdapterAndProperty aap = entry.getKey();
             final PreAndPostValues papv = entry.getValue();
-            
-            papv.setPost(aap.getPropertyValue());
+            ObjectAdapter adapter = aap.getAdapter();
+            if(adapter.isDestroyed()) {
+                // don't touch the object!!!
+                // JDO, for example, will complain otherwise...
+                papv.setPost("[DELETED]");
+            } else {
+                papv.setPost(aap.getPropertyValue());
+            }
         }
     }
 
-    private Set<ObjectAdapter> getChangedObjects() {
-        return changedObjects;
-    }
-
     
     ////////////////////////////////////////////////////////////////////////
     // Dependencies (from context)
@@ -892,5 +958,6 @@ public class IsisTransaction implements TransactionScopedComponent {
     }
 
 
+
     
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
index 01c3fe3..b85fde7 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
@@ -33,6 +33,7 @@ import org.apache.log4j.Logger;
 import org.apache.isis.applib.Identifier;
 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.audit.AuditingService;
 import org.apache.isis.applib.services.publish.EventPayload;
 import org.apache.isis.applib.services.publish.EventPayloadForActionInvocation;
@@ -461,7 +462,7 @@ public class IsisTransactionManager implements SessionScopedComponent {
     protected PublishedObject.PayloadFactory newDefaultObjectPayloadFactory() {
         return new PublishedObject.PayloadFactory() {
             @Override
-            public EventPayload payloadFor(final Object changedObject) {
+            public EventPayload payloadFor(final Object changedObject, ChangeKind changeKind) {
                 return new EventPayloadForObjectChanged<Object>(changedObject);
             }
         };

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemChangedPayloadFactory.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemChangedPayloadFactory.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemChangedPayloadFactory.java
index b714d1a..38fc51a 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemChangedPayloadFactory.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemChangedPayloadFactory.java
@@ -1,5 +1,6 @@
 package dom.todo;
 
+import org.apache.isis.applib.annotation.PublishedObject.ChangeKind;
 import org.apache.isis.applib.annotation.PublishedObject.PayloadFactory;
 import org.apache.isis.applib.services.publish.EventPayload;
 import org.apache.isis.applib.services.publish.EventPayloadForObjectChanged;
@@ -17,7 +18,7 @@ public class ToDoItemChangedPayloadFactory implements PayloadFactory{
         }
     }
     @Override
-    public EventPayload payloadFor(Object changedObject) {
+    public EventPayload payloadFor(Object changedObject, ChangeKind changeKind) {
         return new ToDoItemPayload((ToDoItem) changedObject);
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/7e6fbb33/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/isis.properties
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/isis.properties b/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/isis.properties
index 7117145..3effdbd 100644
--- a/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/isis.properties
+++ b/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/isis.properties
@@ -165,9 +165,10 @@ isis.services = objstore.jdo.todo.ToDoItemsJdo,\
                 org.apache.isis.objectstore.jdo.service.RegisterEntities,\
                 org.apache.isis.objectstore.jdo.applib.service.exceprecog.ExceptionRecognizerCompositeForJdoObjectStore,\
                 org.apache.isis.viewer.restfulobjects.rendering.eventserializer.RestfulObjectsSpecEventSerializer,\
-                org.apache.isis.objectstore.jdo.applib.service.publish.PublishingServiceJdo
-                #org.apache.isis.applib.services.publish.PublishingService$Stderr
-                #com.danhaywood.isis.publishingservice.activemq.ra.PublishingServiceUsingActiveMqRa
+                org.apache.isis.applib.services.audit.AuditingService$Stdout,\
+                org.apache.isis.applib.services.publish.PublishingService$Stderr
+                #org.apache.isis.objectstore.jdo.applib.service.audit.AuditingServiceJdo,\
+                #org.apache.isis.objectstore.jdo.applib.service.publish.PublishingServiceJdo
 
 
 # Specify the (optional) test fixtures