/* * 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.runtime.manager.impl.error; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.persistence.EntityManagerFactory; import org.jbpm.runtime.manager.impl.AbstractRuntimeManager; import org.jbpm.runtime.manager.impl.jpa.EntityManagerFactoryManager; import org.jbpm.runtime.manager.util.TestUtil; import org.jbpm.services.task.events.DefaultTaskEventListener; import org.jbpm.services.task.exception.TaskExecutionException; import org.jbpm.services.task.identity.JBossUserGroupCallbackImpl; import org.jbpm.test.util.AbstractBaseTest; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.kie.api.event.process.DefaultProcessEventListener; import org.kie.api.event.process.ProcessStartedEvent; 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.task.TaskEvent; import org.kie.api.task.TaskLifeCycleEventListener; import org.kie.api.task.TaskService; import org.kie.api.task.model.TaskSummary; import org.kie.internal.io.ResourceFactory; import org.kie.internal.runtime.error.ExecutionError; import org.kie.internal.runtime.error.ExecutionErrorManager; import org.kie.internal.runtime.error.ExecutionErrorStorage; import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext; import org.kie.internal.task.api.EventService; import org.kie.internal.task.api.UserGroupCallback; import bitronix.tm.resource.jdbc.PoolingDataSource; @RunWith(Parameterized.class) public class ExecutionErrorHandlingRuntimeManagerTest extends AbstractBaseTest { @Parameters(name = "Strategy : {0}") public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { {"singleton"}, {"request"}, {"processinstance"}, {"case"} }); } private String strategy; public ExecutionErrorHandlingRuntimeManagerTest(String strategy) { this.strategy = strategy; } private PoolingDataSource pds; private UserGroupCallback userGroupCallback; private EntityManagerFactory emf; private RuntimeManager manager; @Rule public TestName testName = new TestName(); @Before public void setup() { TestUtil.cleanupSingletonSessionId(); pds = TestUtil.setupPoolingDataSource(); emf = EntityManagerFactoryManager.get().getOrCreate("org.jbpm.persistence.jpa"); Properties properties= new Properties(); properties.setProperty("mary", "HR"); properties.setProperty("john", "HR"); userGroupCallback = new JBossUserGroupCallbackImpl(properties); createRuntimeManager(); } @After public void teardown() { if (manager != null) { manager.close(); } EntityManagerFactoryManager.get().clear(); pds.close(); } private void createRuntimeManager() { RuntimeEnvironment environment = createEnvironment(); if ("singleton".equals(strategy)) { manager = RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment, "first"); } else if ("processinstance".equals(strategy)) { manager = RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager(environment, "first"); } else if ("request".equals(strategy)) { manager = RuntimeManagerFactory.Factory.get().newPerRequestRuntimeManager(environment, "first"); } else if ("case".equals(strategy)) { manager = RuntimeManagerFactory.Factory.get().newPerCaseRuntimeManager(environment, "first"); } assertNotNull(manager); } @Test public void testBasicScriptFailure() { RuntimeEngine runtime1 = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession1 = runtime1.getKieSession(); assertNotNull(ksession1); try { ksession1.startProcess("BrokenScriptTask"); fail("Start process should fail due to broken script"); } catch (Throwable e) { // expected } manager.disposeRuntimeEngine(runtime1); ExecutionErrorManager errorManager = ((AbstractRuntimeManager) manager).getExecutionErrorManager(); ExecutionErrorStorage storage = errorManager.getStorage(); List<ExecutionError> errors = storage.list(0, 10); assertNotNull(errors); assertEquals(1, errors.size()); assertExecutionError(errors.get(0), "Process", "BrokenScriptTask", "Hello"); } @Test public void testScriptFailureAfterUserTask() { RuntimeEngine runtime1 = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession1 = runtime1.getKieSession(); assertNotNull(ksession1); ksession1.startProcess("UserTaskWithRollback"); TaskService taskService = runtime1.getTaskService(); List<TaskSummary> tasks = taskService.getTasksAssignedAsPotentialOwner("john", "en-UK"); assertEquals(1, tasks.size()); long taskId = tasks.get(0).getId(); taskService.start(taskId, "john"); Map<String, Object> results = new HashMap<>(); results.put("output1", "rollback"); try { taskService.complete(taskId, "john", results); fail("Complete task should fail due to broken script"); } catch (Throwable e) { // expected } manager.disposeRuntimeEngine(runtime1); ExecutionErrorManager errorManager = ((AbstractRuntimeManager) manager).getExecutionErrorManager(); ExecutionErrorStorage storage = errorManager.getStorage(); List<ExecutionError> errors = storage.list(0, 10); assertNotNull(errors); assertEquals(1, errors.size()); assertExecutionError(errors.get(0), "Process", "UserTaskWithRollback", "Script Task 1"); } @SuppressWarnings("unchecked") @Test public void testUserTaskFailure() { RuntimeEngine runtime1 = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession1 = runtime1.getKieSession(); assertNotNull(ksession1); ksession1.startProcess("UserTaskWithRollback"); TaskService taskService = runtime1.getTaskService(); List<TaskSummary> tasks = taskService.getTasksAssignedAsPotentialOwner("john", "en-UK"); assertEquals(1, tasks.size()); long taskId = tasks.get(0).getId(); try { ((EventService<TaskLifeCycleEventListener>)taskService).registerTaskEventListener(new DefaultTaskEventListener(){ @Override public void afterTaskStartedEvent(TaskEvent event) { throw new TaskExecutionException("On purpose"); } }); taskService.start(taskId, "john"); fail("Start task should fail due to broken script"); } catch (Throwable e) { // expected } manager.disposeRuntimeEngine(runtime1); ExecutionErrorManager errorManager = ((AbstractRuntimeManager) manager).getExecutionErrorManager(); ExecutionErrorStorage storage = errorManager.getStorage(); List<ExecutionError> errors = storage.list(0, 10); assertNotNull(errors); assertEquals(1, errors.size()); assertExecutionError(errors.get(0), "Task", "UserTaskWithRollback", "Hello"); } @Test public void testDataBaseFailureInMemoryStorage() { RuntimeEngine runtime1 = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession1 = runtime1.getKieSession(); assertNotNull(ksession1); ksession1.addEventListener(new DefaultProcessEventListener(){ @Override public void afterProcessStarted(ProcessStartedEvent event) { pds.close(); } }); try { ksession1.startProcess("UserTaskWithRollback"); fail("Start process should fail due to data base error"); } catch (Throwable e) { // expected } int expectedErrors = 1; try { manager.disposeRuntimeEngine(runtime1); } catch (Exception e) { // expected to fail for some strategies due to data source being down expectedErrors++; } ExecutionErrorManager errorManager = ((AbstractRuntimeManager) manager).getExecutionErrorManager(); ExecutionErrorStorage storage = errorManager.getStorage(); List<ExecutionError> errors = storage.list(0, 10); assertNotNull(errors); assertEquals(expectedErrors, errors.size()); assertExecutionError(errors.get(0), "DB", "UserTaskWithRollback", "Hello"); if (expectedErrors == 2) { assertExecutionError(errors.get(1), "DB", "UserTaskWithRollback", "Hello"); } } private RuntimeEnvironment createEnvironment() { ExecutionErrorStorage storage = new ExecutionErrorStorage() { private List<ExecutionError> errors = new ArrayList<>(); @Override public ExecutionError store(ExecutionError error) { this.errors.add(error); return error; } @Override public List<ExecutionError> listByProcessInstance(Long processInstanceId, Integer page, Integer pageSize) { return errors; } @Override public List<ExecutionError> listByDeployment(String deploymentId, Integer page, Integer pageSize) { return errors; } @Override public List<ExecutionError> listByActivity(String activityName, Integer page, Integer pageSize) { return errors; } @Override public List<ExecutionError> list(Integer page, Integer pageSize) { return errors; } @Override public ExecutionError get(String errorId) { return errors.stream().filter(err -> err.getErrorId().equals(errorId)).findFirst().get(); } @Override public void acknowledge(String user, String... errorIds) { for (String errorId : errorIds) { ExecutionError error = get(errorId); error.setAcknowledged(true); error.setAcknowledgedBy(user); error.setAcknowledgedAt(new Date()); } } }; RuntimeEnvironmentBuilder environmentBuilder = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .userGroupCallback(userGroupCallback) .addAsset(ResourceFactory.newClassPathResource("BPMN2-BrokenScriptTask.bpmn2"), ResourceType.BPMN2) .addAsset(ResourceFactory.newClassPathResource("BPMN2-UserTaskWithRollback.bpmn2"), ResourceType.BPMN2); if (testName.getMethodName().contains("InMemoryStorage")) { environmentBuilder.addEnvironmentEntry("ExecutionErrorStorage", storage); } return environmentBuilder.get(); } private void assertExecutionError(ExecutionError error, String type, String processId, String activityName) { assertNotNull(error); assertEquals(type, error.getType()); assertEquals(processId, error.getProcessId()); assertEquals(activityName, error.getActivityName()); assertEquals(manager.getIdentifier(), error.getDeploymentId()); assertNotNull(error.getError()); assertNotNull(error.getErrorMessage()); assertNotNull(error.getActivityId()); assertNotNull(error.getProcessInstanceId()); assertNull(error.getAcknowledgedAt()); assertNull(error.getAcknowledgedBy()); assertFalse(error.isAcknowledged()); } }