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 09:03:40 UTC

[1/2] isis git commit: ISIS-1370: cleaning up the API to PublisherService, renaming EnlistedObjectsService to ChangedObjectsService etc.

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


http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/schema/src/main/resources/org/apache/isis/schema/chg/chg-1.0.xsd
----------------------------------------------------------------------
diff --git a/core/schema/src/main/resources/org/apache/isis/schema/chg/chg-1.0.xsd b/core/schema/src/main/resources/org/apache/isis/schema/chg/chg-1.0.xsd
index 2d48016..e793d71 100644
--- a/core/schema/src/main/resources/org/apache/isis/schema/chg/chg-1.0.xsd
+++ b/core/schema/src/main/resources/org/apache/isis/schema/chg/chg-1.0.xsd
@@ -51,21 +51,9 @@
                         </xs:documentation>
                     </xs:annotation>
                 </xs:element>
-                <xs:element name="created" type="com:oidsDto">
+                <xs:element name="completedAt" type="xs:dateTime" minOccurs="0" maxOccurs="1">
                     <xs:annotation>
-                        <xs:documentation>The list of objects created within the transaction.
-                        </xs:documentation>
-                    </xs:annotation>
-                </xs:element>
-                <xs:element name="updated" type="com:oidsDto">
-                    <xs:annotation>
-                        <xs:documentation>The list of objects updated within the transaction.
-                        </xs:documentation>
-                    </xs:annotation>
-                </xs:element>
-                <xs:element name="deleted" type="com:oidsDto">
-                    <xs:annotation>
-                        <xs:documentation>The list of objects deleted within the transaction.
+                        <xs:documentation>The point in time that these changes were completed.
                         </xs:documentation>
                     </xs:annotation>
                 </xs:element>
@@ -75,8 +63,35 @@
                         </xs:documentation>
                     </xs:annotation>
                 </xs:element>
+                <xs:element name="objects" type="objectsDto"/>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
 
+    <xs:complexType name="objectsDto">
+        <xs:annotation>
+            <xs:documentation>A set of changes to domain objects.</xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="created" type="com:oidsDto">
+                <xs:annotation>
+                    <xs:documentation>The list of objects created within the transaction.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="updated" type="com:oidsDto">
+                <xs:annotation>
+                    <xs:documentation>The list of objects updated within the transaction.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="deleted" type="com:oidsDto">
+                <xs:annotation>
+                    <xs:documentation>The list of objects deleted within the transaction.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
 </xs:schema>


[2/2] isis git commit: ISIS-1370: cleaning up the API to PublisherService, renaming EnlistedObjectsService to ChangedObjectsService etc.

Posted by da...@apache.org.
ISIS-1370: cleaning up the API to PublisherService, renaming EnlistedObjectsService to ChangedObjectsService etc.


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

Branch: refs/heads/ISIS-1291
Commit: 2a89182879e3d472842958054e5087ba4f2bbc6b
Parents: b01781b
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu May 12 10:03:25 2016 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu May 12 10:03:25 2016 +0100

----------------------------------------------------------------------
 adocs/documentation/pom.xml                     |   2 +-
 ...d-dto-vs-interaction-dto-vs-interaction.xlsx | Bin 13776 -> 14110 bytes
 .../applib/services/changes/ChangedObjects.java |  38 +++
 .../applib/services/command/CommandDefault.java | 155 +++++------
 .../services/publish/PublisherService.java      |   7 +-
 .../publish/PublisherServiceLogging.java        |  16 +-
 .../applib/service/DomainChangeJdoAbstract.java |  16 +-
 .../isis/schema/utils/ChangesDtoUtils.java      |  41 +--
 .../core/metamodel/adapter/oid/RootOid.java     |  22 +-
 .../auditing/AuditingServiceInternal.java       |  12 +-
 .../services/changes/AdapterAndProperty.java    | 125 +++++++++
 .../services/changes/ChangedObjectsDefault.java | 178 +++++++++++++
 .../changes/ChangedObjectsServiceInternal.java  | 263 +++++++++++++++++++
 .../services/changes/PreAndPostValues.java      | 104 ++++++++
 .../services/enlist/AdapterAndProperty.java     | 125 ---------
 .../enlist/EnlistedObjectsServiceInternal.java  | 263 -------------------
 .../services/enlist/PreAndPostValues.java       | 104 --------
 .../services/metrics/MetricsServiceDefault.java |   8 +-
 .../PublishingServiceInternalDefault.java       |  63 ++---
 .../system/persistence/PersistenceSession.java  |  20 +-
 .../system/transaction/IsisTransaction.java     |   8 +-
 .../org/apache/isis/schema/chg/chg-1.0.xsd      |  43 ++-
 22 files changed, 902 insertions(+), 711 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/adocs/documentation/pom.xml
----------------------------------------------------------------------
diff --git a/adocs/documentation/pom.xml b/adocs/documentation/pom.xml
index b6e5938..993e5c7 100644
--- a/adocs/documentation/pom.xml
+++ b/adocs/documentation/pom.xml
@@ -33,7 +33,7 @@
 
     <groupId>org.apache.isis.docs</groupId>
     <artifactId>isis-documentation</artifactId>
