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

pefernan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git


The following commit(s) were added to refs/heads/main by this push:
     new e213190e63c [kie-issues #2410] WIH exception handling (#7166)
e213190e63c is described below

commit e213190e63c8f49245f4d2e8a5c532c5eadc2be9
Author: Christine-Jose <[email protected]>
AuthorDate: Thu Oct 15 18:15:14 2026 +0530

    [kie-issues #2410] WIH exception handling (#7166)
    
    * Changes for exception
    
    * Changes for configuring exception handler
    
    * Test failures
    
    * fixing the retry bug
    
    * Fixing the build failure
    
    * Changes for config
    
    * review comments
---
 .../process/instance/LightWorkItemManager.java     |   2 +
 .../instance/node/WorkItemNodeInstance.java        |   2 +-
 .../WorkItemHandlerExceptionHandlingTest.java      | 129 +++++++++++
 .../ProcessWorkItemHandlerExceptionHandler.java    | 117 ++++++++++
 .../impl/KogitoDefaultWorkItemManager.java         |   2 +
 ...ProcessWorkItemHandlerExceptionHandlerTest.java | 253 +++++++++++++++++++++
 6 files changed, 504 insertions(+), 1 deletion(-)

diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
index 73c87902eb3..32f8cf107ad 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java
@@ -29,6 +29,7 @@ import java.util.function.Function;
 import java.util.stream.Stream;
 
 import org.drools.core.process.WorkItem;
+import 
org.jbpm.process.workitem.builtin.ProcessWorkItemHandlerExceptionHandler;
 import org.kie.kogito.internal.process.event.KogitoProcessEventSupport;
 import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
@@ -192,6 +193,7 @@ public class LightWorkItemManager implements 
InternalKogitoWorkItemManager {
         if (handler == null) {
             throw new 
KogitoWorkItemHandlerNotFoundException(workItem.getName());
         }
+        handler = ProcessWorkItemHandlerExceptionHandler.wrapIfNeeded(handler, 
workItem);
 
         KogitoProcessInstance processInstance = 
processInstanceManager.getProcessInstance(workItem.getProcessInstanceStringId());
         WorkItemTransition lastTransition = null;
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
index 20fa12d7e09..247669e5d78 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java
@@ -176,7 +176,7 @@ public class WorkItemNodeInstance extends 
StateBasedNodeInstance implements Even
             getProcessInstance().setState(STATE_ABORTED);
             throw wihnfe;
         } catch (ProcessWorkItemHandlerException handlerException) {
-            if (triggerCount++ < handlerException.getRetries() + 1) {
+            if (triggerCount++ <= handlerException.getRetries()) {
                 this.workItemId = workItem.getStringId();
                 handleWorkItemHandlerException(handlerException, workItem);
             } else {
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/handler/WorkItemHandlerExceptionHandlingTest.java
 
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/handler/WorkItemHandlerExceptionHandlingTest.java
index 90ce62bc5b5..d3b192aeca3 100755
--- 
a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/handler/WorkItemHandlerExceptionHandlingTest.java
+++ 
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/handler/WorkItemHandlerExceptionHandlingTest.java
@@ -20,9 +20,11 @@ package org.jbpm.bpmn2.handler;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 
 import org.jbpm.bpmn2.objects.TestWorkItemHandler;
 import org.jbpm.process.core.context.variable.VariableScope;
+import 
org.jbpm.process.workitem.builtin.ProcessWorkItemHandlerExceptionHandler;
 import org.jbpm.test.utils.ProcessTestHelper;
 import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
 import org.junit.jupiter.api.AfterAll;
@@ -35,6 +37,10 @@ import 
org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener;
 import org.kie.kogito.internal.process.event.KogitoProcessEventListener;
 import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
+import org.kie.kogito.internal.process.workitem.WorkItemTransition;
+import org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static 
org.jbpm.process.core.context.variable.VariableScope.VARIABLE_STRICT_ENABLED_PROPERTY;
@@ -232,4 +238,127 @@ public class WorkItemHandlerExceptionHandlingTest {
         ProcessTestHelper.completeWorkItem(exception, results);
 
     }
+
+    @Test
+    public void testDecoratorWithStrategyComplete() throws Exception {
+        DefaultKogitoWorkItemHandler plainHandler = new 
DefaultKogitoWorkItemHandler() {
+            @Override
+            public Optional<WorkItemTransition> 
activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler 
handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "ScriptTask");
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "COMPLETE");
+                if (workItem.getParameter("isCheckedCheckbox") != null) {
+                    return 
Optional.of(this.workItemLifeCycle.newTransition("complete", 
workItem.getPhaseStatus(), workItem.getParameters()));
+                }
+                throw new RuntimeException("Simulated failure in wrapped WIH");
+            }
+        };
+
+        ProcessWorkItemHandlerExceptionHandler decoratedHandler = new 
ProcessWorkItemHandlerExceptionHandler(plainHandler);
+
+        Application application = ProcessTestHelper.newApplication();
+        ProcessTestHelper.registerHandler(application, "Human Task", 
decoratedHandler);
+        org.kie.kogito.process.Process<UserTaskWithBooleanOutputModel> 
processDefinition = UserTaskWithBooleanOutputProcess.newProcess(application);
+        ScriptTaskProcess.newProcess(application);
+
+        UserTaskWithBooleanOutputModel model = processDefinition.createModel();
+        model.setIsChecked(false);
+        org.kie.kogito.process.ProcessInstance<UserTaskWithBooleanOutputModel> 
instance = processDefinition.createInstance(model);
+        instance.start();
+
+        
assertThat(instance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+        assertThat(instance.variables().getIsChecked()).isTrue();
+    }
+
+    @Test
+    public void testDecoratorWithStrategyRetry() throws Exception {
+        DefaultKogitoWorkItemHandler plainHandler = new 
DefaultKogitoWorkItemHandler() {
+            @Override
+            public Optional<WorkItemTransition> 
activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler 
handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "ScriptTask");
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETRY");
+                if (workItem.getParameter("isCheckedCheckbox") != null) {
+                    return 
Optional.of(this.workItemLifeCycle.newTransition("complete", 
workItem.getPhaseStatus(), workItem.getParameters()));
+                }
+                throw new RuntimeException("Simulated failure in wrapped WIH");
+            }
+        };
+
+        ProcessWorkItemHandlerExceptionHandler decoratedHandler = new 
ProcessWorkItemHandlerExceptionHandler(plainHandler);
+
+        Application application = ProcessTestHelper.newApplication();
+        ProcessTestHelper.registerHandler(application, "Human Task", 
decoratedHandler);
+        org.kie.kogito.process.Process<UserTaskWithBooleanOutputModel> 
processDefinition = UserTaskWithBooleanOutputProcess.newProcess(application);
+        ScriptTaskProcess.newProcess(application);
+
+        org.kie.kogito.process.ProcessInstance<UserTaskWithBooleanOutputModel> 
instance = processDefinition.createInstance(processDefinition.createModel());
+        instance.start();
+
+        
assertThat(instance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+        assertThat(instance.variables().getIsChecked()).isTrue();
+    }
+
+    @Test
+    public void testDecoratorWithStrategyAbort() throws Exception {
+        DefaultKogitoWorkItemHandler plainHandler = new 
DefaultKogitoWorkItemHandler() {
+            @Override
+            public Optional<WorkItemTransition> 
activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler 
handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "ScriptTask");
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "ABORT");
+                throw new RuntimeException("Simulated failure in wrapped WIH");
+            }
+        };
+
+        ProcessWorkItemHandlerExceptionHandler decoratedHandler = new 
ProcessWorkItemHandlerExceptionHandler(plainHandler);
+
+        Application application = ProcessTestHelper.newApplication();
+        ProcessTestHelper.registerHandler(application, "Human Task", 
decoratedHandler);
+        org.kie.kogito.process.Process<UserTaskWithBooleanOutputModel> 
processDefinition = UserTaskWithBooleanOutputProcess.newProcess(application);
+        ScriptTaskProcess.newProcess(application);
+
+        UserTaskWithBooleanOutputModel model = processDefinition.createModel();
+        model.setIsChecked(false);
+        org.kie.kogito.process.ProcessInstance<UserTaskWithBooleanOutputModel> 
instance = processDefinition.createInstance(model);
+        instance.start();
+
+        
assertThat(instance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+        assertThat(instance.variables().getIsChecked()).isFalse();
+    }
+
+    @Test
+    public void testDecoratorWithStrategyRethrow() throws Exception {
+        DefaultKogitoWorkItemHandler plainHandler = new 
DefaultKogitoWorkItemHandler() {
+            @Override
+            public Optional<WorkItemTransition> 
activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler 
handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "ScriptTask");
+                
workItem.getParameters().put(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETHROW");
+                throw new RuntimeException("Simulated failure in wrapped WIH");
+            }
+        };
+
+        ProcessWorkItemHandlerExceptionHandler decoratedHandler = new 
ProcessWorkItemHandlerExceptionHandler(plainHandler);
+
+        Application application = ProcessTestHelper.newApplication();
+        ProcessTestHelper.registerHandler(application, "Human Task", 
decoratedHandler);
+        org.kie.kogito.process.Process<UserTaskWithBooleanOutputModel> 
processDefinition = UserTaskWithBooleanOutputProcess.newProcess(application);
+        ScriptTaskProcess.newProcess(application);
+
+        UserTaskWithBooleanOutputModel model = processDefinition.createModel();
+        model.setIsChecked(false);
+        org.kie.kogito.process.ProcessInstance<UserTaskWithBooleanOutputModel> 
instance = processDefinition.createInstance(model);
+        instance.start();
+
+        
assertThat(instance.status()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+        assertThat(instance.variables().getIsChecked()).isFalse();
+    }
+
+    @Test
+    public void testRegisteredHandlerIsNotWrappedInManager() throws Exception {
+        TestWorkItemHandler rawHandler = new TestWorkItemHandler();
+        Application application = ProcessTestHelper.newApplication();
+        ProcessTestHelper.registerHandler(application, "MyCustomTask", 
rawHandler);
+
+        org.kie.kogito.process.Process<UserTaskWithBooleanOutputModel> 
processDefinition = UserTaskWithBooleanOutputProcess.newProcess(application);
+        KogitoWorkItemHandler retrievedHandler = 
processDefinition.getKogitoWorkItemHandler("MyCustomTask");
+        assertThat(retrievedHandler).isSameAs(rawHandler);
+    }
 }
