/* 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.gateway; import java.util.ArrayList; import java.util.List; import org.activiti.engine.ActivitiException; import org.activiti.engine.impl.test.PluggableActivitiTestCase; import org.activiti.engine.impl.util.CollectionUtil; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.engine.test.Deployment; /** * @author Joram Barrez */ public class ExclusiveGatewayTest extends PluggableActivitiTestCase { @Deployment public void testDivergingExclusiveGateway() { for (int i = 1; i <= 3; i++) { ProcessInstance pi = runtimeService.startProcessInstanceByKey("exclusiveGwDiverging", CollectionUtil.singletonMap("input", i)); assertEquals("Task " + i, taskService.createTaskQuery().singleResult().getName()); runtimeService.deleteProcessInstance(pi.getId(), "testing deletion"); } } @Deployment public void testMergingExclusiveGateway() { runtimeService.startProcessInstanceByKey("exclusiveGwMerging"); assertEquals(3, taskService.createTaskQuery().count()); } // If there are multiple outgoing seqFlow with valid conditions, the first // defined one should be chosen. @Deployment public void testMultipleValidConditions() { runtimeService.startProcessInstanceByKey("exclusiveGwMultipleValidConditions", CollectionUtil.singletonMap("input", 5)); assertEquals("Task 2", taskService.createTaskQuery().singleResult().getName()); } @Deployment public void testNoSequenceFlowSelected() { try { runtimeService.startProcessInstanceByKey("exclusiveGwNoSeqFlowSelected", CollectionUtil.singletonMap("input", 4)); fail(); } catch (ActivitiException e) { assertTextPresent("No outgoing sequence flow of the exclusive gateway " + "'exclusiveGw' could be selected for continuing the process", e.getMessage()); } } /** * Test for bug ACT-10: whitespaces/newlines in expressions lead to exceptions */ @Deployment public void testWhitespaceInExpression() { // Starting a process instance will lead to an exception if whitespace are incorrectly handled runtimeService.startProcessInstanceByKey("whiteSpaceInExpression", CollectionUtil.singletonMap("input", 1)); } @Deployment(resources = {"org/activiti/engine/test/bpmn/gateway/ExclusiveGatewayTest.testDivergingExclusiveGateway.bpmn20.xml"}) public void testUnknownVariableInExpression() { // Instead of 'input' we're starting a process instance with the name 'iinput' (ie. a typo) try { runtimeService.startProcessInstanceByKey( "exclusiveGwDiverging", CollectionUtil.singletonMap("iinput", 1)); fail(); } catch (ActivitiException e) { assertTextPresent("Unknown property used in expression", e.getMessage()); } } @Deployment public void testDecideBasedOnBeanProperty() { runtimeService.startProcessInstanceByKey("decisionBasedOnBeanProperty", CollectionUtil.singletonMap("order", new ExclusiveGatewayTestOrder(150))); Task task = taskService.createTaskQuery().singleResult(); assertNotNull(task); assertEquals("Standard service", task.getName()); } @Deployment public void testDecideBasedOnListOrArrayOfBeans() { List<ExclusiveGatewayTestOrder> orders = new ArrayList<ExclusiveGatewayTestOrder>(); orders.add(new ExclusiveGatewayTestOrder(50)); orders.add(new ExclusiveGatewayTestOrder(300)); orders.add(new ExclusiveGatewayTestOrder(175)); ProcessInstance pi = runtimeService.startProcessInstanceByKey( "decisionBasedOnListOrArrayOfBeans", CollectionUtil.singletonMap("orders", orders)); Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult(); assertNotNull(task); assertEquals("Gold Member service", task.getName()); // Arrays are usable in exactly the same way ExclusiveGatewayTestOrder[] orderArray = orders.toArray(new ExclusiveGatewayTestOrder[orders.size()]); orderArray[1].setPrice(10); pi = runtimeService.startProcessInstanceByKey( "decisionBasedOnListOrArrayOfBeans", CollectionUtil.singletonMap("orders", orderArray)); task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult(); assertNotNull(task); assertEquals("Basic service", task.getName()); } @Deployment public void testDecideBasedOnBeanMethod() { runtimeService.startProcessInstanceByKey("decisionBasedOnBeanMethod", CollectionUtil.singletonMap("order", new ExclusiveGatewayTestOrder(300))); Task task = taskService.createTaskQuery().singleResult(); assertNotNull(task); assertEquals("Gold Member service", task.getName()); } @Deployment public void testInvalidMethodExpression() { try { runtimeService.startProcessInstanceByKey("invalidMethodExpression", CollectionUtil.singletonMap("order", new ExclusiveGatewayTestOrder(50))); fail(); } catch (ActivitiException e) { assertTextPresent("Unknown method used in expression", e.getMessage()); } } @Deployment public void testDefaultSequenceFlow() { // Input == 1 -> default is not selected String procId = runtimeService.startProcessInstanceByKey("exclusiveGwDefaultSequenceFlow", CollectionUtil.singletonMap("input", 1)).getId(); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Input is one", task.getName()); runtimeService.deleteProcessInstance(procId, null); procId = runtimeService.startProcessInstanceByKey("exclusiveGwDefaultSequenceFlow", CollectionUtil.singletonMap("input", 5)).getId(); task = taskService.createTaskQuery().singleResult(); assertEquals("Default input", task.getName()); } @Deployment public void testNoIdOnSequenceFlow() { runtimeService.startProcessInstanceByKey("noIdOnSequenceFlow", CollectionUtil.singletonMap("input", 3)); Task task = taskService.createTaskQuery().singleResult(); assertEquals("Input is more than one", task.getName()); } public void testInvalidProcessDefinition() { String flowWithoutConditionNoDefaultFlow = "<?xml version='1.0' encoding='UTF-8'?>" + "<definitions id='definitions' xmlns='http://www.omg.org/spec/BPMN/20100524/MODEL' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:activiti='http://activiti.org/bpmn' targetNamespace='Examples'>" + " <process id='exclusiveGwDefaultSequenceFlow'> " + " <startEvent id='theStart' /> " + " <sequenceFlow id='flow1' sourceRef='theStart' targetRef='exclusiveGw' /> " + " <exclusiveGateway id='exclusiveGw' name='Exclusive Gateway' /> " + // no default = "flow3" !! " <sequenceFlow id='flow2' sourceRef='exclusiveGw' targetRef='theTask1'> " + " <conditionExpression xsi:type='tFormalExpression'>${input == 1}</conditionExpression> " + " </sequenceFlow> " + " <sequenceFlow id='flow3' sourceRef='exclusiveGw' targetRef='theTask2'/> " + // one would be OK " <sequenceFlow id='flow4' sourceRef='exclusiveGw' targetRef='theTask2'/> " + // but two unconditional not! " <userTask id='theTask1' name='Input is one' /> " + " <userTask id='theTask2' name='Default input' /> " + " </process>" + "</definitions>"; try { repositoryService.createDeployment().addString("myprocess.bpmn20.xml", flowWithoutConditionNoDefaultFlow).deploy(); fail("Could deploy a process definition with a sequence flow out of a XOR Gateway without condition with is not the default flow."); } catch (ActivitiException ex) { assertTrue( ex.getMessage().startsWith("Exclusive Gateway 'exclusiveGw' has outgoing sequence flow 'flow3' without condition which is not the default flow.")); } String defaultFlowWithCondition = "<?xml version='1.0' encoding='UTF-8'?>" + "<definitions id='definitions' xmlns='http://www.omg.org/spec/BPMN/20100524/MODEL' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:activiti='http://activiti.org/bpmn' targetNamespace='Examples'>" + " <process id='exclusiveGwDefaultSequenceFlow'> " + " <startEvent id='theStart' /> " + " <sequenceFlow id='flow1' sourceRef='theStart' targetRef='exclusiveGw' /> " + " <exclusiveGateway id='exclusiveGw' name='Exclusive Gateway' default='flow3' /> " + " <sequenceFlow id='flow2' sourceRef='exclusiveGw' targetRef='theTask1'> " + " <conditionExpression xsi:type='tFormalExpression'>${input == 1}</conditionExpression> " + " </sequenceFlow> " + " <sequenceFlow id='flow3' sourceRef='exclusiveGw' targetRef='theTask2'> " + " <conditionExpression xsi:type='tFormalExpression'>${input == 3}</conditionExpression> " + " </sequenceFlow> " + " <userTask id='theTask1' name='Input is one' /> " + " <userTask id='theTask2' name='Default input' /> " + " </process>" + "</definitions>"; try { repositoryService.createDeployment().addString("myprocess.bpmn20.xml", defaultFlowWithCondition).deploy(); fail("Could deploy a process definition with a sequence flow out of a XOR Gateway without condition with is not the default flow."); } catch (ActivitiException ex) { assertTrue( ex.getMessage().startsWith("Exclusive Gateway 'exclusiveGw' has outgoing sequence flow 'flow3' which is the default flow but has a condition too.")); } String noOutgoingFlow = "<?xml version='1.0' encoding='UTF-8'?>" + "<definitions id='definitions' xmlns='http://www.omg.org/spec/BPMN/20100524/MODEL' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:activiti='http://activiti.org/bpmn' targetNamespace='Examples'>" + " <process id='exclusiveGwDefaultSequenceFlow'> " + " <startEvent id='theStart' /> " + " <sequenceFlow id='flow1' sourceRef='theStart' targetRef='exclusiveGw' /> " + " <exclusiveGateway id='exclusiveGw' name='Exclusive Gateway' /> " + " </process>" + "</definitions>"; try { repositoryService.createDeployment().addString("myprocess.bpmn20.xml", noOutgoingFlow).deploy(); fail("Could deploy a process definition with a sequence flow out of a XOR Gateway without condition with is not the default flow."); } catch (ActivitiException ex) { ex.printStackTrace(); assertTrue( ex.getMessage().startsWith("Exclusive Gateway 'exclusiveGw' has no outgoing sequence flows.")); } } }