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 2018/02/02 16:31:45 UTC

[isis] branch maint-1.16.1 updated: ISIS-1569: refactors for different implementation of replay

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch maint-1.16.1
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/maint-1.16.1 by this push:
     new 7585283  ISIS-1569: refactors for different implementation of replay
7585283 is described below

commit 7585283a6b66f30960642bc0863052d4398fbaec
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Feb 2 16:21:22 2018 +0000

    ISIS-1569: refactors for different implementation of replay
    
    specifically:
    - factors out CommandExecutionAbstract from BackgroundCommandExecution, and simplifies the latter (removes concept of an OnExceptionPolicy previously introduced)
    - Command#executeIn no longer overloaded to hold state (CommandJdo introduces replayState, not part of API)
    - introduces new CommandDtoProcessorService SPI to globally postprocess CommandDto's; dogfood this within ContentMappingServiceForCommandDto (copying over details)
    
    also
    - removes legacy support within BackgroundCommandExecution/CommandExecutionAbstract
    - made some methods of AbstractIsisSessionTemplate final
    - lets SimpleSession be subclassable
    - allows Clock to be replaceable always
---
 .../org/apache/isis/applib/annotation/Command.java |  10 +-
 .../java/org/apache/isis/applib/clock/Clock.java   |  12 +-
 .../conmap/ContentMappingServiceForCommandDto.java | 102 ++--
 .../ContentMappingServiceForCommandsDto.java       |   7 +-
 .../conmap/spi/CommandDtoProcessorService.java     |  38 ++
 .../authentication/standard/SimpleSession.java     |   2 +-
 .../background/BackgroundCommandExecution.java     | 512 +--------------------
 ...xecution.java => CommandExecutionAbstract.java} | 274 +++--------
 .../AbstractIsisSessionTemplate.java               |   8 +-
 9 files changed, 195 insertions(+), 770 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Command.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Command.java
