/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.jbpm.test.functional.task; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; import javax.persistence.EntityManagerFactory; import org.jbpm.process.audit.AuditLogService; import org.jbpm.process.audit.JPAAuditLogService; import org.jbpm.process.core.timer.TimerServiceRegistry; import org.jbpm.services.task.HumanTaskServiceFactory; import org.jbpm.services.task.identity.JBossUserGroupCallbackImpl; import org.jbpm.services.task.utils.ContentMarshallerHelper; import org.jbpm.test.JbpmTestCase; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.kie.api.KieBase; import org.kie.api.io.ResourceType; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.manager.RuntimeEngine; import org.kie.api.runtime.manager.RuntimeEnvironment; import org.kie.api.runtime.manager.RuntimeEnvironmentBuilder; import org.kie.api.runtime.manager.RuntimeManager; import org.kie.api.runtime.manager.RuntimeManagerFactory; import org.kie.api.runtime.manager.audit.ProcessInstanceLog; import org.kie.api.runtime.process.ProcessInstance; import org.kie.api.task.TaskService; import org.kie.api.task.model.Content; import org.kie.api.task.model.Task; import org.kie.api.task.model.TaskSummary; import org.kie.internal.builder.KnowledgeBuilder; import org.kie.internal.builder.KnowledgeBuilderError; import org.kie.internal.builder.KnowledgeBuilderFactory; import org.kie.internal.io.ResourceFactory; import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext; import org.kie.internal.task.api.InternalTaskService; import org.kie.internal.task.api.TaskModelProvider; import org.kie.internal.task.api.UserGroupCallback; public class ConcurrentHumanTaskTest extends JbpmTestCase { public ConcurrentHumanTaskTest() { super(true, true); } private static final int THREADS = 2; @Before public void populateOrgEntity() { TaskService taskService = HumanTaskServiceFactory.newTaskServiceConfigurator().entityManagerFactory(getEmf()).getTaskService(); ((InternalTaskService)taskService).addUser(TaskModelProvider.getFactory().newUser("krisv")); ((InternalTaskService)taskService).addUser(TaskModelProvider.getFactory().newUser("sales-rep")); ((InternalTaskService)taskService).addUser(TaskModelProvider.getFactory().newUser("john")); ((InternalTaskService)taskService).addUser(TaskModelProvider.getFactory().newUser("Administrator")); ((InternalTaskService)taskService).addGroup(TaskModelProvider.getFactory().newGroup("sales")); ((InternalTaskService)taskService).addGroup(TaskModelProvider.getFactory().newGroup("PM")); ((InternalTaskService)taskService).addGroup(TaskModelProvider.getFactory().newGroup("Administrators")); } @Test(timeout=10000) public void testConcurrentInvocationsIncludingUserTasks() throws Exception { CountDownLatch latch = new CountDownLatch(THREADS); for (int i = 0; i < THREADS; i++) { ProcessRunner pr = new ProcessRunner(i, getEmf(), latch); Thread t = new Thread(pr, i + "-process-runner"); t.start(); } latch.await(); AuditLogService logService = new JPAAuditLogService(getEmf()); List<? extends ProcessInstanceLog> logs = logService.findProcessInstances("com.sample.humantask.concurrent"); assertEquals(2, logs.size()); for (ProcessInstanceLog log : logs) { assertEquals(ProcessInstance.STATE_COMPLETED, log.getStatus().intValue()); } logService.dispose(); } } class ProcessRunner implements Runnable { private int i; private EntityManagerFactory emf; private CountDownLatch latch; public ProcessRunner(int i, EntityManagerFactory emf, CountDownLatch latch) { this.i = i; this.emf = emf; this.latch = latch; } private RuntimeManager getRuntimeManager(String process, int i) { Properties properties = new Properties(); properties.setProperty("krisv", ""); properties.setProperty("sales-rep", "sales"); properties.setProperty("john", "PM"); KnowledgeBuilder knowledgeBuilder = createKBuilder(process, ResourceType.BPMN2); KieBase kieBase = knowledgeBuilder.newKnowledgeBase(); UserGroupCallback userGroupCallback = new JBossUserGroupCallbackImpl( properties); // load up the knowledge base TimerServiceRegistry.getInstance(); RuntimeEnvironment environment = RuntimeEnvironmentBuilder.Factory.get().newDefaultBuilder() .userGroupCallback(userGroupCallback).persistence(true) .entityManagerFactory(emf).knowledgeBase(kieBase).get(); return RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment, "id-" + i); } private KnowledgeBuilder createKBuilder(String resource, ResourceType resourceType) { KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add(ResourceFactory.newClassPathResource(resource), resourceType); if (kbuilder.hasErrors()) { int errors = kbuilder.getErrors().size(); if (errors > 0) { System.out.println("Found " + errors + " errors"); for (KnowledgeBuilderError error : kbuilder.getErrors()) { System.out.println(error.getMessage()); } } throw new IllegalArgumentException("Application process definition has errors, see log for more details"); } return kbuilder; } @Override public void run() { System.out.println(" building runtime: " + i); RuntimeManager manager = getRuntimeManager("org/jbpm/test/functional/task/ConcurrentHumanTask.bpmn", i); RuntimeEngine runtime = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession = runtime.getKieSession(); // start a new process instance Map<String, Object> params = new HashMap<String, Object>(); params.put("userId", "krisv"); params.put("description", "Need a new laptop computer"); ProcessInstance pi = ksession.startProcess("com.sample.humantask.concurrent", params); System.out.println(" starting runtime: " + i); HumanTaskResolver htr = new HumanTaskResolver(pi.getId(), manager, this.latch); Thread t = new Thread(htr, i + "-ht-resolver"); t.start(); } } class HumanTaskResolver implements Runnable { private final long pid; private final RuntimeManager runtime; private CountDownLatch latch; public HumanTaskResolver(long pid, RuntimeManager runtime, CountDownLatch latch) { this.pid = pid; this.runtime = runtime; this.latch = latch; System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>" + pid); } @Override public void run() { System.out.println(pid + " running tasks"); // "sales-rep" reviews request TaskService taskService1 = getTaskService(); List<TaskSummary> tasks1 = taskService1.getTasksAssignedAsPotentialOwner("sales", "en-UK"); TaskSummary task1 = selectTaskForProcessInstance(tasks1); System.out.println("Sales-rep executing task " + task1.getName() + "(" + task1.getId() + ": " + task1.getDescription() + ")"); taskService1.claim(task1.getId(), "sales-rep"); taskService1.start(task1.getId(), "sales-rep"); Map<String, Object> results = new HashMap<String, Object>(); results.put("comment", "Agreed, existing laptop needs replacing"); results.put("outcome", "Accept"); taskService1.complete(task1.getId(), "sales-rep", results); TaskService taskService2 = getTaskService(); // "krisv" approves result List<TaskSummary> tasks2 = taskService2.getTasksAssignedAsPotentialOwner("krisv", "en-UK"); TaskSummary task2 = selectTaskForProcessInstance(tasks2); System.out.println("krisv executing task " + task2.getName() + "(" + task2.getId() + ": " + task2.getDescription() + ")"); taskService2.start(task2.getId(), "krisv"); results = new HashMap<String, Object>(); results.put("outcome", "Agree"); taskService2.complete(task2.getId(), "krisv", results); TaskService taskService3 = getTaskService(); // "john" as manager reviews request List<TaskSummary> tasks3 = taskService3.getTasksAssignedAsPotentialOwner("john", "en-UK"); TaskSummary task3 = selectTaskForProcessInstance(tasks3); System.out.println("john executing task " + task3.getName() + "(" + task3.getId() + ": " + task3.getDescription() + ")"); taskService3.claim(task3.getId(), "john"); taskService3.start(task3.getId(), "john"); results = new HashMap<String, Object>(); results.put("outcome", "Agree"); taskService3.complete(task3.getId(), "john", results); TaskService taskService4 = getTaskService(); // "sales-rep" gets notification List<TaskSummary> tasks4 = taskService4.getTasksAssignedAsPotentialOwner("sales-rep", "en-UK"); TaskSummary task4 = selectTaskForProcessInstance(tasks4); System.out.println("sales-rep executing task " + task4.getName() + "(" + task4.getId() + ": " + task4.getDescription() + ")"); taskService4.start(task4.getId(), "sales-rep"); Task task = taskService4.getTaskById(task4.getId()); Content content = taskService4.getContentById(task.getTaskData().getDocumentContentId()); Object result = ContentMarshallerHelper.unmarshall(content.getContent(), null); Assert.assertNotNull(result); taskService4.complete(task4.getId(), "sales-rep", null); System.out.println("Process instance completed"); runtime.close(); latch.countDown(); } public TaskService getTaskService() { return runtime.getRuntimeEngine(ProcessInstanceIdContext.get(pid)).getTaskService(); } protected TaskSummary selectTaskForProcessInstance(List<TaskSummary> tasks) { for (TaskSummary ts : tasks) { if (ts.getProcessInstanceId().longValue() == pid) { return ts; } } return null; } }