/* 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.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.impl.util.CollectionUtil;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
/**
* @author Joram Barrez
* @author Tom Van Buskirk
* @author Tijs Rademakers
*/
public class InclusiveGatewayTest extends PluggableActivitiTestCase {
private static final String TASK1_NAME = "Task 1";
private static final String TASK2_NAME = "Task 2";
private static final String TASK3_NAME = "Task 3";
private static final String BEAN_TASK1_NAME = "Basic service";
private static final String BEAN_TASK2_NAME = "Standard service";
private static final String BEAN_TASK3_NAME = "Gold Member service";
@Deployment
public void testDivergingInclusiveGateway() {
for (int i = 1; i <= 3; i++) {
ProcessInstance pi = runtimeService.startProcessInstanceByKey("inclusiveGwDiverging", CollectionUtil.singletonMap("input", i));
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
List<String> expectedNames = new ArrayList<String>();
if (i == 1) {
expectedNames.add(TASK1_NAME);
}
if (i <= 2) {
expectedNames.add(TASK2_NAME);
}
expectedNames.add(TASK3_NAME);
for (Task task : tasks) {
System.out.println("task " + task.getName());
}
assertEquals(4 - i, tasks.size());
for (Task task : tasks) {
expectedNames.remove(task.getName());
}
assertEquals(0, expectedNames.size());
runtimeService.deleteProcessInstance(pi.getId(), "testing deletion");
}
}
@Deployment
public void testMergingInclusiveGateway() {
ProcessInstance pi = runtimeService.startProcessInstanceByKey("inclusiveGwMerging", CollectionUtil.singletonMap("input", 2));
assertEquals(1, taskService.createTaskQuery().count());
runtimeService.deleteProcessInstance(pi.getId(), "testing deletion");
}
@Deployment
public void testPartialMergingInclusiveGateway() {
ProcessInstance pi = runtimeService.startProcessInstanceByKey("partialInclusiveGwMerging", CollectionUtil.singletonMap("input", 2));
Task partialTask = taskService.createTaskQuery().singleResult();
assertEquals("partialTask", partialTask.getTaskDefinitionKey());
taskService.complete(partialTask.getId());
Task fullTask = taskService.createTaskQuery().singleResult();
assertEquals("theTask", fullTask.getTaskDefinitionKey());
runtimeService.deleteProcessInstance(pi.getId(), "testing deletion");
}
@Deployment
public void testNoSequenceFlowSelected() {
try {
runtimeService.startProcessInstanceByKey("inclusiveGwNoSeqFlowSelected", CollectionUtil.singletonMap("input", 4));
fail();
} catch (ActivitiException e) {
assertTextPresent("No outgoing sequence flow of the inclusive gateway 'inclusiveGw' could be selected for continuing the process", e.getMessage());
}
}
/**
* Test for ACT-1216: When merging a concurrent execution the parent is not activated correctly
*/
@Deployment
public void testParentActivationOnNonJoiningEnd() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("parentActivationOnNonJoiningEnd");
List<Execution> executionsBefore = runtimeService.createExecutionQuery().list();
assertEquals(3, executionsBefore.size());
// start first round of tasks
List<Task> firstTasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
assertEquals(2, firstTasks.size());
for (Task t: firstTasks) {
taskService.complete(t.getId());
}
// start first round of tasks
List<Task> secondTasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
assertEquals(2, secondTasks.size());
// complete one task
Task task = secondTasks.get(0);
taskService.complete(task.getId());
// should have merged last child execution into parent
List<Execution> executionsAfter = runtimeService.createExecutionQuery().list();
assertEquals(1, executionsAfter.size());
Execution execution = executionsAfter.get(0);
// and should have one active activity
List<String> activeActivityIds = runtimeService.getActiveActivityIds(execution.getId());
assertEquals(1, activeActivityIds.size());
// Completing last task should finish the process instance
Task lastTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
taskService.complete(lastTask.getId());
assertEquals(0l, runtimeService.createProcessInstanceQuery().active().count());
}
/**
* 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("inclusiveWhiteSpaceInExpression", CollectionUtil.singletonMap("input", 1));
}
@Deployment(resources = { "org/activiti/engine/test/bpmn/gateway/InclusiveGatewayTest.testDivergingInclusiveGateway.bpmn20.xml" })
public void testUnknownVariableInExpression() {
// Instead of 'input' we're starting a process instance with the name
// 'iinput' (ie. a typo)
try {
runtimeService.startProcessInstanceByKey("inclusiveGwDiverging", CollectionUtil.singletonMap("iinput", 1));
fail();
} catch (ActivitiException e) {
assertTextPresent("Unknown property used in expression", e.getMessage());
}
}
@Deployment
public void testDecideBasedOnBeanProperty() {
runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnBeanProperty", CollectionUtil.singletonMap("order", new InclusiveGatewayTestOrder(150)));
List<Task> tasks = taskService.createTaskQuery().list();
assertEquals(2, tasks.size());
Map<String, String> expectedNames = new HashMap<String, String>();
expectedNames.put(BEAN_TASK2_NAME, BEAN_TASK2_NAME);
expectedNames.put(BEAN_TASK3_NAME, BEAN_TASK3_NAME);
for (Task task : tasks) {
expectedNames.remove(task.getName());
}
assertEquals(0, expectedNames.size());
}
@Deployment
public void testDecideBasedOnListOrArrayOfBeans() {
List<InclusiveGatewayTestOrder> orders = new ArrayList<InclusiveGatewayTestOrder>();
orders.add(new InclusiveGatewayTestOrder(50));
orders.add(new InclusiveGatewayTestOrder(300));
orders.add(new InclusiveGatewayTestOrder(175));
ProcessInstance pi = null;
try {
pi = runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnListOrArrayOfBeans", CollectionUtil.singletonMap("orders", orders));
fail();
} catch (ActivitiException e) {
// expect an exception to be thrown here
}
orders.set(1, new InclusiveGatewayTestOrder(175));
pi = runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnListOrArrayOfBeans", CollectionUtil.singletonMap("orders", orders));
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
assertNotNull(task);
assertEquals(BEAN_TASK3_NAME, task.getName());
orders.set(1, new InclusiveGatewayTestOrder(125));
pi = runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnListOrArrayOfBeans", CollectionUtil.singletonMap("orders", orders));
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
assertNotNull(tasks);
assertEquals(2, tasks.size());
List<String> expectedNames = new ArrayList<String>();
expectedNames.add(BEAN_TASK2_NAME);
expectedNames.add(BEAN_TASK3_NAME);
for (Task t : tasks) {
expectedNames.remove(t.getName());
}
assertEquals(0, expectedNames.size());
// Arrays are usable in exactly the same way
InclusiveGatewayTestOrder[] orderArray = orders.toArray(new InclusiveGatewayTestOrder[orders.size()]);
orderArray[1].setPrice(10);
pi = runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnListOrArrayOfBeans", CollectionUtil.singletonMap("orders", orderArray));
tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
assertNotNull(tasks);
assertEquals(3, tasks.size());
expectedNames.clear();
expectedNames.add(BEAN_TASK1_NAME);
expectedNames.add(BEAN_TASK2_NAME);
expectedNames.add(BEAN_TASK3_NAME);
for (Task t : tasks) {
expectedNames.remove(t.getName());
}
assertEquals(0, expectedNames.size());
}
@Deployment
public void testDecideBasedOnBeanMethod() {
ProcessInstance pi = runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnBeanMethod",
CollectionUtil.singletonMap("order", new InclusiveGatewayTestOrder(200)));
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
assertNotNull(task);
assertEquals(BEAN_TASK3_NAME, task.getName());
pi = runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnBeanMethod",
CollectionUtil.singletonMap("order", new InclusiveGatewayTestOrder(125)));
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
assertEquals(2, tasks.size());
List<String> expectedNames = new ArrayList<String>();
expectedNames.add(BEAN_TASK2_NAME);
expectedNames.add(BEAN_TASK3_NAME);
for (Task t : tasks) {
expectedNames.remove(t.getName());
}
assertEquals(0, expectedNames.size());
try {
runtimeService.startProcessInstanceByKey("inclusiveDecisionBasedOnBeanMethod", CollectionUtil.singletonMap("order", new InclusiveGatewayTestOrder(300)));
fail();
} catch (ActivitiException e) {
// Should get an exception indicating that no path could be taken
}
}
@Deployment
public void testInvalidMethodExpression() {
try {
runtimeService.startProcessInstanceByKey("inclusiveInvalidMethodExpression", CollectionUtil.singletonMap("order", new InclusiveGatewayTestOrder(50)));
fail();
} catch (ActivitiException e) {
assertTextPresent("Unknown method used in expression", e.getMessage());
}
}
@Deployment
public void testDefaultSequenceFlow() {
// Input == 1 -> default is not selected, other 2 tasks are selected
ProcessInstance pi = runtimeService.startProcessInstanceByKey("inclusiveGwDefaultSequenceFlow", CollectionUtil.singletonMap("input", 1));
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
assertEquals(2, tasks.size());
Map<String, String> expectedNames = new HashMap<String, String>();
expectedNames.put("Input is one", "Input is one");
expectedNames.put("Input is three or one", "Input is three or one");
for (Task t : tasks) {
expectedNames.remove(t.getName());
}
assertEquals(0, expectedNames.size());
runtimeService.deleteProcessInstance(pi.getId(), null);
// Input == 3 -> default is not selected, "one or three" is selected
pi = runtimeService.startProcessInstanceByKey("inclusiveGwDefaultSequenceFlow", CollectionUtil.singletonMap("input", 3));
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
assertEquals("Input is three or one", task.getName());
// Default input
pi = runtimeService.startProcessInstanceByKey("inclusiveGwDefaultSequenceFlow", CollectionUtil.singletonMap("input", 5));
task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
assertEquals("Default input", task.getName());
}
@Deployment
public void testNoIdOnSequenceFlow() {
ProcessInstance pi = runtimeService.startProcessInstanceByKey("inclusiveNoIdOnSequenceFlow", CollectionUtil.singletonMap("input", 3));
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
assertEquals("Input is more than one", task.getName());
// Both should be enabled on 1
pi = runtimeService.startProcessInstanceByKey("inclusiveNoIdOnSequenceFlow", CollectionUtil.singletonMap("input", 1));
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
assertEquals(2, tasks.size());
Map<String, String> expectedNames = new HashMap<String, String>();
expectedNames.put("Input is one", "Input is one");
expectedNames.put("Input is more than one", "Input is more than one");
for (Task t : tasks) {
expectedNames.remove(t.getName());
}
assertEquals(0, expectedNames.size());
}
/** This test the isReachable() check thaty is done to check if
* upstream tokens can reach the inclusive gateway.
*
* In case of loops, special care needs to be taken in the algorithm,
* or else stackoverflows will happen very quickly.
*/
@Deployment
public void testLoop() {
ProcessInstance pi = runtimeService.startProcessInstanceByKey("inclusiveTestLoop",
CollectionUtil.singletonMap("counter", 1));
Task task = taskService.createTaskQuery().singleResult();
assertEquals("task C", task.getName());
taskService.complete(task.getId());
assertEquals(0, taskService.createTaskQuery().count());
for (Execution execution : runtimeService.createExecutionQuery().list()) {
System.out.println(((ExecutionEntity) execution).getActivityId());
}
assertEquals("Found executions: " + runtimeService.createExecutionQuery().list(), 0, runtimeService.createExecutionQuery().count());
assertProcessEnded(pi.getId());
}
}