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

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

commit 8a9da8b86b514806bbd1f7e48ccba62b549b7c51
Author: Dan Haywood <[email protected]>
AuthorDate: Fri Nov 4 10:46:37 2022 +0000

    ISIS-3267: improves RunBackgroundCommands, run each command in its own 
transaction.
    
    Also refine the API of CommandExecutorService, return back a Try<Bookmark> 
rather than a Bookmark
---
 .../services/command/CommandExecutorService.java   |  31 +++---
 .../services/command/CommandOutcomeHandler.java    |   8 +-
 .../command/CommandExecutorServiceDefault.java     | 117 +++++++++------------
 .../commandlog/applib/dom/CommandLogEntry.java     |   7 +-
 .../applib/job/RunBackgroundCommandsJob.java       |  71 +++++++------
 .../jobcallables/ReplicateAndRunCommands.java      |   3 -
 6 files changed, 111 insertions(+), 126 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandExecutorService.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandExecutorService.java
index 4670f2a2c9..2f1569af6e 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandExecutorService.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandExecutorService.java
@@ -27,6 +27,7 @@ import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.applib.services.iactn.Interaction;
 import org.apache.causeway.applib.services.iactnlayer.InteractionContext;
 import org.apache.causeway.applib.services.user.UserMemento;
+import org.apache.causeway.commons.functional.Try;
 import org.apache.causeway.schema.cmd.v2.CommandDto;
 
 import lombok.val;
