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();
+}