-    <version>1.12.0-SNAPSHOT</version>
+    <version>1.13.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <name>Apache Isis Docs</name>

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/adocs/documentation/src/main/excel/command-dto-vs-interaction-dto-vs-interaction.xlsx
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/excel/command-dto-vs-interaction-dto-vs-interaction.xlsx b/adocs/documentation/src/main/excel/command-dto-vs-interaction-dto-vs-interaction.xlsx
index ca26513..5006103 100644
Binary files a/adocs/documentation/src/main/excel/command-dto-vs-interaction-dto-vs-interaction.xlsx and b/adocs/documentation/src/main/excel/command-dto-vs-interaction-dto-vs-interaction.xlsx differ

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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
new file mode 100644
index 0000000..6a194a2
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/changes/ChangedObjects.java
@@ -0,0 +1,38 @@
+/*
+ *  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/2a891828/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandDefault.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandDefault.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandDefault.java
index 5b6b1e4..48cc7cd 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandDefault.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandDefault.java
@@ -35,13 +35,15 @@ import org.apache.isis.applib.util.ObjectContracts;
 
 public class CommandDefault implements Command3 {
 
+    //region > constructor
+
     public CommandDefault() {
         setExecutor(Executor.OTHER);
     }
-    
-    // //////////////////////////////////////
-    // actionIdentifier (property)
-    // //////////////////////////////////////
+
+    //endregion
+
+    //region > actionIdentifier (property)
 
     private String actionIdentifier;
     public String getMemberIdentifier() {
@@ -53,9 +55,9 @@ public class CommandDefault implements Command3 {
         this.actionIdentifier = actionIdentifier;
     }
 
-    // //////////////////////////////////////
-    // targetClass (property)
-    // //////////////////////////////////////
+    //endregion
+
+    //region > targetClass (property)
 
     private String targetClass;
     public String getTargetClass() {
@@ -67,10 +69,10 @@ public class CommandDefault implements Command3 {
         this.targetClass = targetClass;
     }
 
-    // //////////////////////////////////////
-    // targetAction (property)
-    // //////////////////////////////////////
-    
+    //endregion
+
+    //region > targetAction (property)
+
     private String targetAction;
     public String getTargetAction() {
         return targetAction;
@@ -81,12 +83,10 @@ public class CommandDefault implements Command3 {
         this.targetAction = targetAction;
     }
 
+    //endregion
 
+    //region > arguments (property)
 
-    // //////////////////////////////////////
-    // arguments (property)
-    // //////////////////////////////////////
-    
     private String arguments;
     public String getArguments() {
         return arguments;
@@ -97,11 +97,10 @@ public class CommandDefault implements Command3 {
         this.arguments = arguments;
     }
     
-    
-    // //////////////////////////////////////
-    // memento (property)
-    // //////////////////////////////////////
-    
+    //endregion
+
+    //region > memento (property)
+
     private String memento;
     
     @Override
@@ -113,11 +112,10 @@ public class CommandDefault implements Command3 {
         this.memento = memento;
     }
     
+    //endregion
+
+    //region > target (property)
 
-    // //////////////////////////////////////
-    // target (property)
-    // //////////////////////////////////////
-    
     private Bookmark target;
     public Bookmark getTarget() {
         return target;
@@ -127,10 +125,9 @@ public class CommandDefault implements Command3 {
         this.target = target;
     }
 
+    //endregion
 
-    // //////////////////////////////////////
-    // timestamp (property)
-    // //////////////////////////////////////
+    //region > timestamp (property)
 
     private Timestamp timestamp;
     public Timestamp getTimestamp() {
@@ -142,12 +139,10 @@ public class CommandDefault implements Command3 {
         this.timestamp = timestamp;
     }
 
-    
-    // //////////////////////////////////////
-    // startedAt (property)
-    // //////////////////////////////////////
-    
-    
+    //endregion
+
+    //region > startedAt (property)
+
     private Timestamp startedAt;
     @Override
     public Timestamp getStartedAt() {
@@ -158,11 +153,10 @@ public class CommandDefault implements Command3 {
         this.startedAt = startedAt;
     }
 
-    
-    // //////////////////////////////////////
-    // completedAt (property)
-    // //////////////////////////////////////
-    
+    //endregion
+
+    //region > completedAt (property)
+
     private Timestamp completedAt;
 
     @Override
@@ -175,9 +169,9 @@ public class CommandDefault implements Command3 {
         this.completedAt = completed;
     }
 
-    // //////////////////////////////////////
-    // user (property)
-    // //////////////////////////////////////
+    //endregion
+
+    //region > user (property)
 
     private String user;
     public String getUser() {
@@ -189,11 +183,9 @@ public class CommandDefault implements Command3 {
         this.user = user;
     }
 
+    //endregion
 
-    // //////////////////////////////////////
-    // actionInteractionEvent (property)
-    // //////////////////////////////////////
-
+    //region > actionInteractionEvent (peek/pop/flush)
 
     @Deprecated
     @Override
@@ -234,9 +226,9 @@ public class CommandDefault implements Command3 {
     }
 
 
-    // //////////////////////////////////////
-    // actionDomainEvent (property)
-    // //////////////////////////////////////
+    //endregion
+
+    //region > actionDomainEvent (peek/pop/flush)
 
     private final LinkedList<ActionDomainEvent<?>> actionDomainEvents = Lists.newLinkedList();
 
@@ -266,10 +258,9 @@ public class CommandDefault implements Command3 {
         return events;
     }
 
+    //endregion
 
-    // //////////////////////////////////////
-    // executor (property)
-    // //////////////////////////////////////
+    //region > executor (property)
 
     private Executor executor;
     
@@ -286,9 +277,9 @@ public class CommandDefault implements Command3 {
         this.executor = nature;
     }
 
-    // //////////////////////////////////////
-    // executionType (property)
-    // //////////////////////////////////////
+    //endregion
+
+    //region > executionType (property)
 
     private ExecuteIn executionType;
 
@@ -306,9 +297,9 @@ public class CommandDefault implements Command3 {
     }
 
 
-    // //////////////////////////////////////
-    // parent (property)
-    // //////////////////////////////////////
+    //endregion
+
+    //region > parent (property)
 
     private Command parent;
     
@@ -323,10 +314,10 @@ public class CommandDefault implements Command3 {
     }
 
     
-    // //////////////////////////////////////
-    // result (property)
-    // //////////////////////////////////////
-    
+    //endregion
+
+    //region > result (property)
+
     private Bookmark result;
     
     @Override
@@ -338,10 +329,9 @@ public class CommandDefault implements Command3 {
         this.result = result;
     }
 
+    //endregion
 
-    // //////////////////////////////////////
-    // exceptionStackTrace (property)
-    // //////////////////////////////////////
+    //region > exceptionStackTrace (property)
 
     private String exceptionStackTrace;
     
@@ -354,10 +344,10 @@ public class CommandDefault implements Command3 {
         this.exceptionStackTrace = exceptionStackTrace;
     }
     
-    // //////////////////////////////////////
-    // transactionId (property)
-    // //////////////////////////////////////
-    
+    //endregion
+
+    //region > transactionId (property)
+
     private UUID transactionId;
     
     @Override
@@ -370,10 +360,9 @@ public class CommandDefault implements Command3 {
     }
     
 
-    
-    // //////////////////////////////////////
-    // persistence
-    // //////////////////////////////////////
+    //endregion
+
+    //region > persistence
 
     private Persistence persistence;
     
@@ -387,10 +376,10 @@ public class CommandDefault implements Command3 {
         this.persistence = persistence; 
     }
 
-    // //////////////////////////////////////
-    // persistHint
-    // //////////////////////////////////////
-    
+    //endregion
+
+    //region > persistHint
+
     private boolean persistHint;
     
     public boolean isPersistHint() {
@@ -402,9 +391,9 @@ public class CommandDefault implements Command3 {
     }
 
 
-    // //////////////////////////////////////
-    // next
-    // //////////////////////////////////////
+    //endregion
+
+    //region > next
 
     private final Map<String, AtomicInteger> sequenceByName = Maps.newHashMap();
 
@@ -421,14 +410,16 @@ public class CommandDefault implements Command3 {
         return next.get();
     }
 
+    //endregion
 
-    // //////////////////////////////////////
-    // toString
-    // //////////////////////////////////////
+    //region > toString
 
     @Override
     public String toString() {
         return ObjectContracts.toString(this, "startedAt","user","memberIdentifier","target","transactionId");
     }
 
+    //endregion
+
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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 2ea2a90..d1b4ff9 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
@@ -18,11 +18,9 @@
  */
 package org.apache.isis.applib.services.publish;
 
-import java.util.List;
-
 import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.iactn.Interaction;
+import org.apache.isis.applib.services.changes.ChangedObjects;
 
 /**
  * Replaces {@link PublishingService}.
@@ -41,7 +39,8 @@ public interface PublisherService {
     @Programmatic
     void publish(final Interaction.Execution<?, ?> execution);
 
-    void publish(final List<Bookmark> created, final List<Bookmark> updated, final List<Bookmark> deleted);
+    @Programmatic
+    void publish(final ChangedObjects changedObjects);
 }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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 9d6fe64..d3bf438 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
@@ -18,8 +18,6 @@
  */
 package org.apache.isis.applib.services.publish;
 
