提交 b8410776 编写于 作者: F falko.menge

ACT-1068 Implemented Error Event Sub-Processes for Errors thrown from Java Delegates

上级 d9bfadb7
/* 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.impl.bpmn.behavior;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
/**
* Specialization of the Start Event for Event Sub-Processes.
*
* @author Falko Menge
*/
public class EventSubProcessStartEventActivityBehavior extends NoneStartEventActivityBehavior {
// TODO: non-interrupting [no destroyScope, setConcurrent(true)]
// public void execute(ActivityExecution execution) throws Exception {
// if(!interrupting) {
// ActivityExecution executionForEventSubProcess = execution.createExecution();
// executionForEventSubProcess.setScope(true);
// executionForEventSubProcess.setConcurrent(true);
// executionForEventSubProcess.setActive(true);
// leave(executionForEventSubProcess);
// }
// }
}
......@@ -13,11 +13,14 @@
package org.activiti.engine.impl.bpmn.helper;
import java.util.List;
import java.util.logging.Logger;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.BpmnError;
import org.activiti.engine.impl.bpmn.behavior.BoundaryEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.EventSubProcessStartEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.SubProcessActivityBehavior;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmScope;
......@@ -40,30 +43,81 @@ public class ErrorPropagation {
protected static final Logger LOG = Logger.getLogger(ErrorPropagation.class.getName());
public static void propagateError(BpmnError error, ActivityExecution execution) throws Exception {
String errorCode = error.getErrorCode();
// find local error handler
PvmActivity errorEventHandler = findLocalErrorEventHandler(execution, errorCode);
// execute error handler
String eventHandlerId = null;
if (errorEventHandler != null) {
eventHandlerId = errorEventHandler.getId();
}
propagateError(errorCode, eventHandlerId, execution);
}
private static PvmActivity findLocalErrorEventHandler(ActivityExecution execution, String errorCode) {
PvmActivity errorEventHandler = null;
PvmScope scope = execution.getActivity();
while (errorEventHandler == null && scope != null) {
// search for error handler with same error code as thrown Error
// search for Error Event Sub-Process with same error code as thrown Error
for (PvmActivity activity : scope.getActivities()) {
if (((ActivityImpl) activity).getActivityBehavior() instanceof BoundaryEventActivityBehavior
&& "boundaryError".equals(activity.getProperty("type"))
&& error.getErrorCode().equals(activity.getProperty("errorCode"))) {
errorEventHandler = activity;
break;
if (((ActivityImpl) activity).getActivityBehavior() instanceof SubProcessActivityBehavior
&& Boolean.TRUE.equals(activity.getProperty("triggeredByEvent"))) {
List<? extends PvmActivity> activities = activity.getActivities();
for (PvmActivity eventSubProcessActivity : activities) {
if (((ActivityImpl) eventSubProcessActivity).getActivityBehavior() instanceof EventSubProcessStartEventActivityBehavior
&& "errorStartEvent".equals(eventSubProcessActivity.getProperty("type"))
&& errorCode.equals(eventSubProcessActivity.getProperty("errorCode"))) {
errorEventHandler = activity;
break;
}
}
if (errorEventHandler != null) {
break;
}
}
}
// search for generic error handler if no error handler with that error code has been found
// search for generic Error Event Sub-Process if no error handler with that error code has been found
if (errorEventHandler == null) {
for (PvmActivity activity : scope.getActivities()) {
if (((ActivityImpl) activity).getActivityBehavior() instanceof SubProcessActivityBehavior
&& Boolean.TRUE.equals(activity.getProperty("triggeredByEvent"))) {
List<? extends PvmActivity> activities = activity.getActivities();
for (PvmActivity eventSubProcessActivity : activities) {
if (((ActivityImpl) eventSubProcessActivity).getActivityBehavior() instanceof EventSubProcessStartEventActivityBehavior
&& "errorStartEvent".equals(eventSubProcessActivity.getProperty("type"))
&& eventSubProcessActivity.getProperty("errorCode") == null) {
errorEventHandler = activity;
break;
}
}
if (errorEventHandler != null) {
break;
}
}
}
}
// search for Boundary Error Event with same error code as thrown Error
if (errorEventHandler == null) {
for (PvmActivity activity : scope.getActivities()) {
if (((ActivityImpl) activity).getActivityBehavior() instanceof BoundaryEventActivityBehavior
&& "boundaryError".equals(activity.getProperty("type"))
&& (activity.getProperty("errorCode") == null || "".equals(activity.getProperty("errorCode")))) {
&& errorCode.equals(activity.getProperty("errorCode"))) {
errorEventHandler = activity;
break;
}
}
}
// search for generic Boundary Error Event if no error handler with that error code has been found
if (errorEventHandler == null) {
for (PvmActivity activity : scope.getActivities()) {
if (((ActivityImpl) activity).getActivityBehavior() instanceof BoundaryEventActivityBehavior
&& "boundaryError".equals(activity.getProperty("type"))
&& activity.getProperty("errorCode") == null) {
errorEventHandler = activity;
break;
}
}
}
// search for error handlers in parent scopes
if (errorEventHandler == null) {
......@@ -74,13 +128,7 @@ public class ErrorPropagation {
}
}
}
// execute error handler
String eventHandlerId = null;
if (errorEventHandler != null) {
eventHandlerId = errorEventHandler.getId();
}
propagateError(error.getErrorCode(), eventHandlerId, execution);
return errorEventHandler;
}
public static void propagateError(String errorCode, String eventHandlerId, ActivityExecution execution) {
......@@ -108,31 +156,10 @@ public class ErrorPropagation {
}
protected static void executeCatchInSuperProcess(String errorCode, ActivityExecution superExecution) {
ActivityExecution outgoingExecution = superExecution;
ActivityImpl catchingActivity = (ActivityImpl) outgoingExecution.getActivity();
boolean found = false;
while (!found && outgoingExecution != null && catchingActivity != null) {
for (ActivityImpl nestedActivity : catchingActivity.getActivities()) {
if ("boundaryError".equals(nestedActivity.getProperty("type"))
&& (nestedActivity.getProperty("errorCode") == null
|| errorCode.equals(nestedActivity.getProperty("errorCode")))) {
found = true;
catchingActivity = nestedActivity;
}
}
if (!found) {
if (outgoingExecution.isConcurrent()) {
outgoingExecution = outgoingExecution.getParent();
} else if (outgoingExecution.isScope()) {
catchingActivity = catchingActivity.getParentActivity();
outgoingExecution = outgoingExecution.getParent();
}
}
}
if (found) {
outgoingExecution.executeActivity(catchingActivity);
PvmActivity catchingActivity = findLocalErrorEventHandler(superExecution, errorCode);
if (catchingActivity != null) {
ActivityExecution outgoingExecution = ScopeUtil.findScopeExecution((ExecutionEntity) superExecution, catchingActivity.getParent());
executeEventHandler((ActivityImpl) catchingActivity, outgoingExecution);
} else { // no matching catch found, going one level up in process hierarchy
ActivityExecution superSuperExecution = getSuperExecution(superExecution);
if (superSuperExecution != null) {
......@@ -158,8 +185,16 @@ public class ErrorPropagation {
ActivityImpl catchingScope = borderEventActivity.getParentActivity();
if (catchingScope == null) {
throw new ActivitiException(borderEventActivityId + " is suppossed to be a nested activity, "
+ "but no parent activity can be found.");
// only allowed for Event Sub-Processes, but not for Boundary Events, because a top-level process cannot have Boundary Events
if (borderEventActivity.getActivityBehavior() instanceof SubProcessActivityBehavior) {
ExecutionEntity scopeExecution = ScopeUtil.findScopeExecution((ExecutionEntity) execution, borderEventActivity.getParent());
ActivityExecution executionForEventSubProcess = interruptExecutionAndCreateEventSubProcessExecution(scopeExecution);
executionForEventSubProcess.executeActivity(borderEventActivity);
return;
} else {
throw new ActivitiException(borderEventActivityId + " is suppossed to be a nested activity, "
+ "but no parent activity can be found.");
}
}
boolean matchingParentFound = false;
......@@ -193,10 +228,26 @@ public class ErrorPropagation {
}
if (matchingParentFound && leavingExecution != null) {
leavingExecution.executeActivity(borderEventActivity);
executeEventHandler(borderEventActivity, leavingExecution);
} else {
throw new ActivitiException("No matching parent execution for activity " + borderEventActivityId + " found");
}
}
private static void executeEventHandler(ActivityImpl borderEventActivity, ActivityExecution leavingExecution) {
if (borderEventActivity.getActivityBehavior() instanceof SubProcessActivityBehavior) {
leavingExecution = interruptExecutionAndCreateEventSubProcessExecution((ExecutionEntity) leavingExecution);
}
leavingExecution.executeActivity(borderEventActivity);
}
private static ActivityExecution interruptExecutionAndCreateEventSubProcessExecution(ExecutionEntity scopeExecution) {
ScopeUtil.destroyScope(scopeExecution, "Event caught by interrupting Event Sub-Process");
ActivityExecution executionForEventSubProcess = scopeExecution.createExecution();
executionForEventSubProcess.setScope(true);
executionForEventSubProcess.setConcurrent(false);
executionForEventSubProcess.setActive(true);
return executionForEventSubProcess;
}
}
......@@ -34,6 +34,7 @@ import org.activiti.engine.impl.bpmn.behavior.CallActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.CancelBoundaryEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.CancelEndEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ErrorEndEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.EventSubProcessStartEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.EventBasedGatewayActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.InclusiveGatewayActivityBehavior;
......@@ -719,12 +720,26 @@ public class BpmnParse extends Parse {
}
} else {
scope.setProperty(PROPERTYNAME_INITIAL, startEventActivity);
// BPMN 2.0 Spec: "The Error Start Event is only allowed for triggering an in-line Event Sub-Process."
Element errorEventDefinition = startEventElement.element("errorEventDefinition");
if (errorEventDefinition != null) {
startEventActivity.setProperty("type", "errorStartEvent");
String errorRef = errorEventDefinition.attribute("errorRef");
Error error = null;
if (errorRef != null) {
error = errors.get(errorRef);
startEventActivity.setProperty("errorCode", error == null ? errorRef : error.getErrorCode());
}
startEventActivity.setActivityBehavior(new EventSubProcessStartEventActivityBehavior());
}
}
// Currently only none start events supported
// Currently only none and error start events supported
// TODO: a subprocess is only allowed to have a none start event
startEventActivity.setActivityBehavior(new NoneStartEventActivityBehavior());
// TODO: a subprocess is only allowed to have a none start event unless it is an Event Sub-Process
if (startEventActivity.getActivityBehavior() == null) {
startEventActivity.setActivityBehavior(new NoneStartEventActivityBehavior());
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseStartEvent(startEventElement, scope, startEventActivity);
......@@ -2223,6 +2238,8 @@ public class BpmnParse extends Parse {
activity.setAsync(isAsync(subProcessElement));
activity.setExclusive(isExclusive(subProcessElement));
activity.setProperty("triggeredByEvent", parseBooleanAttribute(subProcessElement.attribute("triggeredByEvent"), false));
activity.setScope(true);
activity.setActivityBehavior(new SubProcessActivityBehavior());
......@@ -2251,7 +2268,7 @@ public class BpmnParse extends Parse {
}
/**
* Parses a call activity (currenly only supporting calling subprocesses).
* Parses a call activity (currently only supporting calling subprocesses).
*
* @param callActivityElement
* The XML element defining the call activity
......
/* 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 org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
/**
* @author Falko Menge
*/
public class ErrorEventSubProcessTest extends PluggableActivitiTestCase {
@Deployment
public void FAILING_testCatchErrorInEmbeddedSubProcess() {
String procId = runtimeService.startProcessInstanceByKey("CatchErrorInEmbeddedSubProcess").getId();
assertThatErrorHasBeenCaught(procId);
}
@Deployment
public void testCatchErrorThrownByScriptTaskInEmbeddedSubProcess() {
String procId = runtimeService.startProcessInstanceByKey("CatchErrorThrownByScriptTaskInEmbeddedSubProcess").getId();
assertThatErrorHasBeenCaught(procId);
}
@Deployment
public void testCatchErrorThrownByScriptTaskInEmbeddedSubProcessWithErrorCode() {
String procId = runtimeService.startProcessInstanceByKey("CatchErrorThrownByScriptTaskInEmbeddedSubProcessWithErrorCode").getId();
assertThatErrorHasBeenCaught(procId);
}
@Deployment
public void testCatchErrorThrownByScriptTaskInTopLevelProcess() {
String procId = runtimeService.startProcessInstanceByKey("CatchErrorThrownByScriptTaskInTopLevelProcess").getId();
assertThatErrorHasBeenCaught(procId);
}
@Deployment
public void testCatchErrorThrownByScriptTaskInsideSubProcessInTopLevelProcess() {
String procId = runtimeService.startProcessInstanceByKey("CatchErrorThrownByScriptTaskInsideSubProcessInTopLevelProcess").getId();
assertThatErrorHasBeenCaught(procId);
}
@Deployment(resources = {
"org/activiti/engine/test/bpmn/event/error/ErrorEventSubProcessTest.testThrowErrorInScriptTaskInsideCallActivitiCatchInTopLevelProcess.bpmn20.xml",
"org/activiti/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorThrownByJavaDelegateOnCallActivity-child.bpmn20.xml" })
public void testThrowErrorInScriptTaskInsideCallActivitiCatchInTopLevelProcess() {
String procId = runtimeService.startProcessInstanceByKey("testThrowErrorInScriptTaskInsideCallActivitiCatchInTopLevelProcess").getId();
assertThatErrorHasBeenCaught(procId);
}
private void assertThatErrorHasBeenCaught(String procId) {
// The process will throw an error event,
// which is caught and escalated by a User Task
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);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<error id="myError" errorCode="123" />
<process id="CatchErrorInEmbeddedSubProcess">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="subprocess" />
<subProcess id="subprocess">
<startEvent id="subprocessStart" />
<sequenceFlow id="subFlow1" sourceRef="subprocessStart" targetRef="subprocessEnd" />
<endEvent id="subprocessEnd">
<errorEventDefinition errorRef="myError" />
</endEvent>
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition /> <!-- no errorCode: catch any error -->
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Escalated Task" />
</subProcess>
</subProcess>
<sequenceFlow id="flow3" sourceRef="subprocess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="CatchErrorThrownByScriptTaskInEmbeddedSubProcess">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="subprocess" />
<subProcess id="subprocess">
<startEvent id="subprocessStart" />
<sequenceFlow id="subFlow1" sourceRef="subprocessStart" targetRef="subprocessScriptTask" />
<scriptTask id="subprocessScriptTask" scriptFormat="groovy">
<script>
throw new org.activiti.engine.delegate.BpmnError("123");
</script>
</scriptTask>
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition /> <!-- no errorCode: catch any error -->
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Escalated Task" />
</subProcess>
</subProcess>
<sequenceFlow id="flow3" sourceRef="subprocess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<error id="myError" errorCode="123" />
<process id="CatchErrorThrownByScriptTaskInEmbeddedSubProcessWithErrorCode">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="subprocess" />
<subProcess id="subprocess">
<startEvent id="subprocessStart" />
<sequenceFlow id="subFlow1" sourceRef="subprocessStart" targetRef="subprocessScriptTask" />
<scriptTask id="subprocessScriptTask" scriptFormat="groovy">
<script>
throw new org.activiti.engine.delegate.BpmnError("123");
</script>
</scriptTask>
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition errorRef="myError" />
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Escalated Task" />
</subProcess>
<sequenceFlow id="flow5" sourceRef="eventSubProcess" targetRef="workaroundForACT-1072" />
<endEvent id="workaroundForACT-1072" />
</subProcess>
<sequenceFlow id="flow3" sourceRef="subprocess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="CatchErrorThrownByScriptTaskInTopLevelProcess">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="scriptTask" />
<scriptTask id="scriptTask" scriptFormat="groovy">
<script>
throw new org.activiti.engine.delegate.BpmnError("123");
</script>
</scriptTask>
<sequenceFlow id="flow3" sourceRef="scriptTask" targetRef="theEnd" />
<endEvent id="theEnd" />
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition /> <!-- no errorCode: catch any error -->
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Escalated Task" />
</subProcess>
<sequenceFlow id="flow5" sourceRef="eventSubProcess" targetRef="workaroundForACT-1072" />
<endEvent id="workaroundForACT-1072" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="CatchErrorThrownByScriptTaskInsideSubProcessInTopLevelProcess">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="subprocess" />
<subProcess id="subprocess">
<startEvent id="subprocessStart" />
<sequenceFlow id="subFlow1" sourceRef="subprocessStart" targetRef="subprocessScriptTask" />
<scriptTask id="subprocessScriptTask" scriptFormat="groovy">
<script>
throw new org.activiti.engine.delegate.BpmnError("123");
</script>
</scriptTask>
</subProcess>
<sequenceFlow id="flow3" sourceRef="subprocess" targetRef="theEnd" />
<endEvent id="theEnd" />
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition /> <!-- no errorCode: catch any error -->
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Escalated Task" />
</subProcess>
<sequenceFlow id="flow5" sourceRef="eventSubProcess" targetRef="workaroundForACT-1072" />
<endEvent id="workaroundForACT-1072" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="testThrowErrorInScriptTaskInsideCallActivitiCatchInTopLevelProcess">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="subprocess" />
<subProcess id="subprocess">
<startEvent id="subprocessStart" />
<sequenceFlow id="subFlow1" sourceRef="subprocessStart" targetRef="callSubProcess" />
<callActivity id="callSubProcess" calledElement="catchErrorThrownByJavaDelegateOnCallActivity-child" />
</subProcess>
<sequenceFlow id="flow3" sourceRef="subprocess" targetRef="theEnd" />
<endEvent id="theEnd" />
<subProcess id="eventSubProcess" triggeredByEvent="true">
<startEvent id="catchError">
<errorEventDefinition /> <!-- no errorCode: catch any error -->
</startEvent>
<sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
<userTask id="taskAfterErrorCatch" name="Escalated Task" />
</subProcess>
<sequenceFlow id="flow5" sourceRef="eventSubProcess" targetRef="workaroundForACT-1072" />
<endEvent id="workaroundForACT-1072" />
</process>
</definitions>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册