diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/BoundaryEventXMLConverter.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/BoundaryEventXMLConverter.java index 5667d75e4e3..544bdb3d60c 100644 --- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/BoundaryEventXMLConverter.java +++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/BoundaryEventXMLConverter.java @@ -24,6 +24,7 @@ import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.converter.child.BaseChildElementParser; import org.flowable.bpmn.converter.child.InParameterParser; +import org.flowable.bpmn.converter.child.OutParameterParser; import org.flowable.bpmn.converter.child.VariableListenerEventDefinitionParser; import org.flowable.bpmn.converter.util.BpmnXMLUtil; import org.flowable.bpmn.model.BaseElement; @@ -123,6 +124,7 @@ protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, X @Override protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception { BoundaryEvent boundaryEvent = (BoundaryEvent) element; + didWriteExtensionStartElement = BpmnXMLUtil.writeIOParameters(ELEMENT_IN_PARAMETERS, boundaryEvent.getInParameters(), didWriteExtensionStartElement, xtw); didWriteExtensionStartElement = writeVariableListenerDefinition(boundaryEvent, didWriteExtensionStartElement, xtw); return didWriteExtensionStartElement; } diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/EndEventXMLConverter.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/EndEventXMLConverter.java index 45e03715eb1..c492687f165 100644 --- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/EndEventXMLConverter.java +++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/EndEventXMLConverter.java @@ -12,9 +12,14 @@ */ package org.flowable.bpmn.converter; +import java.util.HashMap; +import java.util.Map; + import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; +import org.flowable.bpmn.converter.child.BaseChildElementParser; +import org.flowable.bpmn.converter.child.OutParameterParser; import org.flowable.bpmn.converter.util.BpmnXMLUtil; import org.flowable.bpmn.model.BaseElement; import org.flowable.bpmn.model.BpmnModel; @@ -25,6 +30,13 @@ */ public class EndEventXMLConverter extends BaseBpmnXMLConverter { + protected Map childParserMap = new HashMap<>(); + + public EndEventXMLConverter() { + OutParameterParser outParameterParser = new OutParameterParser(); + childParserMap.put(outParameterParser.getElementName(), outParameterParser); + } + @Override public Class getBpmnElementType() { return EndEvent.class; @@ -42,8 +54,8 @@ protected BaseElement convertXMLToElement(XMLStreamReader xtr, BpmnModel model) BpmnXMLUtil.addXMLLocation(endEvent, xtr); BpmnXMLUtil.addCustomAttributes(xtr, endEvent, defaultElementAttributes, defaultActivityAttributes); - - parseChildElements(getXMLElementName(), endEvent, model, xtr); + + parseChildElements(getXMLElementName(), endEvent, childParserMap, model, xtr); return endEvent; } @@ -51,6 +63,13 @@ protected BaseElement convertXMLToElement(XMLStreamReader xtr, BpmnModel model) protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, XMLStreamWriter xtw) throws Exception { } + @Override + protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception { + EndEvent endEvent = (EndEvent) element; + didWriteExtensionStartElement = BpmnXMLUtil.writeIOParameters(ELEMENT_OUT_PARAMETERS, endEvent.getOutParameters(), didWriteExtensionStartElement, xtw); + return didWriteExtensionStartElement; + } + @Override protected void writeAdditionalChildElements(BaseElement element, BpmnModel model, XMLStreamWriter xtw) throws Exception { EndEvent endEvent = (EndEvent) element; diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/StartEventXMLConverter.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/StartEventXMLConverter.java index 871d5b97b7e..5170bcc5e38 100644 --- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/StartEventXMLConverter.java +++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/StartEventXMLConverter.java @@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.converter.child.BaseChildElementParser; +import org.flowable.bpmn.converter.child.InParameterParser; import org.flowable.bpmn.converter.child.VariableListenerEventDefinitionParser; import org.flowable.bpmn.converter.util.BpmnXMLUtil; import org.flowable.bpmn.model.BaseElement; @@ -46,6 +47,8 @@ public class StartEventXMLConverter extends BaseBpmnXMLConverter { new ExtensionAttribute(ATTRIBUTE_SAME_DEPLOYMENT)); public StartEventXMLConverter() { + InParameterParser inParameterParser = new InParameterParser(); + childParserMap.put(inParameterParser.getElementName(), inParameterParser); VariableListenerEventDefinitionParser variableListenerEventDefinitionParser = new VariableListenerEventDefinitionParser(); childParserMap.put(variableListenerEventDefinitionParser.getElementName(), variableListenerEventDefinitionParser); } @@ -123,6 +126,7 @@ protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, X @Override protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception { StartEvent startEvent = (StartEvent) element; + didWriteExtensionStartElement = BpmnXMLUtil.writeIOParameters(ELEMENT_IN_PARAMETERS, startEvent.getInParameters(), didWriteExtensionStartElement, xtw); didWriteExtensionStartElement = writeVariableListenerDefinition(startEvent, didWriteExtensionStartElement, xtw); didWriteExtensionStartElement = writeFormProperties(startEvent, didWriteExtensionStartElement, xtw); return didWriteExtensionStartElement; diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/InParameterParser.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/InParameterParser.java index d8ed1ee5c3b..236eaf050f2 100644 --- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/InParameterParser.java +++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/InParameterParser.java @@ -22,9 +22,8 @@ import org.flowable.bpmn.model.BaseElement; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.CallActivity; -import org.flowable.bpmn.model.CaseServiceTask; -import org.flowable.bpmn.model.Event; import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.HasInParameters; import org.flowable.bpmn.model.IOParameter; public class InParameterParser extends BaseChildElementParser { @@ -67,14 +66,8 @@ public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, Bp parameter.setTransient(true); } - if (parentElement instanceof CallActivity) { - ((CallActivity) parentElement).getInParameters().add(parameter); - - } else if (parentElement instanceof CaseServiceTask) { - ((CaseServiceTask) parentElement).getInParameters().add(parameter); - - } else if (parentElement instanceof Event) { - ((Event) parentElement).getInParameters().add(parameter); + if (parentElement instanceof HasInParameters) { + ((HasInParameters) parentElement).addInParameter(parameter); } BpmnXMLUtil.addCustomAttributes(xtr, parameter, defaultInParameterAttributes); diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/OutParameterParser.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/OutParameterParser.java index ebe7e2bfee1..174bfd58fa4 100644 --- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/OutParameterParser.java +++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/child/OutParameterParser.java @@ -22,9 +22,8 @@ import org.flowable.bpmn.model.BaseElement; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.CallActivity; -import org.flowable.bpmn.model.CaseServiceTask; -import org.flowable.bpmn.model.Event; import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.HasOutParameters; import org.flowable.bpmn.model.IOParameter; public class OutParameterParser extends BaseChildElementParser { @@ -64,15 +63,8 @@ public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, Bp BpmnXMLUtil.addCustomAttributes(xtr, parameter, defaultOutParameterAttributes); - if (parentElement instanceof CallActivity) { - CallActivity callActivity = (CallActivity) parentElement; - callActivity.getOutParameters().add(parameter); - - } else if (parentElement instanceof CaseServiceTask) { - ((CaseServiceTask) parentElement).getOutParameters().add(parameter); - - } else if (parentElement instanceof Event) { - ((Event) parentElement).getOutParameters().add(parameter); + if (parentElement instanceof HasOutParameters) { + ((HasOutParameters) parentElement).addOutParameter(parameter); } } else if (parentElement instanceof CallActivity) { diff --git a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/util/BpmnXMLUtil.java b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/util/BpmnXMLUtil.java index e314a9bb1d0..81b4ba19be6 100644 --- a/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/util/BpmnXMLUtil.java +++ b/modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/util/BpmnXMLUtil.java @@ -411,7 +411,7 @@ protected static void writeExtensionElement(ExtensionElement extensionElement, M public static boolean writeIOParameters(String elementName, List parameterList, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception { - if (parameterList.isEmpty()) { + if (parameterList == null || parameterList.isEmpty()) { return didWriteExtensionStartElement; } diff --git a/modules/flowable-bpmn-converter/src/main/resources/org/flowable/impl/bpmn/parser/flowable-bpmn-extensions.xsd b/modules/flowable-bpmn-converter/src/main/resources/org/flowable/impl/bpmn/parser/flowable-bpmn-extensions.xsd index f98dae5288b..fe256f96826 100644 --- a/modules/flowable-bpmn-converter/src/main/resources/org/flowable/impl/bpmn/parser/flowable-bpmn-extensions.xsd +++ b/modules/flowable-bpmn-converter/src/main/resources/org/flowable/impl/bpmn/parser/flowable-bpmn-extensions.xsd @@ -789,6 +789,7 @@ + @@ -802,6 +803,7 @@ + diff --git a/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/BoundaryErrorEventConverterTest.java b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/BoundaryErrorEventConverterTest.java new file mode 100644 index 00000000000..8aa2d2b847e --- /dev/null +++ b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/BoundaryErrorEventConverterTest.java @@ -0,0 +1,42 @@ +/* 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.flowable.editor.language.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.IOParameter; +import org.flowable.editor.language.xml.util.BpmnXmlConverterTest; + +/** + * @author Filip Hrisafov + */ +class BoundaryErrorEventConverterTest { + + @BpmnXmlConverterTest("boundaryErrorEventWithInParameters.bpmn") + void validateErrorBoundaryWithInParameters(BpmnModel model) { + FlowElement flowElement = model.getFlowElement("theErrorBoundary"); + assertThat(flowElement).isInstanceOfSatisfying(BoundaryEvent.class, event -> { + assertThat(event.getInParameters()) + .extracting(IOParameter::getTarget, IOParameter::getSource, IOParameter::getSourceExpression) + .containsExactlyInAnyOrder( + tuple("targetErrorVar", "sourceVar", null), + tuple("targetErrorVarFromExpression", null, "${'test'}") + ); + }); + } + +} diff --git a/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/EndErrorEventConverterTest.java b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/EndErrorEventConverterTest.java new file mode 100644 index 00000000000..18cd4b0f161 --- /dev/null +++ b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/EndErrorEventConverterTest.java @@ -0,0 +1,43 @@ +/* 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.flowable.editor.language.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.IOParameter; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.editor.language.xml.util.BpmnXmlConverterTest; + +/** + * @author Filip Hrisafov + */ +class EndErrorEventConverterTest { + + @BpmnXmlConverterTest("endErrorEventWithOutParameters.bpmn") + void validateStartErrorWithInParameters(BpmnModel model) { + FlowElement flowElement = model.getFlowElement("theSubProcessEnd"); + assertThat(flowElement).isInstanceOfSatisfying(EndEvent.class, event -> { + assertThat(event.getOutParameters()) + .extracting(IOParameter::getTarget, IOParameter::getSource, IOParameter::getSourceExpression) + .containsExactlyInAnyOrder( + tuple("targetErrorVar", "sourceVar", null), + tuple("targetErrorVarFromExpression", null, "${'test'}") + ); + }); + } + +} diff --git a/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/StartErrorEventConverterTest.java b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/StartErrorEventConverterTest.java new file mode 100644 index 00000000000..a08ff910c7a --- /dev/null +++ b/modules/flowable-bpmn-converter/src/test/java/org/flowable/editor/language/xml/StartErrorEventConverterTest.java @@ -0,0 +1,43 @@ +/* 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.flowable.editor.language.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.IOParameter; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.editor.language.xml.util.BpmnXmlConverterTest; + +/** + * @author Filip Hrisafov + */ +class StartErrorEventConverterTest { + + @BpmnXmlConverterTest("startErrorEventWithInParameters.bpmn") + void validateStartErrorWithInParameters(BpmnModel model) { + FlowElement flowElement = model.getFlowElement("startErrorSubProcess"); + assertThat(flowElement).isInstanceOfSatisfying(StartEvent.class, event -> { + assertThat(event.getInParameters()) + .extracting(IOParameter::getTarget, IOParameter::getSource, IOParameter::getSourceExpression) + .containsExactlyInAnyOrder( + tuple("targetErrorVar", "sourceVar", null), + tuple("targetErrorVarFromExpression", null, "${'test'}") + ); + }); + } + +} diff --git a/modules/flowable-bpmn-converter/src/test/resources/boundaryErrorEventWithInParameters.bpmn b/modules/flowable-bpmn-converter/src/test/resources/boundaryErrorEventWithInParameters.bpmn new file mode 100644 index 00000000000..0a48816fa13 --- /dev/null +++ b/modules/flowable-bpmn-converter/src/test/resources/boundaryErrorEventWithInParameters.bpmn @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-bpmn-converter/src/test/resources/endErrorEventWithOutParameters.bpmn b/modules/flowable-bpmn-converter/src/test/resources/endErrorEventWithOutParameters.bpmn new file mode 100644 index 00000000000..c489581ab00 --- /dev/null +++ b/modules/flowable-bpmn-converter/src/test/resources/endErrorEventWithOutParameters.bpmn @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-bpmn-converter/src/test/resources/startErrorEventWithInParameters.bpmn b/modules/flowable-bpmn-converter/src/test/resources/startErrorEventWithInParameters.bpmn new file mode 100644 index 00000000000..b4481194a61 --- /dev/null +++ b/modules/flowable-bpmn-converter/src/test/resources/startErrorEventWithInParameters.bpmn @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CallActivity.java b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CallActivity.java index aa37099cb47..f1e242b50c7 100644 --- a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CallActivity.java +++ b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CallActivity.java @@ -19,7 +19,7 @@ * @author Tijs Rademakers * @author Joram Barrez */ -public class CallActivity extends Activity { +public class CallActivity extends Activity implements HasOutParameters, HasInParameters { protected String calledElement; protected String calledElementType; @@ -59,18 +59,32 @@ public void setSameDeployment(boolean sameDeployment) { this.sameDeployment = sameDeployment; } + @Override public List getInParameters() { return inParameters; } + @Override + public void addInParameter(IOParameter inParameter) { + inParameters.add(inParameter); + } + + @Override public void setInParameters(List inParameters) { this.inParameters = inParameters; } + @Override public List getOutParameters() { return outParameters; } + @Override + public void addOutParameter(IOParameter outParameter) { + this.outParameters.add(outParameter); + } + + @Override public void setOutParameters(List outParameters) { this.outParameters = outParameters; } diff --git a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java index 3a3e84d8944..bc6460f439a 100644 --- a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java +++ b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/CaseServiceTask.java @@ -18,7 +18,7 @@ /** * @author Tijs Rademakers */ -public class CaseServiceTask extends ServiceTask { +public class CaseServiceTask extends ServiceTask implements HasOutParameters, HasInParameters { protected String caseDefinitionKey; protected String caseInstanceName; @@ -79,18 +79,32 @@ public void setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) { this.fallbackToDefaultTenant = fallbackToDefaultTenant; } + @Override public List getInParameters() { return inParameters; } + @Override + public void addInParameter(IOParameter inParameter) { + inParameters.add(inParameter); + } + + @Override public void setInParameters(List inParameters) { this.inParameters = inParameters; } + @Override public List getOutParameters() { return outParameters; } + @Override + public void addOutParameter(IOParameter outParameter) { + this.outParameters.add(outParameter); + } + + @Override public void setOutParameters(List outParameters) { this.outParameters = outParameters; } diff --git a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/Event.java b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/Event.java index 61b2527169c..9952bbca6e7 100644 --- a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/Event.java +++ b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/Event.java @@ -18,7 +18,7 @@ /** * @author Tijs Rademakers */ -public abstract class Event extends FlowNode { +public abstract class Event extends FlowNode implements HasOutParameters, HasInParameters { protected List eventDefinitions = new ArrayList<>(); protected List inParameters = new ArrayList<>(); @@ -36,18 +36,32 @@ public void addEventDefinition(EventDefinition eventDefinition) { eventDefinitions.add(eventDefinition); } + @Override public List getInParameters() { return inParameters; } + @Override + public void addInParameter(IOParameter inParameter) { + inParameters.add(inParameter); + } + + @Override public void setInParameters(List inParameters) { this.inParameters = inParameters; } + @Override public List getOutParameters() { return outParameters; } + @Override + public void addOutParameter(IOParameter outParameter) { + this.outParameters.add(outParameter); + } + + @Override public void setOutParameters(List outParameters) { this.outParameters = outParameters; } diff --git a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/HasInParameters.java b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/HasInParameters.java new file mode 100644 index 00000000000..aea7a838d8d --- /dev/null +++ b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/HasInParameters.java @@ -0,0 +1,28 @@ +/* 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.flowable.bpmn.model; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public interface HasInParameters { + + List getInParameters(); + + void addInParameter(IOParameter inParameter); + + void setInParameters(List inParameters); + +} diff --git a/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/HasOutParameters.java b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/HasOutParameters.java new file mode 100644 index 00000000000..6c419a280fe --- /dev/null +++ b/modules/flowable-bpmn-model/src/main/java/org/flowable/bpmn/model/HasOutParameters.java @@ -0,0 +1,28 @@ +/* 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.flowable.bpmn.model; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public interface HasOutParameters { + + List getOutParameters(); + + void addOutParameter(IOParameter outParameter); + + void setOutParameters(List outParameters); + +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/delegate/BpmnError.java b/modules/flowable-engine/src/main/java/org/flowable/engine/delegate/BpmnError.java index 8791dd478b8..0c3c3a19a92 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/delegate/BpmnError.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/delegate/BpmnError.java @@ -13,8 +13,12 @@ package org.flowable.engine.delegate; +import java.util.HashMap; + import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.common.engine.impl.el.VariableContainerWrapper; import org.flowable.engine.impl.bpmn.parser.Error; /** @@ -32,6 +36,7 @@ public class BpmnError extends FlowableException { private static final long serialVersionUID = 1L; private String errorCode; + private VariableContainer additionalDataContainer; public BpmnError(String errorCode) { super(""); @@ -56,4 +61,19 @@ protected void setErrorCode(String errorCode) { public String getErrorCode() { return errorCode; } + + public VariableContainer getAdditionalDataContainer() { + return additionalDataContainer; + } + + public void setAdditionalDataContainer(VariableContainer additionalDataContainer) { + this.additionalDataContainer = additionalDataContainer; + } + + public void addAdditionalData(String name, Object value) { + if (this.additionalDataContainer == null) { + this.additionalDataContainer = new VariableContainerWrapper(new HashMap<>()); + } + this.additionalDataContainer.setVariable(name, value); + } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ErrorEndEventActivityBehavior.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ErrorEndEventActivityBehavior.java index 9a6a4769659..36187101a57 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ErrorEndEventActivityBehavior.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ErrorEndEventActivityBehavior.java @@ -12,8 +12,17 @@ */ package org.flowable.engine.impl.bpmn.behavior; +import java.util.List; +import java.util.Map; + +import org.flowable.bpmn.model.IOParameter; +import org.flowable.common.engine.impl.el.ExpressionManager; +import org.flowable.common.engine.impl.el.VariableContainerWrapper; +import org.flowable.engine.delegate.BpmnError; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.bpmn.helper.ErrorPropagation; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.IOParameterUtil; /** * @author Joram Barrez @@ -24,14 +33,23 @@ public class ErrorEndEventActivityBehavior extends FlowNodeActivityBehavior { private static final long serialVersionUID = 1L; protected String errorCode; + protected List outParameters; - public ErrorEndEventActivityBehavior(String errorCode) { + public ErrorEndEventActivityBehavior(String errorCode, List outParameters) { this.errorCode = errorCode; + this.outParameters = outParameters; } @Override public void execute(DelegateExecution execution) { - ErrorPropagation.propagateError(errorCode, execution); + ExpressionManager expressionManager = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager(); + Map payload = IOParameterUtil.extractOutVariables(outParameters, execution, expressionManager); + Object errorMessage = payload.remove("errorMessage"); + BpmnError error = new BpmnError(errorCode, errorMessage != null ? errorMessage.toString() : null); + if (!payload.isEmpty()) { + error.setAdditionalDataContainer(new VariableContainerWrapper(payload)); + } + ErrorPropagation.propagateError(error, execution); } public String getErrorCode() { diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java index 3ee1eaec107..fc53b62abba 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java @@ -37,6 +37,8 @@ import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.common.engine.impl.el.ExpressionManager; import org.flowable.common.engine.impl.util.CollectionUtil; import org.flowable.common.engine.impl.util.ReflectUtil; import org.flowable.engine.delegate.BpmnError; @@ -46,6 +48,7 @@ import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.IOParameterUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,12 +66,21 @@ public class ErrorPropagation { private static final Logger LOGGER = LoggerFactory.getLogger(ErrorPropagation.class); public static void propagateError(BpmnError error, DelegateExecution execution) { - propagateError(error.getErrorCode(), execution); + propagateError(new BpmnErrorVariableContainer(error, execution.getTenantId()), execution); } public static void propagateError(String errorCode, DelegateExecution execution) { + propagateError(new BpmnErrorVariableContainer(errorCode, execution.getTenantId()), execution); + } + + protected static void propagateError(String errorCode, Throwable exception, DelegateExecution execution) { + propagateError(new BpmnErrorVariableContainer(errorCode, exception, execution.getTenantId()), execution); + } + + protected static void propagateError(BpmnErrorVariableContainer errorVariableContainer, DelegateExecution execution) { Map> eventMap = new HashMap<>(); Set rootProcessDefinitionIds = new HashSet<>(); + String errorCode = errorVariableContainer.getErrorCode(); if (!execution.getProcessInstanceId().equals(execution.getRootProcessInstanceId())) { ExecutionEntity parentExecution = (ExecutionEntity) execution; while (parentExecution.getParentId() != null || parentExecution.getSuperExecutionId() != null) { @@ -89,16 +101,21 @@ public static void propagateError(String errorCode, DelegateExecution execution) eventMap.putAll(findCatchingEventsForProcess(execution.getProcessDefinitionId(), errorCode)); if (eventMap.size() > 0) { - executeCatch(eventMap, execution, errorCode); + executeCatch(eventMap, execution, errorVariableContainer); } if (eventMap.size() == 0) { - throw new BpmnError(errorCode, "No catching boundary event found for error with errorCode '" + errorCode + "', neither in same process nor in parent process"); + BpmnError bpmnError = new BpmnError(errorCode, + "No catching boundary event found for error with errorCode '" + errorCode + "', neither in same process nor in parent process"); + bpmnError.setAdditionalDataContainer(errorVariableContainer); + throw bpmnError; } } - protected static void executeCatch(Map> eventMap, DelegateExecution delegateExecution, String errorId) { + protected static void executeCatch(Map> eventMap, DelegateExecution delegateExecution, + BpmnErrorVariableContainer errorVariableContainer) { Set toDeleteProcessInstanceIds = new HashSet<>(); + String errorId = errorVariableContainer.getErrorCode(); LOGGER.debug("Executing error catch for error={}, execution={}, eventMap={}", errorId, delegateExecution, eventMap); Event matchingEvent = null; ExecutionEntity currentExecution = (ExecutionEntity) delegateExecution; @@ -204,23 +221,26 @@ protected static void executeCatch(Map> eventMap, DelegateEx } } - executeEventHandler(matchingEvent, parentExecution, currentExecution, errorId); + executeEventHandler(matchingEvent, parentExecution, currentExecution, errorVariableContainer); } else { throw new FlowableException("No matching parent execution for error code " + errorId + " found"); } } - protected static void executeEventHandler(Event event, ExecutionEntity parentExecution, ExecutionEntity currentExecution, String errorId) { + protected static void executeEventHandler(Event event, ExecutionEntity parentExecution, ExecutionEntity currentExecution, + BpmnErrorVariableContainer errorVariableContainer) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); FlowableEventDispatcher eventDispatcher = null; + String errorId = errorVariableContainer.getErrorCode(); String errorCode = errorId; BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(parentExecution.getProcessDefinitionId()); if (bpmnModel != null) { String modelError = bpmnModel.getErrors().get(errorId); if (modelError != null) { errorCode = modelError; + errorVariableContainer.setErrorCode(errorCode); } } @@ -253,7 +273,7 @@ protected static void executeEventHandler(Event event, ExecutionEntity parentExe } ExecutionEntity eventSubProcessExecution = executionEntityManager.createChildExecution(parentExecution); - injectErrorContext(event, eventSubProcessExecution, errorCode); + injectErrorContext(event, eventSubProcessExecution, errorVariableContainer, processEngineConfiguration.getExpressionManager()); if (event.getSubProcess() != null) { eventSubProcessExecution.setCurrentFlowElement(event.getSubProcess()); CommandContextUtil.getActivityInstanceEntityManager().recordActivityStart(eventSubProcessExecution); @@ -282,7 +302,7 @@ protected static void executeEventHandler(Event event, ExecutionEntity parentExe boundaryExecution = childExecution; } } - injectErrorContext(event, boundaryExecution, errorCode); + injectErrorContext(event, boundaryExecution, errorVariableContainer, processEngineConfiguration.getExpressionManager()); CommandContextUtil.getAgenda().planTriggerExecutionOperation(boundaryExecution); } } @@ -339,7 +359,7 @@ protected static Map> findCatchingEventsForProcess(String pr public static boolean mapException(Exception e, ExecutionEntity execution, List exceptionMap) { String errorCode = findMatchingExceptionMapping(e, exceptionMap); if (errorCode != null) { - propagateError(errorCode, execution); + propagateError(errorCode, e, execution); return true; } else { ExecutionEntity callActivityExecution = null; @@ -361,7 +381,7 @@ public static boolean mapException(Exception e, ExecutionEntity execution, List< if (CollectionUtil.isNotEmpty(callActivity.getMapExceptions())) { errorCode = findMatchingExceptionMapping(e, callActivity.getMapExceptions()); if (errorCode != null) { - propagateError(errorCode, callActivityExecution); + propagateError(errorCode, e, callActivityExecution); return true; } } @@ -487,7 +507,8 @@ public static void handleException(Throwable exc, Executio } } - protected static void injectErrorContext(Event event, ExecutionEntity execution, String errorCode) { + protected static void injectErrorContext(Event event, ExecutionEntity execution, BpmnErrorVariableContainer errorSourceContainer, + ExpressionManager expressionManager) { for (EventDefinition eventDefinition : event.getEventDefinitions()) { if (!(eventDefinition instanceof ErrorEventDefinition)) { @@ -495,12 +516,16 @@ protected static void injectErrorContext(Event event, ExecutionEntity execution, } ErrorEventDefinition definition = (ErrorEventDefinition) eventDefinition; + IOParameterUtil.processInParameters(event.getInParameters(), errorSourceContainer, execution, expressionManager); + String variableName = definition.getErrorVariableName(); if (variableName == null || variableName.isEmpty()) { continue; } + String errorCode = errorSourceContainer.getErrorCode(); + if (definition.getErrorVariableTransient() != null && definition.getErrorVariableTransient()) { if (definition.getErrorVariableLocalScope() != null && definition.getErrorVariableLocalScope()) { execution.setTransientVariableLocal(variableName, errorCode); @@ -517,4 +542,85 @@ protected static void injectErrorContext(Event event, ExecutionEntity execution, } } + protected static class BpmnErrorVariableContainer implements VariableContainer { + + protected String errorCode; + protected Throwable error; + protected VariableContainer additionalDataContainer; + protected String tenantId; + + protected BpmnErrorVariableContainer(String errorCode, String tenantId) { + this.errorCode = errorCode; + this.tenantId = tenantId; + } + + protected BpmnErrorVariableContainer(BpmnError error, String tenantId) { + this.error = error; + this.additionalDataContainer = error.getAdditionalDataContainer(); + this.errorCode = error.getErrorCode(); + this.tenantId = tenantId; + } + + protected BpmnErrorVariableContainer(String errorCode, Throwable error, String tenantId) { + this.error = error; + if (error instanceof BpmnError) { + this.additionalDataContainer = ((BpmnError) error).getAdditionalDataContainer(); + } + this.errorCode = errorCode; + this.tenantId = tenantId; + } + + protected String getErrorCode() { + return errorCode; + } + + protected void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public VariableContainer getAdditionalDataContainer() { + return additionalDataContainer; + } + + @Override + public boolean hasVariable(String variableName) { + if ("errorCode".equals(variableName) || "error".equals(variableName) || "errorMessage".equals(variableName)) { + return true; + } else if (additionalDataContainer != null) { + return additionalDataContainer.hasVariable(variableName); + } + return false; + } + + @Override + public Object getVariable(String variableName) { + if ("errorCode".equals(variableName)) { + return errorCode; + } else if ("error".equals(variableName)) { + return error; + } else if ("errorMessage".equals(variableName)) { + return error != null ? error.getMessage() : null; + } else if (additionalDataContainer != null) { + return additionalDataContainer.getVariable(variableName); + } + + return null; + } + + @Override + public void setVariable(String variableName, Object variableValue) { + throw new UnsupportedOperationException("Not allowed to set variables in a bpmn error variable container"); + } + + @Override + public void setTransientVariable(String variableName, Object variableValue) { + throw new UnsupportedOperationException("Not allowed to set variables in a bpmn error variable container"); + } + + @Override + public String getTenantId() { + return tenantId; + } + } + } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/parser/factory/DefaultActivityBehaviorFactory.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/parser/factory/DefaultActivityBehaviorFactory.java index c1868290b21..bd33b18b22e 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/parser/factory/DefaultActivityBehaviorFactory.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/parser/factory/DefaultActivityBehaviorFactory.java @@ -630,7 +630,7 @@ public NoneEndEventActivityBehavior createNoneEndEventActivityBehavior(EndEvent @Override public ErrorEndEventActivityBehavior createErrorEndEventActivityBehavior(EndEvent endEvent, ErrorEventDefinition errorEventDefinition) { - return new ErrorEndEventActivityBehavior(errorEventDefinition.getErrorCode()); + return new ErrorEndEventActivityBehavior(errorEventDefinition.getErrorCode(), endEvent.getOutParameters()); } @Override diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.java index 2fb02e98df9..0cbcb8cb5c7 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.java @@ -14,20 +14,24 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import java.util.stream.Stream; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.el.VariableContainerWrapper; import org.flowable.common.engine.impl.history.HistoryLevel; import org.flowable.common.engine.impl.util.CollectionUtil; import org.flowable.engine.delegate.BpmnError; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.impl.test.HistoryTestHelper; import org.flowable.engine.impl.test.PluggableFlowableTestCase; +import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.test.Deployment; import org.flowable.task.api.Task; import org.junit.jupiter.api.Test; @@ -258,6 +262,40 @@ public void testCatchErrorEndEventOnCallActivityNoSpecificError() { assertProcessEnded(procId); } + @Test + @Deployment(resources = { "org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventCatchWithParameters.bpmn20.xml", + "org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParameters.bpmn20.xml" }) + public void testCatchErrorEndEventWithOutputParametersOnCallActivity() { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("catchError") + .transientVariable("testVar", "For test") + .transientVariable("errorMessageToUseVar", "Test error message") + .start(); + + assertThat(runtimeService.getVariables(processInstance.getId())) + .contains( + entry("outVar", "For test"), + entry("outExpressionVar", "For test-testing"), + entry("errorMessageVar", "Test error message") + ); + } + + @Test + @Deployment(resources = { "org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventCatchWithParameters.bpmn20.xml", + "org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParametersAndErrorMessageFromExpression.bpmn20.xml" }) + public void testCatchErrorEndEventWithOutputParametersAndErrorMessageFromExpressionOnCallActivity() { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("catchError") + .start(); + + assertThat(runtimeService.getVariables(processInstance.getId())) + .contains( + entry("outVar", null), + entry("outExpressionVar", null), + entry("errorMessageVar", "Custom error message") + ); + } + @Test @Deployment(resources = { "org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.subprocess.bpmn20.xml" }) public void testUncaughtError() { @@ -430,6 +468,22 @@ public void testCatchErrorOnGroovyScriptTask() { assertProcessEnded(procId); } + @Test + @Deployment + public void testCatchErrorOnGroovyScriptTaskWithInputParameters() { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("catchErrorOnScriptTask") + .start(); + + assertThat(processInstance.getProcessVariables()) + .containsOnly( + entry("handledErrorCodeVar", "errorOne"), + entry("handledErrorCodeVarWithExpression", "errorOne-testing"), + entry("handledErrorMessage", "Error One"), + entry("handledCustomParameter", "Custom value") + ); + } + @Test @Deployment(resources = { "org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorOnJavaScriptScriptTask.bpmn20.xml" }) public void testCatchErrorOnJavaScriptScriptTask() { @@ -591,6 +645,58 @@ public void catchErrorThrownByTriggerableFutureJavaDelegateProvidedByDelegateExp assertThat(task.getName()).isEqualTo("Triggered Escalated Task"); } + @Test + @Deployment + public void testCatchErrorWithInputParametersThrownByExpressionOnServiceTask() { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("errorProcess") + .transientVariable("errorCodeVar", "test123") + .transientVariable("errorMessageVar", "Test message") + .transientVariable("customErrorValueVar", "Custom property value") + .transientVariable("bpmnErrorBean", new BpmnErrorBean()) + .start(); + + Task task = taskService.createTaskQuery().singleResult(); + assertThat(task).isNotNull(); + assertThat(task.getName()).isEqualTo("Escalated Task"); + + assertThat(runtimeService.getVariables(processInstance.getId())) + .containsOnly( + entry("handledErrorCodeVar", "test123"), + entry("handledErrorCodeVarWithExpression", "test123-testing"), + entry("handledErrorMessage", "Test message"), + entry("handledCustomParameter", "Custom property value"), + entry("fromTransientHandledVar", "Custom property value") + ); + } + + @Test + @Deployment + public void testCatchErrorWithInputParametersThrownByExpressionOnServiceTaskWithCustomVariableContainer() { + Supplier errorBean = () -> { + BpmnError error = new BpmnError("test123", "Test message"); + error.setAdditionalDataContainer(new VariableContainerWrapper(Collections.singletonMap("customErrorProperty", "Custom property value"))); + throw error; + }; + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("errorProcess") + .transientVariable("bpmnErrorBean", errorBean) + .start(); + + Task task = taskService.createTaskQuery().singleResult(); + assertThat(task).isNotNull(); + assertThat(task.getName()).isEqualTo("Escalated Task"); + + assertThat(runtimeService.getVariables(processInstance.getId())) + .containsOnly( + entry("handledErrorCodeVar", "test123"), + entry("handledErrorCodeVarWithExpression", "test123-testing"), + entry("handledErrorMessage", "Test message"), + entry("handledCustomParameter", "Custom property value"), + entry("fromTransientHandledVar", "Custom property value") + ); + } + protected String getWaitingExecutionId(String processInstanceId) { return runtimeService.createExecutionQuery().processInstanceId(processInstanceId).onlyChildExecutions().activityId("serviceTask").singleResult().getId(); } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BpmnErrorBean.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BpmnErrorBean.java index 78399f8a43a..058d85a0cdb 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BpmnErrorBean.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/BpmnErrorBean.java @@ -39,6 +39,18 @@ public Future throwBpmnErrorInFuture() { }); } + public void throwComplexBpmnError(String errorCode, String errorMessage, String... additionalData) { + BpmnError error = new BpmnError(errorCode, errorMessage); + if (additionalData.length > 1) { + for (int i = 1; i < additionalData.length; i+=2) { + String key = additionalData[i - 1]; + String value = additionalData[i]; + error.addAdditionalData(key, value); + } + } + throw error; + } + public JavaDelegate getDelegate() { return new ThrowBpmnErrorDelegate(); } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.java index 96012476258..427deeb2c2b 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.java @@ -13,6 +13,7 @@ package org.flowable.engine.test.bpmn.event.error; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.tuple; import java.util.HashMap; @@ -231,6 +232,28 @@ public void testRetriggerEventSubProcessError() { } } + @Test + @Deployment + public void testCatchErrorWithInputParametersThrownByExpressionOnServiceTask() { + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("errorProcess") + .transientVariable("bpmnErrorBean", new BpmnErrorBean()) + .start(); + + Task task = taskService.createTaskQuery().singleResult(); + assertThat(task).isNotNull(); + assertThat(task.getName()).isEqualTo("Escalated Task"); + + assertThat(runtimeService.getVariables(processInstance.getId())) + .containsOnly( + entry("handledErrorCodeVar", "subProcessError"), + entry("handledErrorCodeVarWithExpression", "subProcessError-testing"), + entry("handledErrorMessage", "Sub process error message"), + entry("handledCustomParameter", "Custom value"), + entry("fromTransientHandledVar", "Custom value") + ); + } + private void assertThatErrorHasBeenCaught(String procId) { // The process will throw an error event, // which is caught and escalated by a User org.flowable.task.service.Task diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.java index f58c4786943..6ad43790043 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.java @@ -14,6 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; import java.util.HashMap; import java.util.Map; @@ -121,6 +122,26 @@ public void testRootCauseSingleDirectMap() { assertThat(FlagDelegate.isVisited()).isTrue(); } + @Test + @Deployment + public void testRootCauseSingleDirectMapInputErrorMessage() { + FlagDelegate.reset(); + + runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("processWithSingleExceptionMap") + .transientVariable("exceptionClass", BoundaryErrorParentException.class.getName()) + .transientVariable("exceptionMessage", "Message from main") + .transientVariable("nestedExceptionClass", IllegalArgumentException.class.getName()) + .transientVariable("nestedExceptionMessage", "Message from cause") + .start(); + assertThat(FlagDelegate.isVisited()).isTrue(); + assertThat(FlagDelegate.getVariables()) + .contains( + entry("errorMessageVar", "Message from main"), + entry("errorCauseMessageVar", "Message from cause") + ); + } + // exception does not match the single mapping @Test @Deployment(resources = "org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testClassDelegateSingleDirectMap.bpmn20.xml") @@ -331,6 +352,26 @@ public void testCallProcessSingleDirectMap() { assertThat(FlagDelegate.isVisited()).isTrue(); } + + @Test + @Deployment(resources = { + "org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testCallProcessSingleDirectMapInputErrorMessage.bpmn20.xml", + "org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testCallProcessCalee.bpmn20.xml" }) + public void testCallProcessSingleDirectMapInputErrorMessage() { + FlagDelegate.reset(); + + runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("callProcessWithSingleExceptionMap") + .variable("exceptionClass", BoundaryErrorParentException.class.getName()) + .variable("exceptionMessage", "Message from main") + .start(); + assertThat(FlagDelegate.isVisited()).isTrue(); + assertThat(FlagDelegate.getVariables()) + .contains( + entry("errorMessageVar", "Message from main") + ); + } + @Test @Deployment(resources = { "org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testExpressionCallProcessSingleDirectMap.bpmn20.xml", diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorParentException.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorParentException.java index 74dcb23a784..fcea859ea39 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorParentException.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorParentException.java @@ -23,8 +23,16 @@ public class BoundaryErrorParentException extends RuntimeException { public BoundaryErrorParentException() { } + public BoundaryErrorParentException(String message) { + super(message); + } + public BoundaryErrorParentException(Throwable cause) { super(cause); } + public BoundaryErrorParentException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryEventChildException.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryEventChildException.java index 246b3971306..2e1444a0808 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryEventChildException.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryEventChildException.java @@ -21,7 +21,15 @@ public class BoundaryEventChildException extends BoundaryErrorParentException { public BoundaryEventChildException() { } + public BoundaryEventChildException(String message) { + super(message); + } + public BoundaryEventChildException(Throwable cause) { super(cause); } + + public BoundaryEventChildException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/FlagDelegate.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/FlagDelegate.java index e49689e1b11..54be32fbb62 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/FlagDelegate.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/FlagDelegate.java @@ -12,6 +12,8 @@ */ package org.flowable.engine.test.bpmn.event.error.mapError; +import java.util.Map; + import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate; @@ -20,19 +22,25 @@ */ public class FlagDelegate implements JavaDelegate { static boolean visited; + static Map variables; public static void reset() { visited = false; + variables = null; } public static boolean isVisited() { return visited; } + public static Map getVariables() { + return variables; + } + @Override public void execute(DelegateExecution execution) { visited = true; - + variables = execution.getVariables(); } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowCustomExceptionDelegate.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowCustomExceptionDelegate.java index fb34137d08d..75f3e26e483 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowCustomExceptionDelegate.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowCustomExceptionDelegate.java @@ -33,8 +33,13 @@ public void execute(DelegateExecution execution) { if (StringUtils.isNotEmpty(exceptionClassName)) { RuntimeException exception = null; try { + Object exceptionMessage = execution.getVariable("exceptionMessage"); Class clazz = Class.forName(exceptionClassName); - exception = (RuntimeException) clazz.newInstance(); + if (exceptionMessage != null) { + exception = (RuntimeException) clazz.getConstructor(String.class).newInstance(exceptionMessage.toString()); + } else { + exception = (RuntimeException) clazz.getConstructor().newInstance(); + } } catch (Exception e) { throw new FlowableException("Class not found", e); diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowNestedCustomExceptionDelegate.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowNestedCustomExceptionDelegate.java index 8bda7f40ac4..e61b2a32ba6 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowNestedCustomExceptionDelegate.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/error/mapError/ThrowNestedCustomExceptionDelegate.java @@ -29,13 +29,26 @@ public void execute(DelegateExecution execution) { String exceptionClassName = exceptionClassVar.toString(); String nestedExceptionClassName = nestedExceptionClassVar.toString(); + Object exceptionMessage = execution.getVariable("exceptionMessage"); + Object nestedExceptionMessage = execution.getVariable("nestedExceptionMessage"); + if (StringUtils.isNotEmpty(exceptionClassName) && StringUtils.isNotEmpty(nestedExceptionClassName)) { RuntimeException exception = null; RuntimeException nestedException = null; try { - nestedException = (RuntimeException) Class.forName(nestedExceptionClassName).newInstance(); - exception = (RuntimeException) Class.forName(exceptionClassName).getConstructor(Throwable.class).newInstance(nestedException); + if (nestedExceptionMessage != null) { + nestedException = (RuntimeException) Class.forName(nestedExceptionClassName).getConstructor(String.class).newInstance(nestedExceptionMessage.toString()); + } else { + nestedException = (RuntimeException) Class.forName(nestedExceptionClassName).getConstructor().newInstance(); + } + + if (exceptionMessage != null) { + exception = (RuntimeException) Class.forName(exceptionClassName).getConstructor(String.class, Throwable.class) + .newInstance(exceptionMessage.toString(), nestedException); + } else { + exception = (RuntimeException) Class.forName(exceptionClassName).getConstructor(Throwable.class).newInstance(nestedException); + } } catch (Exception e) { throw new FlowableException("Class not found", e); diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventCatchWithParameters.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventCatchWithParameters.bpmn20.xml new file mode 100644 index 00000000000..7f71b03400d --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventCatchWithParameters.bpmn20.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParameters.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParameters.bpmn20.xml new file mode 100644 index 00000000000..87b98b19170 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParameters.bpmn20.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParametersAndErrorMessageFromExpression.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParametersAndErrorMessageFromExpression.bpmn20.xml new file mode 100644 index 00000000000..f2a0f2f4cfb --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.callActivityWithErrorEndEventThrowWithOutputParametersAndErrorMessageFromExpression.bpmn20.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorOnGroovyScriptTaskWithInputParameters.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorOnGroovyScriptTaskWithInputParameters.bpmn20.xml new file mode 100644 index 00000000000..d3ed62fc5fd --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorOnGroovyScriptTaskWithInputParameters.bpmn20.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTask.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTask.bpmn20.xml new file mode 100644 index 00000000000..7772b1b20cc --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTask.bpmn20.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTaskWithCustomVariableContainer.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTaskWithCustomVariableContainer.bpmn20.xml new file mode 100644 index 00000000000..4fe5e63345f --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/BoundaryErrorEventTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTaskWithCustomVariableContainer.bpmn20.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTask.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTask.bpmn20.xml new file mode 100644 index 00000000000..15533d6822c --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/ErrorEventSubProcessTest.testCatchErrorWithInputParametersThrownByExpressionOnServiceTask.bpmn20.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testCallProcessSingleDirectMapInputErrorMessage.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testCallProcessSingleDirectMapInputErrorMessage.bpmn20.xml new file mode 100644 index 00000000000..8ec77eb633d --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testCallProcessSingleDirectMapInputErrorMessage.bpmn20.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + org.flowable.engine.test.bpmn.event.error.mapError.BoundaryErrorParentException + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testRootCauseSingleDirectMapInputErrorMessage.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testRootCauseSingleDirectMapInputErrorMessage.bpmn20.xml new file mode 100644 index 00000000000..a0140e891c8 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/error/mapError/BoundaryErrorMapTest.testRootCauseSingleDirectMapInputErrorMessage.bpmn20.xml @@ -0,0 +1,28 @@ + + + + + + + + + org.flowable.engine.test.bpmn.event.error.mapError.BoundaryErrorParentException + + + + + + + + + + + + + + + + \ No newline at end of file