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 2014/09/25 21:39:38 UTC

git commit: ISIS-865: checks if any (non-Command) objects have been dirtied in an xactn initiated by an action with safe-semantics; if so, and if "isis.persistor.ensureSafeSemantics" is "true", will abort xactn.

Repository: isis
Updated Branches:
  refs/heads/master 06776394a -> a33021644


ISIS-865: checks if any (non-Command) objects have been dirtied in an xactn initiated by an action with safe-semantics; if so, and if "isis.persistor.ensureSafeSemantics" is "true", will abort xactn.

Whichever way setting, will log with ERROR severity.

NB: this feature requires a CommandService impl that supports the new Command2 API.

Also supported in integ tests.

The default is taken to be "false", ie only log error, don't abort xactn.  This is for backward compatibility with 1.6.0 and previous versions.


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

Branch: refs/heads/master
Commit: a33021644049c1b2aef71623ce69ee6d98c9d1d0
Parents: 0677639
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Sep 25 20:39:18 2014 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Sep 25 20:39:18 2014 +0100

----------------------------------------------------------------------
 .../eventbus/ActionInteractionEvent.java        |  17 ++
 .../metamodel/facets/InteractionHelper.java     |   8 +
 .../IsisConfigurationForJdoIntegTests.java      |   5 +-
 .../exploration/ExplorationAuthenticator.java   |   2 +-
 .../persistence/PersistenceConstants.java       |   7 +
 .../system/transaction/IsisTransaction.java     | 162 +++++++++++++------
 .../integration/SimpleAppSystemInitializer.java |   5 +
 .../SimpleObjectsTest_listAll_and_create.java   |   5 +-
 .../main/webapp/WEB-INF/persistor.properties    |  23 ++-
 .../java/integration/ToDoSystemInitializer.java |   4 +
 .../integration/tests/ToDoItemIntegTest.java    |   1 +
 .../integration/tests/ToDoItemsIntegTest.java   |  10 +-
 .../main/webapp/WEB-INF/persistor.properties    |  25 ++-
 13 files changed, 221 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInteractionEvent.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInteractionEvent.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInteractionEvent.java
index 16ccdd9..40091d6 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInteractionEvent.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInteractionEvent.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.util.ObjectContracts;
 
@@ -93,6 +94,22 @@ public abstract class ActionInteractionEvent<S> extends AbstractInteractionEvent
     }
     //endregion
 
