Index: engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/DelegateBean.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/DelegateBean.java (revision 13396e10cc0801825458604bd2ee22f2799e5d48) +++ engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/DelegateBean.java (revision ) @@ -32,6 +32,6 @@ public List resolveCollection(DelegateExecution delegateExecution) { DelegateEvent.recordEventFor(delegateExecution); - return Arrays.asList("1"); + return Arrays.asList("1", "2", "3"); } } Index: engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/SubEvent.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/SubEvent.java (revision ) +++ engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/SubEvent.java (revision ) @@ -0,0 +1,330 @@ +/* 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.camunda.bpm.engine.test.bpmn.multiinstance; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.camunda.bpm.engine.ProcessEngineServices; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.variable.VariableMap; +import org.camunda.bpm.engine.variable.value.TypedValue; +import org.camunda.bpm.model.bpmn.BpmnModelInstance; +import org.camunda.bpm.model.bpmn.instance.FlowElement; + +/** + * @author Thorben Lindhauer + * + */ +public class SubEvent implements DelegateExecution { + + protected static final List RECORDED_EVENTS = new ArrayList(); + + protected String activityInstanceId; + protected String businessKey; + protected String currentActivityId; + protected String currentActivityName; + protected String currentTransitionId; + protected String eventName; + protected String id; + protected String parentActivityInstanceId; + protected String parentId; + protected String processBusinessKey; + protected String processDefinitionId; + protected String processInstanceId; + protected String tenantId; + protected String variableScopeKey; + + public static SubEvent fromExecution(DelegateExecution delegateExecution) { + SubEvent event = new SubEvent(); + + event.activityInstanceId = delegateExecution.getActivityInstanceId(); + event.businessKey = delegateExecution.getBusinessKey(); + event.currentActivityId = delegateExecution.getCurrentActivityId(); + event.currentActivityName = delegateExecution.getCurrentActivityName(); + event.currentTransitionId = delegateExecution.getCurrentTransitionId(); + event.eventName = delegateExecution.getEventName(); + event.id = delegateExecution.getId(); + event.parentActivityInstanceId = delegateExecution.getParentActivityInstanceId(); + event.parentId = delegateExecution.getParentId(); + event.processBusinessKey = delegateExecution.getProcessBusinessKey(); + event.processDefinitionId = delegateExecution.getProcessDefinitionId(); + event.processInstanceId = delegateExecution.getProcessInstanceId(); + event.tenantId = delegateExecution.getTenantId(); + event.variableScopeKey = delegateExecution.getVariableScopeKey(); + + return event; + } + + public static void clearEvents() { + RECORDED_EVENTS.clear(); + } + + public static void recordEventFor(DelegateExecution execution) { + RECORDED_EVENTS.add(SubEvent.fromExecution(execution)); + } + + public static List getEvents() { + return RECORDED_EVENTS; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getEventName() { + return eventName; + } + + @Override + public String getBusinessKey() { + return businessKey; + } + + @Override + public String getVariableScopeKey() { + return variableScopeKey; + } + + protected RuntimeException notYetImplemented() { + return new RuntimeException("Recording this method is not implemented"); + } + + protected RuntimeException cannotModifyState() { + return new RuntimeException("This event is read-only; cannot modify state"); + } + + @Override + public Map getVariables() { + throw notYetImplemented(); + } + + @Override + public VariableMap getVariablesTyped() { + throw notYetImplemented(); + } + + @Override + public VariableMap getVariablesTyped(boolean deserializeValues) { + throw notYetImplemented(); + } + + @Override + public Map getVariablesLocal() { + throw notYetImplemented(); + } + + @Override + public VariableMap getVariablesLocalTyped() { + throw notYetImplemented(); + } + + @Override + public VariableMap getVariablesLocalTyped(boolean deserializeValues) { + throw notYetImplemented(); + } + + @Override + public Object getVariable(String variableName) { + throw notYetImplemented(); + } + + @Override + public Object getVariableLocal(String variableName) { + throw notYetImplemented(); + } + + @Override + public T getVariableTyped(String variableName) { + throw notYetImplemented(); + } + + @Override + public T getVariableTyped(String variableName, boolean deserializeValue) { + throw notYetImplemented(); + } + + @Override + public T getVariableLocalTyped(String variableName) { + throw notYetImplemented(); + } + + @Override + public T getVariableLocalTyped(String variableName, boolean deserializeValue) { + throw notYetImplemented(); + } + + @Override + public Set getVariableNames() { + throw notYetImplemented(); + } + + @Override + public Set getVariableNamesLocal() { + throw notYetImplemented(); + } + + @Override + public void setVariable(String variableName, Object value) { + throw cannotModifyState(); + } + + @Override + public void setVariableLocal(String variableName, Object value) { + throw cannotModifyState(); + } + + @Override + public void setVariables(Map variables) { + throw cannotModifyState(); + } + + @Override + public void setVariablesLocal(Map variables) { + throw cannotModifyState(); + } + + @Override + public boolean hasVariables() { + throw notYetImplemented(); + } + + @Override + public boolean hasVariablesLocal() { + throw notYetImplemented(); + } + + @Override + public boolean hasVariable(String variableName) { + throw notYetImplemented(); + } + + @Override + public boolean hasVariableLocal(String variableName) { + throw notYetImplemented(); + } + + @Override + public void removeVariable(String variableName) { + throw cannotModifyState(); + } + + @Override + public void removeVariableLocal(String variableName) { + throw cannotModifyState(); + } + + @Override + public void removeVariables(Collection variableNames) { + throw cannotModifyState(); + } + + @Override + public void removeVariablesLocal(Collection variableNames) { + throw cannotModifyState(); + } + + @Override + public void removeVariables() { + throw cannotModifyState(); + } + + @Override + public void removeVariablesLocal() { + throw cannotModifyState(); + } + + @Override + public BpmnModelInstance getBpmnModelInstance() { + throw notYetImplemented(); + } + + @Override + public FlowElement getBpmnModelElementInstance() { + throw notYetImplemented(); + } + + @Override + public ProcessEngineServices getProcessEngineServices() { + throw notYetImplemented(); + } + + public String getActivityInstanceId() { + return activityInstanceId; + } + + public String getCurrentActivityId() { + return currentActivityId; + } + + public String getCurrentActivityName() { + return currentActivityName; + } + + public String getCurrentTransitionId() { + return currentTransitionId; + } + + public String getParentActivityInstanceId() { + return parentActivityInstanceId; + } + + public String getParentId() { + return parentId; + } + + public String getProcessBusinessKey() { + return processBusinessKey; + } + + public String getProcessDefinitionId() { + return processDefinitionId; + } + + + public String getProcessInstanceId() { + return processInstanceId; + } + + public String getTenantId() { + return tenantId; + } + + @Override + public void setVariable(String variableName, Object value, String activityId) { + this.cannotModifyState(); + } + + @Override + public DelegateExecution getProcessInstance() { + throw notYetImplemented(); + } + + @Override + public DelegateExecution getSuperExecution() { + throw notYetImplemented(); + } + + @Override + public boolean isCanceled() { + throw notYetImplemented(); + } + + + +} Index: engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/SubBean.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/SubBean.java (revision ) +++ engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/SubBean.java (revision ) @@ -0,0 +1,32 @@ +/* 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.camunda.bpm.engine.test.bpmn.multiinstance; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.camunda.bpm.engine.delegate.DelegateExecution; + +public class SubBean implements Serializable { + + private static final long serialVersionUID = 1L; + + protected static List RECORDED_EVENTS = new ArrayList(); + + public void doIt(DelegateExecution delegateExecution) { + + SubEvent.recordEventFor(delegateExecution); + } +} Index: engine/src/main/java/org/camunda/bpm/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- engine/src/main/java/org/camunda/bpm/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java (revision 13396e10cc0801825458604bd2ee22f2799e5d48) +++ engine/src/main/java/org/camunda/bpm/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java (revision ) @@ -15,6 +15,7 @@ import static org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull; +import java.io.Serializable; import java.util.Collection; import java.util.Iterator; @@ -56,7 +57,10 @@ protected String collectionVariable; protected String collectionElementVariable; - @Override + private Collection collectionExpressionValue; + private Collection collectionVariableValue; + + @Override public void execute(ActivityExecution execution) throws Exception { int nrOfInstances = resolveNrOfInstances(execution); if (nrOfInstances == 0) { @@ -82,9 +86,9 @@ if (usesCollection() && collectionElementVariable != null) { Collection collection = null; if (collectionExpression != null) { - collection = (Collection) collectionExpression.getValue(execution); + collection = collectionExpressionValue; } else if (collectionVariable != null) { - collection = (Collection) execution.getVariable(collectionVariable); + collection = collectionVariableValue; } Object value = getElementAtIndex(loopCounter, collection); @@ -102,16 +106,18 @@ nrOfInstances = resolveLoopCardinality(execution); } else if (collectionExpression != null) { Object obj = collectionExpression.getValue(execution); - if (!(obj instanceof Collection)) { + if (!(obj instanceof Collection) || !(obj instanceof Serializable)) { throw LOG.unresolvableExpressionException(collectionExpression.getExpressionText(), "Collection"); } - nrOfInstances = ((Collection) obj).size(); + collectionExpressionValue = ((Collection) obj); + nrOfInstances = collectionExpressionValue.size(); } else if (collectionVariable != null) { Object obj = execution.getVariable(collectionVariable); - if (!(obj instanceof Collection)) { + if (!(obj instanceof Collection) || !(obj instanceof Serializable)) { throw LOG.invalidVariableTypeException(collectionVariable, "Collection"); } - nrOfInstances = ((Collection) obj).size(); + collectionVariableValue = ((Collection) obj); + nrOfInstances = collectionVariableValue.size(); } else { throw LOG.resolveCollectionExpressionOrVariableReferenceException(); } Index: engine/src/test/resources/org/camunda/bpm/engine/test/bpmn/multiinstance/MultiInstanceTest.testSequentialSubProcessOnCollectionExpression.bpmn20.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- engine/src/test/resources/org/camunda/bpm/engine/test/bpmn/multiinstance/MultiInstanceTest.testSequentialSubProcessOnCollectionExpression.bpmn20.xml (revision ) +++ engine/src/test/resources/org/camunda/bpm/engine/test/bpmn/multiinstance/MultiInstanceTest.testSequentialSubProcessOnCollectionExpression.bpmn20.xml (revision ) @@ -0,0 +1,42 @@ + + + + + + + sub + + + + sid-80FBB955-C0F8-43B2-84E9-9864C54E88D4 + sid-F12788B2-2E2A-46E8-9390-B1A058986EB2 + + + + sub-start + sub-end + + + serviceTask_sub + + + serviceTask_sub + + + + + + + + + + sub + + + + + \ No newline at end of file Index: engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/MultiInstanceTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/MultiInstanceTest.java (revision 13396e10cc0801825458604bd2ee22f2799e5d48) +++ engine/src/test/java/org/camunda/bpm/engine/test/bpmn/multiinstance/MultiInstanceTest.java (revision ) @@ -397,19 +397,33 @@ } @Deployment - public void FAILING_testParallelUserTasksBasedOnCollectionExpression() { + public void testParallelUserTasksBasedOnCollectionExpression() { DelegateEvent.clearEvents(); - runtimeService.startProcessInstanceByKey("process", - Variables.createVariables().putValue("myBean", new DelegateBean())); + runtimeService.startProcessInstanceByKey("process", Variables.createVariables().putValue("myBean", new DelegateBean())); List recordedEvents = DelegateEvent.getEvents(); - assertEquals(2, recordedEvents.size()); + assertEquals(1, recordedEvents.size()); assertEquals("miTasks#multiInstanceBody", recordedEvents.get(0).getCurrentActivityId()); - assertEquals("miTasks#multiInstanceBody", recordedEvents.get(1).getCurrentActivityId()); // or miTasks DelegateEvent.clearEvents(); + } + + @Deployment + public void testSequentialSubProcessOnCollectionExpression() { + DelegateEvent.clearEvents(); + SubEvent.clearEvents(); + + runtimeService.startProcessInstanceByKey("process", Variables.createVariables().putValue("myBean", new DelegateBean()).putValue("mySubBean", new SubBean())); + + assertEquals(3, SubEvent.getEvents().size()); + List delegateEvents = DelegateEvent.getEvents(); + assertEquals(1, delegateEvents.size()); + assertEquals("sub#multiInstanceBody", delegateEvents.get(0).getCurrentActivityId()); + + SubEvent.clearEvents(); + DelegateEvent.clearEvents(); } @Deployment