-import java.util.List;
-
 import javax.annotation.PostConstruct;
 import javax.inject.Inject;
 
@@ -28,9 +26,8 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-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.user.UserService;
 import org.apache.isis.schema.chg.v1.ChangesDto;
@@ -63,20 +60,13 @@ public class PublisherServiceLogging implements PublisherService {
     }
 
     @Override
-    public void publish(
-            final List<Bookmark> created,
-            final List<Bookmark> updated,
-            final List<Bookmark> deleted) {
+    public void publish(final ChangedObjects changedObjects) {
 
         if(!LOG.isDebugEnabled()) {
             return;
         }
 
-        final Command command = commandContext.getCommand();
-        final String transactionId = command.getTransactionId().toString();
-        final String userName = userService.getUser().getName();
-
-        final ChangesDto changesDto = ChangesDtoUtils.newChangesDto(transactionId, userName, created, updated, deleted);
+        final ChangesDto changesDto = changedObjects.getDto();
 
         LOG.debug(ChangesDtoUtils.toXml(changesDto));
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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 20d646c..6dfa7da 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
@@ -39,6 +39,8 @@ import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.BookmarkService;
 import org.apache.isis.applib.services.metamodel.MetaModelService2;
+import org.apache.isis.applib.services.publish.PublisherService;
+import org.apache.isis.applib.services.publish.PublishingService;
 import org.apache.isis.applib.util.ObjectContracts;
 
 import static org.apache.isis.applib.annotation.Optionality.MANDATORY;
@@ -66,7 +68,17 @@ public abstract class DomainChangeJdoAbstract {
     public static enum ChangeType {
         COMMAND,
         AUDIT_ENTRY,
-        PUBLISHED_EVENT;
+        /**
+         * As per {@link PublishingService}.
+         *
+         * @deprecated - replaced by {@link #PUBLISHED_INTERACTION} (because {@link PublishingService} has been replaced by {@link PublisherService}).
+         */
+        @Deprecated
+        PUBLISHED_EVENT,
+        /**
+         * As per {@link PublisherService}.
+         */
+        PUBLISHED_INTERACTION;
         @Override
         public String toString() {
             return name().replace("_", " ");
@@ -78,7 +90,7 @@ public abstract class DomainChangeJdoAbstract {
     
     private final ChangeType type;
     /**
-     * Distinguishes <tt>CommandJdo</tt>s, <tt>AuditEntryJdo</tt>s and <tt>PublishedEventJdo</tt>s      * when these are shown mixed together in a (standalone) table.
+     * Distinguishes commands from audit entries from published events/interactions (when these are shown mixed together in a (standalone) table).
      */
     @Property
     @PropertyLayout(

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/applib/src/main/java/org/apache/isis/schema/utils/ChangesDtoUtils.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/schema/utils/ChangesDtoUtils.java b/core/applib/src/main/java/org/apache/isis/schema/utils/ChangesDtoUtils.java
index 6d2100b..7fa619e 100644
--- a/core/applib/src/main/java/org/apache/isis/schema/utils/ChangesDtoUtils.java
+++ b/core/applib/src/main/java/org/apache/isis/schema/utils/ChangesDtoUtils.java
@@ -26,6 +26,7 @@ import java.io.StringReader;
 import java.io.Writer;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.sql.Timestamp;
 import java.util.Collection;
 import java.util.List;
 
@@ -39,8 +40,10 @@ import com.google.common.io.Resources;
 
 import org.apache.isis.applib.services.bookmark.Bookmark;
 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;
 
 public final class ChangesDtoUtils {
 
@@ -96,44 +99,6 @@ public final class ChangesDtoUtils {
     }
     //endregion
 
-    //region > newChangesDto
-
-    public static ChangesDto newChangesDto(
-            final String transactionId,
-            final String userName,
-            final List<Bookmark> created,
-            final List<Bookmark> updated, final List<Bookmark> deleted) {
-
-        final ChangesDto changesDto = new ChangesDto();
-
-        changesDto.setMajorVersion("1");
-        changesDto.setMinorVersion("0");
-
-        changesDto.setTransactionId(transactionId);
-
-        changesDto.setCreated(new OidsDto());
-        changesDto.setUpdated(new OidsDto());
-        changesDto.setDeleted(new OidsDto());
-
-        changesDto.setTransactionId(transactionId);
-        changesDto.setUser(userName);
-
-        changesDto.getCreated().getOid().addAll(asList(created));
-        changesDto.getUpdated().getOid().addAll(asList(updated));
-        changesDto.getDeleted().getOid().addAll(asList(deleted));
-
-        return changesDto;
-    }
-
-    private static Collection<OidDto> asList(final List<Bookmark> bookmarks) {
-        final List<OidDto> oids = Lists.newArrayList();
-        for (Bookmark bookmark : bookmarks) {
-            oids.add(bookmark.toOidDto());
-        }
-        return oids;
-    }
-
-    //endregion
 
 
     //region > debugging (dump)

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/oid/RootOid.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/oid/RootOid.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/oid/RootOid.java
index 2d30b35..696d886 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/oid/RootOid.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/oid/RootOid.java
@@ -35,6 +35,8 @@ import org.apache.isis.core.commons.matchers.IsisMatchers;
 import org.apache.isis.core.commons.url.UrlEncodingUtils;
 import org.apache.isis.core.metamodel.adapter.version.Version;
 import org.apache.isis.core.metamodel.spec.ObjectSpecId;
+import org.apache.isis.schema.common.v1.BookmarkObjectState;
+import org.apache.isis.schema.common.v1.OidDto;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -221,12 +223,29 @@ public class RootOid implements TypedOid, Serializable {
     }
     //endregion
 
-    //region > bookmark
+    //region > asBookmark, asOidDto
     public Bookmark asBookmark() {
         final String objectType = state.asBookmarkObjectState().getCode() + getObjectSpecId().asString();
         final String identifier = getIdentifier();
         return new Bookmark(objectType, identifier);
     }
+
+    public OidDto asOidDto() {
+
+        final OidDto oidDto = new OidDto();
+
+        oidDto.setType(getObjectSpecId().asString());
+        oidDto.setId(getIdentifier());
+
+        final Bookmark.ObjectState objectState = state.asBookmarkObjectState();
+        final BookmarkObjectState bookmarkState = objectState.toBookmarkState();
+        // persistent is assumed if not specified...
+        oidDto.setObjectState(
+                bookmarkState != BookmarkObjectState.PERSISTENT ? bookmarkState : null);
+
+        return oidDto;
+    }
+
     //endregion
 
     //region > equals, hashCode
@@ -269,6 +288,7 @@ public class RootOid implements TypedOid, Serializable {
         return enString(new OidMarshaller());
     }
 
+
     //endregion
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/auditing/AuditingServiceInternal.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/auditing/AuditingServiceInternal.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/auditing/AuditingServiceInternal.java
index 35874de..d3cfebe 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/auditing/AuditingServiceInternal.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/auditing/AuditingServiceInternal.java
@@ -36,9 +36,9 @@ import org.apache.isis.applib.services.user.UserService;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 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.runtime.services.enlist.AdapterAndProperty;
-import org.apache.isis.core.runtime.services.enlist.EnlistedObjectsServiceInternal;
-import org.apache.isis.core.runtime.services.enlist.PreAndPostValues;
+import org.apache.isis.core.runtime.services.changes.AdapterAndProperty;
+import org.apache.isis.core.runtime.services.changes.ChangedObjectsServiceInternal;
+import org.apache.isis.core.runtime.services.changes.PreAndPostValues;
 
 /**
  * Wrapper around {@link org.apache.isis.applib.services.audit.AuditingService3}.  Is a no-op if there is no injected service.
@@ -55,7 +55,7 @@ public class AuditingServiceInternal {
     @Programmatic
     public void audit() {
         final Set<Map.Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties =
-                enlistedObjectsServiceInternal.getChangedObjectProperties();
+                changedObjectsServiceInternal.getChangedObjectProperties();
 
         try {
             if(!canAudit()) {
@@ -71,7 +71,7 @@ public class AuditingServiceInternal {
 
         } finally {
             // not needed in production, but is required for integration testing
-            enlistedObjectsServiceInternal.clearChangedObjectProperties();
+            changedObjectsServiceInternal.clearChangedObjectProperties();
         }
     }
 
@@ -113,7 +113,7 @@ public class AuditingServiceInternal {
     private AuditingService3 auditingServiceIfAny;
 
     @Inject
-    private EnlistedObjectsServiceInternal enlistedObjectsServiceInternal;
+    private ChangedObjectsServiceInternal changedObjectsServiceInternal;
 
     @Inject
     UserService userService;

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/AdapterAndProperty.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/AdapterAndProperty.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/AdapterAndProperty.java
new file mode 100644
index 0000000..a1572ec
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/AdapterAndProperty.java
@@ -0,0 +1,125 @@
+/*
+ *  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.util.Map;
+
+import com.google.common.base.Function;
+
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.oid.RootOid;
+import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+
+public class AdapterAndProperty {
+
+    private final ObjectAdapter objectAdapter;
+    private final ObjectAssociation property;
+    private final Bookmark bookmark;
+    private final String propertyId;
+    private final String bookmarkStr;
+
+    public static AdapterAndProperty of(ObjectAdapter adapter, ObjectAssociation property) {
+        return new AdapterAndProperty(adapter, property);
+    }
+
+    private AdapterAndProperty(ObjectAdapter adapter, ObjectAssociation property) {
+        this.objectAdapter = adapter;
+        this.property = property;
+
+        final RootOid oid = (RootOid) adapter.getOid();
+
+        final String objectType = oid.getObjectSpecId().asString();
+        final String identifier = oid.getIdentifier();
+        bookmark = new Bookmark(objectType, identifier);
+        bookmarkStr = bookmark.toString();
+
+        propertyId = property.getId();
+    }
+
+    public ObjectAdapter getAdapter() {
+        return objectAdapter;
+    }
+
+    public ObjectAssociation getProperty() {
+        return property;
+    }
+
+    public Bookmark getBookmark() {
+        return bookmark;
+    }
+
+    public String getPropertyId() {
+        return propertyId;
+    }
+
+    public String getMemberId() {
+        return property.getIdentifier().toClassAndNameIdentityString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        final AdapterAndProperty that = (AdapterAndProperty) o;
+
+        if (bookmarkStr != null ? !bookmarkStr.equals(that.bookmarkStr) : that.bookmarkStr != null)
+            return false;
+        if (propertyId != null ? !propertyId.equals(that.propertyId) : that.propertyId != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = propertyId != null ? propertyId.hashCode() : 0;
+        result = 31 * result + (bookmarkStr != null ? bookmarkStr.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return bookmarkStr + " , " + getProperty().getId();
+    }
+
+    public Object getPropertyValue() {
+        ObjectAdapter referencedAdapter = property.get(objectAdapter, InteractionInitiatedBy.FRAMEWORK);
+        return referencedAdapter == null ? null : referencedAdapter.getObject();
+    }
+
+    static class Functions {
+        private Functions() {
+        }
+
+        static final Function<Map.Entry<AdapterAndProperty, PreAndPostValues>, ObjectAdapter> GET_ADAPTER = new Function<Map.Entry<AdapterAndProperty, PreAndPostValues>, ObjectAdapter>() {
+            @Override
+            public ObjectAdapter apply(Map.Entry<AdapterAndProperty, PreAndPostValues> input) {
+                final AdapterAndProperty aap = input.getKey();
+                return aap.getAdapter();
+            }
+        };
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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
new file mode 100644
index 0000000..f2d203a
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsDefault.java
@@ -0,0 +1,178 @@
+/*
+ *  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/2a891828/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
new file mode 100644
index 0000000..d907c0a
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/ChangedObjectsServiceInternal.java
@@ -0,0 +1,263 @@
+/*
+ *  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.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import javax.enterprise.context.RequestScoped;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+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.PublishedObject;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
+
+@DomainService(nature = NatureOfService.DOMAIN)
+@RequestScoped
+public class ChangedObjectsServiceInternal {
+
+    // used for auditing
+    private final Map<AdapterAndProperty, PreAndPostValues> changedObjectProperties = Maps.newLinkedHashMap();
+
+    // used for publishing
+    private final Map<ObjectAdapter,PublishedObject.ChangeKind> changeKindByEnlistedAdapter = Maps.newLinkedHashMap();
+
+    /**
+     * 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 post-modification values are captured when the transaction commits.
+     *
+     * <p>
+     * Supported by the JDO object store; check documentation for support in other objectstores.
+     */
+    @Programmatic
+    public void enlistCreated(final ObjectAdapter adapter) {
+
+        enlistForPublishing(adapter, PublishedObject.ChangeKind.CREATE);
+
+        for (ObjectAssociation property : adapter.getSpecification().getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES)) {
+            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+            if(property.isNotPersisted()) {
+                continue;
+            }
+            if(changedObjectProperties.containsKey(aap)) {
+                // already enlisted, so ignore
+                return;
+            }
+            PreAndPostValues papv = PreAndPostValues.pre(IsisTransaction.Placeholder.NEW);
+            changedObjectProperties.put(aap, papv);
+        }
+    }
+
+    /**
+     * 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 when the transaction commits.
+     *
+     * <p>
+     * Supported by the JDO object store; check documentation for support in other objectstores.
+     */
+    @Programmatic
+    public void enlistUpdating(final ObjectAdapter adapter) {
+
+        enlistForPublishing(adapter, PublishedObject.ChangeKind.UPDATE);
+
+        for (ObjectAssociation property : adapter.getSpecification().getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES)) {
+            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+            if(property.isNotPersisted()) {
+                continue;
+            }
+            if(changedObjectProperties.containsKey(aap)) {
+                // already enlisted, so ignore
+                return;
+            }
+            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  when the transaction commits.  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.
+     */
+    @Programmatic
+    public void enlistDeleting(final ObjectAdapter adapter) {
+
+        final boolean enlisted = enlistForPublishing(adapter, PublishedObject.ChangeKind.DELETE);
+        if(!enlisted) {
+            return;
+        }
+
+        for (ObjectAssociation property : adapter.getSpecification().getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES)) {
+            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+            if(property.isNotPersisted()) {
+                continue;
+            }
+            if(changedObjectProperties.containsKey(aap)) {
+                // already enlisted, so ignore
+                return;
+            }
+            PreAndPostValues papv = PreAndPostValues.pre(aap.getPropertyValue());
+            changedObjectProperties.put(aap, papv);
+        }
+    }
+
+
+    /**
+     *
+     * @param adapter
+     * @param current
+     * @return <code>true</code> if successfully enlisted, <code>false</code> if was already enlisted
+     */
+    private boolean enlistForPublishing(final ObjectAdapter adapter, final PublishedObject.ChangeKind current) {
+        final PublishedObject.ChangeKind previous = changeKindByEnlistedAdapter.get(adapter);
+        if(previous == null) {
+            changeKindByEnlistedAdapter.put(adapter, current);
+            return true;
+        }
+        switch (previous) {
+        case CREATE:
+            switch (current) {
+            case DELETE:
+                changeKindByEnlistedAdapter.remove(adapter);
+            case CREATE:
+            case UPDATE:
+                return false;
+            }
+            break;
+        case UPDATE:
+            switch (current) {
+            case DELETE:
+                changeKindByEnlistedAdapter.put(adapter, current);
+                return true;
+            case CREATE:
+            case UPDATE:
+                return false;
+            }
+            break;
+        case DELETE:
+            return false;
+        }
+        return previous == null;
+    }
+
+
+    @Programmatic
+    public boolean hasChangedAdapters() {
+        final Set<Map.Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties = getChangedObjectProperties();
+
+        final Set<ObjectAdapter> changedAdapters = Sets.newHashSet(
+                Iterables.filter(
+                        Iterables.transform(
+                                changedObjectProperties,
+                                AdapterAndProperty.Functions.GET_ADAPTER),
+                        Predicates.not(IS_COMMAND)));
+        return !changedAdapters.isEmpty();
+    }
+
+    private static final Predicate<ObjectAdapter> IS_COMMAND = new Predicate<ObjectAdapter>() {
+        @Override
+        public boolean apply(ObjectAdapter input) {
+            return Command.class.isAssignableFrom(input.getSpecification().getCorrespondingClass());
+        }
+    };
+
+    @Programmatic
+    public Set<Map.Entry<AdapterAndProperty, PreAndPostValues>> getChangedObjectProperties() {
+        final Map<AdapterAndProperty, PreAndPostValues> processedObjectProperties =  getAdapterAndPropertyPreAndPostValuesMap();
+
+        return Collections.unmodifiableSet(
+                Sets.filter(processedObjectProperties.entrySet(), PreAndPostValues.Predicates.CHANGED));
+    }
+
+    private Map<AdapterAndProperty, PreAndPostValues> getAdapterAndPropertyPreAndPostValuesMap() {
+        final Map<AdapterAndProperty, PreAndPostValues> processedObjectProperties = Maps.newLinkedHashMap();
+
+        while(!changedObjectProperties.isEmpty()) {
+
+            final Set<AdapterAndProperty> keys = Sets.newLinkedHashSet(changedObjectProperties.keySet());
+            for (final AdapterAndProperty aap : keys) {
+
+                final PreAndPostValues papv = changedObjectProperties.remove(aap);
+
+                final ObjectAdapter adapter = aap.getAdapter();
+                if(adapter.isDestroyed()) {
+                    // don't touch the object!!!
+                    // JDO, for example, will complain otherwise...
+                    papv.setPost(IsisTransaction.Placeholder.DELETED);
+                } else {
+                    papv.setPost(aap.getPropertyValue());
+                }
+
+                // if we encounter the same objectProperty again, this will simply overwrite it
+                processedObjectProperties.put(aap, papv);
+            }
+        }
+        return processedObjectProperties;
+    }
+
+    @Programmatic
+    public void clearChangedObjectProperties() {
+        changedObjectProperties.clear();
+    }
+
+
+    @Programmatic
+    public Map<ObjectAdapter, PublishedObject.ChangeKind> getChangeKindByEnlistedAdapter() {
+        return changeKindByEnlistedAdapter;
+    }
+
+
+    static String asString(Object object) {
+        return object != null? object.toString(): null;
+    }
+
+    @Programmatic
+    public int numberObjectsDirtied() {
+        return changedObjectProperties.size();
+    }
+
+    @Programmatic
+    public int numberObjectPropertiesModified() {
+        return changedObjectProperties.size();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/PreAndPostValues.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/PreAndPostValues.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/PreAndPostValues.java
new file mode 100644
index 0000000..4a5cb39
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/changes/PreAndPostValues.java
@@ -0,0 +1,104 @@
+/*
+ *  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.util.Map;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+
+import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
+
+public class PreAndPostValues {
+
+    public static class Predicates {
+        public final static Predicate<Map.Entry<?, PreAndPostValues>> CHANGED = new Predicate<Map.Entry<?, PreAndPostValues>>() {
+            @Override
+            public boolean apply(Map.Entry<?, PreAndPostValues> input) {
+                final PreAndPostValues papv = input.getValue();
+                return papv.differ();
+            }
+        };
+    }
+
+    private final Object pre;
+    /**
+     * Eagerly calculated because it could be that the object referenced ends up being deleted by the time that the xactn completes.
+     */
+    private final String preString;
+
+    /**
+     * Updated in {@link #setPost(Object)}
+     */
+    private Object post;
+    /**
+     * Updated in {@link #setPost(Object)}, along with {@link #post}.
+     */
+    private String postString;
+
+    public static PreAndPostValues pre(Object preValue) {
+        return new PreAndPostValues(preValue, null);
+    }
+
+    private PreAndPostValues(Object pre, Object post) {
+        this.pre = pre;
+        this.post = post;
+        this.preString = ChangedObjectsServiceInternal.asString(pre);
+    }
+
+    /**
+     * The object that was referenced before this object was changed
+     * <p/>
+     * <p/>
+     * Note that this referenced object itself could end up being deleted in the course of the transaction; in which case use
+     * {@link #getPreString()} which is the eagerly cached <tt>toString</tt> of said object.
+     */
+    public Object getPre() {
+        return pre;
+    }
+
+    public String getPreString() {
+        return preString;
+    }
+
+    public Object getPost() {
+        return post;
+    }
+
+    public String getPostString() {
+        return postString;
+    }
+
+    public void setPost(Object post) {
+        this.post = post;
+        this.postString = ChangedObjectsServiceInternal.asString(post);
+    }
+
+    @Override
+    public String toString() {
+        return getPre() + " -> " + getPost();
+    }
+
+    public boolean differ() {
+        if (getPre() == IsisTransaction.Placeholder.NEW || getPost() == IsisTransaction.Placeholder.DELETED) {
+            return true;
+        }
+        return !Objects.equal(getPre(), getPost());
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/AdapterAndProperty.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/AdapterAndProperty.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/AdapterAndProperty.java
deleted file mode 100644
index ef39483..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/AdapterAndProperty.java
+++ /dev/null
@@ -1,125 +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.enlist;
-
-import java.util.Map;
-
-import com.google.common.base.Function;
-
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.adapter.oid.RootOid;
-import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
-
-public class AdapterAndProperty {
-
-    private final ObjectAdapter objectAdapter;
-    private final ObjectAssociation property;
-    private final Bookmark bookmark;
-    private final String propertyId;
-    private final String bookmarkStr;
-
-    public static AdapterAndProperty of(ObjectAdapter adapter, ObjectAssociation property) {
-        return new AdapterAndProperty(adapter, property);
-    }
-
-    private AdapterAndProperty(ObjectAdapter adapter, ObjectAssociation property) {
-        this.objectAdapter = adapter;
-        this.property = property;
-
-        final RootOid oid = (RootOid) adapter.getOid();
-
-        final String objectType = oid.getObjectSpecId().asString();
-        final String identifier = oid.getIdentifier();
-        bookmark = new Bookmark(objectType, identifier);
-        bookmarkStr = bookmark.toString();
-
-        propertyId = property.getId();
-    }
-
-    public ObjectAdapter getAdapter() {
-        return objectAdapter;
-    }
-
-    public ObjectAssociation getProperty() {
-        return property;
-    }
-
-    public Bookmark getBookmark() {
-        return bookmark;
-    }
-
-    public String getPropertyId() {
-        return propertyId;
-    }
-
-    public String getMemberId() {
-        return property.getIdentifier().toClassAndNameIdentityString();
-    }
-
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o)
-            return true;
-        if (o == null || getClass() != o.getClass())
-            return false;
-
-        final AdapterAndProperty that = (AdapterAndProperty) o;
-
-        if (bookmarkStr != null ? !bookmarkStr.equals(that.bookmarkStr) : that.bookmarkStr != null)
-            return false;
-        if (propertyId != null ? !propertyId.equals(that.propertyId) : that.propertyId != null)
-            return false;
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = propertyId != null ? propertyId.hashCode() : 0;
-        result = 31 * result + (bookmarkStr != null ? bookmarkStr.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return bookmarkStr + " , " + getProperty().getId();
-    }
-
-    public Object getPropertyValue() {
-        ObjectAdapter referencedAdapter = property.get(objectAdapter, InteractionInitiatedBy.FRAMEWORK);
-        return referencedAdapter == null ? null : referencedAdapter.getObject();
-    }
-
-    static class Functions {
-        private Functions() {
-        }
-
-        static final Function<Map.Entry<AdapterAndProperty, PreAndPostValues>, ObjectAdapter> GET_ADAPTER = new Function<Map.Entry<AdapterAndProperty, PreAndPostValues>, ObjectAdapter>() {
-            @Override
-            public ObjectAdapter apply(Map.Entry<AdapterAndProperty, PreAndPostValues> input) {
-                final AdapterAndProperty aap = input.getKey();
-                return aap.getAdapter();
-            }
-        };
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
deleted file mode 100644
index 053dc86..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
+++ /dev/null
@@ -1,263 +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.enlist;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-import javax.enterprise.context.RequestScoped;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-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.PublishedObject;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.spec.feature.Contributed;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
-import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
-
-@DomainService(nature = NatureOfService.DOMAIN)
-@RequestScoped
-public class EnlistedObjectsServiceInternal {
-
-    // used for auditing
-    private final Map<AdapterAndProperty, PreAndPostValues> changedObjectProperties = Maps.newLinkedHashMap();
-
-    // used for publishing
-    private final Map<ObjectAdapter,PublishedObject.ChangeKind> changeKindByEnlistedAdapter = Maps.newLinkedHashMap();
-
-    /**
-     * 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 post-modification values are captured when the transaction commits.
-     *
-     * <p>
-     * Supported by the JDO object store; check documentation for support in other objectstores.
-     */
-    @Programmatic
-    public void enlistCreated(final ObjectAdapter adapter) {
-
-        enlistForPublishing(adapter, PublishedObject.ChangeKind.CREATE);
-
-        for (ObjectAssociation property : adapter.getSpecification().getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES)) {
-            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
-            if(property.isNotPersisted()) {
-                continue;
-            }
-            if(changedObjectProperties.containsKey(aap)) {
-                // already enlisted, so ignore
-                return;
-            }
-            PreAndPostValues papv = PreAndPostValues.pre(IsisTransaction.Placeholder.NEW);
-            changedObjectProperties.put(aap, papv);
-        }
-    }
-
-    /**
-     * 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 when the transaction commits.
-     *
-     * <p>
-     * Supported by the JDO object store; check documentation for support in other objectstores.
-     */
-    @Programmatic
-    public void enlistUpdating(final ObjectAdapter adapter) {
-
-        enlistForPublishing(adapter, PublishedObject.ChangeKind.UPDATE);
-
-        for (ObjectAssociation property : adapter.getSpecification().getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES)) {
-            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
-            if(property.isNotPersisted()) {
-                continue;
-            }
-            if(changedObjectProperties.containsKey(aap)) {
-                // already enlisted, so ignore
-                return;
-            }
-            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  when the transaction commits.  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.
-     */
-    @Programmatic
-    public void enlistDeleting(final ObjectAdapter adapter) {
-
-        final boolean enlisted = enlistForPublishing(adapter, PublishedObject.ChangeKind.DELETE);
-        if(!enlisted) {
-            return;
-        }
-
-        for (ObjectAssociation property : adapter.getSpecification().getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES)) {
-            final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
-            if(property.isNotPersisted()) {
-                continue;
-            }
-            if(changedObjectProperties.containsKey(aap)) {
-                // already enlisted, so ignore
-                return;
-            }
-            PreAndPostValues papv = PreAndPostValues.pre(aap.getPropertyValue());
-            changedObjectProperties.put(aap, papv);
-        }
-    }
-
-
-    /**
-     *
-     * @param adapter
-     * @param current
-     * @return <code>true</code> if successfully enlisted, <code>false</code> if was already enlisted
-     */
-    private boolean enlistForPublishing(final ObjectAdapter adapter, final PublishedObject.ChangeKind current) {
-        final PublishedObject.ChangeKind previous = changeKindByEnlistedAdapter.get(adapter);
-        if(previous == null) {
-            changeKindByEnlistedAdapter.put(adapter, current);
-            return true;
-        }
-        switch (previous) {
-        case CREATE:
-            switch (current) {
-            case DELETE:
-                changeKindByEnlistedAdapter.remove(adapter);
-            case CREATE:
-            case UPDATE:
-                return false;
-            }
-            break;
-        case UPDATE:
-            switch (current) {
-            case DELETE:
-                changeKindByEnlistedAdapter.put(adapter, current);
-                return true;
-            case CREATE:
-            case UPDATE:
-                return false;
-            }
-            break;
-        case DELETE:
-            return false;
-        }
-        return previous == null;
-    }
-
-
-    @Programmatic
-    public boolean hasChangedAdapters() {
-        final Set<Map.Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties = getChangedObjectProperties();
-
-        final Set<ObjectAdapter> changedAdapters = Sets.newHashSet(
-                Iterables.filter(
-                        Iterables.transform(
-                                changedObjectProperties,
-                                AdapterAndProperty.Functions.GET_ADAPTER),
-                        Predicates.not(IS_COMMAND)));
-        return !changedAdapters.isEmpty();
-    }
-
-    private static final Predicate<ObjectAdapter> IS_COMMAND = new Predicate<ObjectAdapter>() {
-        @Override
-        public boolean apply(ObjectAdapter input) {
-            return Command.class.isAssignableFrom(input.getSpecification().getCorrespondingClass());
-        }
-    };
-
-    @Programmatic
-    public Set<Map.Entry<AdapterAndProperty, PreAndPostValues>> getChangedObjectProperties() {
-        final Map<AdapterAndProperty, PreAndPostValues> processedObjectProperties =  getAdapterAndPropertyPreAndPostValuesMap();
-
-        return Collections.unmodifiableSet(
-                Sets.filter(processedObjectProperties.entrySet(), PreAndPostValues.Predicates.CHANGED));
-    }
-
-    private Map<AdapterAndProperty, PreAndPostValues> getAdapterAndPropertyPreAndPostValuesMap() {
-        final Map<AdapterAndProperty, PreAndPostValues> processedObjectProperties = Maps.newLinkedHashMap();
-
-        while(!changedObjectProperties.isEmpty()) {
-
-            final Set<AdapterAndProperty> keys = Sets.newLinkedHashSet(changedObjectProperties.keySet());
-            for (final AdapterAndProperty aap : keys) {
-
-                final PreAndPostValues papv = changedObjectProperties.remove(aap);
-
-                final ObjectAdapter adapter = aap.getAdapter();
-                if(adapter.isDestroyed()) {
-                    // don't touch the object!!!
-                    // JDO, for example, will complain otherwise...
-                    papv.setPost(IsisTransaction.Placeholder.DELETED);
-                } else {
-                    papv.setPost(aap.getPropertyValue());
-                }
-
-                // if we encounter the same objectProperty again, this will simply overwrite it
-                processedObjectProperties.put(aap, papv);
-            }
-        }
-        return processedObjectProperties;
-    }
-
-    @Programmatic
-    public void clearChangedObjectProperties() {
-        changedObjectProperties.clear();
-    }
-
-
-    @Programmatic
-    public Map<ObjectAdapter, PublishedObject.ChangeKind> getChangeKindByEnlistedAdapter() {
-        return changeKindByEnlistedAdapter;
-    }
-
-
-    static String asString(Object object) {
-        return object != null? object.toString(): null;
-    }
-
-    @Programmatic
-    public int numberObjectsDirtied() {
-        return changedObjectProperties.size();
-    }
-
-    @Programmatic
-    public int numberObjectPropertiesModified() {
-        return changedObjectProperties.size();
-    }
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/PreAndPostValues.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/PreAndPostValues.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/PreAndPostValues.java
deleted file mode 100644
index c9d59be..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/PreAndPostValues.java
+++ /dev/null
@@ -1,104 +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.enlist;
-
-import java.util.Map;
-
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-
-import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
-
-public class PreAndPostValues {
-
-    public static class Predicates {
-        public final static Predicate<Map.Entry<?, PreAndPostValues>> CHANGED = new Predicate<Map.Entry<?, PreAndPostValues>>() {
-            @Override
-            public boolean apply(Map.Entry<?, PreAndPostValues> input) {
-                final PreAndPostValues papv = input.getValue();
-                return papv.differ();
-            }
-        };
-    }
-
-    private final Object pre;
-    /**
-     * Eagerly calculated because it could be that the object referenced ends up being deleted by the time that the xactn completes.
-     */
-    private final String preString;
-
-    /**
-     * Updated in {@link #setPost(Object)}
-     */
-    private Object post;
-    /**
-     * Updated in {@link #setPost(Object)}, along with {@link #post}.
-     */
-    private String postString;
-
-    public static PreAndPostValues pre(Object preValue) {
-        return new PreAndPostValues(preValue, null);
-    }
-
-    private PreAndPostValues(Object pre, Object post) {
-        this.pre = pre;
-        this.post = post;
-        this.preString = EnlistedObjectsServiceInternal.asString(pre);
-    }
-
-    /**
-     * The object that was referenced before this object was changed
-     * <p/>
-     * <p/>
-     * Note that this referenced object itself could end up being deleted in the course of the transaction; in which case use
-     * {@link #getPreString()} which is the eagerly cached <tt>toString</tt> of said object.
-     */
-    public Object getPre() {
-        return pre;
-    }
-
-    public String getPreString() {
-        return preString;
-    }
-
-    public Object getPost() {
-        return post;
-    }
-
-    public String getPostString() {
-        return postString;
-    }
-
-    public void setPost(Object post) {
-        this.post = post;
-        this.postString = EnlistedObjectsServiceInternal.asString(post);
-    }
-
-    @Override
-    public String toString() {
-        return getPre() + " -> " + getPost();
-    }
-
-    public boolean differ() {
-        if (getPre() == IsisTransaction.Placeholder.NEW || getPost() == IsisTransaction.Placeholder.DELETED) {
-            return true;
-        }
-        return !Objects.equal(getPre(), getPost());
-    }
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
index 889b887..f06c81d 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
@@ -30,7 +30,7 @@ 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.services.metrics.MetricsService;
-import org.apache.isis.core.runtime.services.enlist.EnlistedObjectsServiceInternal;
+import org.apache.isis.core.runtime.services.changes.ChangedObjectsServiceInternal;
 
 @RequestScoped
 @DomainService(nature = NatureOfService.DOMAIN)
@@ -45,12 +45,12 @@ public class MetricsServiceDefault implements MetricsService, InstanceLifecycleL
 
     @Override
     public int numberObjectsDirtied() {
-        return enlistedObjectsServiceInternal.numberObjectsDirtied();
+        return changedObjectsServiceInternal.numberObjectsDirtied();
     }
 
     @Override
     public int numberObjectPropertiesModified() {
-        return enlistedObjectsServiceInternal.numberObjectPropertiesModified();
+        return changedObjectsServiceInternal.numberObjectPropertiesModified();
     }
 
     @Programmatic
@@ -61,7 +61,7 @@ public class MetricsServiceDefault implements MetricsService, InstanceLifecycleL
 
 
     @Inject
-    EnlistedObjectsServiceInternal enlistedObjectsServiceInternal;
+    ChangedObjectsServiceInternal changedObjectsServiceInternal;
 
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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
index 7af1598..870cd21 100644
--- 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
@@ -41,7 +41,9 @@ 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;
@@ -65,7 +67,8 @@ import org.apache.isis.core.metamodel.facets.object.publishedobject.PublishedObj
 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.enlist.EnlistedObjectsServiceInternal;
+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;
@@ -118,7 +121,7 @@ public class PublishingServiceInternalDefault implements PublishingServiceIntern
         // 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(enlistedObjectsServiceInternal.getChangeKindByEnlistedAdapter());
+        changeKindByEnlistedAdapter.putAll(changedObjectsServiceInternal.getChangeKindByEnlistedAdapter());
 
         publishObjectsToPublishingService(changeKindByEnlistedAdapter);
         publishObjectsToPublisherServices(changeKindByEnlistedAdapter);
@@ -172,53 +175,33 @@ public class PublishingServiceInternalDefault implements PublishingServiceIntern
         publishingServiceIfAny.publish(metadata, payload);
     }
 
-    private void publishObjectsToPublisherServices(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
+    private void publishObjectsToPublisherServices(
+            final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
 
-        final List<Bookmark> created = Lists.newArrayList();
-        final List<Bookmark> updated = Lists.newArrayList();
-        final List<Bookmark> deleted = Lists.newArrayList();
-
-        for (final Map.Entry<ObjectAdapter, ChangeKind> adapterAndChange : changeKindByEnlistedAdapter.entrySet()) {
-            final ObjectAdapter enlistedAdapter = adapterAndChange.getKey();
+        if(changeKindByEnlistedAdapter.isEmpty()) {
+            return;
+        }
 
-            final PublishedObjectFacet publishedObjectFacet =
-                    enlistedAdapter.getSpecification().getFacet(PublishedObjectFacet.class);
+        final ChangedObjects changedObjects = newEnlistedObjects(changeKindByEnlistedAdapter);
 
-            if(publishedObjectFacet == null) {
-                continue;
-            }
+        for (PublisherService publisherService : publisherServices) {
+            publisherService.publish(changedObjects);
+        }
+    }
 
-            final ChangeKind changeKind = adapterAndChange.getValue();
+    private ChangedObjects newEnlistedObjects(final Map<ObjectAdapter, ChangeKind> changeKindByEnlistedAdapter) {
 
-            final RootOid rootOid = (RootOid) enlistedAdapter.getOid();
-            final Bookmark bookmark = rootOid.asBookmark();
-
-            switch (changeKind) {
-            case CREATE:
-                created.add(bookmark);
-                break;
-            case UPDATE:
-                updated.add(bookmark);
-                break;
-            case DELETE:
-                deleted.add(bookmark);
-                break;
-            default:
-                // shouldn't happen
-                throw new RuntimeException("ChangeKind '" + changeKind + "' not recognized");
-            }
-        }
+        final Command command = commandContext.getCommand();
+        final UUID transactionUuid = command.getTransactionId();
 
-        if(created.isEmpty() && updated.isEmpty() && deleted.isEmpty()) {
-            return;
-        }
+        final String userName = userService.getUser().getName();
+        final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
 
-        for (PublisherService publisherService : publisherServices) {
-            publisherService.publish(created, updated, deleted);
-        }
+        return new ChangedObjectsDefault(transactionUuid, userName, timestamp, changeKindByEnlistedAdapter);
     }
 
 
+
     @Programmatic
     public void publishAction(
             final Interaction.Execution execution,
@@ -386,7 +369,7 @@ public class PublishingServiceInternalDefault implements PublishingServiceIntern
     private PublishingService publishingServiceIfAny;
 
     @Inject
-    private EnlistedObjectsServiceInternal enlistedObjectsServiceInternal;
+    private ChangedObjectsServiceInternal changedObjectsServiceInternal;
 
     @Inject
     private InteractionDtoServiceInternal interactionDtoServiceInternal;

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
index d1c1058..bf3dc06 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
@@ -122,7 +122,7 @@ import org.apache.isis.core.runtime.persistence.objectstore.transaction.Transact
 import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindAllInstances;
 import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindUsingApplibQueryDefault;
 import org.apache.isis.core.runtime.runner.opts.OptionHandlerFixtureAbstract;
-import org.apache.isis.core.runtime.services.enlist.EnlistedObjectsServiceInternal;
+import org.apache.isis.core.runtime.services.changes.ChangedObjectsServiceInternal;
 import org.apache.isis.core.runtime.services.metrics.MetricsServiceDefault;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.persistence.adaptermanager.OidAdapterHashMap;
@@ -2050,10 +2050,10 @@ public class PersistenceSession implements
     public void enlistDeletingAndInvokeIsisRemovingCallbackFacet(final Persistable pojo) {
         ObjectAdapter adapter = adapterFor(pojo);
 
-        final EnlistedObjectsServiceInternal enlistedObjectsServiceInternal =
-                getServicesInjector().lookupService(EnlistedObjectsServiceInternal.class);
+        final ChangedObjectsServiceInternal changedObjectsServiceInternal =
+                getServicesInjector().lookupService(ChangedObjectsServiceInternal.class);
 
-        enlistedObjectsServiceInternal.enlistDeleting(adapter);
+        changedObjectsServiceInternal.enlistDeleting(adapter);
 
         CallbackFacet.Util.callCallback(adapter, RemovingCallbackFacet.class);
         postLifecycleEventIfRequired(adapter, RemovingLifecycleEventFacet.class);
@@ -2243,10 +2243,10 @@ public class PersistenceSession implements
             postLifecycleEventIfRequired(adapter, PersistedLifecycleEventFacet.class);
 
 
-            final EnlistedObjectsServiceInternal enlistedObjectsServiceInternal =
-                    getServicesInjector().lookupService(EnlistedObjectsServiceInternal.class);
+            final ChangedObjectsServiceInternal changedObjectsServiceInternal =
+                    getServicesInjector().lookupService(ChangedObjectsServiceInternal.class);
 
-            enlistedObjectsServiceInternal.enlistCreated(adapter);
+            changedObjectsServiceInternal.enlistCreated(adapter);
 
         } else {
             // updating;
@@ -2291,10 +2291,10 @@ public class PersistenceSession implements
         postLifecycleEventIfRequired(adapter, UpdatingLifecycleEventFacet.class);
 
 
-        final EnlistedObjectsServiceInternal enlistedObjectsServiceInternal =
-                getServicesInjector().lookupService(EnlistedObjectsServiceInternal.class);
+        final ChangedObjectsServiceInternal changedObjectsServiceInternal =
+                getServicesInjector().lookupService(ChangedObjectsServiceInternal.class);
 
-        enlistedObjectsServiceInternal.enlistUpdating(adapter);
+        changedObjectsServiceInternal.enlistUpdating(adapter);
 
         ensureRootObject(pojo);
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/2a891828/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 54abc1e..920d6d1 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
@@ -50,7 +50,7 @@ import org.apache.isis.core.runtime.persistence.objectstore.transaction.CreateOb
 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.services.auditing.AuditingServiceInternal;
-import org.apache.isis.core.runtime.services.enlist.EnlistedObjectsServiceInternal;
+import org.apache.isis.core.runtime.services.changes.ChangedObjectsServiceInternal;
 
 import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
 import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState;
@@ -192,7 +192,7 @@ public class IsisTransaction implements TransactionScopedComponent {
     private final InteractionContext interactionContext;
     private final PublishingServiceInternal publishingServiceInternal;
     private final AuditingServiceInternal auditingServiceInternal;
-    private final EnlistedObjectsServiceInternal enlistedObjectsServiceInternal;
+    private final ChangedObjectsServiceInternal changedObjectsServiceInternal;
 
 
     private final UUID transactionId;
@@ -224,7 +224,7 @@ public class IsisTransaction implements TransactionScopedComponent {
 
         this.publishingServiceInternal = lookupService(PublishingServiceInternal.class);
         this.auditingServiceInternal = lookupService(AuditingServiceInternal.class);
-        this.enlistedObjectsServiceInternal = lookupService(EnlistedObjectsServiceInternal.class);
+        this.changedObjectsServiceInternal = lookupService(ChangedObjectsServiceInternal.class);
 
 
         this.transactionId = transactionId;
@@ -407,7 +407,7 @@ public class IsisTransaction implements TransactionScopedComponent {
             final Command command = commandContext.getCommand();
 
             // ensure that any changed objects means that the command should be persisted
-            final boolean hasChangedAdapters = enlistedObjectsServiceInternal.hasChangedAdapters();
+            final boolean hasChangedAdapters = changedObjectsServiceInternal.hasChangedAdapters();
             if(hasChangedAdapters && command.getMemberIdentifier() != null) {
                 command.setPersistHint(true);
             }