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 2012/12/06 11:10:36 UTC

[42/51] [partial] ISIS-188: moving modules into core

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionDefault.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionDefault.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionDefault.java
new file mode 100644
index 0000000..c834745
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionDefault.java
@@ -0,0 +1,345 @@
+/*
+ *  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.runtimes.dflt.runtime.system.session;
+
+import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.components.SessionScopedComponent;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebugString;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.imageloader.TemplateImageLoader;
+import org.apache.isis.core.runtime.userprofile.UserProfile;
+import org.apache.isis.runtimes.dflt.runtime.system.DeploymentType;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.runtimes.dflt.runtime.system.transaction.IsisTransaction;
+import org.apache.isis.runtimes.dflt.runtime.system.transaction.IsisTransactionManager;
+
+/**
+ * Analogous to a Hibernate <tt>Session</tt>, holds the current set of
+ * components for a specific execution context (such as on a thread).
+ * 
+ * <p>
+ * The <tt>IsisContext</tt> class (in <tt>nof-core</tt>) is responsible for
+ * locating the current execution context.
+ * 
+ * @see IsisSessionFactory
+ */
+public class IsisSessionDefault implements IsisSession {
+
+    private static final Logger LOG = Logger.getLogger(IsisSessionDefault.class);
+
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/MM HH:mm:ss,SSS");
+    private static int nextId = 1;
+
+    private final IsisSessionFactory executionContextFactory;
+
+    private final AuthenticationSession authenticationSession;
+    private PersistenceSession persistenceSession; // only non-final so can be
+                                                   // replaced in tests.
+    private final UserProfile userProfile;
+
+    private final int id;
+    private long accessTime;
+    private String debugSnapshot;
+
+    public IsisSessionDefault(final IsisSessionFactory sessionFactory, final AuthenticationSession authenticationSession, final PersistenceSession persistenceSession, final UserProfile userProfile) {
+
+        // global context
+        ensureThatArg(sessionFactory, is(not(nullValue())), "execution context factory is required");
+
+        // session
+        ensureThatArg(authenticationSession, is(not(nullValue())), "authentication session is required");
+        ensureThatArg(persistenceSession, is(not(nullValue())), "persistence session is required");
+        ensureThatArg(userProfile, is(not(nullValue())), "user profile is required");
+
+        this.executionContextFactory = sessionFactory;
+
+        this.authenticationSession = authenticationSession;
+        this.persistenceSession = persistenceSession;
+        this.userProfile = userProfile;
+
+        setSessionOpenTime(System.currentTimeMillis());
+
+        this.id = nextId++;
+    }
+
+    // //////////////////////////////////////////////////////
+    // open, close
+    // //////////////////////////////////////////////////////
+
+    @Override
+    public void open() {
+        persistenceSession.open();
+    }
+
+    /**
+     * Closes session.
+     */
+    @Override
+    public void close() {
+        takeSnapshot();
+        getPersistenceSession().close();
+    }
+
+    // //////////////////////////////////////////////////////
+    // shutdown
+    // //////////////////////////////////////////////////////
+
+    /**
+     * Shuts down all components.
+     */
+    @Override
+    public void closeAll() {
+        close();
+
+        shutdownIfRequired(persistenceSession);
+    }
+
+    private void shutdownIfRequired(final Object o) {
+        if (o instanceof SessionScopedComponent) {
+            final SessionScopedComponent requiresSetup = (SessionScopedComponent) o;
+            requiresSetup.close();
+        }
+    }
+
+    // //////////////////////////////////////////////////////
+    // ExecutionContextFactory
+    // //////////////////////////////////////////////////////
+
+    @Override
+    public IsisSessionFactory getSessionFactory() {
+        return executionContextFactory;
+    }
+
+    /**
+     * Convenience method.
+     */
+    public DeploymentType getDeploymentType() {
+        return executionContextFactory.getDeploymentType();
+    }
+
+    /**
+     * Convenience method.
+     */
+    public IsisConfiguration getConfiguration() {
+        return executionContextFactory.getConfiguration();
+    }
+
+    /**
+     * Convenience method.
+     */
+    public SpecificationLoaderSpi getSpecificationLoader() {
+        return executionContextFactory.getSpecificationLoader();
+    }
+
+    /**
+     * Convenience method.
+     */
+    public TemplateImageLoader getTemplateImageLoader() {
+        return executionContextFactory.getTemplateImageLoader();
+    }
+
+    // //////////////////////////////////////////////////////
+    // AuthenticationSession
+    // //////////////////////////////////////////////////////
+
+    /**
+     * Returns the security session representing this user for this execution
+     * context.
+     */
+    @Override
+    public AuthenticationSession getAuthenticationSession() {
+        return authenticationSession;
+    }
+
+    private String getSessionUserName() {
+        return getAuthenticationSession().getUserName();
+    }
+
+    // //////////////////////////////////////////////////////
+    // Id
+    // //////////////////////////////////////////////////////
+
+    /**
+     * Returns an descriptive identifier for this {@link IsisSessionDefault}.
+     */
+    @Override
+    public String getId() {
+        return "#" + id + getSessionUserName();
+    }
+
+    // //////////////////////////////////////////////////////
+    // Persistence Session
+    // //////////////////////////////////////////////////////
+
+    @Override
+    public PersistenceSession getPersistenceSession() {
+        return persistenceSession;
+    }
+
+    // //////////////////////////////////////////////////////
+    // Perspective
+    // //////////////////////////////////////////////////////
+
+    @Override
+    public UserProfile getUserProfile() {
+        return userProfile;
+    }
+
+    // //////////////////////////////////////////////////////
+    // Session Open Time
+    // //////////////////////////////////////////////////////
+
+    protected long getSessionOpenTime() {
+        return accessTime;
+    }
+
+    private void setSessionOpenTime(final long accessTime) {
+        this.accessTime = accessTime;
+    }
+
+    // //////////////////////////////////////////////////////
+    // Transaction
+    // //////////////////////////////////////////////////////
+
+    /**
+     * Convenience method that returns the {@link IsisTransaction} of the
+     * session, if any.
+     */
+    @Override
+    public IsisTransaction getCurrentTransaction() {
+        return getTransactionManager().getTransaction();
+    }
+
+    // //////////////////////////////////////////////////////
+    // testSetObjectPersistor
+    // //////////////////////////////////////////////////////
+
+    /**
+     * Should only be called in tests.
+     */
+    public void testSetObjectPersistor(final PersistenceSession objectPersistor) {
+        this.persistenceSession = objectPersistor;
+    }
+
+    // //////////////////////////////////////////////////////
+    // toString
+    // //////////////////////////////////////////////////////
+
+    @Override
+    public String toString() {
+        final ToString asString = new ToString(this);
+        asString.append("context", getId());
+        appendState(asString);
+        return asString.toString();
+    }
+
+    // //////////////////////////////////////////////////////
+    // Debugging
+    // //////////////////////////////////////////////////////
+
+    @Override
+    public void debugAll(final DebugBuilder debug) {
+        debug.startSection("Isis Context Snapshot");
+        debug.appendln(debugSnapshot);
+        debug.endSection();
+    }
+
+    @Override
+    public void debug(final DebugBuilder debug) {
+        debug.appendAsHexln("hash", hashCode());
+        debug.appendln("context id", id);
+        debug.appendln("accessed", DATE_FORMAT.format(new Date(getSessionOpenTime())));
+        debugState(debug);
+    }
+
+    public void takeSnapshot() {
+        if (!LOG.isDebugEnabled()) {
+            return;
+        }
+        final DebugString debug = new DebugString();
+        debug(debug);
+        debug.indent();
+        debug.appendln();
+
+        debug(debug, getPersistenceSession());
+        if (getCurrentTransaction() != null) {
+            debug(debug, getCurrentTransaction().getUpdateNotifier());
+            debug(debug, getCurrentTransaction().getMessageBroker());
+        }
+        debugSnapshot = debug.toString();
+
+        LOG.debug(debugSnapshot);
+    }
+
+    private void debug(final DebugBuilder debug, final Object object) {
+        if (object instanceof DebuggableWithTitle) {
+            final DebuggableWithTitle d = (DebuggableWithTitle) object;
+            debug.startSection(d.debugTitle());
+            d.debugData(debug);
+            debug.endSection();
+        } else {
+            debug.appendln("no debug for " + object);
+        }
+    }
+
+    public void appendState(final ToString asString) {
+        asString.append("authenticationSession", getAuthenticationSession());
+        asString.append("persistenceSession", getPersistenceSession());
+        asString.append("transaction", getCurrentTransaction());
+        if (getCurrentTransaction() != null) {
+            asString.append("messageBroker", getCurrentTransaction().getMessageBroker());
+            asString.append("updateNotifier", getCurrentTransaction().getUpdateNotifier());
+        }
+    }
+
+    @Override
+    public void debugState(final DebugBuilder debug) {
+        debug.appendln("authenticationSession", getAuthenticationSession());
+        debug.appendln("persistenceSession", getPersistenceSession());
+        debug.appendln("transaction", getCurrentTransaction());
+        if (getCurrentTransaction() != null) {
+            debug.appendln("messageBroker", getCurrentTransaction().getMessageBroker());
+            debug.appendln("updateNotifier", getCurrentTransaction().getUpdateNotifier());
+        }
+    }
+
+    // /////////////////////////////////////////////////////
+    // Dependencies (from constructor)
+    // /////////////////////////////////////////////////////
+
+    private IsisTransactionManager getTransactionManager() {
+        return getPersistenceSession().getTransactionManager();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactory.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactory.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactory.java
new file mode 100644
index 0000000..56a5014
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactory.java
@@ -0,0 +1,105 @@
+/*
+ *  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.runtimes.dflt.runtime.system.session;
+
+import java.util.List;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.components.ApplicationScopedComponent;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.metamodel.adapter.oid.Oid;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+import org.apache.isis.core.runtime.authorization.AuthorizationManager;
+import org.apache.isis.core.runtime.imageloader.TemplateImageLoader;
+import org.apache.isis.core.runtime.userprofile.UserProfileLoader;
+import org.apache.isis.runtimes.dflt.runtime.system.DeploymentType;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSessionFactory;
+
+/**
+ * Analogous to a Hibernate <tt>SessionFactory</tt>.
+ * 
+ * @see IsisSession
+ */
+public interface IsisSessionFactory extends ApplicationScopedComponent {
+
+    /**
+     * Creates and {@link IsisSession#open() open}s the {@link IsisSession}.
+     */
+    IsisSession openSession(final AuthenticationSession session);
+
+    /**
+     * The {@link ApplicationScopedComponent application-scoped}
+     * {@link DeploymentType}.
+     */
+    public DeploymentType getDeploymentType();
+
+    /**
+     * The {@link ApplicationScopedComponent application-scoped}
+     * {@link IsisConfiguration}.
+     */
+    public IsisConfiguration getConfiguration();
+
+    /**
+     * The {@link ApplicationScopedComponent application-scoped}
+     * {@link SpecificationLoaderSpi}.
+     */
+    public SpecificationLoaderSpi getSpecificationLoader();
+
+    /**
+     * The {@link ApplicationScopedComponent application-scoped}
+     * {@link TemplateImageLoader}.
+     */
+    public TemplateImageLoader getTemplateImageLoader();
+
+    /**
+     * The {@link AuthenticationManager} that will be used to authenticate and
+     * create {@link AuthenticationSession}s
+     * {@link IsisSession#getAuthenticationSession() within} the
+     * {@link IsisSession}.
+     */
+    public AuthenticationManager getAuthenticationManager();
+
+    /**
+     * The {@link AuthorizationManager} that will be used to authorize access to
+     * domain objects.
+     */
+    public AuthorizationManager getAuthorizationManager();
+
+    /**
+     * The {@link PersistenceSessionFactory} that will be used to create
+     * {@link PersistenceSession} {@link IsisSession#getPersistenceSession()
+     * within} the {@link IsisSession}.
+     */
+    public PersistenceSessionFactory getPersistenceSessionFactory();
+
+    public UserProfileLoader getUserProfileLoader();
+
+    public List<Object> getServices();
+
+    /**
+     * The {@link OidMarshaller} to use for marshalling and unmarshalling {@link Oid}s
+     * into strings.
+     */
+	public OidMarshaller getOidMarshaller();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryAbstract.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryAbstract.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryAbstract.java
new file mode 100644
index 0000000..ed34750
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryAbstract.java
@@ -0,0 +1,197 @@
+/*
+ *  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.runtimes.dflt.runtime.system.session;
+
+import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+
+import java.util.List;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.components.ApplicationScopedComponent;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.commons.lang.JavaClassUtils;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+import org.apache.isis.core.runtime.authorization.AuthorizationManager;
+import org.apache.isis.core.runtime.imageloader.TemplateImageLoader;
+import org.apache.isis.core.runtime.userprofile.UserProfile;
+import org.apache.isis.core.runtime.userprofile.UserProfileLoader;
+import org.apache.isis.runtimes.dflt.runtime.installerregistry.InstallerLookup;
+import org.apache.isis.runtimes.dflt.runtime.system.DeploymentType;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSessionFactory;
+
+/**
+ * Creates an implementation of
+ * {@link IsisSessionFactory#openSession(AuthenticationSession)} to create an
+ * {@link IsisSession}, but delegates to subclasses to actually obtain the
+ * components that make up that {@link IsisSession}.
+ * 
+ * <p>
+ * The idea is that one subclass can use the {@link InstallerLookup} design to
+ * lookup installers for components (and hence create the components
+ * themselves), whereas another subclass might simply use Spring (or another DI
+ * container) to inject in the components according to some Spring-configured
+ * application context.
+ */
+public abstract class IsisSessionFactoryAbstract implements IsisSessionFactory {
+
+    private final DeploymentType deploymentType;
+    private final IsisConfiguration configuration;
+    private final TemplateImageLoader templateImageLoader;
+    private final SpecificationLoaderSpi specificationLoaderSpi;
+    private final AuthenticationManager authenticationManager;
+    private final AuthorizationManager authorizationManager;
+    private final PersistenceSessionFactory persistenceSessionFactory;
+    private final UserProfileLoader userProfileLoader;
+    private final List<Object> serviceList;
+    private final OidMarshaller oidMarshaller;
+
+    public IsisSessionFactoryAbstract(final DeploymentType deploymentType, final IsisConfiguration configuration, final SpecificationLoaderSpi specificationLoader, final TemplateImageLoader templateImageLoader, final AuthenticationManager authenticationManager,
+            final AuthorizationManager authorizationManager, final UserProfileLoader userProfileLoader, final PersistenceSessionFactory persistenceSessionFactory, final List<Object> serviceList, OidMarshaller oidMarshaller) {
+
+        ensureThatArg(deploymentType, is(not(nullValue())));
+        ensureThatArg(configuration, is(not(nullValue())));
+        ensureThatArg(specificationLoader, is(not(nullValue())));
+        ensureThatArg(templateImageLoader, is(not(nullValue())));
+        ensureThatArg(authenticationManager, is(not(nullValue())));
+        ensureThatArg(authorizationManager, is(not(nullValue())));
+        ensureThatArg(userProfileLoader, is(not(nullValue())));
+        ensureThatArg(persistenceSessionFactory, is(not(nullValue())));
+        ensureThatArg(serviceList, is(not(nullValue())));
+
+        this.deploymentType = deploymentType;
+        this.configuration = configuration;
+        this.templateImageLoader = templateImageLoader;
+        this.specificationLoaderSpi = specificationLoader;
+        this.authenticationManager = authenticationManager;
+        this.authorizationManager = authorizationManager;
+        this.userProfileLoader = userProfileLoader;
+        this.persistenceSessionFactory = persistenceSessionFactory;
+        this.serviceList = serviceList;
+        this.oidMarshaller = oidMarshaller;
+    }
+
+    // ///////////////////////////////////////////
+    // init, shutdown
+    // ///////////////////////////////////////////
+
+    /**
+     * Wires components as necessary, and then
+     * {@link ApplicationScopedComponent#init() init}ializes all.
+     */
+    @Override
+    public void init() {
+        templateImageLoader.init();
+
+        specificationLoaderSpi.setServiceClasses(JavaClassUtils.toClasses(serviceList));
+
+        specificationLoaderSpi.init();
+
+        // must come after init of spec loader.
+        specificationLoaderSpi.injectInto(persistenceSessionFactory);
+        persistenceSessionFactory.setServices(serviceList);
+        userProfileLoader.setServices(serviceList);
+
+        authenticationManager.init();
+        authorizationManager.init();
+        persistenceSessionFactory.init();
+    }
+
+    @Override
+    public void shutdown() {
+        persistenceSessionFactory.shutdown();
+        authenticationManager.shutdown();
+        specificationLoaderSpi.shutdown();
+        templateImageLoader.shutdown();
+        userProfileLoader.shutdown();
+    }
+
+    @Override
+    public IsisSession openSession(final AuthenticationSession authenticationSession) {
+        final PersistenceSession persistenceSession = persistenceSessionFactory.createPersistenceSession();
+        ensureThatArg(persistenceSession, is(not(nullValue())));
+
+        final UserProfile userProfile = userProfileLoader.getProfile(authenticationSession);
+        ensureThatArg(userProfile, is(not(nullValue())));
+
+        // inject into persistenceSession any/all application-scoped components
+        // that it requires
+        getSpecificationLoader().injectInto(persistenceSession);
+
+        final IsisSessionDefault isisSessionDefault = new IsisSessionDefault(this, authenticationSession, persistenceSession, userProfile);
+
+        return isisSessionDefault;
+    }
+
+    @Override
+    public IsisConfiguration getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public DeploymentType getDeploymentType() {
+        return deploymentType;
+    }
+
+    @Override
+    public SpecificationLoaderSpi getSpecificationLoader() {
+        return specificationLoaderSpi;
+    }
+
+    @Override
+    public TemplateImageLoader getTemplateImageLoader() {
+        return templateImageLoader;
+    }
+
+    @Override
+    public AuthenticationManager getAuthenticationManager() {
+        return authenticationManager;
+    }
+
+    @Override
+    public AuthorizationManager getAuthorizationManager() {
+        return authorizationManager;
+    }
+
+    @Override
+    public PersistenceSessionFactory getPersistenceSessionFactory() {
+        return persistenceSessionFactory;
+    }
+
+    @Override
+    public UserProfileLoader getUserProfileLoader() {
+        return userProfileLoader;
+    }
+
+    @Override
+    public List<Object> getServices() {
+        return serviceList;
+    }
+    
+    @Override
+    public OidMarshaller getOidMarshaller() {
+    	return oidMarshaller;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryDefault.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryDefault.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryDefault.java
new file mode 100644
index 0000000..2a7dcbd
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/IsisSessionFactoryDefault.java
@@ -0,0 +1,45 @@
+/*
+ *  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.runtimes.dflt.runtime.system.session;
+
+import java.util.List;
+
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+import org.apache.isis.core.runtime.authorization.AuthorizationManager;
+import org.apache.isis.core.runtime.imageloader.TemplateImageLoader;
+import org.apache.isis.core.runtime.userprofile.UserProfileLoader;
+import org.apache.isis.runtimes.dflt.runtime.system.DeploymentType;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSessionFactory;
+
+/**
+ * As its superclass, but provides a default for some of more basic components
+ * (that is, where the core framework offers only a single implementation).
+ */
+public class IsisSessionFactoryDefault extends IsisSessionFactoryAbstract {
+
+    public IsisSessionFactoryDefault(final DeploymentType deploymentType, final IsisConfiguration configuration, final TemplateImageLoader templateImageLoader, final SpecificationLoaderSpi specificationLoader, final AuthenticationManager authenticationManager,
+            final AuthorizationManager authorizationManager, final UserProfileLoader userProfileLoader, final PersistenceSessionFactory persistenceSessionFactory, final List<Object> servicesList, OidMarshaller oidMarshaller) {
+        super(deploymentType, configuration, specificationLoader, templateImageLoader, authenticationManager, authorizationManager, userProfileLoader, persistenceSessionFactory, servicesList, oidMarshaller);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/SessionFactoryException.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/SessionFactoryException.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/SessionFactoryException.java
new file mode 100644
index 0000000..67438ca
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/session/SessionFactoryException.java
@@ -0,0 +1,41 @@
+/*
+ *  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.runtimes.dflt.runtime.system.session;
+
+public class SessionFactoryException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    public SessionFactoryException() {
+    }
+
+    public SessionFactoryException(final String message) {
+        super(message);
+    }
+
+    public SessionFactoryException(final Throwable cause) {
+        super(cause);
+    }
+
+    public SessionFactoryException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/EnlistedObjectDirtying.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/EnlistedObjectDirtying.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/EnlistedObjectDirtying.java
new file mode 100644
index 0000000..8fd5aff
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/EnlistedObjectDirtying.java
@@ -0,0 +1,49 @@
+/*
+ *  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.runtimes.dflt.runtime.system.transaction;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.Dirtiable;
+
+/**
+ * As called by the {@link IsisTransactionManager}.
+ * 
+ * <p>
+ * Dirtiable support.
+ */
+public interface EnlistedObjectDirtying {
+
+    /**
+     * Mark as {@link #objectChanged(ObjectAdapter) changed } all
+     * {@link Dirtiable} objects that have been
+     * {@link Dirtiable#markDirty(ObjectAdapter) manually marked} as dirty.
+     * 
+     * <p>
+     * Called by the {@link IsisTransactionManager}.
+     */
+    void objectChangedAllDirty();
+
+    /**
+     * Set as {@link Dirtiable#clearDirty(ObjectAdapter) clean} any
+     * {@link Dirtiable} objects.
+     */
+    void clearAllDirty();
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransaction.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransaction.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransaction.java
new file mode 100644
index 0000000..8a56cf9
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransaction.java
@@ -0,0 +1,689 @@
+/*
+ *  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.runtimes.dflt.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.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.isis.core.commons.components.TransactionScopedComponent;
+import org.apache.isis.core.commons.ensure.Ensure;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.ResolveState;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.CreateObjectCommand;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.DestroyObjectCommand;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.PersistenceCommand;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.SaveObjectCommand;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.TransactionalResource;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.log4j.Logger;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * Used by the {@link IsisTransactionManager} to captures a set of changes to be
+ * 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.
+ */
+public class IsisTransaction implements TransactionScopedComponent {
+
+    public static enum State {
+        /**
+         * Started, still in progress.
+         * 
+         * <p>
+         * May {@link IsisTransaction#flush() flush},
+         * {@link IsisTransaction#commit() commit} or
+         * {@link IsisTransaction#abort() abort}.
+         */
+        IN_PROGRESS(true, true, true, false),
+        /**
+         * Started, but has hit an exception.
+         * 
+         * <p>
+         * May not {@link IsisTransaction#flush()} or
+         * {@link IsisTransaction#commit() commit} (will throw an
+         * {@link IllegalStateException}), but can only
+         * {@link IsisTransaction#abort() abort}.
+         * 
+         * <p>
+         * Similar to <tt>setRollbackOnly</tt> in EJBs.
+         */
+        MUST_ABORT(false, false, true, false),
+        /**
+         * Completed, having successfully committed.
+         * 
+         * <p>
+         * May not {@link IsisTransaction#flush()} or
+         * {@link IsisTransaction#abort() abort} or
+         * {@link IsisTransaction#commit() commit} (will throw
+         * {@link IllegalStateException}).
+         */
+        COMMITTED(false, false, false, true),
+        /**
+         * Completed, having aborted.
+         * 
+         * <p>
+         * May not {@link IsisTransaction#flush()},
+         * {@link IsisTransaction#commit() commit} or
+         * {@link IsisTransaction#abort() abort} (will throw
+         * {@link IllegalStateException}).
+         */
+        ABORTED(false, false, false, true);
+
+        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;
+        }
+
+        /**
+         * Whether it is valid to {@link IsisTransaction#flush() flush} this
+         * {@link IsisTransaction transaction}.
+         */
+        public boolean canFlush() {
+            return canFlush;
+        }
+
+        /**
+         * Whether it is valid to {@link IsisTransaction#commit() commit} this
+         * {@link IsisTransaction transaction}.
+         */
+        public boolean canCommit() {
+            return canCommit;
+        }
+
+        /**
+         * Whether it is valid to {@link IsisTransaction#abort() abort} this
+         * {@link IsisTransaction transaction}.
+         */
+        public boolean canAbort() {
+            return canAbort;
+        }
+
+        /**
+         * Whether the {@link IsisTransaction transaction} is complete (and so a
+         * new one can be started).
+         */
+        public boolean isComplete() {
+            return isComplete;
+        }
+
+    }
+
+
+    private static final Logger LOG = Logger.getLogger(IsisTransaction.class);
+
+
+    private final TransactionalResource objectStore;
+    private final List<PersistenceCommand> commands = Lists.newArrayList();
+    private final IsisTransactionManager transactionManager;
+    private final MessageBroker messageBroker;
+    private final UpdateNotifier updateNotifier;
+    private final List<IsisException> exceptions = Lists.newArrayList();
+
+    private State state;
+
+    private RuntimeException cause;
+
+    public IsisTransaction(final IsisTransactionManager transactionManager, final MessageBroker messageBroker, final UpdateNotifier updateNotifier, final TransactionalResource objectStore) {
+        
+        ensureThatArg(transactionManager, is(not(nullValue())), "transaction manager is required");
+        ensureThatArg(messageBroker, is(not(nullValue())), "message broker is required");
+        ensureThatArg(updateNotifier, is(not(nullValue())), "update notifier is required");
+
+        this.transactionManager = transactionManager;
+        this.messageBroker = messageBroker;
+        this.updateNotifier = updateNotifier;
+
+        this.state = State.IN_PROGRESS;
+
+        this.objectStore = objectStore;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("new transaction " + this);
+        }
+    }
+
+    // ////////////////////////////////////////////////////////////////
+    // State
+    // ////////////////////////////////////////////////////////////////
+
+    public State getState() {
+        return state;
+    }
+
+    private void setState(final State state) {
+        this.state = state;
+    }
+
+    
+    // //////////////////////////////////////////////////////////
+    // Commands
+    // //////////////////////////////////////////////////////////
+
+    /**
+     * Add the non-null command to the list of commands to execute at the end of
+     * the transaction.
+     */
+    public void addCommand(final PersistenceCommand command) {
+        if (command == null) {
+            return;
+        }
+
+        final ObjectAdapter onObject = command.onAdapter();
+
+        // Saves are ignored when preceded by another save, or a delete
+        if (command instanceof SaveObjectCommand) {
+            if (alreadyHasCreate(onObject) || alreadyHasSave(onObject)) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("ignored command as object already created/saved" + command);
+                }
+                return;
+            }
+
+            if (alreadyHasDestroy(onObject)) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("ignored command " + command + " as object no longer exists");
+                }
+                return;
+            }
+        }
+
+        // Destroys are ignored when preceded by a create, or another destroy
+        if (command instanceof DestroyObjectCommand) {
+            if (alreadyHasCreate(onObject)) {
+                removeCreate(onObject);
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("ignored both create and destroy command " + command);
+                }
+                return;
+            }
+
+            if (alreadyHasSave(onObject)) {
+                removeSave(onObject);
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("removed prior save command " + command);
+                }
+            }
+
+            if (alreadyHasDestroy(onObject)) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("ignored command " + command + " as command already recorded");
+                }
+                return;
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("add command " + command);
+        }
+        commands.add(command);
+    }
+
+
+
+    /////////////////////////////////////////////////////////////////////////
+    // 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
+    // ////////////////////////////////////////////////////////////////
+
+    public synchronized final void flush() {
+        ensureThatState(getState().canFlush(), is(true), "state is: " + getState());
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("flush transaction " + this);
+        }
+
+        try {
+            doFlush();
+        } catch (final RuntimeException ex) {
+            setState(State.MUST_ABORT);
+            setAbortCause(ex);
+            throw ex;
+        }
+    }
+
+    /**
+     * Mandatory hook method for subclasses to persist all pending changes.
+     * 
+     * <p>
+     * Called by both {@link #commit()} and by {@link #flush()}:
+     * <table>
+     * <tr>
+     * <th>called from</th>
+     * <th>next {@link #getState() state} if ok</th>
+     * <th>next {@link #getState() state} if exception</th>
+     * </tr>
+     * <tr>
+     * <td>{@link #commit()}</td>
+     * <td>{@link State#COMMITTED}</td>
+     * <td>{@link State#ABORTED}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #flush()}</td>
+     * <td>{@link State#IN_PROGRESS}</td>
+     * <td>{@link State#MUST_ABORT}</td>
+     * </tr>
+     * </table>
+     */
+    private void doFlush() {
+        
+        try {
+            doAudit(getAuditEntries());
+            
+            objectStore.execute(Collections.unmodifiableList(commands));
+            
+            for (final PersistenceCommand command : commands) {
+                if (command instanceof DestroyObjectCommand) {
+                    final ObjectAdapter adapter = command.onAdapter();
+                    adapter.setVersion(null);
+                    adapter.changeState(ResolveState.DESTROYED);
+                }
+            }
+        } finally {
+            // even if there's an exception, we want to clear the commands
+            // this is because the Wicket viewer uses an implementation of IsisContext 
+            // whereby there are several threads which could be sharing the same context
+            // if the first fails, we don't want the others to pick up the same command list
+            // and try again
+            commands.clear();
+        }
+    }
+
+    
+    /**
+     * Hook method for subtypes to audit as required.
+     */
+    protected void doAudit(Set<Entry<AdapterAndProperty, PreAndPostValues>> auditEntries) {
+        for (Entry<AdapterAndProperty, PreAndPostValues> auditEntry : auditEntries) {
+            LOG.info(auditEntry.getKey() + ": " + auditEntry.getValue());
+        }
+    }
+
+
+    
+    // ////////////////////////////////////////////////////////////////
+    // commit
+    // ////////////////////////////////////////////////////////////////
+
+    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");
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("commit transaction " + this);
+        }
+
+        if (getState() == State.COMMITTED) {
+            if (LOG.isInfoEnabled()) {
+                LOG.info("already committed; ignoring");
+            }
+            return;
+        }
+        
+        try {
+            doFlush();
+            setState(State.COMMITTED);
+        } catch (final RuntimeException ex) {
+            setAbortCause(ex);
+            throw ex;
+        }
+    }
+
+    
+    // ////////////////////////////////////////////////////////////////
+    // abort
+    // ////////////////////////////////////////////////////////////////
+
+    public synchronized final void abort() {
+        ensureThatState(getState().canAbort(), is(true), "state is: " + getState());
+        if (LOG.isInfoEnabled()) {
+            LOG.info("abort transaction " + this);
+        }
+
+        setState(State.ABORTED);
+    }
+
+    
+
+    private void setAbortCause(final RuntimeException cause) {
+        this.cause = cause;
+    }
+
+    /**
+     * 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;
+    }
+
+    
+    
+    // //////////////////////////////////////////////////////////
+    // Helpers
+    // //////////////////////////////////////////////////////////
+
+    private boolean alreadyHasCommand(final Class<?> commandClass, final ObjectAdapter onObject) {
+        return getCommand(commandClass, onObject) != null;
+    }
+
+    private boolean alreadyHasCreate(final ObjectAdapter onObject) {
+        return alreadyHasCommand(CreateObjectCommand.class, onObject);
+    }
+
+    private boolean alreadyHasDestroy(final ObjectAdapter onObject) {
+        return alreadyHasCommand(DestroyObjectCommand.class, onObject);
+    }
+
+    private boolean alreadyHasSave(final ObjectAdapter onObject) {
+        return alreadyHasCommand(SaveObjectCommand.class, onObject);
+    }
+
+    private PersistenceCommand getCommand(final Class<?> commandClass, final ObjectAdapter onObject) {
+        for (final PersistenceCommand command : commands) {
+            if (command.onAdapter().equals(onObject)) {
+                if (commandClass.isAssignableFrom(command.getClass())) {
+                    return command;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void removeCommand(final Class<?> commandClass, final ObjectAdapter onObject) {
+        final PersistenceCommand toDelete = getCommand(commandClass, onObject);
+        commands.remove(toDelete);
+    }
+
+    private void removeCreate(final ObjectAdapter onObject) {
+        removeCommand(CreateObjectCommand.class, onObject);
+    }
+
+    private void removeSave(final ObjectAdapter onObject) {
+        removeCommand(SaveObjectCommand.class, onObject);
+    }
+
+    // ////////////////////////////////////////////////////////////////
+    // toString
+    // ////////////////////////////////////////////////////////////////
+
+    @Override
+    public String toString() {
+        return appendTo(new ToString(this)).toString();
+    }
+
+    protected ToString appendTo(final ToString str) {
+        str.append("state", state);
+        str.append("commands", commands.size());
+        return str;
+    }
+
+
+    // ////////////////////////////////////////////////////////////////
+    // Depenendencies (from constructor)
+    // ////////////////////////////////////////////////////////////////
+
+    /**
+     * The owning {@link IsisTransactionManager transaction manager}.
+     * 
+     * <p>
+     * Injected in constructor
+     */
+    public IsisTransactionManager getTransactionManager() {
+        return transactionManager;
+    }
+
+    /**
+     * The {@link MessageBroker} for this transaction.
+     * 
+     * <p>
+     * Injected in constructor
+     */
+    public MessageBroker getMessageBroker() {
+        return messageBroker;
+    }
+
+    /**
+     * The {@link UpdateNotifier} for this transaction.
+     * 
+     * <p>
+     * Injected in constructor
+     */
+    public UpdateNotifier getUpdateNotifier() {
+        return updateNotifier;
+    }
+
+    public static class AdapterAndProperty {
+        private final ObjectAdapter objectAdapter;
+        private final ObjectAssociation property;
+        
+        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;
+        }
+        
+        public ObjectAdapter getAdapter() {
+            return objectAdapter;
+        }
+        public ObjectAssociation getProperty() {
+            return property;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((objectAdapter == null) ? 0 : objectAdapter.hashCode());
+            result = prime * result + ((property == null) ? 0 : property.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            AdapterAndProperty other = (AdapterAndProperty) obj;
+            if (objectAdapter == null) {
+                if (other.objectAdapter != null)
+                    return false;
+            } else if (!objectAdapter.equals(other.objectAdapter))
+                return false;
+            if (property == null) {
+                if (other.property != null)
+                    return false;
+            } else if (!property.equals(other.property))
+                return false;
+            return true;
+        }
+        
+        @Override
+        public String toString() {
+            return getAdapter().getOid().enStringNoVersion(getMarshaller()) + " , " + getProperty().getId();
+        }
+
+        protected OidMarshaller getMarshaller() {
+            return new OidMarshaller();
+        }
+
+        private Object getPropertyValue() {
+            ObjectAdapter referencedAdapter = property.get(objectAdapter);
+            return referencedAdapter == null ? null : referencedAdapter.getObject();
+        }
+    }
+   
+    
+    ////////////////////////////////////////////////////////////////////////
+    // Auditing
+    ////////////////////////////////////////////////////////////////////////
+
+    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();
+            }};
+            
+        private final Object pre;
+        private Object post;
+        
+        public static PreAndPostValues pre(Object preValue) {
+            return new PreAndPostValues(preValue, null);
+        }
+
+        private PreAndPostValues(Object pre, Object post) {
+            this.pre = pre;
+            this.post = post;
+        }
+        public Object getPre() {
+            return pre;
+        }
+        
+        public Object getPost() {
+            return post;
+        }
+        
+        public void setPost(Object post) {
+            this.post = post;
+        }
+        
+        @Override
+        public String toString() {
+            return getPre() + " -> " + getPost();
+        }
+
+        public boolean differ() {
+            return !Objects.equal(getPre(), getPost());
+        }
+    }
+    
+   
+    private final Map<AdapterAndProperty, PreAndPostValues> auditLog = Maps.newLinkedHashMap();
+    
+
+    public void auditDirty(ObjectAdapter adapter) {
+        for (ObjectAssociation property : adapter.getSpecification().getAssociations(ObjectAssociationFilters.PROPERTIES)) {
+            audit(adapter, property);
+        }
+    }
+    
+    private void audit(ObjectAdapter adapter, ObjectAssociation property) {
+        final AdapterAndProperty aap = AdapterAndProperty.of(adapter, property);
+        PreAndPostValues papv = PreAndPostValues.pre(aap.getPropertyValue());
+        auditLog.put(aap, papv);
+    }
+
+
+    public Set<Entry<AdapterAndProperty, PreAndPostValues>> getAuditEntries() {
+        updatePostValues(auditLog.entrySet());
+
+        return Collections.unmodifiableSet(Sets.filter(auditLog.entrySet(), PreAndPostValues.CHANGED));
+    }
+
+    private void updatePostValues(Set<Entry<AdapterAndProperty, PreAndPostValues>> entrySet) {
+        for (Entry<AdapterAndProperty, PreAndPostValues> entry : entrySet) {
+            final AdapterAndProperty aap = entry.getKey();
+            final PreAndPostValues papv = entry.getValue();
+            
+            papv.setPost(aap.getPropertyValue());
+        }
+    }
+
+
+    
+    ////////////////////////////////////////////////////////////////////////
+    // Dependencies (from context)
+    ////////////////////////////////////////////////////////////////////////
+
+    
+    protected AdapterManager getAdapterManager() {
+        return IsisContext.getPersistenceSession().getAdapterManager();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransactionManager.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransactionManager.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransactionManager.java
new file mode 100644
index 0000000..fb994de
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/IsisTransactionManager.java
@@ -0,0 +1,438 @@
+/*
+ *  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.runtimes.dflt.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.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.components.SessionScopedComponent;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.runtimes.dflt.runtime.persistence.ObjectPersistenceException;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.PersistenceCommand;
+import org.apache.isis.runtimes.dflt.runtime.persistence.objectstore.transaction.TransactionalResource;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.runtimes.dflt.runtime.system.session.IsisSession;
+
+public class IsisTransactionManager implements SessionScopedComponent {
+
+
+    private static final Logger LOG = Logger.getLogger(IsisTransactionManager.class);
+
+    private final EnlistedObjectDirtying objectPersistor;
+    private final TransactionalResource transactionalResource;
+
+    private int transactionLevel;
+    
+    private IsisSession session;
+
+    /**
+     * Holds the current or most recently completed transaction.
+     */
+    private IsisTransaction transaction;
+
+
+    // ////////////////////////////////////////////////////////////////
+    // constructor
+    // ////////////////////////////////////////////////////////////////
+
+    public IsisTransactionManager(final EnlistedObjectDirtying objectPersistor, final TransactionalResource objectStore) {
+        this.objectPersistor = objectPersistor;
+        this.transactionalResource = objectStore;
+    }
+    
+    
+    public TransactionalResource getTransactionalResource() {
+        return transactionalResource;
+    }
+    
+    // ////////////////////////////////////////////////////////////////
+    // open, close
+    // ////////////////////////////////////////////////////////////////
+
+    @Override
+    public void open() {
+        ensureThatState(session, is(notNullValue()), "session is required");
+    }
+
+    @Override
+    public void close() {
+        if (getTransaction() != null) {
+            try {
+                abortTransaction();
+            } catch (final Exception e2) {
+                LOG.error("failure during abort", e2);
+            }
+        }
+        session = null;
+    }
+
+    // //////////////////////////////////////////////////////
+    // current transaction (if any)
+    // //////////////////////////////////////////////////////
+
+    /**
+     * The current transaction, if any.
+     */
+    public IsisTransaction getTransaction() {
+        return transaction;
+    }
+
+    public int getTransactionLevel() {
+        return transactionLevel;
+    }
+
+
+    
+    /**
+     * Convenience method returning the {@link UpdateNotifier} of the
+     * {@link #getTransaction() current transaction}.
+     */
+    protected UpdateNotifier getUpdateNotifier() {
+        return getTransaction().getUpdateNotifier();
+    }
+
+    /**
+     * Convenience method returning the {@link MessageBroker} of the
+     * {@link #getTransaction() current transaction}.
+     */
+    protected MessageBroker getMessageBroker() {
+        return getTransaction().getMessageBroker();
+    }
+
+    
+    // ////////////////////////////////////////////////////////////////
+    // Transactional Execution
+    // ////////////////////////////////////////////////////////////////
+
+    /**
+     * Run the supplied {@link Runnable block of code (closure)} in a
+     * {@link IsisTransaction transaction}.
+     * 
+     * <p>
+     * If a transaction is {@link IsisContext#inTransaction() in progress}, then
+     * uses that. Otherwise will {@link #startTransaction() start} a transaction
+     * before running the block and {@link #endTransaction() commit} it at the
+     * end. If the closure throws an exception, then will
+     * {@link #abortTransaction() abort} the transaction.
+     */
+    public void executeWithinTransaction(final TransactionalClosure closure) {
+        final boolean initiallyInTransaction = inTransaction();
+        if (!initiallyInTransaction) {
+            startTransaction();
+        }
+        try {
+            closure.preExecute();
+            closure.execute();
+            closure.onSuccess();
+            if (!initiallyInTransaction) {
+                endTransaction();
+            }
+        } catch (final RuntimeException ex) {
+            closure.onFailure();
+            if (!initiallyInTransaction) {
+                // temp TODO fix swallowing of exception
+                // System.out.println(ex.getMessage());
+                // ex.printStackTrace();
+                try {
+                    abortTransaction();
+                } catch (final Exception e) {
+                    LOG.error("Abort failure after exception", e);
+                    // System.out.println(e.getMessage());
+                    // e.printStackTrace();
+                    throw new IsisTransactionManagerException("Abort failure: " + e.getMessage(), ex);
+                }
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Run the supplied {@link Runnable block of code (closure)} in a
+     * {@link IsisTransaction transaction}.
+     * 
+     * <p>
+     * If a transaction is {@link IsisContext#inTransaction() in progress}, then
+     * uses that. Otherwise will {@link #startTransaction() start} a transaction
+     * before running the block and {@link #endTransaction() commit} it at the
+     * end. If the closure throws an exception, then will
+     * {@link #abortTransaction() abort} the transaction.
+     */
+    public <Q> Q executeWithinTransaction(final TransactionalClosureWithReturn<Q> closure) {
+        final boolean initiallyInTransaction = inTransaction();
+        if (!initiallyInTransaction) {
+            startTransaction();
+        }
+        try {
+            closure.preExecute();
+            final Q retVal = closure.execute();
+            closure.onSuccess();
+            if (!initiallyInTransaction) {
+                endTransaction();
+            }
+            return retVal;
+        } catch (final RuntimeException ex) {
+            closure.onFailure();
+            if (!initiallyInTransaction) {
+                abortTransaction();
+            }
+            throw ex;
+        }
+    }
+
+    public boolean inTransaction() {
+        return getTransaction() != null && !getTransaction().getState().isComplete();
+    }
+
+    // ////////////////////////////////////////////////////////////////
+    // create transaction, + hooks
+    // ////////////////////////////////////////////////////////////////
+
+    /**
+     * Creates a new transaction and saves, to be accessible in
+     * {@link #getTransaction()}.
+     */
+    protected final IsisTransaction createTransaction() {
+        return this.transaction = createTransaction(createMessageBroker(), createUpdateNotifier());
+    }
+
+
+    /**
+     * The provided {@link MessageBroker} and {@link UpdateNotifier} are
+     * obtained from the hook methods ( {@link #createMessageBroker()} and
+     * {@link #createUpdateNotifier()}).
+     * 
+     * @see #createMessageBroker()
+     * @see #createUpdateNotifier()
+     */
+    protected IsisTransaction createTransaction(final MessageBroker messageBroker, final UpdateNotifier updateNotifier) {
+        ensureThatArg(messageBroker, is(not(nullValue())));
+        ensureThatArg(updateNotifier, is(not(nullValue())));
+
+        return new IsisTransaction(this, messageBroker, updateNotifier, getTransactionalResource());
+    }
+    
+
+    // //////////////////////////////////////////////////////
+    // start, flush, abort, end
+    // //////////////////////////////////////////////////////
+
+    public synchronized void startTransaction() {
+
+        boolean noneInProgress = false;
+        if (getTransaction() == null || getTransaction().getState().isComplete()) {
+            noneInProgress = true;
+
+            createTransaction();
+            transactionLevel = 0;
+            transactionalResource.startTransaction();
+        }
+
+        transactionLevel++;
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("startTransaction: level " + (transactionLevel - 1) + "->" + (transactionLevel) + (noneInProgress ? " (no transaction in progress or was previously completed; transaction created)" : ""));
+        }
+    }
+
+    public synchronized boolean flushTransaction() {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("flushTransaction");
+        }
+
+        if (getTransaction() != null) {
+            objectPersistor.objectChangedAllDirty();
+            getTransaction().flush();
+        }
+        return false;
+    }
+
+    /**
+     * Ends the transaction if nesting level is 0.
+     */
+    public synchronized void endTransaction() {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("endTransaction: level " + (transactionLevel) + "->" + (transactionLevel - 1));
+        }
+
+        transactionLevel--;
+        if (transactionLevel == 0) {
+
+            //
+            // TODO: granted, this is some fairly byzantine coding.  but I'm trying to account for different types
+            // of object store implementations that could start throwing exceptions at any stage.
+            // 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 (LOG.isDebugEnabled()) {
+                    LOG.debug("endTransaction: committing");
+                }
+                
+                objectPersistor.objectChangedAllDirty();
+                
+                // just in case any additional exceptions were raised...
+                exceptions = this.getTransaction().getExceptionsIfAny();
+            }
+            
+            if(exceptions.isEmpty()) {
+                getTransaction().commit();
+                
+                // in case any additional exceptions were raised...
+                exceptions = this.getTransaction().getExceptionsIfAny();
+            }
+            
+            if(exceptions.isEmpty()) {
+                transactionalResource.endTransaction();
+                
+                // just in case any additional exceptions were raised...
+                exceptions = this.getTransaction().getExceptionsIfAny();
+            }
+            
+            if(!exceptions.isEmpty()) {
+                
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("endTransaction: aborting instead, " + exceptions.size() + " exception(s) have been raised");
+                }
+                abortTransaction();
+                
+                // just in case any additional exceptions were raised...
+                exceptions = this.getTransaction().getExceptionsIfAny();
+                
+                throw exceptionToThrowFrom(exceptions);
+            }
+            
+        } else if (transactionLevel < 0) {
+            LOG.error("endTransaction: transactionLevel=" + transactionLevel);
+            transactionLevel = 0;
+            throw new IllegalStateException(" no transaction running to end (transactionLevel < 0)");
+        }
+    }
+
+
+    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();
+            transactionLevel = 0;
+            transactionalResource.abortTransaction();
+        }
+    }
+
+    public void addCommand(final PersistenceCommand command) {
+        getTransaction().addCommand(command);
+    }
+
+    // //////////////////////////////////////////////////////////////
+    // Hooks
+    // //////////////////////////////////////////////////////////////
+
+
+
+    
+    /**
+     * Overridable hook, used in
+     * {@link #createTransaction(MessageBroker, UpdateNotifier)
+     * 
+     * <p> Called when a new {@link IsisTransaction} is created.
+     */
+    protected MessageBroker createMessageBroker() {
+        return new MessageBrokerDefault();
+    }
+
+    /**
+     * Overridable hook, used in
+     * {@link #createTransaction(MessageBroker, UpdateNotifier)
+     * 
+     * <p> Called when a new {@link IsisTransaction} is created.
+     */
+    protected UpdateNotifier createUpdateNotifier() {
+        return new UpdateNotifierDefault();
+    }
+
+    // ////////////////////////////////////////////////////////////////
+    // helpers
+    // ////////////////////////////////////////////////////////////////
+
+    protected void ensureTransactionInProgress() {
+        ensureThatState(getTransaction() != null && !getTransaction().getState().isComplete(), is(true), "No transaction in progress");
+    }
+
+    protected void ensureTransactionNotInProgress() {
+        ensureThatState(getTransaction() != null && !getTransaction().getState().isComplete(), is(false), "Transaction in progress");
+    }
+
+
+    // //////////////////////////////////////////////////////
+    // debugging
+    // //////////////////////////////////////////////////////
+
+    public void debugData(final DebugBuilder debug) {
+        debug.appendln("Transaction", getTransaction());
+    }
+
+    // ////////////////////////////////////////////////////////////////
+    // Dependencies (injected)
+    // ////////////////////////////////////////////////////////////////
+
+    /**
+     * The owning {@link IsisSession}.
+     * 
+     * <p>
+     * Will be non-<tt>null</tt> when {@link #open() open}ed, but <tt>null</tt>
+     * if {@link #close() close}d .
+     */
+    public IsisSession getSession() {
+        return session;
+    }
+
+    /**
+     * Should be injected prior to {@link #open() opening}
+     */
+    public void setSession(final IsisSession session) {
+        this.session = session;
+    }
+
+
+
+    
+}

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

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBroker.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBroker.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBroker.java
new file mode 100644
index 0000000..8c52aa4
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBroker.java
@@ -0,0 +1,40 @@
+/*
+ *  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.runtimes.dflt.runtime.system.transaction;
+
+import java.util.List;
+
+public interface MessageBroker {
+
+    List<String> getMessages();
+
+    String getMessagesCombined();
+
+    List<String> getWarnings();
+
+    String getWarningsCombined();
+
+    void addWarning(String message);
+
+    void addMessage(String message);
+
+    void ensureEmpty();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBrokerDefault.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBrokerDefault.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBrokerDefault.java
new file mode 100644
index 0000000..a891aed
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/MessageBrokerDefault.java
@@ -0,0 +1,140 @@
+/*
+ *  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.runtimes.dflt.runtime.system.transaction;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.lang.StringUtils;
+
+public class MessageBrokerDefault implements MessageBroker, DebuggableWithTitle {
+
+    private final List<String> messages = Lists.newArrayList();
+    private final List<String> warnings = Lists.newArrayList();
+
+    public MessageBrokerDefault() {
+    }
+
+    // //////////////////////////////////////////////////
+    // Reset / ensureEmpty
+    // //////////////////////////////////////////////////
+
+    public void reset() {
+        warnings.clear();
+        messages.clear();
+    }
+
+    @Override
+    public void ensureEmpty() {
+        if (warnings.size() > 0) {
+            throw new IsisException("Message broker still has warnings");
+        }
+        if (messages.size() > 0) {
+            throw new IsisException("Message broker still has messages");
+        }
+    }
+
+    // //////////////////////////////////////////////////
+    // Messages
+    // //////////////////////////////////////////////////
+
+    @Override
+    public List<String> getMessages() {
+        return copyAndClear(messages);
+    }
+
+    @Override
+    public void addMessage(final String message) {
+        messages.add(message);
+    }
+
+    @Override
+    public String getMessagesCombined() {
+        final List<String> x = messages;
+        final String string = StringUtils.combine(x);
+        return string;
+    }
+
+    // //////////////////////////////////////////////////
+    // Warnings
+    // //////////////////////////////////////////////////
+
+    @Override
+    public List<String> getWarnings() {
+        return copyAndClear(warnings);
+    }
+
+    @Override
+    public void addWarning(final String message) {
+        warnings.add(message);
+    }
+
+    @Override
+    public String getWarningsCombined() {
+        final List<String> x = warnings;
+        final String string = StringUtils.combine(x);
+        return string;
+    }
+
+    // //////////////////////////////////////////////////
+    // Debugging
+    // //////////////////////////////////////////////////
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        debugArray(debug, "Messages", messages);
+        debugArray(debug, "Warnings", messages);
+    }
+
+    private void debugArray(final DebugBuilder debug, final String title, final List<String> vector) {
+        debug.appendln(title);
+        debug.indent();
+        if (vector.size() == 0) {
+            debug.appendln("none");
+        } else {
+            for (final String text : vector) {
+                debug.appendln(text);
+            }
+        }
+        debug.unindent();
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Simple Message Broker";
+    }
+
+    // //////////////////////////////////////////////////
+    // Helpers
+    // //////////////////////////////////////////////////
+
+    private List<String> copyAndClear(final List<String> messages) {
+        final List<String> copy = Collections.unmodifiableList(new ArrayList<String>(messages));
+        messages.clear();
+        return copy;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionUtils.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionUtils.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionUtils.java
new file mode 100644
index 0000000..cf5c081
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionUtils.java
@@ -0,0 +1,49 @@
+/*
+ *  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.runtimes.dflt.runtime.system.transaction;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.runtimes.dflt.runtime.transaction.facets.CollectionClearFacetWrapTransaction;
+
+public class TransactionUtils {
+    private final static Logger LOG = Logger.getLogger(CollectionClearFacetWrapTransaction.class);
+
+    private TransactionUtils() {
+    }
+
+    public static void abort(final IsisTransactionManager transactionManager, final FacetHolder holder) {
+        LOG.info("exception executing " + holder + ", aborting transaction");
+        try {
+            transactionManager.abortTransaction();
+        } catch (final Exception e2) {
+            LOG.error("failure during abort", e2);
+        }
+    }
+
+    /**
+     * TODO: need to downcast the FacetHolder and fetch out an identifier.
+     */
+    public static String getIdentifierFor(final FacetHolder facetHolder) {
+        return facetHolder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dbb64345/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionalClosure.java
----------------------------------------------------------------------
diff --git a/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionalClosure.java b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionalClosure.java
new file mode 100644
index 0000000..fc6b17b
--- /dev/null
+++ b/framework/core/runtime/src/main/java/org/apache/isis/runtimes/dflt/runtime/system/transaction/TransactionalClosure.java
@@ -0,0 +1,31 @@
+/*
+ *  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.runtimes.dflt.runtime.system.transaction;
+
+public interface TransactionalClosure {
+
+    public void preExecute();
+
+    public void execute();
+
+    public void onSuccess();
+
+    public void onFailure();
+}