+
+    //region > actionSemantics
+    private ActionSemantics.Of actionSemantics;
+    public ActionSemantics.Of getActionSemantics() {
+        return actionSemantics;
+    }
+
+    /**
+     * Not API - set by the framework.
+     */
+    public void setActionSemantics(ActionSemantics.Of actionSemantics) {
+        this.actionSemantics = actionSemantics;
+    }
+
+    //endregion
+
     //region > arguments
     private List<Object> arguments;
     /**

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/InteractionHelper.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/InteractionHelper.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/InteractionHelper.java
index 04dda49..8da70ac 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/InteractionHelper.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/InteractionHelper.java
@@ -30,6 +30,7 @@ import org.apache.isis.applib.services.eventbus.*;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 
 
 public class InteractionHelper {
@@ -74,6 +75,13 @@ public class InteractionHelper {
                 final Identifier identifier = identified.getIdentifier();
                 event = newActionInteractionEvent(eventType, identifier, source, arguments);
             }
+
+            if(identified instanceof ObjectAction) {
+                // should always be the case...
+                final ObjectAction objectAction = (ObjectAction) identified;
+                event.setActionSemantics(objectAction.getSemantics());
+            }
+
             event.setPhase(phase);
             getEventBusService().post(event);
             return event;

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/core/objectstore-jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/IsisConfigurationForJdoIntegTests.java
----------------------------------------------------------------------
diff --git a/core/objectstore-jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/IsisConfigurationForJdoIntegTests.java b/core/objectstore-jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/IsisConfigurationForJdoIntegTests.java
index 991e720..6df273f 100644
--- a/core/objectstore-jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/IsisConfigurationForJdoIntegTests.java
+++ b/core/objectstore-jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/IsisConfigurationForJdoIntegTests.java
@@ -26,10 +26,11 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.isis.core.commons.config.IsisConfigurationDefault;
 import org.apache.isis.core.commons.resource.ResourceStreamSource;
+import org.apache.isis.core.runtime.persistence.PersistenceConstants;
 import org.apache.isis.objectstore.jdo.service.RegisterEntities;
 
 public class IsisConfigurationForJdoIntegTests extends IsisConfigurationDefault {
-    
+
     @SuppressWarnings("unused")
     private static final Logger LOG = LoggerFactory.getLogger(IsisConfigurationForJdoIntegTests.class);
 
@@ -76,6 +77,8 @@ public class IsisConfigurationForJdoIntegTests extends IsisConfigurationDefault
 
         // automatically install any fixtures that might have been registered
         add(DataNucleusObjectStore.INSTALL_FIXTURES_KEY , "true");
+
+        add(PersistenceConstants.ENFORCE_SAFE_SEMANTICS, ""+PersistenceConstants.ENFORCE_SAFE_SEMANTICS_DEFAULT);
     }
 
     public final IsisConfigurationForJdoIntegTests addDataNucleusProperty(final String key, final String value) {

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/core/runtime/src/main/java/org/apache/isis/core/runtime/authentication/exploration/ExplorationAuthenticator.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/authentication/exploration/ExplorationAuthenticator.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/authentication/exploration/ExplorationAuthenticator.java
index 6b06faa..eae56c2 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/authentication/exploration/ExplorationAuthenticator.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/authentication/exploration/ExplorationAuthenticator.java
@@ -50,7 +50,7 @@ import org.apache.isis.core.runtime.system.DeploymentType;
  * be:
  * 
  * <pre>
- * &amp;lt:userName&gt; [:&lt;role&gt;[|&lt;role&gt;]...], &lt;userName&gt;...
+ * &lt;:userName&gt; [:&lt;role&gt;[|&lt;role&gt;]...], &lt;userName&gt;...
  * </pre>
  */
 public class ExplorationAuthenticator extends AuthenticatorAbstractForDfltRuntime {

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/PersistenceConstants.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/PersistenceConstants.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/PersistenceConstants.java
index 3d97093..d83327d 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/PersistenceConstants.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/persistence/PersistenceConstants.java
@@ -29,6 +29,13 @@ import org.apache.isis.core.runtime.system.persistence.ObjectFactory;
 public final class PersistenceConstants {
 
 
+    public static final String ENFORCE_SAFE_SEMANTICS = "isis.persistor.enforceSafeSemantics";
+    /**
+     * Default is <code>false</code> only for backward compatibility (to avoid lots of breakages in existing code);
+     * in future might change to <code>true</code>.
+     */
+    public static final boolean ENFORCE_SAFE_SEMANTICS_DEFAULT = false;
+
     /**
      * Default implementation to use as {@link ObjectFactory}.
      */

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/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 6f47ac3..ecd3dbd 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
@@ -19,30 +19,22 @@
 
 package org.apache.isis.core.runtime.system.transaction;
 
-import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
-import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-
 import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.Set;
-import java.util.UUID;
-
+import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.RecoverableException;
+import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.Bulk;
 import org.apache.isis.applib.annotation.PublishedAction;
 import org.apache.isis.applib.annotation.PublishedObject;
@@ -51,16 +43,11 @@ import org.apache.isis.applib.clock.Clock;
 import org.apache.isis.applib.services.audit.AuditingService3;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.command.Command2;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.applib.services.command.spi.CommandService;
-import org.apache.isis.applib.services.publish.EventMetadata;
-import org.apache.isis.applib.services.publish.EventPayload;
-import org.apache.isis.applib.services.publish.EventPayloadForActionInvocation;
-import org.apache.isis.applib.services.publish.EventPayloadForObjectChanged;
-import org.apache.isis.applib.services.publish.EventSerializer;
-import org.apache.isis.applib.services.publish.EventType;
-import org.apache.isis.applib.services.publish.ObjectStringifier;
-import org.apache.isis.applib.services.publish.PublishingService;
+import org.apache.isis.applib.services.eventbus.ActionInteractionEvent;
+import org.apache.isis.applib.services.publish.*;
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.commons.components.TransactionScopedComponent;
 import org.apache.isis.core.commons.ensure.Ensure;
@@ -75,6 +62,7 @@ import org.apache.isis.core.metamodel.adapter.oid.RootOid;
 import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
 import org.apache.isis.core.metamodel.facets.actions.interaction.ActionInvocationFacet;
 import org.apache.isis.core.metamodel.facets.actions.interaction.ActionInvocationFacet.CurrentInvocation;
+import org.apache.isis.core.metamodel.facets.actions.interaction.CommandUtil;
 import org.apache.isis.core.metamodel.facets.actions.publish.PublishedActionFacet;
 import org.apache.isis.core.metamodel.facets.object.audit.AuditableFacet;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
@@ -83,17 +71,18 @@ import org.apache.isis.core.metamodel.runtimecontext.RuntimeContext.TransactionS
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
-import org.apache.isis.core.metamodel.facets.actions.interaction.CommandUtil;
 import org.apache.isis.core.runtime.persistence.ObjectPersistenceException;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.CreateObjectCommand;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.DestroyObjectCommand;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.PersistenceCommand;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.PublishingServiceWithDefaultPayloadFactories;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.SaveObjectCommand;
-import org.apache.isis.core.runtime.persistence.objectstore.transaction.TransactionalResource;
+import org.apache.isis.core.runtime.persistence.PersistenceConstants;
+import org.apache.isis.core.runtime.persistence.objectstore.transaction.*;
 import org.apache.isis.core.runtime.services.eventbus.EventBusServiceDefault;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 
+import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
+import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
 /**
  * Used by the {@link IsisTransactionManager} to captures a set of changes to be
  * applied.
@@ -109,6 +98,13 @@ import org.apache.isis.core.runtime.system.context.IsisContext;
 public class IsisTransaction implements TransactionScopedComponent {
 
 
+    public static final Predicate<ObjectAdapter> IS_COMMAND = new Predicate<ObjectAdapter>() {
+        @Override
+        public boolean apply(ObjectAdapter input) {
+            return Command.class.isAssignableFrom(input.getSpecification().getCorrespondingClass());
+        }
+    };
+
     public static enum State {
         /**
          * Started, still in progress.
@@ -297,11 +293,6 @@ public class IsisTransaction implements TransactionScopedComponent {
             return null;
         }
 
-        EventSerializer eventSerializer = servicesInjector.lookupService(EventSerializer.class);
-        if(eventSerializer == null) {
-            eventSerializer = newSimpleEventSerializer();
-        }
-
         PublishedObject.PayloadFactory objectPayloadFactory = servicesInjector.lookupService(PublishedObject.PayloadFactory.class);
         if(objectPayloadFactory == null) {
             objectPayloadFactory = newDefaultObjectPayloadFactory();
@@ -723,7 +714,10 @@ public class IsisTransaction implements TransactionScopedComponent {
         }
 
         try {
-            preCommitServices();
+            final Set<Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties = getChangedObjectProperties();
+
+            ensureAnySafeSemanticsHonoured(changedObjectProperties);
+            preCommitServices(changedObjectProperties);
         } catch (final RuntimeException ex) {
             setAbortCause(new IsisTransactionManagerException(ex));
             clearCommandServiceIfConfigured();
@@ -731,9 +725,69 @@ public class IsisTransaction implements TransactionScopedComponent {
         }
     }
 
+    private void ensureAnySafeSemanticsHonoured(final Set<Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties) {
+        final CommandContext commandContext = getServiceOrNull(CommandContext.class);
+
+        // playing it safe, but the following guards are almost certainly not necessary.
+        if (commandContext == null) {
+            return;
+        }
+        final Command command = commandContext.getCommand();
+        if (!(command instanceof Command2)) {
+            return;
+        }
+        final Command2 command2 = (Command2) command;
+        final ActionInteractionEvent<?> event = command2.getActionInteractionEvent();
+        if (event == null) {
+            return;
+        }
+        final ActionSemantics.Of actionSemantics = event.getActionSemantics();
+        if(actionSemantics == null) {
+            return;
+        }
+        if (!actionSemantics.isSafe()) {
+            return;
+        }
+
+        final Set<ObjectAdapter> changedAdapters = findChangedAdapters(changedObjectProperties);
+        if(!changedAdapters.isEmpty()) {
+            final String msg = "Action '" + event.getIdentifier().toFullIdentityString() + "'" +
+                    " (with safe semantics)" +
+                    " caused " + changedAdapters.size() + " object" + (changedAdapters.size() != 1 ? "s" : "") +
+                    " to be modified";
+            LOG.error(msg);
+            for (ObjectAdapter changedAdapter : changedAdapters) {
+                final StringBuilder builder = new StringBuilder("  > ")
+                        .append(changedAdapter.getSpecification().getFullIdentifier())
+                        .append(": ");
+                if(!changedAdapter.isDestroyed()) {
+                    builder.append(changedAdapter.titleString(null));
+                } else {
+                    builder.append("(deleted object)");
+                }
+                LOG.error(builder.toString());
+            }
+
+            final boolean enforceSafeSemantics = IsisContext.getConfiguration().getBoolean(PersistenceConstants.ENFORCE_SAFE_SEMANTICS, PersistenceConstants.ENFORCE_SAFE_SEMANTICS_DEFAULT);
+            if(enforceSafeSemantics) {
+                throw new RecoverableException(msg);
+            }
+        }
+    }
+
+    private static Set<ObjectAdapter> findChangedAdapters(
+            final Set<Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties) {
+        return Sets.newHashSet(
+                Iterables.filter(
+                        Iterables.transform(
+                                changedObjectProperties,
+                                AdapterAndProperty.Functions.GET_ADAPTER),
+                        Predicates.not(IS_COMMAND)));
+    }
+
 
-    private void preCommitServices() {
-        doAudit(getChangedObjectProperties());
+    private void preCommitServices(final Set<Entry<AdapterAndProperty, PreAndPostValues>> changedObjectProperties1) {
+        doAudit(changedObjectProperties1);
         
         final String currentUser = getTransactionManager().getAuthenticationSession().getUserName();
         final Timestamp endTimestamp = Clock.getTimeAsJavaSqlTimestamp();
@@ -1025,6 +1079,20 @@ public class IsisTransaction implements TransactionScopedComponent {
             ObjectAdapter referencedAdapter = property.get(objectAdapter);
             return referencedAdapter == null ? null : referencedAdapter.getObject();
         }
+
+        static class Functions {
+            private Functions(){}
+
+            static final Function<Entry<AdapterAndProperty, PreAndPostValues>, ObjectAdapter> GET_ADAPTER = new Function<Entry<AdapterAndProperty, PreAndPostValues>, ObjectAdapter>() {
+                @Override
+                public ObjectAdapter apply(Entry<AdapterAndProperty, PreAndPostValues> input) {
+                    final AdapterAndProperty aap = input.getKey();
+                    return aap.getAdapter();
+                }
+            };
+
+        }
+
     }
 
     
@@ -1033,14 +1101,16 @@ public class IsisTransaction implements TransactionScopedComponent {
     ////////////////////////////////////////////////////////////////////////
 
     public static class PreAndPostValues {
-        
-        private final static Predicate<Entry<?, PreAndPostValues>> CHANGED = new Predicate<Entry<?, PreAndPostValues>>(){
-            @Override
-            public boolean apply(Entry<?, PreAndPostValues> input) {
-                final PreAndPostValues papv = input.getValue();
-                return papv.differ();
-            }};
-            
+
+        static class Predicates {
+            final static Predicate<Entry<?, PreAndPostValues>> CHANGED = new Predicate<Entry<?, PreAndPostValues>>(){
+                @Override
+                public boolean apply(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.
@@ -1199,7 +1269,7 @@ public class IsisTransaction implements TransactionScopedComponent {
     private Set<Entry<AdapterAndProperty, PreAndPostValues>> getChangedObjectProperties() {
         updatePostValues(changedObjectProperties.entrySet());
 
-        return Collections.unmodifiableSet(Sets.filter(changedObjectProperties.entrySet(), PreAndPostValues.CHANGED));
+        return Collections.unmodifiableSet(Sets.filter(changedObjectProperties.entrySet(), PreAndPostValues.Predicates.CHANGED));
     }
 
     private static void updatePostValues(Set<Entry<AdapterAndProperty, PreAndPostValues>> entrySet) {

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/simpleapp/integtests/src/test/java/integration/SimpleAppSystemInitializer.java
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/integtests/src/test/java/integration/SimpleAppSystemInitializer.java b/example/application/simpleapp/integtests/src/test/java/integration/SimpleAppSystemInitializer.java
index f214653..fa67f2a 100644
--- a/example/application/simpleapp/integtests/src/test/java/integration/SimpleAppSystemInitializer.java
+++ b/example/application/simpleapp/integtests/src/test/java/integration/SimpleAppSystemInitializer.java
@@ -18,6 +18,7 @@ package integration;
 
 import org.apache.isis.core.commons.config.IsisConfiguration;
 import org.apache.isis.core.integtestsupport.IsisSystemForTest;
+import org.apache.isis.core.runtime.persistence.PersistenceConstants;
 import org.apache.isis.objectstore.jdo.datanucleus.DataNucleusPersistenceMechanismInstaller;
 import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegTests;
 
@@ -59,6 +60,10 @@ public class SimpleAppSystemInitializer {
 
         private static IsisConfiguration testConfiguration() {
             final IsisConfigurationForJdoIntegTests testConfiguration = new IsisConfigurationForJdoIntegTests();
+
+            // enable stricter checking
+            testConfiguration.put(PersistenceConstants.ENFORCE_SAFE_SEMANTICS, "true");
+
             testConfiguration.addRegisterEntitiesPackagePrefix("dom");
             return testConfiguration;
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/simpleapp/integtests/src/test/java/integration/tests/smoke/SimpleObjectsTest_listAll_and_create.java
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/integtests/src/test/java/integration/tests/smoke/SimpleObjectsTest_listAll_and_create.java b/example/application/simpleapp/integtests/src/test/java/integration/tests/smoke/SimpleObjectsTest_listAll_and_create.java
index 2a19af8..c2146e5 100644
--- a/example/application/simpleapp/integtests/src/test/java/integration/tests/smoke/SimpleObjectsTest_listAll_and_create.java
+++ b/example/application/simpleapp/integtests/src/test/java/integration/tests/smoke/SimpleObjectsTest_listAll_and_create.java
@@ -54,8 +54,11 @@ public class SimpleObjectsTest_listAll_and_create extends SimpleAppIntegTest {
     @Test
     public void create() throws Exception {
 
+        // when
         wrap(simpleObjects).create("Faz");
-        
+        nextTransaction();
+
+        // then
         final List<SimpleObject> all = wrap(simpleObjects).listAll();
         assertThat(all.size(), is(4));
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/persistor.properties
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/persistor.properties b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/persistor.properties
index ef7ca1d..6c9e64e 100644
--- a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/persistor.properties
+++ b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/persistor.properties
@@ -17,10 +17,31 @@
 
 
 
-# for emergency use only
+#################################################################################
+#
+# Persistor
+#
+#################################################################################
+
+
+# whether to prevent a transaction initiated from an action with safe semantics
+# from ever changing any objects (intentionally or otherwise)
+isis.persistor.enforceSafeSemantics=true
+
+
+
+# generally speaking this should not be enabled
 isis.persistor.disableConcurrencyChecking=false
 
 
+
+
+#################################################################################
+#
+# JDBC configuration
+#
+#################################################################################
+
 #
 # configuration file holding the JDO objectstore's JDBC configuration
 # (this is a bit of a hack... just exploiting fact that Isis also loads this file)

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/todoapp/integtests/src/test/java/integration/ToDoSystemInitializer.java
----------------------------------------------------------------------
diff --git a/example/application/todoapp/integtests/src/test/java/integration/ToDoSystemInitializer.java b/example/application/todoapp/integtests/src/test/java/integration/ToDoSystemInitializer.java
index 4fcf811..4f2e933 100644
--- a/example/application/todoapp/integtests/src/test/java/integration/ToDoSystemInitializer.java
+++ b/example/application/todoapp/integtests/src/test/java/integration/ToDoSystemInitializer.java
@@ -18,6 +18,7 @@ package integration;
 
 import org.apache.isis.core.commons.config.IsisConfiguration;
 import org.apache.isis.core.integtestsupport.IsisSystemForTest;
+import org.apache.isis.core.runtime.persistence.PersistenceConstants;
 import org.apache.isis.objectstore.jdo.datanucleus.DataNucleusPersistenceMechanismInstaller;
 import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegTests;
 
@@ -63,6 +64,9 @@ public class ToDoSystemInitializer {
         private static IsisConfiguration testConfiguration() {
             final IsisConfigurationForJdoIntegTests testConfiguration = new IsisConfigurationForJdoIntegTests();
             testConfiguration.addRegisterEntitiesPackagePrefix("dom");
+
+            // enable stricter checking
+            testConfiguration.put(PersistenceConstants.ENFORCE_SAFE_SEMANTICS, "true");
             return testConfiguration;
         }
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemIntegTest.java
----------------------------------------------------------------------
diff --git a/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemIntegTest.java b/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemIntegTest.java
index 5d1d28d..223471e 100644
--- a/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemIntegTest.java
+++ b/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemIntegTest.java
@@ -84,6 +84,7 @@ public class ToDoItemIntegTest extends AbstractToDoIntegTest {
         public void setUp() throws Exception {
             final List<ToDoItem> all = wrap(toDoItems).notYetComplete();
             toDoItem = wrap(all.get(0));
+            nextTransaction();
 
             dueBy = toDoItem.getDueBy();
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemsIntegTest.java
----------------------------------------------------------------------
diff --git a/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemsIntegTest.java b/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemsIntegTest.java
index 8344881..cf5cb86 100644
--- a/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemsIntegTest.java
+++ b/example/application/todoapp/integtests/src/test/java/integration/tests/ToDoItemsIntegTest.java
@@ -66,6 +66,7 @@ public class ToDoItemsIntegTest extends AbstractToDoIntegTest {
 
             // when
             toDoItem.completed();
+            nextTransaction();
 
             // then
             assertThat(wrap(service(ToDoItems.class)).notYetComplete().size(), is(notYetCompletedSize-1));
@@ -73,6 +74,7 @@ public class ToDoItemsIntegTest extends AbstractToDoIntegTest {
 
             // and when
             toDoItem.notYetCompleted();
+            nextTransaction();
 
             // then
             assertThat(wrap(service(ToDoItems.class)).notYetComplete().size(), is(notYetCompletedSize));
@@ -92,20 +94,24 @@ public class ToDoItemsIntegTest extends AbstractToDoIntegTest {
 
             // given
             int size = wrap(toDoItems).notYetComplete().size();
+            nextTransaction();
 
             // when
             final ToDoItem newToDo = toDoItems.newToDo("new todo", ToDoItem.Category.Professional, ToDoItem.Subcategory.OpenSource, null, null);
+            nextTransaction();
 
             // then
             assertThat(newToDo.getDescription(), is("new todo"));
             assertThat(newToDo.getCategory(), is(ToDoItem.Category.Professional));
-            assertThat(wrap(service(ToDoItems.class)).notYetComplete().size(), is(size+1));
+            assertThat(wrap(toDoItems).notYetComplete().size(), is(size+1));
+            nextTransaction();
 
             // when
             newToDo.delete();
+            nextTransaction();
 
             // then
-            assertThat(wrap(service(ToDoItems.class)).notYetComplete().size(), is(size));
+            assertThat(wrap(toDoItems).notYetComplete().size(), is(size));
         }
 
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/a3302164/example/application/todoapp/webapp/src/main/webapp/WEB-INF/persistor.properties
----------------------------------------------------------------------
diff --git a/example/application/todoapp/webapp/src/main/webapp/WEB-INF/persistor.properties b/example/application/todoapp/webapp/src/main/webapp/WEB-INF/persistor.properties
index 913b16a..982997e 100644
--- a/example/application/todoapp/webapp/src/main/webapp/WEB-INF/persistor.properties
+++ b/example/application/todoapp/webapp/src/main/webapp/WEB-INF/persistor.properties
@@ -16,10 +16,33 @@
 #  under the License.
 
 
-# for emergency use only
+#################################################################################
+#
+# Persistor
+#
+#################################################################################
+
+
+# whether to prevent a transaction initiated from an action with safe semantics
+# from ever changing any objects (intentionally or otherwise)
+isis.persistor.enforceSafeSemantics=true
+
+
+
+# generally speaking this should not be enabled
 isis.persistor.disableConcurrencyChecking=false
 
 
+
+
+#################################################################################
+#
+# JDBC configuration
+#
+#################################################################################
+
+
+
 #
 # configuration file holding the JDO objectstore's JDBC configuration
 # (this is a bit of a hack... just exploiting fact that Isis also loads this file)