/* * 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; import javax.naming.InitialContext; import javax.persistence.EntityManagerFactory; import javax.persistence.PessimisticLockException; import javax.transaction.UserTransaction; import org.jbpm.services.task.HumanTaskConfigurator; import org.jbpm.services.task.HumanTaskServiceFactory; import org.jbpm.services.task.wih.ExternalTaskEventListener; import org.jbpm.test.JbpmTestCase; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.kie.api.runtime.EnvironmentName; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.manager.RegisterableItemsFactory; import org.kie.api.runtime.manager.RuntimeEngine; import org.kie.api.runtime.manager.RuntimeEnvironment; import org.kie.api.runtime.process.ProcessInstance; import org.kie.api.task.TaskLifeCycleEventListener; import org.kie.api.task.TaskService; import org.kie.api.task.model.Group; import org.kie.api.task.model.TaskSummary; import org.kie.api.task.model.User; import org.kie.internal.runtime.manager.InternalRuntimeManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PessimisticLockTasksServiceTest extends JbpmTestCase { private static final Logger logger = LoggerFactory.getLogger(PessimisticLockTasksServiceTest.class); protected Map<String, User> users; protected Map<String, Group> groups; protected Properties conf; protected ExternalTaskEventListener externalTaskEventListener; public PessimisticLockTasksServiceTest() { super(true, true); } @Before public void setUp() throws Exception { super.setUp(); } @After public void tearDown() throws Exception { super.tearDown(); } @Test public void testPessimisticLockingOnTask() throws Exception { final List<Exception> exceptions = new ArrayList<Exception>(); addEnvironmentEntry(EnvironmentName.USE_PESSIMISTIC_LOCKING, true); createRuntimeManager("org/jbpm/test/functional/task/Evaluation2.bpmn"); RuntimeEngine runtimeEngine = getRuntimeEngine(); final KieSession ksession = runtimeEngine.getKieSession(); final TaskService taskService = runtimeEngine.getTaskService(); // setup another instance of task service to allow not synchronized access to cause pessimistic lock exception RuntimeEnvironment runtimeEnv = ((InternalRuntimeManager) manager).getEnvironment(); HumanTaskConfigurator configurator = HumanTaskServiceFactory.newTaskServiceConfigurator() .environment(runtimeEnv.getEnvironment()) .entityManagerFactory((EntityManagerFactory) runtimeEnv.getEnvironment().get(EnvironmentName.ENTITY_MANAGER_FACTORY)) .userGroupCallback(runtimeEnv.getUserGroupCallback()); // register task listeners if any RegisterableItemsFactory itemsFactory = runtimeEnv.getRegisterableItemsFactory(); for (TaskLifeCycleEventListener taskListener : itemsFactory.getTaskListeners()) { configurator.listener(taskListener); } final TaskService internalTaskService = configurator.getTaskService(); logger.info("### Starting process ###"); Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put("employee", "salaboy"); ProcessInstance process = ksession.startProcess("com.sample.evaluation", parameters); //The process is in the first Human Task waiting for its completion Assert.assertEquals(ProcessInstance.STATE_ACTIVE, process.getState()); //gets salaboy's tasks List<TaskSummary> salaboysTasks = taskService.getTasksAssignedAsPotentialOwner("salaboy", "en-UK"); Assert.assertEquals(1, salaboysTasks.size()); final long taskId = salaboysTasks.get(0).getId(); final CountDownLatch t2StartLockedTask = new CountDownLatch(1); final CountDownLatch t1Continue = new CountDownLatch(1); Thread t1 = new Thread() { @Override public void run() { try { UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction"); try { ut.begin(); logger.info("Attempting to lock task instance"); taskService.start(taskId, "salaboy"); t2StartLockedTask.countDown(); t1Continue.await(); } finally { ut.rollback(); } } catch (Exception e) { logger.error("Error on thread ", e); } } }; Thread t2 = new Thread() { @Override public void run() { try { UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction"); try { ut.begin(); t2StartLockedTask.await(); logger.info("Trying to start locked task instance"); try { internalTaskService.start(taskId, "salaboy"); } catch (Exception e) { logger.info("Abort failed with error {}", e.getMessage()); exceptions.add(e); } finally { t1Continue.countDown(); } } finally { ut.rollback(); } } catch (Exception e) { logger.error("Error on thread ", e); } } }; t1.start(); t2.start(); t1.join(); t2.join(); assertEquals(1, exceptions.size()); assertEquals(PessimisticLockException.class.getName(), exceptions.get(0).getClass().getName()); taskService.start(salaboysTasks.get(0).getId(), "salaboy"); // complete task within user transaction to make sure no deadlock happens as both task service and ksession are under tx lock UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction"); try { ut.begin(); taskService.complete(salaboysTasks.get(0).getId(), "salaboy", null); ut.commit(); } catch (Exception ex) { ut.rollback(); throw ex; } List<TaskSummary> pmsTasks = taskService.getTasksAssignedAsPotentialOwner("john", "en-UK"); Assert.assertEquals(1, pmsTasks.size()); List<TaskSummary> hrsTasks = taskService.getTasksAssignedAsPotentialOwner("mary", "en-UK"); Assert.assertEquals(1, hrsTasks.size()); ksession.abortProcessInstance(process.getId()); assertProcessInstanceAborted(process.getId()); } }