/* Licensed 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.activiti.engine.test.bpmn.event.error; import java.util.HashMap; import java.util.List; import java.util.Map; import org.activiti.engine.ActivitiException; import org.activiti.engine.delegate.BpmnError; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.history.HistoryLevel; import org.activiti.engine.impl.test.PluggableActivitiTestCase; import org.activiti.engine.impl.util.CollectionUtil; import org.activiti.engine.task.Task; import org.activiti.engine.test.Deployment; /** * @author Joram Barrez * @author Falko Menge */ public class BoundaryErrorEventTest extends PluggableActivitiTestCase { @Deployment public void testCatchErrorOnEmbeddedSubprocess() { runtimeService.startProcessInstanceByKey("boundaryErrorOnEmbeddedSubprocess"); // After process start, usertask in subprocess should exist Task task = taskService.createTaskQuery().singleResult(); assertEquals("subprocessTask", task.getName()); // After task completion, error end event is reached and caught taskService.complete(task.getId()); task = taskService.createTaskQuery().singleResult(); assertEquals("task after catching the error", task.getName()); } public void testThrowErrorWithoutErrorCode() { try { repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testThrowErrorWithoutErrorCode.bpmn20.xml") .deploy(); fail("ActivitiException expected"); } catch (ActivitiException re) { assertTextPresent("'errorCode' is mandatory on errors referenced by throwing error event definitions", re.getMessage()); } } public void testThrowErrorWithEmptyErrorCode() { try { repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testThrowErrorWithEmptyErrorCode.bpmn20.xml") .deploy(); fail("ActivitiException expected"); } catch (ActivitiException re) { assertTextPresent("'errorCode' is mandatory on errors referenced by throwing error event definitions", re.getMessage()); } } @Deployment public void testCatchErrorOnEmbeddedSubprocessWithEmptyErrorCode() { testCatchErrorOnEmbeddedSubprocess(); } @Deployment public void testCatchErrorOnEmbeddedSubprocessWithoutErrorCode() { testCatchErrorOnEmbeddedSubprocess(); } @Deployment public void testCatchErrorOfInnerSubprocessOnOuterSubprocess() { runtimeService.startProcessInstanceByKey("boundaryErrorTest"); List<Task> tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); assertEquals(2, tasks.size()); assertEquals("Inner subprocess task 1", tasks.get(0).getName()); assertEquals("Inner subprocess task 2", tasks.get(1).getName()); // Completing task 2, will cause the end error event to throw error with code 123 taskService.complete(tasks.get(1).getId()); tasks = taskService.createTaskQuery().list(); Task taskAfterError = taskService.createTaskQuery().singleResult(); assertEquals("task outside subprocess", taskAfterError.getName()); } @Deployment public void testCatchErrorInConcurrentEmbeddedSubprocesses() { assertErrorCaughtInConcurrentEmbeddedSubprocesses("boundaryEventTestConcurrentSubprocesses"); } @Deployment public void testCatchErrorInConcurrentEmbeddedSubprocessesThrownByScriptTask() { assertErrorCaughtInConcurrentEmbeddedSubprocesses("catchErrorInConcurrentEmbeddedSubprocessesThrownByScriptTask"); } private void assertErrorCaughtInConcurrentEmbeddedSubprocesses(String processDefinitionKey) { // Completing task A will lead to task D String procId = runtimeService.startProcessInstanceByKey(processDefinitionKey).getId(); List<Task> tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); assertEquals(2, tasks.size()); assertEquals("task A", tasks.get(0).getName()); assertEquals("task B", tasks.get(1).getName()); taskService.complete(tasks.get(0).getId()); Task task = taskService.createTaskQuery().singleResult(); assertEquals("task D", task.getName()); taskService.complete(task.getId()); assertProcessEnded(procId); // Completing task B will lead to task C procId = runtimeService.startProcessInstanceByKey(processDefinitionKey).getId(); tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); assertEquals(2, tasks.size()); assertEquals("task A", tasks.get(0).getName()); assertEquals("task B", tasks.get(1).getName()); taskService.complete(tasks.get(1).getId()); tasks = taskService.createTaskQuery().orderByTaskName().asc().list(); assertEquals(2, tasks.size()); assertEquals("task A", tasks.get(0).getName()); assertEquals("task C", tasks.get(1).getName()); taskService.complete(tasks.get(1).getId()); task = taskService.createTaskQuery().singleResult(); assertEquals("task A", task.getName()); taskService.complete(task.getId()); task = taskService.createTaskQuery().singleResult(); assertEquals("task D", task.getName()); } @Deployment public void testDeeplyNestedErrorThrown() { // Input = 1 -> error1 will be thrown, which will destroy ALL BUT ONE // subprocess, which leads to an end event, which ultimately leads to ending the process instance String procId = runtimeService.startProcessInstanceByKey("deeplyNestedErrorThrown").getId(); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Nested task", task.getName()); taskService.complete(task.getId(), CollectionUtil.singletonMap("input", 1)); assertProcessEnded(procId); // Input == 2 -> error2 will be thrown, leading to a userTask outside all subprocesses procId = runtimeService.startProcessInstanceByKey("deeplyNestedErrorThrown").getId(); task = taskService.createTaskQuery().singleResult(); assertEquals("Nested task", task.getName()); taskService.complete(task.getId(), CollectionUtil.singletonMap("input", 2)); task = taskService.createTaskQuery().singleResult(); assertEquals("task after catch", task.getName()); taskService.complete(task.getId()); assertProcessEnded(procId); } @Deployment public void testDeeplyNestedErrorThrownOnlyAutomaticSteps() { // input == 1 -> error2 is thrown -> caught on subprocess2 -> end event in subprocess -> proc inst end 1 String procId = runtimeService.startProcessInstanceByKey("deeplyNestedErrorThrown", CollectionUtil.singletonMap("input", 1)).getId(); assertProcessEnded(procId); HistoricProcessInstance hip; if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) { hip = historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult(); assertEquals("processEnd1", hip.getEndActivityId()); } // input == 2 -> error2 is thrown -> caught on subprocess1 -> proc inst end 2 procId = runtimeService.startProcessInstanceByKey("deeplyNestedErrorThrown", CollectionUtil.singletonMap("input", 1)).getId(); assertProcessEnded(procId); if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) { hip = historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult(); assertEquals("processEnd1", hip.getEndActivityId()); } } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorOnCallActivity-parent.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess.bpmn20.xml" }) public void testCatchErrorOnCallActivity() { String procId = runtimeService.startProcessInstanceByKey("catchErrorOnCallActivity").getId(); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Task in subprocess", task.getName()); // Completing the task will reach the end error event, // which is caught on the call activity boundary taskService.complete(task.getId()); task = taskService.createTaskQuery().singleResult(); assertEquals("Escalated Task", task.getName()); // Completing the task will end the process instance taskService.complete(task.getId()); assertProcessEnded(procId); } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess.bpmn20.xml" }) public void testUncaughtError() { runtimeService.startProcessInstanceByKey("simpleSubProcess"); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Task in subprocess", task.getName()); try { // Completing the task will reach the end error event, // which is never caught in the process taskService.complete(task.getId()); } catch (BpmnError e) { assertTextPresent("No catching boundary event found for error with errorCode 'myError', neither in same process nor in parent process", e.getMessage()); } } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testUncaughtErrorOnCallActivity-parent.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess.bpmn20.xml" }) public void testUncaughtErrorOnCallActivity() { runtimeService.startProcessInstanceByKey("uncaughtErrorOnCallActivity"); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Task in subprocess", task.getName()); try { // Completing the task will reach the end error event, // which is never caught in the process taskService.complete(task.getId()); } catch (BpmnError e) { assertTextPresent("No catching boundary event found for error with errorCode 'myError', neither in same process nor in parent process", e.getMessage()); } } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByCallActivityOnSubprocess.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess.bpmn20.xml" }) public void testCatchErrorThrownByCallActivityOnSubprocess() { String procId = runtimeService.startProcessInstanceByKey("catchErrorOnSubprocess").getId(); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Task in subprocess", task.getName()); // Completing the task will reach the end error event, // which is caught on the call activity boundary taskService.complete(task.getId()); task = taskService.createTaskQuery().singleResult(); assertEquals("Escalated Task", task.getName()); // Completing the task will end the process instance taskService.complete(task.getId()); assertProcessEnded(procId); } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByCallActivityOnCallActivity.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess2ndLevel.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess.bpmn20.xml" }) public void testCatchErrorThrownByCallActivityOnCallActivity() throws InterruptedException { String procId = runtimeService.startProcessInstanceByKey("catchErrorOnCallActivity2ndLevel").getId(); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Task in subprocess", task.getName()); taskService.complete(task.getId()); task = taskService.createTaskQuery().singleResult(); assertEquals("Escalated Task", task.getName()); // Completing the task will end the process instance taskService.complete(task.getId()); assertProcessEnded(procId); } @Deployment public void testCatchErrorOnParallelMultiInstance() { String procId = runtimeService.startProcessInstanceByKey("catchErrorOnParallelMi").getId(); List<Task> tasks = taskService.createTaskQuery().list(); assertEquals(5, tasks.size()); // Complete two subprocesses, just to make it a bit more complex Map<String, Object> vars = new HashMap<String, Object>(); vars.put("throwError", false); taskService.complete(tasks.get(2).getId(), vars); taskService.complete(tasks.get(3).getId(), vars); // Reach the error event vars.put("throwError", true); taskService.complete(tasks.get(1).getId(), vars); assertEquals(0, taskService.createTaskQuery().count()); assertProcessEnded(procId); } @Deployment public void testCatchErrorOnSequentialMultiInstance() { String procId = runtimeService.startProcessInstanceByKey("catchErrorOnSequentialMi").getId(); // complete one task Map<String, Object> vars = new HashMap<String, Object>(); vars.put("throwError", false); Task task = taskService.createTaskQuery().singleResult(); taskService.complete(task.getId(), vars); // complete second task and throw error vars.put("throwError", true); task = taskService.createTaskQuery().singleResult(); taskService.complete(task.getId(), vars); assertProcessEnded(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateOnServiceTask() { String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnServiceTask").getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateOnServiceTaskNotCancelActivity() { String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnServiceTaskNotCancelActiviti").getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateOnServiceTaskWithErrorCode() { String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnServiceTaskWithErrorCode").getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateOnEmbeddedSubProcess() { String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnEmbeddedSubProcess").getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateOnEmbeddedSubProcessInduction() { String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnEmbeddedSubProcessInduction").getId(); assertThatErrorHasBeenCaught(procId); } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByJavaDelegateOnCallActivity-parent.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByJavaDelegateOnCallActivity-child.bpmn20.xml" }) public void testCatchErrorThrownByJavaDelegateOnCallActivity() { String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnCallActivity-parent").getId(); assertThatErrorHasBeenCaught(procId); } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByJavaDelegateOnCallActivity-child.bpmn20.xml" }) public void testUncaughtErrorThrownByJavaDelegateOnServiceTask() { try { runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnCallActivity-child"); } catch (BpmnError e) { assertTextPresent("No catching boundary event found for error with errorCode '23', neither in same process nor in parent process", e.getMessage()); } } @Deployment(resources = { "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testUncaughtErrorThrownByJavaDelegateOnCallActivity-parent.bpmn20.xml", "org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByJavaDelegateOnCallActivity-child.bpmn20.xml" }) public void testUncaughtErrorThrownByJavaDelegateOnCallActivity() { try { runtimeService.startProcessInstanceByKey("uncaughtErrorThrownByJavaDelegateOnCallActivity-parent"); } catch (BpmnError e) { assertTextPresent("No catching boundary event found for error with errorCode '23', neither in same process nor in parent process", e.getMessage()); } } @Deployment public void testCatchErrorThrownByJavaDelegateOnMultiInstanceServiceTaskSequential() { Map<String, Object> variables = new HashMap<String, Object>(); variables.put("executionsBeforeError", 2); String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnMultiInstanceServiceTaskSequential", variables).getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateOnMultiInstanceServiceTaskParallel() { Map<String, Object> variables = new HashMap<String, Object>(); variables.put("executionsBeforeError", 2); String procId = runtimeService.startProcessInstanceByKey("catchErrorThrownByJavaDelegateOnMultiInstanceServiceTaskParallel", variables).getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testErrorThrownByJavaDelegateNotCaughtByOtherEventType() { String procId = runtimeService.startProcessInstanceByKey("testErrorThrownByJavaDelegateNotCaughtByOtherEventType").getId(); assertThatErrorHasBeenCaught(procId); } private void assertThatErrorHasBeenCaught(String procId) { // The service task will throw an error event, // which is caught on the service task boundary assertEquals("No tasks found in task list.", 1, taskService.createTaskQuery().count()); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Escalated Task", task.getName()); // Completing the task will end the process instance taskService.complete(task.getId()); assertProcessEnded(procId); } @Deployment public void testConcurrentExecutionsInterruptedOnDestroyScope() { // this test makes sure that if the first concurrent execution destroys the scope // (due to the interrupting boundary catch), the second concurrent execution does not // move forward. // if the test fails, it produces a constraint violation in db. runtimeService.startProcessInstanceByKey("process"); } @Deployment public void testCatchErrorThrownByExpressionOnServiceTask() { HashMap<String, Object> variables = new HashMap<String, Object>(); variables.put("bpmnErrorBean", new BpmnErrorBean()); String procId = runtimeService.startProcessInstanceByKey("testCatchErrorThrownByExpressionOnServiceTask", variables).getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByDelegateExpressionOnServiceTask() { HashMap<String, Object> variables = new HashMap<String, Object>(); variables.put("bpmnErrorBean", new BpmnErrorBean()); String procId = runtimeService.startProcessInstanceByKey("testCatchErrorThrownByDelegateExpressionOnServiceTask", variables).getId(); assertThatErrorHasBeenCaught(procId); } @Deployment public void testCatchErrorThrownByJavaDelegateProvidedByDelegateExpressionOnServiceTask() { HashMap<String, Object> variables = new HashMap<String, Object>(); variables.put("bpmnErrorBean", new BpmnErrorBean()); String procId = runtimeService.startProcessInstanceByKey("testCatchErrorThrownByJavaDelegateProvidedByDelegateExpressionOnServiceTask", variables).getId(); assertThatErrorHasBeenCaught(procId); } }