You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/02/28 15:03:33 UTC

[2/2] git commit: ISIS-351: improving exception handling...

Updated Branches:
  refs/heads/master 607f86c83 -> d52c407f1


ISIS-351: improving exception handling...

... looking into the case when hit an DB exception (unique constraint violation) on an edit.
Noticed that the version was being bumped anyway even though change not actually committed to DB.
After some experimentation, determined that if the transaction is aborted during the first phase,
then the version is not bumped up, but if the transaction is aborted during the second phase (by inspecting
for any "abort causes" on the IsisTransaction, then the version got bumped up.

Moved WebRequestCycleForIsis#onEndRequest logic instead into the #onRequestHandlerExecuted callback
which is executed before

Also reworked and simplified a little of the IsisTransaction and IsisTransactionManager code (in particular
the IsisTransactionManager#endTransaction method).


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

Branch: refs/heads/master
Commit: d52c407f1a39380df79fe71c00b3db7635d20642
Parents: c4ff489
Author: Dan Haywood <da...@apache.org>
Authored: Thu Feb 28 13:52:30 2013 +0000
Committer: Dan Haywood <da...@apache.org>
Committed: Thu Feb 28 13:52:30 2013 +0000

----------------------------------------------------------------------
 .../jdo/datanucleus/DataNucleusObjectStore.java    |   26 +++-
 .../persistence/FrameworkSynchronizer.java         |    2 +-
 .../integration/wicket/WebRequestCycleForIsis.java |   42 ++++---
 .../apache/isis/applib/DomainObjectContainer.java  |   12 ++-
 .../core/commons/exceptions/IsisException.java     |   68 +---------
 .../system/transaction/IsisTransaction.java        |  103 +++++----------
 .../transaction/IsisTransactionFlushException.java |   43 ++++++
 .../system/transaction/IsisTransactionManager.java |  102 +++++++++------
 .../IsisTransactionManagerCommitException.java     |   43 ++++++
 ...IsisTransactionManagerDomCallbackException.java |   43 ++++++
 ...sTransactionManagerResourceCommitException.java |   43 ++++++
 .../system/transaction/IsisTransactionTest.java    |   10 +-
 .../WEB-INF/persistor_datanucleus.properties       |    1 +
 13 files changed, 333 insertions(+), 205 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
index 17ef5fe..f8016ea 100644
--- a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
+++ b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/DataNucleusObjectStore.java
@@ -173,8 +173,12 @@ public class DataNucleusObjectStore implements ObjectStoreSpi {
         ensureThatState(persistenceManager, is(notNullValue()));
 
         final IsisTransaction currentTransaction = getTransactionManager().getTransaction();
-        if (currentTransaction != null && currentTransaction.getState().canCommit()) {
-            getTransactionManager().endTransaction();
+        if (currentTransaction != null && !currentTransaction.getState().isComplete()) {
+            if(currentTransaction.getState().canCommit()) {
+                getTransactionManager().endTransaction();
+            } else if(currentTransaction.getState().canAbort()) {
+                getTransactionManager().abortTransaction();
+            }
         }
 
         persistenceManager.close();
@@ -341,15 +345,23 @@ public class DataNucleusObjectStore implements ObjectStoreSpi {
     }
 
     private void executeCommands(final List<PersistenceCommand> commands) {
-        try {
+        
+        // there's no need to do any exception handling here, because we are called
+        // from IsisTransaction.flush() that will catch exceptions and set its abortCause
+        // if need be.
+        
+//        try {
             for (final PersistenceCommand command : commands) {
                 command.execute(null);
             }
             getPersistenceManager().flush();
-        } catch (final RuntimeException e) {
-            LOG.warn("Failure during execution", e);
-            throw e;
-        }
+//        } catch (final RuntimeException e) {
+//            LOG.warn("Failure during execution - aborting transaction and rethrowing", e);
+//            
+//            abortTransaction();
+//            
+//            throw e;
+//        }
     }
 
     // ///////////////////////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
index 70f820c..48c5004 100644
--- a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
+++ b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/persistence/FrameworkSynchronizer.java
@@ -91,7 +91,7 @@ public class FrameworkSynchronizer {
                     // since there was already an adapter, do concurrency check
                     if(previousVersion != null && datastoreVersion != null) {
                         if(previousVersion.different(datastoreVersion)) {
-                            getCurrentTransaction().addException(new ConcurrencyException(getAuthenticationSession().getUserName(), oid, previousVersion, datastoreVersion));
+                            getCurrentTransaction().setAbortCause(new ConcurrencyException(getAuthenticationSession().getUserName(), oid, previousVersion, datastoreVersion));
                         }
                     }
                 } else {

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java b/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
index 8c0d808..680a8bb 100644
--- a/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
+++ b/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
@@ -34,6 +34,7 @@ import org.apache.isis.core.runtime.system.transaction.MessageBroker;
 import org.apache.isis.core.runtime.system.transaction.MessageBrokerDefault;
 import org.apache.isis.viewer.wicket.ui.pages.error.ErrorPage;
 import org.apache.log4j.Logger;
+import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.Session;
 import org.apache.wicket.core.request.handler.PageProvider;
 import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
@@ -74,34 +75,37 @@ public class WebRequestCycleForIsis /*extends WebRequestCycle*/ extends Abstract
     }
 
     @Override
-    public synchronized void onEndRequest(RequestCycle requestCycle) {
-        final IsisSession session = getIsisContext().getSessionInstance();
-        if (session != null) {
-            // in session
-            commitTransactionIfAny();
-            getIsisContext().closeSessionInstance();
-        }
+    public synchronized void onEndRequest(RequestCycle cycle) {
     }
 
-    private void commitTransactionIfAny() {
-        final IsisTransaction transaction = getTransactionManager().getTransaction();
-        if (transaction == null) {
-            return;
-        }
-        if (transaction.getState() == IsisTransaction.State.MUST_ABORT) {
-            getTransactionManager().abortTransaction();
-        } else if (transaction.getState() == IsisTransaction.State.IN_PROGRESS) {
-            getTransactionManager().endTransaction();
+
+    @Override
+    public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler) {
+        final IsisSession session = getIsisContext().getSessionInstance();
+        if (session != null) {
+            try {
+                // will commit (or abort) the transaction;
+                // an abort will cause the exception to be thrown.
+                getTransactionManager().endTransaction();
+            } catch(Exception ex) {
+                throw new RestartResponseException(errorPageFor(ex));
+            } finally {
+                getIsisContext().closeSessionInstance();
+            }
         }
     }
-
+    
     @Override
     public IRequestHandler onException(RequestCycle cycle, Exception ex) {
+        final ErrorPage page = errorPageFor(ex);
+        return new RenderPageRequestHandler(new PageProvider(page));
+    }
+
+    protected ErrorPage errorPageFor(Exception ex) {
         List<ExceptionRecognizer> exceptionRecognizers = getServicesInjector().lookupServices(ExceptionRecognizer.class);
         String message = new ExceptionRecognizerComposite(exceptionRecognizers).recognize(ex);
         final ErrorPage page = message != null ? new ErrorPage(message) : new ErrorPage(ex);
-        
-        return new RenderPageRequestHandler(new PageProvider(page));
+        return page;
     }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/applib/src/main/java/org/apache/isis/applib/DomainObjectContainer.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/DomainObjectContainer.java b/core/applib/src/main/java/org/apache/isis/applib/DomainObjectContainer.java
index dd86fe3..4a1b77d 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/DomainObjectContainer.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/DomainObjectContainer.java
@@ -75,9 +75,10 @@ public interface DomainObjectContainer {
      * Flush all changes to the object store.
      * 
      * <p>
-     * Typically only for use by tests.
+     * Occasionally useful to ensure that newly persisted domain objects
+     * are flushed to the database prior to a subsequent repository query. 
      * 
-     * @return <tt>true</tt>
+     * @return  - is never used, always returns <tt>false</tt>. 
      */
     boolean flush();
 
@@ -85,8 +86,13 @@ public interface DomainObjectContainer {
      * Commit all changes to the object store.
      * 
      * <p>
-     * Typically only for use by tests.
+     * This has been deprecated because the demarcation of transaction
+     * boundaries is a framework responsibility rather than being a
+     * responsibility of the domain object model.
+     * 
+     * @deprecated
      */
+    @Deprecated
     void commit();
 
     // ////////////////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/metamodel/src/main/java/org/apache/isis/core/commons/exceptions/IsisException.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/exceptions/IsisException.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/exceptions/IsisException.java
index b8fb515..36bdfe2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/exceptions/IsisException.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/exceptions/IsisException.java
@@ -19,91 +19,31 @@
 
 package org.apache.isis.core.commons.exceptions;
 
-import java.io.PrintStream;
-import java.io.PrintWriter;
 import java.text.MessageFormat;
 
 /**
  * General exception raised by the framework, typically a system exception.
  */
 public class IsisException extends RuntimeException {
+    
     private static final long serialVersionUID = 1L;
-    private static boolean THROWABLE_SUPPORTS_CAUSE;
-
-    static {
-        // Java 1.4, and after, holds a cause; Java 1.1, 1.2 and .Net do not, so
-        // we need to
-        // do the work ourselves.
-        final Class<?> c = Throwable.class;
-        try {
-            THROWABLE_SUPPORTS_CAUSE = c.getMethod("getCause", new Class[0]) != null;
-        } catch (final Exception ignore) {
-            // this should never occur in proper Java environment
-            THROWABLE_SUPPORTS_CAUSE = false;
-        }
-    }
-
-    private final Throwable cause;
-
+    
     public IsisException() {
-        super("");
-        cause = null;
     }
 
     public IsisException(final String message) {
         super(message);
-        cause = null;
     }
 
     public IsisException(final String messageFormat, final Object... args) {
         super(MessageFormat.format(messageFormat, args));
-        cause = null;
     }
 
     public IsisException(final String message, final Throwable cause) {
-        super(message);
-        this.cause = cause;
+        super(message, cause);
     }
 
     public IsisException(final Throwable cause) {
-        super(cause == null ? null : cause.toString());
-        this.cause = cause;
-    }
-
-    @Override
-    public Throwable getCause() {
-        return (cause == this ? null : cause);
-    }
-
-    @Override
-    public void printStackTrace(final PrintStream s) {
-        if (THROWABLE_SUPPORTS_CAUSE) {
-            super.printStackTrace(s);
-        } else {
-            synchronized (s) {
-                super.printStackTrace(s);
-                final Throwable c = getCause();
-                if (c != null) {
-                    s.print("Root cause: ");
-                    c.printStackTrace(s);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void printStackTrace(final PrintWriter s) {
-        if (THROWABLE_SUPPORTS_CAUSE) {
-            super.printStackTrace(s);
-        } else {
-            synchronized (s) {
-                super.printStackTrace(s);
-                final Throwable c = getCause();
-                if (c != null) {
-                    s.println("Root cause: ");
-                    c.printStackTrace(s);
-                }
-            }
-        }
+        super(cause);
     }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/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 da14ed9..81c6671 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
@@ -74,20 +74,12 @@ import com.google.common.collect.Sets;
  * applied.
  * 
  * <p>
- * The protocol by which the {@link IsisTransactionManager} interacts and uses
- * the {@link IsisTransaction} is not API, because different approaches are
- * used. For the server-side <tt>ObjectStoreTransactionManager</tt>, each object
- * is wrapped in a command generated by the underlying <tt>ObjectStore</tt>. for
- * the client-side <tt>ClientSideTransactionManager</tt>, the transaction simply
- * holds a set of events.
- * 
- * <p>
  * Note that methods such as <tt>flush()</tt>, <tt>commit()</tt> and
  * <tt>abort()</tt> are not part of the API. The place to control transactions
  * is through the {@link IsisTransactionManager transaction manager}, because
  * some implementations may support nesting and such like. It is also the job of
  * the {@link IsisTransactionManager} to ensure that the underlying persistence
- * mechanism (for example, the <tt>ObjectAdapterStore</tt>) is also committed.
+ * mechanism (for example, the <tt>ObjectStore</tt>) is also committed.
  */
 public class IsisTransaction implements TransactionScopedComponent {
 
@@ -100,7 +92,7 @@ public class IsisTransaction implements TransactionScopedComponent {
          * {@link IsisTransaction#commit() commit} or
          * {@link IsisTransaction#abort() abort}.
          */
-        IN_PROGRESS(true, true, true, false),
+        IN_PROGRESS,
         /**
          * Started, but has hit an exception.
          * 
@@ -113,7 +105,7 @@ public class IsisTransaction implements TransactionScopedComponent {
          * <p>
          * Similar to <tt>setRollbackOnly</tt> in EJBs.
          */
-        MUST_ABORT(false, false, true, false),
+        MUST_ABORT,
         /**
          * Completed, having successfully committed.
          * 
@@ -123,7 +115,7 @@ public class IsisTransaction implements TransactionScopedComponent {
          * {@link IsisTransaction#commit() commit} (will throw
          * {@link IllegalStateException}).
          */
-        COMMITTED(false, false, false, true),
+        COMMITTED,
         /**
          * Completed, having aborted.
          * 
@@ -133,26 +125,16 @@ public class IsisTransaction implements TransactionScopedComponent {
          * {@link IsisTransaction#abort() abort} (will throw
          * {@link IllegalStateException}).
          */
-        ABORTED(false, false, false, true);
+        ABORTED;
 
-        private final boolean canFlush;
-        private final boolean canCommit;
-        private final boolean canAbort;
-        private final boolean isComplete;
-
-        private State(final boolean canFlush, final boolean canCommit, final boolean canAbort, final boolean isComplete) {
-            this.canFlush = canFlush;
-            this.canCommit = canCommit;
-            this.canAbort = canAbort;
-            this.isComplete = isComplete;
-        }
+        private State(){}
 
         /**
          * Whether it is valid to {@link IsisTransaction#flush() flush} this
          * {@link IsisTransaction transaction}.
          */
         public boolean canFlush() {
-            return canFlush;
+            return this == IN_PROGRESS;
         }
 
         /**
@@ -160,15 +142,15 @@ public class IsisTransaction implements TransactionScopedComponent {
          * {@link IsisTransaction transaction}.
          */
         public boolean canCommit() {
-            return canCommit;
+            return this == IN_PROGRESS;
         }
 
         /**
-         * Whether it is valid to {@link IsisTransaction#abort() abort} this
+         * Whether it is valid to {@link IsisTransaction#markAsAborted() abort} this
          * {@link IsisTransaction transaction}.
          */
         public boolean canAbort() {
-            return canAbort;
+            return this == IN_PROGRESS || this == MUST_ABORT;
         }
 
         /**
@@ -176,7 +158,11 @@ public class IsisTransaction implements TransactionScopedComponent {
          * new one can be started).
          */
         public boolean isComplete() {
-            return isComplete;
+            return this == COMMITTED || this == ABORTED;
+        }
+
+        public boolean mustAbort() {
+            return this == MUST_ABORT;
         }
     }
 
@@ -189,7 +175,7 @@ public class IsisTransaction implements TransactionScopedComponent {
     private final IsisTransactionManager transactionManager;
     private final org.apache.isis.core.commons.authentication.MessageBroker messageBroker;
     private final UpdateNotifier updateNotifier;
-    private final List<IsisException> exceptions = Lists.newArrayList();
+    private IsisException abortCause;
 
     /**
      * could be null if none has been registered
@@ -202,8 +188,6 @@ public class IsisTransaction implements TransactionScopedComponent {
 
     private State state;
 
-    private RuntimeException cause;
-    
     private final UUID guid;
 
     public IsisTransaction(final IsisTransactionManager transactionManager, final org.apache.isis.core.commons.authentication.MessageBroker messageBroker2, final UpdateNotifier updateNotifier, final TransactionalResource objectStore, final AuditingService auditingService, PublishingServiceWithDefaultPayloadFactories publishingService) {
@@ -314,24 +298,6 @@ public class IsisTransaction implements TransactionScopedComponent {
 
 
 
-    /////////////////////////////////////////////////////////////////////////
-    // for worm-hole handling of exceptions
-    /////////////////////////////////////////////////////////////////////////
-
-    public void ensureExceptionsListIsEmpty() {
-        Ensure.ensureThatArg(exceptions.isEmpty(), is(true), "exceptions list is not empty");
-    }
-
-    public void addException(IsisException exception) {
-        exceptions.add(exception);
-    }
-    
-    public List<IsisException> getExceptionsIfAny() {
-        return Collections.unmodifiableList(exceptions);
-    }
-
-    
-
     // ////////////////////////////////////////////////////////////////
     // flush
     // ////////////////////////////////////////////////////////////////
@@ -349,8 +315,7 @@ public class IsisTransaction implements TransactionScopedComponent {
         try {
             doFlush();
         } catch (final RuntimeException ex) {
-            setState(State.MUST_ABORT);
-            setAbortCause(ex);
+            setAbortCause(new IsisTransactionFlushException(ex));
             throw ex;
         }
     }
@@ -498,7 +463,7 @@ public class IsisTransaction implements TransactionScopedComponent {
     public synchronized final void commit() {
 
         ensureThatState(getState().canCommit(), is(true), "state is: " + getState());
-        ensureThatState(exceptions.isEmpty(), is(true), "cannot commit: " + exceptions.size() + " exceptions have been raised");
+        ensureThatState(abortCause, is(nullValue()), "cannot commit: an abort cause has been set");
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("commit transaction " + this);
@@ -517,7 +482,7 @@ public class IsisTransaction implements TransactionScopedComponent {
             setState(State.COMMITTED);
             doPublish(getChangedObjects());
         } catch (final RuntimeException ex) {
-            setAbortCause(ex);
+            setAbortCause(new IsisTransactionManagerException(ex));
             throw ex;
         }
     }
@@ -527,10 +492,10 @@ public class IsisTransaction implements TransactionScopedComponent {
 
 
     // ////////////////////////////////////////////////////////////////
-    // abort
+    // markAsAborted
     // ////////////////////////////////////////////////////////////////
 
-    public synchronized final void abort() {
+    public synchronized final void markAsAborted() {
         ensureThatState(getState().canAbort(), is(true), "state is: " + getState());
         if (LOG.isInfoEnabled()) {
             LOG.info("abort transaction " + this);
@@ -540,23 +505,25 @@ public class IsisTransaction implements TransactionScopedComponent {
     }
 
     
+    
+    /////////////////////////////////////////////////////////////////////////
+    // handle exceptions on load, flush or commit
+    /////////////////////////////////////////////////////////////////////////
 
-    private void setAbortCause(final RuntimeException cause) {
-        this.cause = cause;
+    public void ensureNoAbortCause() {
+        Ensure.ensureThatArg(abortCause, is(nullValue()), "abort cause has been set");
     }
 
-    /**
-     * The cause (if any) for the transaction being aborted.
-     * 
-     * <p>
-     * Will be set if an exception is thrown while {@link #flush() flush}ing,
-     * {@link #commit() commit}ting or {@link #abort() abort}ing.
-     */
-    public RuntimeException getAbortCause() {
-        return cause;
+    public void setAbortCause(IsisException exception) {
+        setState(State.MUST_ABORT);
+        abortCause = exception;
     }
-
     
+    public IsisException getAbortCause() {
+        return abortCause;
+    }
+
+
     
     // //////////////////////////////////////////////////////////
     // Helpers

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionFlushException.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionFlushException.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionFlushException.java
new file mode 100644
index 0000000..f13ecdb
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionFlushException.java
@@ -0,0 +1,43 @@
+/*
+ *  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.system.transaction;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+
+public class IsisTransactionFlushException extends IsisException {
+
+    private static final long serialVersionUID = 1L;
+
+    public IsisTransactionFlushException() {
+    }
+
+    public IsisTransactionFlushException(final String message) {
+        super(message);
+    }
+
+    public IsisTransactionFlushException(final Throwable cause) {
+        super(cause);
+    }
+
+    public IsisTransactionFlushException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
index c83dd25..8fb6aec 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
@@ -301,13 +301,44 @@ public class IsisTransactionManager implements SessionScopedComponent {
     }
 
     /**
-     * Ends the transaction if nesting level is 0.
+     * Ends the transaction if nesting level is 0 (but will abort the transaction instead, 
+     * even if nesting level is not 0, if an {@link IsisTransaction#getAbortCause() abort cause}
+     * has been {@link IsisTransaction#setAbortCause(IsisException) set}.
+     * 
+     * <p>
+     * If in the process of committing the transaction an exception is thrown, then this will
+     * be handled and will abort the transaction instead.
+     * 
+     * <p>
+     * If an abort cause has been set (or an exception occurs), then will throw this
+     * exception in turn.
      */
     public synchronized void endTransaction() {
         if (LOG.isDebugEnabled()) {
             LOG.debug("endTransaction: level " + (transactionLevel) + "->" + (transactionLevel - 1));
         }
 
+        final IsisTransaction transaction = getTransaction();
+        if (transaction == null) {
+            return;
+        }
+
+        // terminate the transaction early if an abort cause was already set.
+        RuntimeException abortCause = this.getTransaction().getAbortCause();
+        if(transaction.getState().mustAbort() || abortCause != null) {
+            // these two checks are, in fact, equivalent.
+            
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("endTransaction: aborting instead [EARLY TERMINATION], abort cause '" + abortCause.getMessage() + "' has been set");
+            }
+            abortTransaction();
+            
+            // just in case any different exception was raised...
+            abortCause = this.getTransaction().getAbortCause();
+            throw abortCause;
+        }
+        
+        
         transactionLevel--;
         if (transactionLevel == 0) {
 
@@ -317,46 +348,53 @@ public class IsisTransactionManager implements SessionScopedComponent {
             // once the contract/API for the objectstore is better tied down, hopefully can simplify this...
             //
             
-            List<IsisException> exceptions = this.getTransaction().getExceptionsIfAny();
-            if(exceptions.isEmpty()) {
+            if(abortCause == null) {
             
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("endTransaction: committing");
                 }
-                
-                objectPersistor.objectChangedAllDirty();
-                
-                // just in case any additional exceptions were raised...
-                exceptions = this.getTransaction().getExceptionsIfAny();
+
+                try {
+                    objectPersistor.objectChangedAllDirty();
+                } catch(RuntimeException ex) {
+                    // just in case any new exception was raised...
+                    abortCause = ex;
+                }
             }
             
-            if(exceptions.isEmpty()) {
-                getTransaction().commit();
+            if(abortCause == null) {
                 
-                // in case any additional exceptions were raised...
-                exceptions = this.getTransaction().getExceptionsIfAny();
+                try {
+                    getTransaction().commit();
+                } catch(RuntimeException ex) {
+                    // just in case any new exception was raised...
+                    abortCause = ex;
+                }
             }
             
-            if(exceptions.isEmpty()) {
-                transactionalResource.endTransaction();
-                
-                // just in case any additional exceptions were raised...
-                exceptions = this.getTransaction().getExceptionsIfAny();
+            if(abortCause == null) {
+                try {
+                    transactionalResource.endTransaction();
+                } catch(RuntimeException ex) {
+                    // just in case any new exception was raised...
+                    abortCause = ex;
+                }
             }
             
-            if(!exceptions.isEmpty()) {
+            if(abortCause != null) {
                 
                 if (LOG.isDebugEnabled()) {
-                    LOG.debug("endTransaction: aborting instead, " + exceptions.size() + " exception(s) have been raised");
+                    LOG.debug("endTransaction: aborting instead, abort cause has been set");
+                }
+                try {
+                    abortTransaction();
+                } catch(RuntimeException ex) {
+                    // just in case any new exception was raised...
+                    abortCause = ex;
                 }
-                abortTransaction();
-                
-                // just in case any additional exceptions were raised...
-                exceptions = this.getTransaction().getExceptionsIfAny();
                 
-                throw exceptionToThrowFrom(exceptions);
+                throw abortCause;
             }
-            
         } else if (transactionLevel < 0) {
             LOG.error("endTransaction: transactionLevel=" + transactionLevel);
             transactionLevel = 0;
@@ -365,21 +403,9 @@ public class IsisTransactionManager implements SessionScopedComponent {
     }
 
 
-    private IsisException exceptionToThrowFrom(List<IsisException> exceptions) {
-        if(exceptions.size() == 1) {
-            return exceptions.get(0);
-        } 
-        final StringBuilder buf = new StringBuilder();
-        for (IsisException ope : exceptions) {
-            buf.append(ope.getMessage()).append("\n");
-        }
-        return new IsisException(buf.toString());
-    }
-    
-
     public synchronized void abortTransaction() {
         if (getTransaction() != null) {
-            getTransaction().abort();
+            getTransaction().markAsAborted();
             transactionLevel = 0;
             transactionalResource.abortTransaction();
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerCommitException.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerCommitException.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerCommitException.java
new file mode 100644
index 0000000..2d88353
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerCommitException.java
@@ -0,0 +1,43 @@
+/*
+ *  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.system.transaction;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+
+public class IsisTransactionManagerCommitException extends IsisException {
+
+    private static final long serialVersionUID = 1L;
+
+    public IsisTransactionManagerCommitException() {
+    }
+
+    public IsisTransactionManagerCommitException(final String message) {
+        super(message);
+    }
+
+    public IsisTransactionManagerCommitException(final Throwable cause) {
+        super(cause);
+    }
+
+    public IsisTransactionManagerCommitException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerDomCallbackException.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerDomCallbackException.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerDomCallbackException.java
new file mode 100644
index 0000000..8201b0f
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerDomCallbackException.java
@@ -0,0 +1,43 @@
+/*
+ *  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.system.transaction;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+
+public class IsisTransactionManagerDomCallbackException extends IsisException {
+
+    private static final long serialVersionUID = 1L;
+
+    public IsisTransactionManagerDomCallbackException() {
+    }
+
+    public IsisTransactionManagerDomCallbackException(final String message) {
+        super(message);
+    }
+
+    public IsisTransactionManagerDomCallbackException(final Throwable cause) {
+        super(cause);
+    }
+
+    public IsisTransactionManagerDomCallbackException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerResourceCommitException.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerResourceCommitException.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerResourceCommitException.java
new file mode 100644
index 0000000..da13e54
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManagerResourceCommitException.java
@@ -0,0 +1,43 @@
+/*
+ *  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.system.transaction;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+
+public class IsisTransactionManagerResourceCommitException extends IsisException {
+
+    private static final long serialVersionUID = 1L;
+
+    public IsisTransactionManagerResourceCommitException() {
+    }
+
+    public IsisTransactionManagerResourceCommitException(final String message) {
+        super(message);
+    }
+
+    public IsisTransactionManagerResourceCommitException(final Throwable cause) {
+        super(cause);
+    }
+
+    public IsisTransactionManagerResourceCommitException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/core/runtime/src/test/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionTest.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/test/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionTest.java b/core/runtime/src/test/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionTest.java
index 5320556..6ecadec 100644
--- a/core/runtime/src/test/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionTest.java
+++ b/core/runtime/src/test/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionTest.java
@@ -196,7 +196,7 @@ public class IsisTransactionTest {
 
         transaction.addCommand(command);
         transaction.addCommand(command2);
-        transaction.abort();
+        transaction.markAsAborted();
     }
 
 
@@ -305,14 +305,14 @@ public class IsisTransactionTest {
 
     @Test(expected = IllegalStateException.class)
     public void shouldThrowExceptionIfAttemptToAbortAnAlreadyAbortedTransaction() throws Exception {
-        transaction.abort();
+        transaction.markAsAborted();
 
-        transaction.abort();
+        transaction.markAsAborted();
     }
 
     @Test(expected = IllegalStateException.class)
     public void shouldThrowExceptionIfAttemptToCommitAnAlreadyAbortedTransaction() throws Exception {
-        transaction.abort();
+        transaction.markAsAborted();
 
         transaction.commit();
     }
@@ -327,7 +327,7 @@ public class IsisTransactionTest {
 
         transaction.commit();
 
-        transaction.abort();
+        transaction.markAsAborted();
     }
 
     @Test(expected = IllegalStateException.class)

http://git-wip-us.apache.org/repos/asf/isis/blob/d52c407f/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties b/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties
index 8591588..06701df 100644
--- a/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties
+++ b/example/application/quickstart_wicket_restful_jdo/viewer-webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties
@@ -27,6 +27,7 @@ isis.persistor.datanucleus.impl.datanucleus.validateConstraints=true
 
 # L2 cache (on by default)
 isis.persistor.datanucleus.impl.datanucleus.cache.level2.type=none
+isis.persistor.datanucleus.impl.datanucleus.cache.level2.mode=ENABLE_SELECTIVE
 
 #see http://www.datanucleus.org/products/datanucleus/jdo/transaction_types.html#optimistic
 isis.persistor.datanucleus.impl.datanucleus.persistenceByReachabilityAtCommit=false