From 0dc978ceff09cfc78c2e2d98d58a8f7446131a1e Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Fri, 26 Sep 2014 11:02:10 +0300 Subject: [PATCH] obey maxRetries when NextAction.retryAfter(...) is used --- .../executor/WorkflowStateProcessor.java | 5 ++-- .../AbstractWorkflowDefinition.java | 29 +++++++++++++++---- .../executor/WorkflowStateProcessorTest.java | 17 ++++++++++- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessor.java index 453241158..1c74436d1 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -162,14 +162,15 @@ private NextAction processState(WorkflowInstance instance, WorkflowDefinition execution.setFailed(); } execution.setNextActivation(nextAction.getActivation()); + execution.setNextStateReason(nextAction.getReason()); + execution.setSaveTrace(nextAction.isSaveTrace()); if (nextAction.getNextState() == null) { execution.setNextState(definition.getState(instance.state)); execution.setRetry(true); + definition.handleRetryAfter(execution, nextAction.getActivation()); } else { execution.setNextState(nextAction.getNextState()); } - execution.setNextStateReason(nextAction.getReason()); - execution.setSaveTrace(nextAction.isSaveTrace()); objectMapper.storeArguments(execution, method, args); return nextAction; } diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/workflow/definition/AbstractWorkflowDefinition.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/workflow/definition/AbstractWorkflowDefinition.java index 44e148f76..c80a3ad78 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/workflow/definition/AbstractWorkflowDefinition.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/workflow/definition/AbstractWorkflowDefinition.java @@ -3,6 +3,7 @@ import static com.nitorcreations.nflow.engine.workflow.definition.WorkflowStateType.end; import static com.nitorcreations.nflow.engine.workflow.definition.WorkflowStateType.manual; import static java.lang.String.format; +import static org.joda.time.DateTime.now; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -187,26 +188,44 @@ void requireStateMethodExists(S state) { * Handle retries for the state execution. The default implementation moves * the workflow to a failure state after the maximum retry attempts is * exceeded. If there is no failure state defined for the retried state, moves - * the workflow to the generic error state. If the maximum retry attempts is - * not exceeded, schedules the next attempt for the state based on workflow - * settings. + * the workflow to the generic error state and stops processing. Error state + * handler method, if it exists, is not executed. If the maximum retry attempts + * is not exceeded, schedules the next attempt for the state based on workflow + * settings. This method is called when an unexpected exception happens during + * state method handling. * @param execution State execution information. */ public void handleRetry(StateExecutionImpl execution) { + handleRetryAfter(execution, getSettings().getErrorTransitionActivation(execution.getRetries())); + } + + /** + * Handle retries for the state execution. The default implementation moves + * the workflow to a failure state after the maximum retry attempts is + * exceeded. If there is no failure state defined for the retried state, moves + * the workflow to the generic error state and stops processing. Error state + * handler method, if it exists, is not executed. If the maximum retry attempts + * is not exceeded, schedules the next attempt to the given activation time. + * This method is called when a retry attempt is explicitly requested by a + * state handling method. + * @param execution State execution information. + * @param activation Time for next retry attempt. + */ + public void handleRetryAfter(StateExecutionImpl execution, DateTime activation) { if (execution.getRetries() >= getSettings().maxRetries) { execution.setRetry(false); WorkflowState failureState = failureTransitions.get(execution.getCurrentStateName()); if (failureState != null) { execution.setNextState(failureState); execution.setNextStateReason("Max retry count exceeded"); - execution.setNextActivation(new DateTime()); + execution.setNextActivation(now()); } else { execution.setNextState(errorState); execution.setNextStateReason("Max retry count exceeded, no failure state defined"); execution.setNextActivation(null); } } else { - execution.setNextActivation(getSettings().getErrorTransitionActivation(execution.getRetries())); + execution.setNextActivation(activation); } } diff --git a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessorTest.java b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessorTest.java index 95e84cd57..185aac0e8 100644 --- a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessorTest.java +++ b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowStateProcessorTest.java @@ -40,7 +40,6 @@ import org.mockito.Mockito; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nitorcreations.nflow.engine.internal.executor.WorkflowStateProcessor; import com.nitorcreations.nflow.engine.internal.workflow.ObjectStringMapper; import com.nitorcreations.nflow.engine.listener.WorkflowExecutorListener; import com.nitorcreations.nflow.engine.listener.WorkflowExecutorListener.ListenerContext; @@ -128,6 +127,22 @@ public void runWorkflowThroughToFailureState() { assertThat(action.getAllValues().get(1), matchesWorkflowInstanceAction(FailingTestWorkflow.State.failure, 0)); } + @Test + public void maxRetryIsObeyedForManualRetry() { + WorkflowDefinition wf = new FailingTestWorkflow(); + doReturn(wf).when(workflowDefinitions).getWorkflowDefinition(eq("test")); + WorkflowInstance instance = constructWorkflowInstanceBuilder() + .setType("test").setId(Integer.valueOf(1)).setProcessing(true) + .setState("retryingState").setRetries(wf.getSettings().maxRetries).build(); + when(workflowInstances.getWorkflowInstance(instance.id)).thenReturn(instance); + + executor.run(); + + verify(workflowInstances).updateWorkflowInstance(update.capture(), action.capture()); + assertThat(update.getValue(), matchesWorkflowInstance(FailingTestWorkflow.State.error, 0, false, is(nullValue(DateTime.class)))); + assertThat(action.getValue(), matchesWorkflowInstanceAction(FailingTestWorkflow.State.retryingState, wf.getSettings().maxRetries)); + } + @Test public void goToErrorStateWhenStateMethodReturnsNull() { WorkflowDefinition wf = new FailingTestWorkflow();