index 1defbbb..39cc97f 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/Command.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Command.java
@@ -87,19 +87,11 @@ public @interface Command {
          * @deprecated - use {@link CommandExecuteIn#REPLAYABLE}
          */
         @Deprecated
-        REPLAYABLE,
-        /**
-         * For framework use, not intended to be used in application code.
-         *
-         * @deprecated - use {@link CommandExecuteIn#EXCLUDED}
-         */
-        @Deprecated
-        EXCLUDED;
+        REPLAYABLE;
 
         public boolean isForeground() { return this == FOREGROUND; }
         public boolean isBackground() { return this == BACKGROUND; }
         public boolean isReplayable() { return this == REPLAYABLE; }
-        public boolean isExcluded() { return this == EXCLUDED; }
 
     }
 
diff --git a/core/applib/src/main/java/org/apache/isis/applib/clock/Clock.java b/core/applib/src/main/java/org/apache/isis/applib/clock/Clock.java
index 90ae270..cd7c6c9 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/clock/Clock.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/clock/Clock.java
@@ -29,7 +29,6 @@ import org.joda.time.LocalDate;
 import org.joda.time.LocalDateTime;
 
 import org.apache.isis.applib.Defaults;
-import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.fixtures.FixtureClock;
 
 /**
@@ -50,7 +49,6 @@ import org.apache.isis.applib.fixtures.FixtureClock;
  */
 public abstract class Clock {
     protected static Clock instance;
-    private static boolean isReplaceable = true;
 
     /**
      * Returns the (singleton) instance of {@link Clock}.
@@ -65,7 +63,6 @@ public abstract class Clock {
     public final static Clock getInstance() {
         if (!isInitialized()) {
             instance = new SystemClock();
-            isReplaceable = false;
         }
         return instance;
     }
@@ -126,11 +123,6 @@ public abstract class Clock {
         return new DateTime(getTime(), Defaults.getTimeZone());
     }
 
-    private static void ensureReplaceable() {
-        if (!isReplaceable && instance != null) {
-            throw new RecoverableException("Clock already set up");
-        }
-    }
 
     public static Timestamp getTimeAsJavaSqlTimestamp() {
         return new java.sql.Timestamp(getTimeAsDateTime().getMillis());
@@ -142,7 +134,6 @@ public abstract class Clock {
      * @return whether a clock was removed.
      */
     protected static boolean remove() {
-        ensureReplaceable();
         if (instance == null) {
             return false;
         }
@@ -151,7 +142,6 @@ public abstract class Clock {
     }
 
     protected Clock() {
-        ensureReplaceable();
         instance = this;
     }
 
@@ -174,6 +164,8 @@ public abstract class Clock {
 }
 
 final class SystemClock extends Clock {
+
+    SystemClock() {}
     @Override
     protected long time() {
         return System.currentTimeMillis();
diff --git a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java
index a11b9d3..00f7541 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java
@@ -27,7 +27,9 @@ import javax.ws.rs.core.MediaType;
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.conmap.spi.CommandDtoProcessorService;
 import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandDtoProcessor;
 import org.apache.isis.applib.services.command.CommandWithDto;
 import org.apache.isis.applib.services.metamodel.MetaModelService5;
@@ -49,7 +51,7 @@ public class ContentMappingServiceForCommandDto implements ContentMappingService
             return null;
         }
 
-        return asProcessedDto(object, metaModelService);
+        return asProcessedDto(object);
     }
 
     /**
@@ -57,27 +59,30 @@ public class ContentMappingServiceForCommandDto implements ContentMappingService
      */
     @Programmatic
     public CommandDto map(final CommandWithDto commandWithDto) {
-        return asProcessedDto(commandWithDto, metaModelService);
+        return asProcessedDto(commandWithDto);
     }
 
-    static CommandDto asProcessedDto(
-            final Object object,
-            final MetaModelService5 metaModelService) {
-
+    CommandDto asProcessedDto(final Object object) {
         if (!(object instanceof CommandWithDto)) {
             return null;
         }
         final CommandWithDto commandWithDto = (CommandWithDto) object;
-        return asProcessedDto(commandWithDto, metaModelService);
+        return asProcessedDto(commandWithDto);
     }
 
-    private static CommandDto asProcessedDto(
-            CommandWithDto commandWithDto,
-            final MetaModelService5 metaModelService) {
-        final CommandDto commandDto = commandWithDto.asDto();
+    private CommandDto asProcessedDto(final CommandWithDto commandWithDto) {
+        CommandDto commandDto = commandWithDto.asDto();
 
-        copyOver(commandWithDto, commandDto);
+        // global processors
+        for (final CommandDtoProcessorService commandDtoProcessorService : commandDtoProcessorServices) {
+            commandDto = commandDtoProcessorService.process(commandWithDto, commandDto);
+            if(commandDto == null) {
+                // any processor could return null, effectively breaking the chain.
+                return null;
+            }
+        }
 
+        // specific processors for this specific member (action or property)
         final CommandDtoProcessor commandDtoProcessor =
                 metaModelService.commandDtoProcessorFor(commandDto.getMember().getLogicalMemberIdentifier());
         if (commandDtoProcessor == null) {
@@ -86,39 +91,58 @@ public class ContentMappingServiceForCommandDto implements ContentMappingService
         return commandDtoProcessor.process(commandWithDto, commandDto);
     }
 
-    private static void copyOver(final CommandWithDto commandWithDto, final CommandDto commandDto) {
 
-        // for some reason this isn't being persisted initially, so patch it in.  TODO: should fix this
-        commandDto.setUser(commandWithDto.getUser());
+    /**
+     * Uses the SPI infrastructure to copy over standard properties from {@link Command} to {@link CommandDto}.
+     */
+    @DomainService(
+            nature = NatureOfService.DOMAIN,
+            // specify quite a high priority since custom processors will probably want to run after this one
+            // (but can choose to run before if they wish)
+            menuOrder = "1000"
+    )
+    public static class CopyOverFromCommand implements CommandDtoProcessorService {
+
+        public CommandDto process(final Command command, CommandDto commandDto) {
+
+            // for some reason this isn't being persisted initially, so patch it in.  TODO: should fix this
+            commandDto.setUser(command.getUser());
+
+            // the timestamp field was only introduced in v1.4 of cmd.xsd, so there's no guarantee
+            // it will have been populated.  We therefore copy the value in from CommandWithDto entity.
+            if(commandDto.getTimestamp() == null) {
+                final Timestamp timestamp = command.getTimestamp();
+                commandDto.setTimestamp(JavaSqlTimestampXmlGregorianCalendarAdapter.print(timestamp));
+            }
+
+            CommandDtoUtils.setUserData(commandDto,
+                    CommandWithDto.USERDATA_KEY_TARGET_CLASS, command.getTargetClass());
+            CommandDtoUtils.setUserData(commandDto,
+                    CommandWithDto.USERDATA_KEY_TARGET_ACTION, command.getTargetAction());
+            CommandDtoUtils.setUserData(commandDto,
+                    CommandWithDto.USERDATA_KEY_ARGUMENTS, command.getArguments());
+
+            final Bookmark result = command.getResult();
+            CommandDtoUtils.setUserData(commandDto,
+                    CommandWithDto.USERDATA_KEY_RETURN_VALUE, result != null ? result.toString() : null);
+            // knowing whether there was an exception is on the master is used to determine whether to
+            // continue when replayed on the slave if an exception occurs there also
+            CommandDtoUtils.setUserData(commandDto,
+                    CommandWithDto.USERDATA_KEY_EXCEPTION, command.getException());
+
+            PeriodDto timings = CommandDtoUtils.timingsFor(commandDto);
+            timings.setStartedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(command.getStartedAt()));
+            timings.setCompletedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(command.getCompletedAt()));
 
-        // the timestamp field was only introduced in v1.4 of cmd.xsd, so there's no guarantee
-        // it will have been populated.  We therefore copy the value in from CommandWithDto entity.
-        if(commandDto.getTimestamp() == null) {
-            final Timestamp timestamp = commandWithDto.getTimestamp();
-            commandDto.setTimestamp(JavaSqlTimestampXmlGregorianCalendarAdapter.print(timestamp));
+            return commandDto;
         }
-
-        CommandDtoUtils.setUserData(commandDto,
-                CommandWithDto.USERDATA_KEY_TARGET_CLASS, commandWithDto.getTargetClass());
-        CommandDtoUtils.setUserData(commandDto,
-                CommandWithDto.USERDATA_KEY_TARGET_ACTION, commandWithDto.getTargetAction());
-        CommandDtoUtils.setUserData(commandDto,
-                CommandWithDto.USERDATA_KEY_ARGUMENTS, commandWithDto.getArguments());
-
-        final Bookmark result = commandWithDto.getResult();
-        CommandDtoUtils.setUserData(commandDto,
-                CommandWithDto.USERDATA_KEY_RETURN_VALUE, result != null ? result.toString() : null);
-        // knowing whether there was an exception is on the master is used to determine whether to
-        // continue when replayed on the slave if an exception occurs there also
-        CommandDtoUtils.setUserData(commandDto,
-                CommandWithDto.USERDATA_KEY_EXCEPTION, commandWithDto.getException());
-
-        PeriodDto timings = CommandDtoUtils.timingsFor(commandDto);
-        timings.setStartedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(commandWithDto.getStartedAt()));
-        timings.setCompletedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(commandWithDto.getCompletedAt()));
     }
 
+
     @Inject
     MetaModelService5 metaModelService;
 
+    @Inject
+    List<CommandDtoProcessorService> commandDtoProcessorServices;
+
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java
index 4ba434e..95ba22a 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java
@@ -81,11 +81,14 @@ public class ContentMappingServiceForCommandsDto implements ContentMappingServic
         return new CommandsDto();
     }
 
-    private static CommandDto asDto(final Object object, MetaModelService5 metaModelService5) {
-        return ContentMappingServiceForCommandDto.asProcessedDto(object, metaModelService5);
+    private CommandDto asDto(final Object object, MetaModelService5 metaModelService5) {
+        return contentMappingServiceForCommandDto.asProcessedDto(object);
     }
 
     @Inject
     MetaModelService5 metaModelService5;
 
+    @Inject
+    ContentMappingServiceForCommandDto contentMappingServiceForCommandDto;
+
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/conmap/spi/CommandDtoProcessorService.java b/core/applib/src/main/java/org/apache/isis/applib/conmap/spi/CommandDtoProcessorService.java
new file mode 100644
index 0000000..4fe8c69
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/conmap/spi/CommandDtoProcessorService.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.conmap.spi;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.conmap.ContentMappingServiceForCommandDto;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.command.CommandDtoProcessor;
+import org.apache.isis.schema.cmd.v1.CommandDto;
+
+/**
+ * Optional SPI called by {@link ContentMappingServiceForCommandDto}.
+ *
+ * Similar to {@link CommandDtoProcessor}, but applied to all {@link CommandDto}s globally.
+ */
+public interface CommandDtoProcessorService {
+
+    @Programmatic
+    CommandDto process(final Command command, CommandDto commandDto);
+
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/runtime/authentication/standard/SimpleSession.java b/core/metamodel/src/main/java/org/apache/isis/core/runtime/authentication/standard/SimpleSession.java
index ca6e8c4..8e5c416 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/runtime/authentication/standard/SimpleSession.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/runtime/authentication/standard/SimpleSession.java
@@ -26,7 +26,7 @@ import java.util.List;
 import org.apache.isis.core.commons.authentication.AuthenticationSessionAbstract;
 import org.apache.isis.core.commons.encoding.DataInputExtended;
 
-public final class SimpleSession extends AuthenticationSessionAbstract {
+public class SimpleSession extends AuthenticationSessionAbstract {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java
index 9127933..f42bd6c 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java
@@ -16,56 +16,17 @@
  */
 package org.apache.isis.core.runtime.services.background;
 
-import java.sql.Timestamp;
-import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 
-import com.google.common.base.Function;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.isis.applib.services.background.ActionInvocationMemento;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.bookmark.BookmarkService2;
-import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.Command.Executor;
-import org.apache.isis.applib.services.command.CommandWithDto;
-import org.apache.isis.applib.services.iactn.Interaction;
-import org.apache.isis.applib.services.iactn.InteractionContext;
-import org.apache.isis.applib.services.jaxb.JaxbService;
-import org.apache.isis.applib.services.sudo.SudoService;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
-import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
-import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
-import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.spec.feature.Contributed;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
-import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.isis.core.runtime.services.memento.MementoServiceDefault;
-import org.apache.isis.core.runtime.sessiontemplate.AbstractIsisSessionTemplate;
 import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
 import org.apache.isis.core.runtime.system.transaction.TransactionalClosure;
-import org.apache.isis.core.runtime.system.transaction.TransactionalClosureWithReturn;
-import org.apache.isis.schema.cmd.v1.ActionDto;
-import org.apache.isis.schema.cmd.v1.CommandDto;
-import org.apache.isis.schema.cmd.v1.MemberDto;
-import org.apache.isis.schema.cmd.v1.ParamDto;
-import org.apache.isis.schema.cmd.v1.ParamsDto;
-import org.apache.isis.schema.cmd.v1.PropertyDto;
-import org.apache.isis.schema.common.v1.InteractionType;
-import org.apache.isis.schema.common.v1.OidDto;
-import org.apache.isis.schema.common.v1.OidsDto;
-import org.apache.isis.schema.common.v1.ValueWithTypeDto;
-import org.apache.isis.schema.utils.CommandDtoUtils;
-import org.apache.isis.schema.utils.CommonDtoUtils;
 
 /**
  * Intended to be used as a base class for executing queued up {@link Command background action}s.
@@ -74,55 +35,19 @@ import org.apache.isis.schema.utils.CommonDtoUtils;
  * This implementation uses the {@link #findBackgroundCommandsToExecute() hook method} so that it is
  * independent of the location where the actions have actually been persisted to.
  */
-public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemplate {
+public abstract class BackgroundCommandExecution extends CommandExecutionAbstract {
 
     private final static Logger LOG = LoggerFactory.getLogger(BackgroundCommandExecution.class);
 
-    public enum OnExceptionPolicy {
-        /**
-         * For example, regular background commands.
-         */
-        CONTINUE,
-        /**
-         * For example, replayable commands.
-         *
-         * To be precise, replay will quit only if there is an exception on the slave where there
-         * was none on the master.  Put another way, a replicated command that failed on the master
-         * will not cause the slave to stop executing if it caused an exception.
-         */
-        QUIT,
-    }
-
-    public enum SudoPolicy {
-        /**
-         * For example, regular background commands.
-         */
-        NO_SWITCH,
-        /**
-         * For example, replayable commands.
-         */
-        SWITCH,
-    }
-
-    private final MementoServiceDefault mementoService;
-    private final OnExceptionPolicy onExceptionPolicy;
-    private final SudoPolicy sudoPolicy;
-
     /**
      * Defaults to the historical defaults * for running background commands.
      */
     public BackgroundCommandExecution() {
-        this(OnExceptionPolicy.CONTINUE, SudoPolicy.NO_SWITCH);
+        this(SudoPolicy.NO_SWITCH);
     }
 
-    public BackgroundCommandExecution(
-            final OnExceptionPolicy onExceptionPolicy,
-            final SudoPolicy sudoPolicy) {
-        this.onExceptionPolicy = onExceptionPolicy;
-        this.sudoPolicy = sudoPolicy;
-
-        // same as configured by BackgroundServiceDefault
-        mementoService = new MementoServiceDefault().withNoEncoding();
+    public BackgroundCommandExecution(final SudoPolicy sudoPolicy) {
+        super(sudoPolicy);
     }
 
     // //////////////////////////////////////
@@ -140,15 +65,10 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
             }
         });
 
-        LOG.debug("{}: Found {} to execute", getClass().getName(), commands.size());
+        LOG.debug("Found {} to execute", commands.size());
 
         for (final Command command : commands) {
-
-            final boolean shouldContinue = execute(transactionManager, command);
-            if(!shouldContinue) {
-                LOG.info("NOT continuing to process any further commands, quitting execution");
-                return;
-            }
+            execute(transactionManager, command);
         }
     }
 
@@ -157,425 +77,5 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
      */
     protected abstract List<? extends Command> findBackgroundCommandsToExecute();
 
-    // //////////////////////////////////////
-
-    /**
-     * @return - whether to process any further commands.
-     */
-    private boolean execute(
-            final IsisTransactionManager transactionManager,
-            final Command command) {
-
-        try {
-            return executeCommandWithinTran(transactionManager, command);
-        } catch (final RuntimeException ex) {
-
-            // attempting to commit the xactn itself could cause an issue
-            // so we need extra exception handling here, it seems.
-
-            org.apache.isis.applib.annotation.Command.ExecuteIn executeIn = command.getExecuteIn();
-            LOG.warn("Exception when committing for: {} {}", executeIn, command.getMemberIdentifier(), ex);
-
-            //
-            // the previous transaction will have been aborted as part of the recovery handling within
-            // TransactionManager's executeCommandWithinTran
-            //
-            // we therefore start a new xactn in order to make sure that this background command is marked as a failure
-            // so it won't be attempted again.
-            //
-            transactionManager.executeWithinTransaction(new TransactionalClosure(){
-                @Override
-                public void execute() {
-
-                    final Interaction backgroundInteraction = interactionContext.getInteraction();
-                    final Interaction.Execution currentExecution = backgroundInteraction.getCurrentExecution();
-                    command.setStartedAt(
-                            currentExecution != null
-                                    ? currentExecution.getStartedAt()
-                                    : clockService.nowAsJavaSqlTimestamp());
-
-                    command.setCompletedAt(clockService.nowAsJavaSqlTimestamp());
-                    command.setException(Throwables.getStackTraceAsString(ex));
-                }
-            });
-
-            return determineIfContinue(ex);
-        }
-    }
-
-    /**
-     * @return - whether to process any further commands.
-     */
-    private Boolean executeCommandWithinTran(
-            final IsisTransactionManager transactionManager,
-            final Command command) {
-
-        return transactionManager.executeWithinTransaction(
-                command,
-                new TransactionalClosureWithReturn<Boolean>() {
-            @Override
-            public Boolean execute() {
-
-                return executeCommandPerSudoPolicy(transactionManager, command);
-            }
-        });
-    }
-
-    /**
-     * @return - whether to process any further commands.
-     */
-    private Boolean executeCommandPerSudoPolicy(
-            final IsisTransactionManager transactionManager,
-            final Command command) {
-
-        switch (sudoPolicy) {
-        case NO_SWITCH:
-            return executeCommand(transactionManager, command);
-        case SWITCH:
-            final String user = command.getUser();
-            return sudoService.sudo(user, new Callable<Boolean>() {
-                @Override
-                public Boolean call() {
-                    return executeCommand(transactionManager, command);
-                }
-            });
-        default:
-            throw new IllegalStateException("Unrecognized sudoPolicy: " + sudoPolicy);
-        }
-    }
-
-    /**
-     * Simply delegates to {@link #doExecuteCommand(IsisTransactionManager, Command)}.
-     *
-     * Overridable, so execution policy can be adjusted if required.
-     *
-     * @return - whether to process any further commands.
-     */
-    protected Boolean executeCommand(
-            final IsisTransactionManager transactionManager,
-            final Command command) {
-        return doExecuteCommand(transactionManager, command);
-    }
-
-    /**
-     * Not overrideable, but intended to be called by {@link #executeCommand(IsisTransactionManager, Command)} (which is).
-     *
-     * @return - whether to process any further commands.
-     */
-    protected final Boolean doExecuteCommand(
-            final IsisTransactionManager transactionManager,
-            final Command command) {
-
-        // setup for us by IsisTransactionManager; will have the transactionId of the backgroundCommand
-        final Interaction backgroundInteraction = interactionContext.getInteraction();
-
-        final String memento = command.getMemento();
-
-        org.apache.isis.applib.annotation.Command.ExecuteIn executeIn = command.getExecuteIn();
-
-        LOG.info("Executing: {} {}", executeIn, command.getMemberIdentifier());
-
-        RuntimeException exceptionIfAny = null;
-        String origExceptionIfAny = null;
-
-        try {
-            command.setExecutor(Executor.BACKGROUND);
-
-            // responsibility for setting the Command#startedAt is in the ActionInvocationFacet or
-            // PropertySetterFacet, but tthis is run if the domain object was found.  If the domain object is
-            // thrown then we would have a command with only completedAt, which is inconsistent.
-            // Therefore instead we copy down from the backgroundInteraction (similar to how we populate the
-            // completedAt at the end)
-            final Interaction.Execution priorExecution = backgroundInteraction.getPriorExecution();
-
-            final Timestamp startedAt = priorExecution != null
-                    ? priorExecution.getStartedAt()
-                    : clockService.nowAsJavaSqlTimestamp();
-            final Timestamp completedAt =
-                    priorExecution != null
-                            ? priorExecution.getCompletedAt()
-                            : clockService.nowAsJavaSqlTimestamp();  // close enough...
-
-            command.setStartedAt(startedAt);
-            command.setCompletedAt(completedAt);
-
-            final boolean legacy = memento.startsWith("<memento");
-            if(legacy) {
-
-                final ActionInvocationMemento aim = new ActionInvocationMemento(mementoService, memento);
-
-                final String actionId = aim.getActionId();
-
-                final Bookmark targetBookmark = aim.getTarget();
-                final Object targetObject = bookmarkService.lookup(
-                        targetBookmark, BookmarkService2.FieldResetPolicy.RESET);
-
-                final ObjectAdapter targetAdapter = adapterFor(targetObject);
-                final ObjectSpecification specification = targetAdapter.getSpecification();
-
-                final ObjectAction objectAction = findActionElseNull(specification, actionId);
-                if(objectAction == null) {
-                    throw new RuntimeException(String.format("Unknown action '%s'", actionId));
-                }
-
-                // TODO: background commands won't work for mixin actions...
-                // ... we obtain the target from the bookmark service (above), which will
-                // simply fail for a mixin.  Instead we would need to serialize out the mixedInAdapter
-                // and also capture the mixinType within the aim memento.
-                final ObjectAdapter mixedInAdapter = null;
-
-                final ObjectAdapter[] argAdapters = argAdaptersFor(aim);
-                final ObjectAdapter resultAdapter = objectAction.execute(
-                        targetAdapter, mixedInAdapter, argAdapters, InteractionInitiatedBy.FRAMEWORK);
-
-                if(resultAdapter != null) {
-                    Bookmark resultBookmark = CommandUtil.bookmarkFor(resultAdapter);
-                    command.setResult(resultBookmark);
-                    backgroundInteraction.getCurrentExecution().setReturned(resultAdapter.getObject());
-                }
-
-            } else {
-
-                final CommandDto dto = jaxbService.fromXml(CommandDto.class, memento);
-
-                // if the command being executed was replayed, then in its userData it will holds
-                // details of any exception that might have occurred.
-                origExceptionIfAny = CommandDtoUtils.getUserData(dto, CommandWithDto.USERDATA_KEY_EXCEPTION);
-
-                final MemberDto memberDto = dto.getMember();
-                final String memberId = memberDto.getMemberIdentifier();
-
-                final OidsDto oidsDto = CommandDtoUtils.targetsFor(dto);
-                final List<OidDto> targetOidDtos = oidsDto.getOid();
-
-                final InteractionType interactionType = memberDto.getInteractionType();
-                if(interactionType == InteractionType.ACTION_INVOCATION) {
-
-                    final ActionDto actionDto = (ActionDto) memberDto;
-
-                    for (OidDto targetOidDto : targetOidDtos) {
-
-                        final ObjectAdapter targetAdapter = adapterFor(targetOidDto);
-                        final ObjectAction objectAction = findObjectAction(targetAdapter, memberId);
-
-                        // we pass 'null' for the mixedInAdapter; if this action _is_ a mixin then
-                        // it will switch the targetAdapter to be the mixedInAdapter transparently
-                        final ObjectAdapter[] argAdapters = argAdaptersFor(actionDto);
-                        final ObjectAdapter resultAdapter = objectAction.execute(
-                                targetAdapter, null, argAdapters, InteractionInitiatedBy.FRAMEWORK);
-
-                        //
-                        // for the result adapter, we could alternatively have used...
-                        // (priorExecution populated by the push/pop within the interaction object)
-                        //
-                        // final Interaction.Execution priorExecution = backgroundInteraction.getPriorExecution();
-                        // Object unused = priorExecution.getReturned();
-                        //
-
-                        // REVIEW: this doesn't really make sense if >1 action
-                        // in any case, the capturing of the action interaction should be the
-                        // responsibility of auditing/profiling
-                        if(resultAdapter != null) {
-                            Bookmark resultBookmark = CommandUtil.bookmarkFor(resultAdapter);
-                            command.setResult(resultBookmark);
-                        }
-                    }
-                } else {
-
-                    final PropertyDto propertyDto = (PropertyDto) memberDto;
-
-                    for (OidDto targetOidDto : targetOidDtos) {
-
-                        final Bookmark bookmark = Bookmark.from(targetOidDto);
-                        final Object targetObject = bookmarkService.lookup(bookmark);
-
-                        final ObjectAdapter targetAdapter = adapterFor(targetObject);
-
-                        final OneToOneAssociation property = findOneToOneAssociation(targetAdapter, memberId);
-
-                        final ObjectAdapter newValueAdapter = newValueAdapterFor(propertyDto);
-
-                        property.set(targetAdapter, newValueAdapter, InteractionInitiatedBy.FRAMEWORK);
-                        // there is no return value for property modifications.
-                    }
-                }
-
-            }
-
-        } catch (RuntimeException ex) {
-
-            LOG.warn("Exception for: {} {}", executeIn, command.getMemberIdentifier(), ex);
-
-            // hmmm, this doesn't really make sense if >1 action
-            //
-            // in any case, the capturing of the result of the action invocation should be the
-            // responsibility of the interaction...
-            command.setException(Throwables.getStackTraceAsString(ex));
-
-            // lower down the stack the IsisTransactionManager will have set the transaction to abort
-            // however, we don't want that to occur (because any changes made to the backgroundCommand itself
-            // would also be rolled back, and it would keep getting picked up again by a scheduler for
-            // processing); instead we clear the abort cause and ensure we can continue.
-            transactionManager.getCurrentTransaction().clearAbortCauseAndContinue();
-
-            // checked at the end
-            exceptionIfAny = ex;
-
-        }
-
-        // it's possible that there is no priorExecution, specifically if there was an exception
-        // invoking the action.  We therefore need to guard that case.
-        final Interaction.Execution priorExecution = backgroundInteraction.getPriorExecution();
-        final Timestamp completedAt =
-                priorExecution != null
-                        ? priorExecution.getCompletedAt()
-                        : clockService.nowAsJavaSqlTimestamp();  // close enough...
-        command.setCompletedAt(completedAt);
-
-        // if we hit an exception processing this command but the master did not, then quit if instructed
-        return determineIfContinue(origExceptionIfAny, exceptionIfAny);
-    }
-
-    private static ObjectAction findObjectAction(
-            final ObjectAdapter targetAdapter,
-            final String actionId) throws RuntimeException {
-
-        final ObjectSpecification specification = targetAdapter.getSpecification();
-
-        final ObjectAction objectAction = findActionElseNull(specification, actionId);
-        if(objectAction == null) {
-            throw new RuntimeException(String.format("Unknown action '%s'", actionId));
-        }
-        return objectAction;
-    }
-
-    private static OneToOneAssociation findOneToOneAssociation(
-            final ObjectAdapter targetAdapter,
-            final String propertyId) throws RuntimeException {
-
-        final ObjectSpecification specification = targetAdapter.getSpecification();
-
-        final OneToOneAssociation property = findOneToOneAssociationElseNull(specification, propertyId);
-        if(property == null) {
-            throw new RuntimeException(String.format("Unknown property '%s'", propertyId));
-        }
-        return property;
-    }
-
-    private boolean determineIfContinue(
-            final String origExceptionIfAny,
-            final RuntimeException exceptionIfAny) {
-
-        if(origExceptionIfAny != null) {
-            return true;
-        }
-
-        // there was no exception on master, so do we continue?
-        return determineIfContinue(exceptionIfAny);
-    }
-
-    private boolean determineIfContinue(final RuntimeException exceptionIfAny) {
-        final boolean shouldQuit = exceptionIfAny != null &&
-                      onExceptionPolicy == OnExceptionPolicy.QUIT;
-        return !shouldQuit;
-    }
-
-    private ObjectAdapter newValueAdapterFor(final PropertyDto propertyDto) {
-        final ValueWithTypeDto newValue = propertyDto.getNewValue();
-        final Object arg = CommonDtoUtils.getValue(newValue);
-        return adapterFor(arg);
-    }
-
-    private static ObjectAction findActionElseNull(
-            final ObjectSpecification specification,
-            final String actionId) {
-        final List<ObjectAction> objectActions = specification.getObjectActions(Contributed.INCLUDED);
-        for (final ObjectAction objectAction : objectActions) {
-            if(objectAction.getIdentifier().toClassAndNameIdentityString().equals(actionId)) {
-                return objectAction;
-            }
-        }
-        return null;
-    }
-
-    private static OneToOneAssociation findOneToOneAssociationElseNull(
-            final ObjectSpecification specification,
-            final String propertyId) {
-        final List<ObjectAssociation> associations = specification.getAssociations(Contributed.INCLUDED);
-        for (final ObjectAssociation association : associations) {
-            if( association.getIdentifier().toClassAndNameIdentityString().equals(propertyId) &&
-                association instanceof OneToOneAssociation) {
-                return (OneToOneAssociation) association;
-            }
-        }
-        return null;
-    }
-
-    private ObjectAdapter[] argAdaptersFor(final ActionInvocationMemento aim)  {
-        final int numArgs = aim.getNumArgs();
-        final List<ObjectAdapter> argumentAdapters = Lists.newArrayList();
-        for(int i=0; i<numArgs; i++) {
-            final ObjectAdapter argAdapter = argAdapterFor(aim, i);
-            argumentAdapters.add(argAdapter);
-        }
-        return argumentAdapters.toArray(new ObjectAdapter[]{});
-    }
-
-    private ObjectAdapter argAdapterFor(final ActionInvocationMemento aim, int num) {
-        final Class<?> argType;
-        try {
-            argType = aim.getArgType(num);
-            final Object arg = aim.getArg(num, argType);
-            if(arg == null) {
-                return null;
-            }
-            return adapterFor(arg);
-
-        } catch (ClassNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private ObjectAdapter[] argAdaptersFor(final ActionDto actionDto) {
-        final List<ParamDto> params = paramDtosFrom(actionDto);
-        final List<ObjectAdapter> args = Lists.newArrayList(
-                Iterables.transform(params, new Function<ParamDto, ObjectAdapter>() {
-                    @Override
-                    public ObjectAdapter apply(final ParamDto paramDto) {
-                        final Object arg = CommonDtoUtils.getValue(paramDto);
-                        return adapterFor(arg);
-                    }
-                })
-        );
-        return args.toArray(new ObjectAdapter[]{});
-    }
-
-    private static List<ParamDto> paramDtosFrom(final ActionDto actionDto) {
-        final ParamsDto parameters = actionDto.getParameters();
-        if (parameters != null) {
-            final List<ParamDto> parameterList = parameters.getParameter();
-            if (parameterList != null) {
-                return parameterList;
-            }
-        }
-        return Collections.emptyList();
-    }
-
-    // //////////////////////////////////////
-
-    @javax.inject.Inject
-    BookmarkService2 bookmarkService;
-
-    @javax.inject.Inject
-    JaxbService jaxbService;
-
-    @javax.inject.Inject
-    InteractionContext interactionContext;
-
-    @javax.inject.Inject
-    SudoService sudoService;
-
-    @javax.inject.Inject
-    ClockService clockService;
 
 }
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/CommandExecutionAbstract.java
similarity index 59%
copy from core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java
copy to core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/CommandExecutionAbstract.java
index 9127933..188a9e2 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundCommandExecution.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/CommandExecutionAbstract.java
@@ -35,7 +35,6 @@ import org.apache.isis.applib.services.bookmark.BookmarkService2;
 import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.Command.Executor;
-import org.apache.isis.applib.services.command.CommandWithDto;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.iactn.InteractionContext;
 import org.apache.isis.applib.services.jaxb.JaxbService;
@@ -48,9 +47,7 @@ import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.isis.core.runtime.services.memento.MementoServiceDefault;
 import org.apache.isis.core.runtime.sessiontemplate.AbstractIsisSessionTemplate;
-import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
 import org.apache.isis.core.runtime.system.transaction.TransactionalClosure;
 import org.apache.isis.core.runtime.system.transaction.TransactionalClosureWithReturn;
@@ -68,30 +65,10 @@ import org.apache.isis.schema.utils.CommandDtoUtils;
 import org.apache.isis.schema.utils.CommonDtoUtils;
 
 /**
- * Intended to be used as a base class for executing queued up {@link Command background action}s.
- * 
- * <p>
- * This implementation uses the {@link #findBackgroundCommandsToExecute() hook method} so that it is
- * independent of the location where the actions have actually been persisted to.
  */
-public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemplate {
+public abstract class CommandExecutionAbstract extends AbstractIsisSessionTemplate {
 
-    private final static Logger LOG = LoggerFactory.getLogger(BackgroundCommandExecution.class);
-
-    public enum OnExceptionPolicy {
-        /**
-         * For example, regular background commands.
-         */
-        CONTINUE,
-        /**
-         * For example, replayable commands.
-         *
-         * To be precise, replay will quit only if there is an exception on the slave where there
-         * was none on the master.  Put another way, a replicated command that failed on the master
-         * will not cause the slave to stop executing if it caused an exception.
-         */
-        QUIT,
-    }
+    private final static Logger LOG = LoggerFactory.getLogger(CommandExecutionAbstract.class);
 
     public enum SudoPolicy {
         /**
@@ -104,65 +81,30 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
         SWITCH,
     }
 
-    private final MementoServiceDefault mementoService;
-    private final OnExceptionPolicy onExceptionPolicy;
     private final SudoPolicy sudoPolicy;
 
-    /**
-     * Defaults to the historical defaults * for running background commands.
-     */
-    public BackgroundCommandExecution() {
-        this(OnExceptionPolicy.CONTINUE, SudoPolicy.NO_SWITCH);
-    }
-
-    public BackgroundCommandExecution(
-            final OnExceptionPolicy onExceptionPolicy,
-            final SudoPolicy sudoPolicy) {
-        this.onExceptionPolicy = onExceptionPolicy;
+    protected CommandExecutionAbstract(final SudoPolicy sudoPolicy) {
         this.sudoPolicy = sudoPolicy;
-
-        // same as configured by BackgroundServiceDefault
-        mementoService = new MementoServiceDefault().withNoEncoding();
-    }
-
-    // //////////////////////////////////////
-
-    
-    protected void doExecute(Object context) {
-
-        final PersistenceSession persistenceSession = getPersistenceSession();
-        final IsisTransactionManager transactionManager = getTransactionManager(persistenceSession);
-        final List<Command> commands = Lists.newArrayList();
-        transactionManager.executeWithinTransaction(new TransactionalClosure() {
-            @Override
-            public void execute() {
-                commands.addAll(findBackgroundCommandsToExecute());
-            }
-        });
-
-        LOG.debug("{}: Found {} to execute", getClass().getName(), commands.size());
-
-        for (final Command command : commands) {
-
-            final boolean shouldContinue = execute(transactionManager, command);
-            if(!shouldContinue) {
-                LOG.info("NOT continuing to process any further commands, quitting execution");
-                return;
-            }
-        }
     }
 
-    /**
-     * Mandatory hook method
-     */
-    protected abstract List<? extends Command> findBackgroundCommandsToExecute();
-
     // //////////////////////////////////////
 
     /**
-     * @return - whether to process any further commands.
+     * Executes the command within a transaction, and with respect to the specified {@link SudoPolicy}
+     * specified in the constructor.
+     * 
+     * <p>
+     *     Intended to be called from an override of {@link #doExecute(Object)},
+     *     not from {@link #doExecuteWithTransaction(Object)} (because the error handling is different).
+     * </p>
+     *
+     * <p>
+     *     Subclasses can add additional policies (eg adjusting the clock) by overriding {@link #executeCommand(IsisTransactionManager, Command)} and delegating to {@link #doExecuteCommand(IsisTransactionManager, Command)} as required.
+     * </p>
+     *
+     * @return - any exception arising.  We don't throw this exception, because an exception might be what is expected (eg if replaying a command that itself failed to execute).
      */
-    private boolean execute(
+    protected final Exception execute(
             final IsisTransactionManager transactionManager,
             final Command command) {
 
@@ -199,32 +141,26 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
                 }
             });
 
-            return determineIfContinue(ex);
+            return ex;
         }
     }
 
-    /**
-     * @return - whether to process any further commands.
-     */
-    private Boolean executeCommandWithinTran(
+    private Exception executeCommandWithinTran(
             final IsisTransactionManager transactionManager,
             final Command command) {
 
         return transactionManager.executeWithinTransaction(
                 command,
-                new TransactionalClosureWithReturn<Boolean>() {
+                new TransactionalClosureWithReturn<Exception>() {
             @Override
-            public Boolean execute() {
+            public Exception execute() {
 
                 return executeCommandPerSudoPolicy(transactionManager, command);
             }
         });
     }
 
-    /**
-     * @return - whether to process any further commands.
-     */
-    private Boolean executeCommandPerSudoPolicy(
+    private Exception executeCommandPerSudoPolicy(
             final IsisTransactionManager transactionManager,
             final Command command) {
 
@@ -233,9 +169,9 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
             return executeCommand(transactionManager, command);
         case SWITCH:
             final String user = command.getUser();
-            return sudoService.sudo(user, new Callable<Boolean>() {
+            return sudoService.sudo(user, new Callable<Exception>() {
                 @Override
-                public Boolean call() {
+                public Exception call() {
                     return executeCommand(transactionManager, command);
                 }
             });
@@ -247,22 +183,25 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
     /**
      * Simply delegates to {@link #doExecuteCommand(IsisTransactionManager, Command)}.
      *
-     * Overridable, so execution policy can be adjusted if required.
+     * Overridable, so execution policy can be adjusted if required,
+     * eg when replaying, adjust the clock before executing the command.
      *
-     * @return - whether to process any further commands.
+     * @return - any exception arising
      */
-    protected Boolean executeCommand(
+    protected Exception executeCommand(
             final IsisTransactionManager transactionManager,
             final Command command) {
         return doExecuteCommand(transactionManager, command);
     }
 
     /**
-     * Not overrideable, but intended to be called by {@link #executeCommand(IsisTransactionManager, Command)} (which is).
+     * Not overrideable, but intended to be called by
+     * {@link #executeCommand(IsisTransactionManager, Command)} (which is).
      *
-     * @return - whether to process any further commands.
+     * @return - any exception arising.  We don't throw this exception, because an exception might be
+     *           what is expected (eg if replaying a command that itself failed to execute).
      */
-    protected final Boolean doExecuteCommand(
+    protected final Exception doExecuteCommand(
             final IsisTransactionManager transactionManager,
             final Command command) {
 
@@ -276,13 +215,12 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
         LOG.info("Executing: {} {}", executeIn, command.getMemberIdentifier());
 
         RuntimeException exceptionIfAny = null;
-        String origExceptionIfAny = null;
 
         try {
             command.setExecutor(Executor.BACKGROUND);
 
             // responsibility for setting the Command#startedAt is in the ActionInvocationFacet or
-            // PropertySetterFacet, but tthis is run if the domain object was found.  If the domain object is
+            // PropertySetterFacet, but this is run if the domain object was found.  If the domain object is
             // thrown then we would have a command with only completedAt, which is inconsistent.
             // Therefore instead we copy down from the backgroundInteraction (similar to how we populate the
             // completedAt at the end)
@@ -299,107 +237,64 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
             command.setStartedAt(startedAt);
             command.setCompletedAt(completedAt);
 
-            final boolean legacy = memento.startsWith("<memento");
-            if(legacy) {
-
-                final ActionInvocationMemento aim = new ActionInvocationMemento(mementoService, memento);
-
-                final String actionId = aim.getActionId();
-
-                final Bookmark targetBookmark = aim.getTarget();
-                final Object targetObject = bookmarkService.lookup(
-                        targetBookmark, BookmarkService2.FieldResetPolicy.RESET);
-
-                final ObjectAdapter targetAdapter = adapterFor(targetObject);
-                final ObjectSpecification specification = targetAdapter.getSpecification();
-
-                final ObjectAction objectAction = findActionElseNull(specification, actionId);
-                if(objectAction == null) {
-                    throw new RuntimeException(String.format("Unknown action '%s'", actionId));
-                }
-
-                // TODO: background commands won't work for mixin actions...
-                // ... we obtain the target from the bookmark service (above), which will
-                // simply fail for a mixin.  Instead we would need to serialize out the mixedInAdapter
-                // and also capture the mixinType within the aim memento.
-                final ObjectAdapter mixedInAdapter = null;
-
-                final ObjectAdapter[] argAdapters = argAdaptersFor(aim);
-                final ObjectAdapter resultAdapter = objectAction.execute(
-                        targetAdapter, mixedInAdapter, argAdapters, InteractionInitiatedBy.FRAMEWORK);
-
-                if(resultAdapter != null) {
-                    Bookmark resultBookmark = CommandUtil.bookmarkFor(resultAdapter);
-                    command.setResult(resultBookmark);
-                    backgroundInteraction.getCurrentExecution().setReturned(resultAdapter.getObject());
-                }
-
-            } else {
-
-                final CommandDto dto = jaxbService.fromXml(CommandDto.class, memento);
-
-                // if the command being executed was replayed, then in its userData it will holds
-                // details of any exception that might have occurred.
-                origExceptionIfAny = CommandDtoUtils.getUserData(dto, CommandWithDto.USERDATA_KEY_EXCEPTION);
+            final CommandDto dto = jaxbService.fromXml(CommandDto.class, memento);
 
-                final MemberDto memberDto = dto.getMember();
-                final String memberId = memberDto.getMemberIdentifier();
+            final MemberDto memberDto = dto.getMember();
+            final String memberId = memberDto.getMemberIdentifier();
 
-                final OidsDto oidsDto = CommandDtoUtils.targetsFor(dto);
-                final List<OidDto> targetOidDtos = oidsDto.getOid();
+            final OidsDto oidsDto = CommandDtoUtils.targetsFor(dto);
+            final List<OidDto> targetOidDtos = oidsDto.getOid();
 
-                final InteractionType interactionType = memberDto.getInteractionType();
-                if(interactionType == InteractionType.ACTION_INVOCATION) {
+            final InteractionType interactionType = memberDto.getInteractionType();
+            if(interactionType == InteractionType.ACTION_INVOCATION) {
 
-                    final ActionDto actionDto = (ActionDto) memberDto;
+                final ActionDto actionDto = (ActionDto) memberDto;
 
-                    for (OidDto targetOidDto : targetOidDtos) {
+                for (OidDto targetOidDto : targetOidDtos) {
 
-                        final ObjectAdapter targetAdapter = adapterFor(targetOidDto);
-                        final ObjectAction objectAction = findObjectAction(targetAdapter, memberId);
+                    final ObjectAdapter targetAdapter = adapterFor(targetOidDto);
+                    final ObjectAction objectAction = findObjectAction(targetAdapter, memberId);
 
-                        // we pass 'null' for the mixedInAdapter; if this action _is_ a mixin then
-                        // it will switch the targetAdapter to be the mixedInAdapter transparently
-                        final ObjectAdapter[] argAdapters = argAdaptersFor(actionDto);
-                        final ObjectAdapter resultAdapter = objectAction.execute(
-                                targetAdapter, null, argAdapters, InteractionInitiatedBy.FRAMEWORK);
+                    // we pass 'null' for the mixedInAdapter; if this action _is_ a mixin then
+                    // it will switch the targetAdapter to be the mixedInAdapter transparently
+                    final ObjectAdapter[] argAdapters = argAdaptersFor(actionDto);
+                    final ObjectAdapter resultAdapter = objectAction.execute(
+                            targetAdapter, null, argAdapters, InteractionInitiatedBy.FRAMEWORK);
 
-                        //
-                        // for the result adapter, we could alternatively have used...
-                        // (priorExecution populated by the push/pop within the interaction object)
-                        //
-                        // final Interaction.Execution priorExecution = backgroundInteraction.getPriorExecution();
-                        // Object unused = priorExecution.getReturned();
-                        //
+                    //
+                    // for the result adapter, we could alternatively have used...
+                    // (priorExecution populated by the push/pop within the interaction object)
+                    //
+                    // final Interaction.Execution priorExecution = backgroundInteraction.getPriorExecution();
+                    // Object unused = priorExecution.getReturned();
+                    //
 
-                        // REVIEW: this doesn't really make sense if >1 action
-                        // in any case, the capturing of the action interaction should be the
-                        // responsibility of auditing/profiling
-                        if(resultAdapter != null) {
-                            Bookmark resultBookmark = CommandUtil.bookmarkFor(resultAdapter);
-                            command.setResult(resultBookmark);
-                        }
+                    // REVIEW: this doesn't really make sense if >1 action
+                    // in any case, the capturing of the action interaction should be the
+                    // responsibility of auditing/profiling
+                    if(resultAdapter != null) {
+                        Bookmark resultBookmark = CommandUtil.bookmarkFor(resultAdapter);
+                        command.setResult(resultBookmark);
                     }
-                } else {
+                }
+            } else {
 
-                    final PropertyDto propertyDto = (PropertyDto) memberDto;
+                final PropertyDto propertyDto = (PropertyDto) memberDto;
 
-                    for (OidDto targetOidDto : targetOidDtos) {
+                for (OidDto targetOidDto : targetOidDtos) {
 
-                        final Bookmark bookmark = Bookmark.from(targetOidDto);
-                        final Object targetObject = bookmarkService.lookup(bookmark);
+                    final Bookmark bookmark = Bookmark.from(targetOidDto);
+                    final Object targetObject = bookmarkService.lookup(bookmark);
 
-                        final ObjectAdapter targetAdapter = adapterFor(targetObject);
+                    final ObjectAdapter targetAdapter = adapterFor(targetObject);
 
-                        final OneToOneAssociation property = findOneToOneAssociation(targetAdapter, memberId);
+                    final OneToOneAssociation property = findOneToOneAssociation(targetAdapter, memberId);
 
-                        final ObjectAdapter newValueAdapter = newValueAdapterFor(propertyDto);
+                    final ObjectAdapter newValueAdapter = newValueAdapterFor(propertyDto);
 
-                        property.set(targetAdapter, newValueAdapter, InteractionInitiatedBy.FRAMEWORK);
-                        // there is no return value for property modifications.
-                    }
+                    property.set(targetAdapter, newValueAdapter, InteractionInitiatedBy.FRAMEWORK);
+                    // there is no return value for property modifications.
                 }
-
             }
 
         } catch (RuntimeException ex) {
@@ -432,8 +327,7 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
                         : clockService.nowAsJavaSqlTimestamp();  // close enough...
         command.setCompletedAt(completedAt);
 
-        // if we hit an exception processing this command but the master did not, then quit if instructed
-        return determineIfContinue(origExceptionIfAny, exceptionIfAny);
+        return exceptionIfAny;
     }
 
     private static ObjectAction findObjectAction(
@@ -462,24 +356,6 @@ public abstract class BackgroundCommandExecution extends AbstractIsisSessionTemp
         return property;
     }
 
-    private boolean determineIfContinue(
-            final String origExceptionIfAny,
-            final RuntimeException exceptionIfAny) {
-
-        if(origExceptionIfAny != null) {
-            return true;
-        }
-
-        // there was no exception on master, so do we continue?
-        return determineIfContinue(exceptionIfAny);
-    }
-
-    private boolean determineIfContinue(final RuntimeException exceptionIfAny) {
-        final boolean shouldQuit = exceptionIfAny != null &&
-                      onExceptionPolicy == OnExceptionPolicy.QUIT;
-        return !shouldQuit;
-    }
-
     private ObjectAdapter newValueAdapterFor(final PropertyDto propertyDto) {
         final ValueWithTypeDto newValue = propertyDto.getNewValue();
         final Object arg = CommonDtoUtils.getValue(newValue);
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/sessiontemplate/AbstractIsisSessionTemplate.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/sessiontemplate/AbstractIsisSessionTemplate.java
index a554d7c..ac40524 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/sessiontemplate/AbstractIsisSessionTemplate.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/sessiontemplate/AbstractIsisSessionTemplate.java
@@ -90,7 +90,7 @@ public abstract class AbstractIsisSessionTemplate {
 
     // //////////////////////////////////////
 
-    protected ObjectAdapter adapterFor(final Object targetObject) {
+    protected final ObjectAdapter adapterFor(final Object targetObject) {
         if(targetObject instanceof OidDto) {
             final OidDto oidDto = (OidDto) targetObject;
             return adapterFor(oidDto);
@@ -117,17 +117,17 @@ public abstract class AbstractIsisSessionTemplate {
         return getPersistenceSession().adapterFor(targetObject);
     }
 
-    protected ObjectAdapter adapterFor(final OidDto oidDto) {
+    protected final ObjectAdapter adapterFor(final OidDto oidDto) {
         final Bookmark bookmark = Bookmark.from(oidDto);
         return adapterFor(bookmark);
     }
 
-    protected ObjectAdapter adapterFor(final Bookmark bookmark) {
+    protected final ObjectAdapter adapterFor(final Bookmark bookmark) {
         final RootOid rootOid = RootOid.create(bookmark);
         return adapterFor(rootOid);
     }
 
-    protected ObjectAdapter adapterFor(final RootOid rootOid) {
+    protected final ObjectAdapter adapterFor(final RootOid rootOid) {
         return getPersistenceSession().adapterFor(rootOid);
     }
     

-- 
To stop receiving notification emails like this one, please contact
danhaywood@apache.org.