diff --git 
a/kogito-jbpm/process-workitems/src/main/java/org/jbpm/process/workitem/builtin/ProcessWorkItemHandlerExceptionHandler.java
 
b/kogito-jbpm/process-workitems/src/main/java/org/jbpm/process/workitem/builtin/ProcessWorkItemHandlerExceptionHandler.java
new file mode 100644
index 00000000000..a2e8a34cc3a
--- /dev/null
+++ 
b/kogito-jbpm/process-workitems/src/main/java/org/jbpm/process/workitem/builtin/ProcessWorkItemHandlerExceptionHandler.java
@@ -0,0 +1,117 @@
+/*
+ * 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.jbpm.process.workitem.builtin;
+
+import java.util.Arrays;
+
+import org.kie.api.runtime.process.ProcessWorkItemHandlerException;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
+import org.kie.kogito.internal.process.workitem.WorkItemTransition;
+
+public class ProcessWorkItemHandlerExceptionHandler extends 
AbstractExceptionHandlingTaskHandler {
+
+    public static final String ERROR_HANDLER_PROCESS_ID = 
"ErrorHandlerProcessId";
+    public static final String ERROR_HANDLING_STRATEGY = 
"ErrorHandlingStrategy";
+    public static final String ERROR_HANDLING_RETRIES = "ErrorHandlingRetries";
+
+    public static boolean canDecorate(KogitoWorkItemHandler handler, 
KogitoWorkItem workItem) {
+        if (handler == null || handler instanceof 
AbstractExceptionHandlingTaskHandler || workItem == null) {
+            return false;
+        }
+        Object processId = workItem.getParameter(ERROR_HANDLER_PROCESS_ID);
+        Object strategy = workItem.getParameter(ERROR_HANDLING_STRATEGY);
+        return processId != null && !processId.toString().trim().isEmpty()
+                && strategy != null && !strategy.toString().trim().isEmpty();
+    }
+
+    public static KogitoWorkItemHandler wrapIfNeeded(KogitoWorkItemHandler 
handler, KogitoWorkItem workItem) {
+        if (canDecorate(handler, workItem)) {
+            return new ProcessWorkItemHandlerExceptionHandler(handler);
+        }
+        return handler;
+    }
+
+    public ProcessWorkItemHandlerExceptionHandler(KogitoWorkItemHandler 
originalTaskHandler) {
+        super(originalTaskHandler);
+    }
+
+    public ProcessWorkItemHandlerExceptionHandler(Class<? extends 
KogitoWorkItemHandler> originalTaskHandlerClass) {
+        super(originalTaskHandlerClass);
+    }
+
+    @Override
+    public void handleException(KogitoWorkItemManager manager, 
KogitoWorkItemHandler originalTaskHandler, KogitoWorkItem workItem, 
WorkItemTransition transition, Throwable cause) {
+        if (cause instanceof ProcessWorkItemHandlerException) {
+            throw (ProcessWorkItemHandlerException) cause;
+        }
+
+        Object processIdObj = workItem.getParameter(ERROR_HANDLER_PROCESS_ID);
+        Object strategyObj = workItem.getParameter(ERROR_HANDLING_STRATEGY);
+        Object retriesObj = workItem.getParameter(ERROR_HANDLING_RETRIES);
+
+        String processId = processIdObj != null ? 
processIdObj.toString().trim() : null;
+        String strategyStr = strategyObj != null ? 
strategyObj.toString().trim() : null;
+
+        if (processId != null && !processId.isEmpty() && strategyStr != null 
&& !strategyStr.isEmpty()) {
+            ProcessWorkItemHandlerException.HandlingStrategy strategy;
+            if (strategyObj instanceof 
ProcessWorkItemHandlerException.HandlingStrategy) {
+                strategy = (ProcessWorkItemHandlerException.HandlingStrategy) 
strategyObj;
+            } else {
+                try {
+                    strategy = 
ProcessWorkItemHandlerException.HandlingStrategy.valueOf(strategyStr.toUpperCase());
+                } catch (IllegalArgumentException e) {
+                    throw new IllegalArgumentException("Invalid " + 
ERROR_HANDLING_STRATEGY + " value: '" + strategyObj + "'. Expected one of: "
+                            + 
Arrays.toString(ProcessWorkItemHandlerException.HandlingStrategy.values()) + 
".", e);
+                }
+            }
+
+            int retries = 0;
+            if (retriesObj instanceof Number) {
+                retries = ((Number) retriesObj).intValue();
+            } else if (retriesObj instanceof String) {
+                String retriesStr = ((String) retriesObj).trim();
+                if (!retriesStr.isEmpty()) {
+                    try {
+                        retries = Integer.parseInt(retriesStr);
+                    } catch (NumberFormatException nfe) {
+                        throw new IllegalArgumentException("Invalid " + 
ERROR_HANDLING_RETRIES + " value: '" + retriesObj + "'. Expected an integer.", 
nfe);
+                    }
+                }
+            } else if (retriesObj != null) {
+                throw new IllegalArgumentException("Invalid " + 
ERROR_HANDLING_RETRIES + " value: '" + retriesObj + "'. Expected an integer.");
+            }
+
+            throw new ProcessWorkItemHandlerException(
+                    processId,
+                    strategy,
+                    cause instanceof Exception ? (Exception) cause : new 
RuntimeException(cause),
+                    retries);
+        }
+
+        if (cause instanceof RuntimeException) {
+            throw (RuntimeException) cause;
+        }
+        if (cause instanceof Error) {
+            throw (Error) cause;
+        }
+        throw new RuntimeException(cause);
+    }
+}
diff --git 
a/kogito-jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/KogitoDefaultWorkItemManager.java
 
b/kogito-jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/KogitoDefaultWorkItemManager.java
index 55304c9a2bc..5d01a0e5c7a 100755
--- 
a/kogito-jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/KogitoDefaultWorkItemManager.java
+++ 
b/kogito-jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/KogitoDefaultWorkItemManager.java
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.drools.core.process.WorkItem;
+import 
org.jbpm.process.workitem.builtin.ProcessWorkItemHandlerExceptionHandler;
 import org.kie.internal.runtime.Closeable;
 import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
 import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
@@ -117,6 +118,7 @@ public class KogitoDefaultWorkItemManager implements 
InternalKogitoWorkItemManag
         if (handler == null) {
             throw new 
KogitoWorkItemHandlerNotFoundException(workItem.getName());
         }
+        handler = ProcessWorkItemHandlerExceptionHandler.wrapIfNeeded(handler, 
workItem);
         WorkItemTransition lastTransition = null;
         Optional<WorkItemTransition> nextTransition = Optional.of(transition);
         while (nextTransition.isPresent()) {
diff --git 
a/kogito-jbpm/process-workitems/src/test/java/org/jbpm/process/workitem/builtin/ProcessWorkItemHandlerExceptionHandlerTest.java
 
b/kogito-jbpm/process-workitems/src/test/java/org/jbpm/process/workitem/builtin/ProcessWorkItemHandlerExceptionHandlerTest.java
new file mode 100644
index 00000000000..0bb4bea0eff
--- /dev/null
+++ 
b/kogito-jbpm/process-workitems/src/test/java/org/jbpm/process/workitem/builtin/ProcessWorkItemHandlerExceptionHandlerTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.jbpm.process.workitem.builtin;
+
+import java.util.Collections;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+import org.kie.api.runtime.process.ProcessWorkItemHandlerException;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler;
+import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
+import org.kie.kogito.internal.process.workitem.WorkItemTransition;
+import org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler;
+import org.kie.kogito.process.workitems.impl.KogitoWorkItemImpl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class ProcessWorkItemHandlerExceptionHandlerTest {
+
+    private static class SuccessfulWorkItemHandler extends 
DefaultKogitoWorkItemHandler {
+        @Override
+        public Optional<WorkItemTransition> 
activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler 
handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+            return 
Optional.of(this.workItemLifeCycle.newTransition("complete", 
workItem.getPhaseStatus(), Collections.emptyMap()));
+        }
+    }
+
+    private static class FailingWorkItemHandler extends 
DefaultKogitoWorkItemHandler {
+        private final RuntimeException exception;
+
+        public FailingWorkItemHandler(RuntimeException exception) {
+            this.exception = exception;
+        }
+
+        @Override
+        public Optional<WorkItemTransition> 
activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler 
handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+            throw exception;
+        }
+    }
+
+    @Test
+    public void testSuccessfulExecution() {
+        SuccessfulWorkItemHandler originalHandler = new 
SuccessfulWorkItemHandler();
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        Optional<WorkItemTransition> result = handler.transitionToPhase(null, 
workItem, handler.startingTransition(Collections.emptyMap()));
+        assertThat(result).isPresent();
+        assertThat(result.get().id()).isEqualTo("complete");
+    }
+
+    @Test
+    public void testFailingExecutionWithoutErrorConfig() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isSameAs(originalException);
+    }
+
+    @Test
+    public void testFailingExecutionWithPartialConfig() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        // Only process id
+        KogitoWorkItemImpl workItemOnlyProcessId = new KogitoWorkItemImpl();
+        
workItemOnlyProcessId.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        assertThatThrownBy(() -> handler.transitionToPhase(null, 
workItemOnlyProcessId, handler.startingTransition(Collections.emptyMap())))
+                .isSameAs(originalException);
+
+        // Only strategy
+        KogitoWorkItemImpl workItemOnlyStrategy = new KogitoWorkItemImpl();
+        
workItemOnlyStrategy.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETRY");
+        assertThatThrownBy(() -> handler.transitionToPhase(null, 
workItemOnlyStrategy, handler.startingTransition(Collections.emptyMap())))
+                .isSameAs(originalException);
+    }
+
+    @Test
+    public void testFailingExecutionWithErrorConfig() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "retry");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_RETRIES,
 3);
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isInstanceOf(ProcessWorkItemHandlerException.class)
+                .satisfies(e -> {
+                    ProcessWorkItemHandlerException ex = 
(ProcessWorkItemHandlerException) e;
+                    assertThat(ex.getProcessId()).isEqualTo("error_handling");
+                    
assertThat(ex.getStrategy()).isEqualTo(ProcessWorkItemHandlerException.HandlingStrategy.RETRY);
+                    assertThat(ex.getRetries()).isEqualTo(3);
+                    assertThat(ex.getCause()).isSameAs(originalException);
+                });
+    }
+
+    @Test
+    public void testFailingExecutionWithStringRetries() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "COMPLETE");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_RETRIES,
 " 5 ");
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isInstanceOf(ProcessWorkItemHandlerException.class)
+                .satisfies(e -> {
+                    ProcessWorkItemHandlerException ex = 
(ProcessWorkItemHandlerException) e;
+                    assertThat(ex.getProcessId()).isEqualTo("error_handling");
+                    
assertThat(ex.getStrategy()).isEqualTo(ProcessWorkItemHandlerException.HandlingStrategy.COMPLETE);
+                    assertThat(ex.getRetries()).isEqualTo(5);
+                    assertThat(ex.getCause()).isSameAs(originalException);
+                });
+    }
+
+    @Test
+    public void testFailingExecutionWithInvalidStrategy() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "INVALID_STRATEGY");
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid ErrorHandlingStrategy value: 
'INVALID_STRATEGY'");
+    }
+
+    @Test
+    public void testFailingExecutionWithEnumStrategyAndObjectProcessId() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 new StringBuilder("error_proc_obj"));
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 ProcessWorkItemHandlerException.HandlingStrategy.ABORT);
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_RETRIES,
 2L);
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isInstanceOf(ProcessWorkItemHandlerException.class)
+                .satisfies(e -> {
+                    ProcessWorkItemHandlerException ex = 
(ProcessWorkItemHandlerException) e;
+                    assertThat(ex.getProcessId()).isEqualTo("error_proc_obj");
+                    
assertThat(ex.getStrategy()).isEqualTo(ProcessWorkItemHandlerException.HandlingStrategy.ABORT);
+                    assertThat(ex.getRetries()).isEqualTo(2);
+                    assertThat(ex.getCause()).isSameAs(originalException);
+                });
+    }
+
+    @Test
+    public void testFailingExecutionWithInvalidRetries() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETRY");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_RETRIES,
 "not-a-number");
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid ErrorHandlingRetries value: 
'not-a-number'");
+    }
+
+    @Test
+    public void testFailingExecutionWithInvalidRetriesType() {
+        RuntimeException originalException = new 
IllegalStateException("Service unavailable");
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETRY");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_RETRIES,
 Boolean.TRUE);
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid ErrorHandlingRetries value: 
'true'. Expected an integer.");
+    }
+
+    @Test
+    public void testDoNotDoubleWrapProcessWorkItemHandlerException() {
+        ProcessWorkItemHandlerException originalException = new 
ProcessWorkItemHandlerException("sub_process", 
ProcessWorkItemHandlerException.HandlingStrategy.ABORT, new RuntimeException());
+        FailingWorkItemHandler originalHandler = new 
FailingWorkItemHandler(originalException);
+        ProcessWorkItemHandlerExceptionHandler handler = new 
ProcessWorkItemHandlerExceptionHandler(originalHandler);
+
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_handling");
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETRY");
+
+        assertThatThrownBy(() -> handler.transitionToPhase(null, workItem, 
handler.startingTransition(Collections.emptyMap())))
+                .isSameAs(originalException);
+    }
+
+    @Test
+    public void testCanDecorateAndWrapIfNeeded() {
+        SuccessfulWorkItemHandler rawHandler = new SuccessfulWorkItemHandler();
+
+        // Null checks
+        assertThat(ProcessWorkItemHandlerExceptionHandler.canDecorate(null, 
null)).isFalse();
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.canDecorate(rawHandler, 
null)).isFalse();
+
+        // Work item without error params
+        KogitoWorkItemImpl workItem = new KogitoWorkItemImpl();
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.canDecorate(rawHandler, 
workItem)).isFalse();
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.wrapIfNeeded(rawHandler, 
workItem)).isSameAs(rawHandler);
+
+        // Work item with partial params
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLER_PROCESS_ID,
 "error_proc");
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.canDecorate(rawHandler, 
workItem)).isFalse();
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.wrapIfNeeded(rawHandler, 
workItem)).isSameAs(rawHandler);
+
+        // Work item with both params
+        
workItem.setParameter(ProcessWorkItemHandlerExceptionHandler.ERROR_HANDLING_STRATEGY,
 "RETRY");
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.canDecorate(rawHandler, 
workItem)).isTrue();
+        KogitoWorkItemHandler wrapped = 
ProcessWorkItemHandlerExceptionHandler.wrapIfNeeded(rawHandler, workItem);
+        
assertThat(wrapped).isInstanceOf(ProcessWorkItemHandlerExceptionHandler.class);
+
+        // Already decorated handler should not be double-wrapped
+        assertThat(ProcessWorkItemHandlerExceptionHandler.canDecorate(wrapped, 
workItem)).isFalse();
+        
assertThat(ProcessWorkItemHandlerExceptionHandler.wrapIfNeeded(wrapped, 
workItem)).isSameAs(wrapped);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to