@@ -99,10 +100,10 @@ public interface CommandExecutorService {
      * persistent equivalent) afterwards (for example, setting its {@link 
Command#getCommandDto() commandDto} field.
      *
      * @param interactionContextPolicy - policy to use
-     * @param command - the {@link Command} to be executed
+     * @param command                  - the {@link Command} to be executed
      * @return - a bookmark representing the result of executing the command 
(could be null)
      */
-    Bookmark executeCommand(
+    Try<Bookmark> executeCommand(
             InteractionContextPolicy interactionContextPolicy,
             Command command
     );
@@ -111,10 +112,10 @@ public interface CommandExecutorService {
      * Executes the specified command (represented as a {@link CommandDto} 
using the required {@link InteractionContextPolicy}.
      *
      * <p>
-     *     IMPORTANT: THIS METHOD HAS SIGNIFICANT SIDE-EFFECTS.  Specifically, 
the {@link Command} of the executing
-     *     thread (obtained using {@link 
org.apache.causeway.applib.services.iactn.InteractionProvider} to obtain the
-     *     {@link Interaction}, and then {@link Interaction#getCommand()} to 
obtain the {@link Command}) will be
-     *     UPDATED to hold the {@link CommandDto} passed in.
+     * IMPORTANT: THIS METHOD HAS SIGNIFICANT SIDE-EFFECTS.  Specifically, the 
{@link Command} of the executing
+     * thread (obtained using {@link 
org.apache.causeway.applib.services.iactn.InteractionProvider} to obtain the
+     * {@link Interaction}, and then {@link Interaction#getCommand()} to 
obtain the {@link Command}) will be
+     * UPDATED to hold the {@link CommandDto} passed in.
      * </p>
      *
      * <p>
@@ -123,12 +124,11 @@ public interface CommandExecutorService {
      * </p>
      *
      * @param interactionContextPolicy - policy to use
-     * @param commandDto - the {@link CommandDto} to be executed
-     * @param outcomeHandler - callback to handle the result
-     *
+     * @param commandDto               - the {@link CommandDto} to be executed
+     * @param outcomeHandler           - callback to handle the result
      * @return - a bookmark representing the result of executing the command 
(could be null)
      */
-    Bookmark executeCommand(
+    Try<Bookmark> executeCommand(
             InteractionContextPolicy interactionContextPolicy,
             CommandDto commandDto,
             CommandOutcomeHandler outcomeHandler);
@@ -137,12 +137,12 @@ public interface CommandExecutorService {
      * As per {@link #executeCommand(InteractionContextPolicy, Command)}, with 
a policy of {@link InteractionContextPolicy#NO_SWITCH no switch}.
      *
      * <p>
-     *     Note that this method updates the Command as a side-effect.
+     * Note that this method updates the Command as a side-effect.
      * </p>
      *
      * @see #executeCommand(InteractionContextPolicy, Command)
      */
-    Bookmark executeCommand(
+    Try<Bookmark> executeCommand(
             Command command
     );
 
@@ -150,15 +150,14 @@ public interface CommandExecutorService {
      * As per {@link #executeCommand(InteractionContextPolicy, CommandDto, 
CommandOutcomeHandler)}, with a policy of {@link 
InteractionContextPolicy#NO_SWITCH no switch}.
      *
      * <p>
-     *     Note that this method has significant side-effects.
+     * Note that this method has significant side-effects.
      * </p>
      *
-     * @see #executeCommand(InteractionContextPolicy, CommandDto, 
CommandOutcomeHandler)
-     *
      * @param commandDto
      * @param outcomeHandler
+     * @see #executeCommand(InteractionContextPolicy, CommandDto, 
CommandOutcomeHandler)
      */
-    Bookmark executeCommand(
+    Try<Bookmark> executeCommand(
             CommandDto commandDto,
             CommandOutcomeHandler outcomeHandler);
 
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandOutcomeHandler.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandOutcomeHandler.java
index 9e3a175c2d..a8311c9765 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandOutcomeHandler.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandOutcomeHandler.java
@@ -34,7 +34,7 @@ public interface CommandOutcomeHandler {
         @Override public Timestamp getStartedAt() { return null; }
         @Override public void setStartedAt(final Timestamp startedAt) { }
         @Override public void setCompletedAt(final Timestamp completedAt) { }
-        @Override public void setResult(final Try<Bookmark> resultBookmark) { }
+        @Override public void setResult(final Try<Bookmark> result) { }
     };
 
     /**
@@ -64,8 +64,10 @@ public interface CommandOutcomeHandler {
     void setCompletedAt(Timestamp completedAt);
 
     /**
-     * Sets the result of the execute, represented as a {@link Bookmark}, on 
the underlying {@link Command} (or persistent equivalent).
+     * Handle the result of the execute, represented either as a {@link 
Bookmark} (success case) or an exception (failure),
+     * on the underlying {@link Command} (or persistent equivalent).
      */
-    void setResult(Try<Bookmark> resultBookmark);
+    void setResult(Try<Bookmark> result);
+
 
 }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
index ddae64e1a6..c536c815e0 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
@@ -21,6 +21,7 @@ package org.apache.causeway.core.runtimeservices.command;
 import java.sql.Timestamp;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 
@@ -28,6 +29,7 @@ import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Named;
 
+import org.apache.causeway.commons.functional.Try;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
@@ -47,7 +49,6 @@ import 
org.apache.causeway.applib.services.xactn.TransactionService;
 import org.apache.causeway.applib.util.schema.CommandDtoUtils;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.functional.IndexedFunction;
-import org.apache.causeway.commons.functional.Try;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
@@ -98,12 +99,12 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
     @Inject @Getter final SpecificationLoader specificationLoader;
 
     @Override
-    public Bookmark executeCommand(final Command command) {
+    public Try<Bookmark> executeCommand(final Command command) {
         return executeCommand(InteractionContextPolicy.NO_SWITCH, command);
     }
 
     @Override
-    public Bookmark executeCommand(
+    public Try<Bookmark> executeCommand(
             final InteractionContextPolicy interactionContextPolicy,
             final Command command) {
 
@@ -111,7 +112,7 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
     }
 
     @Override
-    public Bookmark executeCommand(
+    public Try<Bookmark> executeCommand(
             final CommandDto dto,
             final CommandOutcomeHandler outcomeHandler) {
 
@@ -119,7 +120,7 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
     }
 
     @Override
-    public Bookmark executeCommand(
+    public Try<Bookmark> executeCommand(
             final InteractionContextPolicy interactionContextPolicy,
             final CommandDto dto,
             final CommandOutcomeHandler outcomeHandler) {
@@ -127,10 +128,10 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
         return doExecute(interactionContextPolicy, dto, outcomeHandler);
     }
 
-    private Bookmark doExecute(
+    private Try<Bookmark> doExecute(
             final InteractionContextPolicy interactionContextPolicy,
             final CommandDto dto,
-            final CommandOutcomeHandler commandUpdater) {
+            final CommandOutcomeHandler commandOutcomeHandler) {
 
         val interaction = 
iInteractionLayerTracker.currentInteractionElseFail();
         val command = interaction.getCommand();
@@ -138,9 +139,9 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
             command.updater().setCommandDto(dto);
         }
 
-        copyStartedAtFromInteractionExecution(commandUpdater);
+        copyStartedAtFromInteractionExecution(commandOutcomeHandler);
 
-        val result = 
transactionService.callWithinCurrentTransactionElseCreateNew(
+        Try<Bookmark> result = 
transactionService.callWithinCurrentTransactionElseCreateNew(
             () -> {
                 if (interactionContextPolicy == 
InteractionContextPolicy.NO_SWITCH) {
                     // short-circuit
@@ -151,12 +152,30 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
                         () -> doExecuteCommand(dto));
             });
 
-        result.ifFailure(ex->{
-            log.warn("Exception when executing : {}",
-                    dto.getMember().getLogicalMemberIdentifier(), ex);
-        });
+        // capture result (success or failure)
+        commandOutcomeHandler.setResult(result);
 
-        return handleOutcomeAndSetCompletedAt(commandUpdater, result);
+
+        //
+        // also, copy over the completedAt at to the command.
+        //
+        // NB: it's possible that there is no priorExecution, specifically if
+        // there was an exception when performing the action 
invocation/property
+        // edit.  We therefore need to guard that case.
+        //
+        val priorExecution = interaction.getPriorExecution();
+        if(priorExecution != null) {
+
+            if (commandOutcomeHandler.getStartedAt() == null) {
+                // TODO: REVIEW - don't think this can happen ...
+                //  Interaction/Execution is an in-memory object.
+                
commandOutcomeHandler.setStartedAt(priorExecution.getStartedAt());
+            }
+            val completedAt = priorExecution.getCompletedAt();
+            commandOutcomeHandler.setCompletedAt(completedAt);
+        }
+
+        return result;
     }
 
     private void copyStartedAtFromInteractionExecution(
@@ -178,18 +197,18 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
                 dto.getMember().getLogicalMemberIdentifier(),
                 dto.getTimestamp(), dto.getInteractionId());
 
-        final MemberDto memberDto = dto.getMember();
-        final String logicalMemberIdentifier = 
memberDto.getLogicalMemberIdentifier();
+        val memberDto = dto.getMember();
+        val logicalMemberIdentifier = memberDto.getLogicalMemberIdentifier();
 
-        final OidsDto oidsDto = CommandDtoUtils.targetsFor(dto);
-        final List<OidDto> targetOidDtos = oidsDto.getOid();
+        val oidsDto = CommandDtoUtils.targetsFor(dto);
+        val targetOidDtoList = oidsDto.getOid();
 
-        final InteractionType interactionType = memberDto.getInteractionType();
+        val interactionType = memberDto.getInteractionType();
         if(interactionType == InteractionType.ACTION_INVOCATION) {
 
-            final ActionDto actionDto = (ActionDto) memberDto;
+            val actionDto = (ActionDto) memberDto;
 
-            for (OidDto targetOidDto : targetOidDtos) {
+            for (val targetOidDto : targetOidDtoList) {
 
                 val targetAdapter = 
valueMarshaller.recoverReferenceFrom(targetOidDto);
                 val objectAction = findObjectAction(targetAdapter, 
logicalMemberIdentifier);
@@ -198,9 +217,9 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
                 // it will switch the targetAdapter to be the mixedInAdapter 
transparently
                 val argAdapters = argAdaptersFor(actionDto);
 
-                final InteractionHead head = 
objectAction.interactionHead(targetAdapter);
+                val interactionHead = 
objectAction.interactionHead(targetAdapter);
 
-                val resultAdapter = objectAction.execute(head, argAdapters, 
InteractionInitiatedBy.FRAMEWORK);
+                val resultAdapter = objectAction.execute(interactionHead, 
argAdapters, InteractionInitiatedBy.FRAMEWORK);
 
                 // flush any Causeway PersistenceCommands pending
                 // (else might get transient objects for the return value)
@@ -222,9 +241,9 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
             }
         } else {
 
-            final PropertyDto propertyDto = (PropertyDto) memberDto;
+            val propertyDto = (PropertyDto) memberDto;
 
-            for (OidDto targetOidDto : targetOidDtos) {
+            for (val targetOidDto : targetOidDtoList) {
 
                 val targetAdapter = 
valueMarshaller.recoverReferenceFrom(targetOidDto);
 
@@ -233,7 +252,7 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
                             Bookmark.forOidDto(targetOidDto));
                 }
 
-                final OneToOneAssociation property = 
findOneToOneAssociation(targetAdapter, logicalMemberIdentifier);
+                val property = findOneToOneAssociation(targetAdapter, 
logicalMemberIdentifier);
 
                 val newValueAdapter = 
valueMarshaller.recoverPropertyFrom(propertyDto);
 
@@ -245,55 +264,19 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
         return null;
     }
 
-    private Bookmark handleOutcomeAndSetCompletedAt(
-            final CommandOutcomeHandler outcomeHandler,
-            final Try<Bookmark> result) {
-
-
-        //
-        // copy over the outcome
-        //
-        outcomeHandler.setResult(result);
-
-        //
-        // also, copy over the completedAt at to the command.
-        //
-        // NB: it's possible that there is no priorExecution, specifically if
-        // there was an exception when performing the action 
invocation/property
-        // edit.  We therefore need to guard that case.
-        //
-        val interaction = 
iInteractionLayerTracker.currentInteractionElseFail();
-
-        final Execution<?, ?> priorExecution = interaction.getPriorExecution();
-        if(priorExecution != null) {
-
-            if (outcomeHandler.getStartedAt() == null) {
-                // TODO: REVIEW - don't think this can happen ...
-                //  Interaction/Execution is an in-memory object.
-                outcomeHandler.setStartedAt(priorExecution.getStartedAt());
-            }
-            final Timestamp completedAt =
-                    priorExecution.getCompletedAt();
-            outcomeHandler.setCompletedAt(completedAt);
-        }
-
-        return result.getValue().orElse(null);
-    }
-
-    // //////////////////////////////////////
 
     private static ObjectAction findObjectAction(
             final ManagedObject targetAdapter,
             final String logicalMemberIdentifier) throws RuntimeException {
 
-        final ObjectSpecification specification = 
targetAdapter.getSpecification();
+        val objectSpecification = targetAdapter.getSpecification();
 
         // we use the local identifier because the fullyQualified version 
includes the class name.
         // that is a problem for us if the property is inherited, because it 
will be the class name of the declaring
         // superclass, rather than the concrete class of the target that we 
are inspecting here.
         val localActionId = localPartOf(logicalMemberIdentifier);
 
-        final ObjectAction objectAction = findActionElseNull(specification, 
localActionId);
+        val objectAction = findActionElseNull(objectSpecification, 
localActionId);
         if(objectAction == null) {
             throw new RuntimeException(String.format("Unknown action '%s'", 
localActionId));
         }
@@ -309,9 +292,9 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
         // superclass, rather than the concrete class of the target that we 
are inspecting here.
         val localPropertyId = localPartOf(logicalMemberIdentifier);
 
-        final ObjectSpecification specification = 
targetAdapter.getSpecification();
+        val objectSpecification = targetAdapter.getSpecification();
 
-        final OneToOneAssociation property = 
findOneToOneAssociationElseNull(specification, localPropertyId);
+        val property = findOneToOneAssociationElseNull(objectSpecification, 
localPropertyId);
         if(property == null) {
             throw new RuntimeException(String.format("Unknown property '%s'", 
localPropertyId));
         }
@@ -344,7 +327,7 @@ public class CommandExecutorServiceDefault implements 
CommandExecutorService {
 
     private Can<ManagedObject> argAdaptersFor(final ActionDto actionDto) {
 
-        final Identifier actionIdentifier = 
valueMarshaller.actionIdentifier(actionDto);
+        val actionIdentifier = valueMarshaller.actionIdentifier(actionDto);
 
         return streamParamDtosFrom(actionDto)
                 .map(IndexedFunction.zeroBased((i, paramDto)->
diff --git 
a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
 
b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
index ebe5361a66..fc92c295ac 100644
--- 
a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
+++ 
b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
@@ -685,6 +685,7 @@ implements Comparable<CommandLogEntry>, DomainChangeRecord, 
HasCommandDto {
     @Programmatic
     public CommandOutcomeHandler outcomeHandler() {
         return new CommandOutcomeHandler() {
+
             @Override
             public java.sql.Timestamp getStartedAt() {
                 return CommandLogEntry.this.getStartedAt();
@@ -701,9 +702,9 @@ implements Comparable<CommandLogEntry>, DomainChangeRecord, 
HasCommandDto {
             }
 
             @Override
-            public void setResult(final Try<Bookmark> resultBookmark) {
-                
CommandLogEntry.this.setResult(resultBookmark.getValue().orElse(null));
-                
CommandLogEntry.this.setException(resultBookmark.getFailure().orElse(null));
+            public void setResult(Try<Bookmark> result) {
+                result.ifSuccess(bookmarkIfAny -> 
bookmarkIfAny.ifPresent(CommandLogEntry.this::setResult));
+                result.ifFailure(CommandLogEntry.this::setException);
             }
         };
     }
diff --git 
a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/job/RunBackgroundCommandsJob.java
 
b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/job/RunBackgroundCommandsJob.java
index 0906fe3dec..f956f97828 100644
--- 
a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/job/RunBackgroundCommandsJob.java
+++ 
b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/job/RunBackgroundCommandsJob.java
@@ -1,10 +1,17 @@
 package org.apache.causeway.extensions.commandlog.applib.job;
 
 import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
 import lombok.val;
 
 import java.sql.Timestamp;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -16,10 +23,12 @@ import 
org.apache.causeway.applib.services.iactnlayer.InteractionContext;
 import org.apache.causeway.applib.services.iactnlayer.InteractionService;
 import org.apache.causeway.applib.services.user.UserMemento;
 import org.apache.causeway.applib.services.xactn.TransactionService;
+import org.apache.causeway.applib.util.JaxbUtil;
 import org.apache.causeway.commons.functional.ThrowingRunnable;
 import org.apache.causeway.commons.functional.Try;
 import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry;
 import 
org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
+import org.apache.causeway.schema.cmd.v2.CommandDto;
 import org.quartz.DisallowConcurrentExecution;
 import org.quartz.Job;
 import org.quartz.JobExecutionContext;
@@ -37,6 +46,7 @@ import org.springframework.transaction.annotation.Propagation;
 @Component
 @DisallowConcurrentExecution
 @PersistJobDataAfterExecution
+@Log4j2
 public class RunBackgroundCommandsJob implements Job {
 
     @Inject InteractionService interactionService;
@@ -44,8 +54,6 @@ public class RunBackgroundCommandsJob implements Job {
     @Inject CommandLogEntryRepository<? extends CommandLogEntry> 
commandLogEntryRepository;
     @Inject CommandExecutorService commandExecutorService;
 
-    private final static 
JavaSqlJaxbAdapters.TimestampToXMLGregorianCalendarAdapter 
gregorianCalendarAdapter  = new 
JavaSqlJaxbAdapters.TimestampToXMLGregorianCalendarAdapter();;
-
     public void execute(final JobExecutionContext quartzContext) {
         val user = UserMemento.ofNameAndRoleNames("scheduler_user", 
"admin_role");
         val interactionContext = 
InteractionContext.builder().user(user).build();
@@ -56,40 +64,35 @@ public class RunBackgroundCommandsJob implements Job {
 
         @Override
         public void run() {
-            transactionService.runTransactional(Propagation.REQUIRES_NEW, () 
-> {
-                val notYetStartedEntries = 
commandLogEntryRepository.findBackgroundAndNotYetStarted();
-                for (val commandLogEntry : notYetStartedEntries) {
-                    val commandDto = commandLogEntry.getCommandDto();
-                    
commandExecutorService.executeCommand(CommandExecutorService.InteractionContextPolicy.NO_SWITCH,
 commandDto, new OutcomeHandler(commandLogEntry));
-                }
-            }).ifFailureFail();
 
+            // we obtain the list of Commands first; we use their CommandDto 
as it is serializable across transactions
+            val commandDtosIfAny =
+                    transactionService.callTransactional(
+                            Propagation.REQUIRES_NEW,
+                            () -> 
commandLogEntryRepository.findBackgroundAndNotYetStarted()
+                                    .stream()
+                                    .map(CommandLogEntry::getCommandDto)
+                                    .collect(Collectors.toList())
+                    )
+                    .ifFailureFail()    // we give up if unable to find these
+                    .getValue();        // the success case wrapped in an 
optional
+
+            // for each command, we execute within its own transaction.  
Failure of one should not impact the next.
+            commandDtosIfAny.ifPresent(
+                commandDtos -> {
+                for (val commandDto : commandDtos) {
+                    
transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
+                        // it's necessary to look up the CommandLogEntry again 
because we are within a new transaction.
+                        val commandLogEntryIfAny = 
commandLogEntryRepository.findByInteractionId(UUID.fromString(commandDto.getInteractionId()));
+
+                        // finally, we execute
+                        commandLogEntryIfAny.ifPresent(commandLogEntry ->
+                                commandExecutorService.executeCommand(
+                                        
CommandExecutorService.InteractionContextPolicy.NO_SWITCH, commandDto, 
commandLogEntry.outcomeHandler()));
+                    }).ifFailure(throwable -> log.error("Failed to execute 
command: " + JaxbUtil.toXml(commandDto), throwable));
+                }
+            });
         }
     }
 
-    @RequiredArgsConstructor
-    private class OutcomeHandler implements CommandOutcomeHandler {
-
-        private final CommandLogEntry commandLogEntry;
-
-        @Override
-        public Timestamp getStartedAt() {
-            return commandLogEntry.getStartedAt();
-        }
-
-        @Override
-        public void setStartedAt(Timestamp startedAt) {
-            commandLogEntry.setStartedAt(startedAt);
-        }
-
-        @Override
-        public void setCompletedAt(Timestamp completedAt) {
-            commandLogEntry.setCompletedAt(completedAt);
-        }
-
-        @Override
-        public void setResult(Try<Bookmark> resultBookmark) {
-            resultBookmark.ifSuccess(bookmarkIfAny -> 
bookmarkIfAny.ifPresent(commandLogEntry::setResult));
-        }
-    }
 }
diff --git 
a/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
 
b/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
index 500ad84d10..c33211c5d0 100644
--- 
a/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
+++ 
b/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
@@ -164,10 +164,7 @@ public class ReplicateAndRunCommands implements 
Callable<SecondaryStatus> {
                     return;
                 }
             }
-
         });
-
-
     }
 
     private ReplayState executeCommandInTranAndAnalyse(final CommandLogEntry 
commandLogEntry) {

Reply via email to