/* Copyright 2013 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. 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.jbpm.bpmn2; import java.io.StringReader; import java.time.OffsetDateTime; 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 org.drools.core.process.instance.impl.WorkItemImpl; import org.drools.core.util.IoUtils; import org.jbpm.bpmn2.objects.NotAvailableGoodsReport; import org.jbpm.bpmn2.objects.Person; import org.jbpm.bpmn2.objects.TestWorkItemHandler; import org.jbpm.test.util.CountDownProcessEventListener; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.kie.api.KieBase; import org.kie.api.KieServices; import org.kie.api.builder.KieRepository; import org.kie.api.event.process.DefaultProcessEventListener; import org.kie.api.event.process.ProcessStartedEvent; import org.kie.api.io.Resource; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.process.ProcessInstance; import org.kie.api.runtime.process.WorkItem; import org.kie.internal.io.ResourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RunWith(Parameterized.class) public class StartEventTest extends JbpmBpmn2TestCase { @Parameters public static Collection<Object[]> persistence() { Object[][] data = new Object[][] { { false }, { true } }; return Arrays.asList(data); }; private static final Logger logger = LoggerFactory.getLogger(StartEventTest.class); private KieSession ksession; public StartEventTest(boolean persistence) { super(persistence); } @BeforeClass public static void setup() throws Exception { setUpDataSource(); } @Before public void prepare() { clearHistory(); } @After public void dispose() { if (ksession != null) { ksession.dispose(); ksession = null; } } @Test public void testConditionalStart() throws Exception { KieBase kbase = createKnowledgeBaseWithoutDumper("BPMN2-ConditionalStart.bpmn2"); ksession = createKnowledgeSession(kbase); Person person = new Person(); person.setName("jack"); ksession.insert(person); person = new Person(); person.setName("john"); ksession.insert(person); } @Test(timeout=10000) public void testTimerStartCycleLegacy() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 5); KieBase kbase = createKnowledgeBase("BPMN2-TimerStartCycleLegacy.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); logger.debug("About to start ###### " + new Date()); assertEquals(0, list.size()); // then wait 5 times 5oo ms as that is period configured on the process countDownListener.waitTillCompleted(); ksession.dispose(); assertEquals(5, getNumberOfProcessInstances("Minimal")); } @Test(timeout=10000) public void testTimerStart() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 5); KieBase kbase = createKnowledgeBase("BPMN2-TimerStart.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); countDownListener.waitTillCompleted(); assertEquals(5, getNumberOfProcessInstances("Minimal")); } @Test(timeout=10000) public void testTimerStartDateISO() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 1); byte[] content = IoUtils.readBytesFromInputStream(this.getClass().getResourceAsStream("/BPMN2-TimerStartDate.bpmn2")); String processContent = new String(content, "UTF-8"); OffsetDateTime plusTwoSeconds = OffsetDateTime.now().plusSeconds(2); processContent = processContent.replaceFirst("#\\{date\\}", plusTwoSeconds.toString()); Resource resource = ResourceFactory.newReaderResource(new StringReader(processContent)); resource.setSourcePath("/BPMN2-TimerStartDate.bpmn2"); resource.setTargetPath("/BPMN2-TimerStartDate.bpmn2"); KieBase kbase = createKnowledgeBaseFromResources(resource); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); countDownListener.waitTillCompleted(); assertEquals(1, list.size()); } @Test(timeout=10000) public void testTimerStartCycleISO() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 6); KieBase kbase = createKnowledgeBase("BPMN2-TimerStartISO.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); countDownListener.waitTillCompleted(); assertEquals(6, getNumberOfProcessInstances("Minimal")); } @Test(timeout=10000) public void testTimerStartDuration() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 1); KieBase kbase = createKnowledgeBase("BPMN2-TimerStartDuration.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); countDownListener.waitTillCompleted(); assertEquals(1, getNumberOfProcessInstances("Minimal")); } @Test(timeout=10000) public void testTimerStartCron() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 5); KieBase kbase = createKnowledgeBase("BPMN2-TimerStartCron.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void afterProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); // Timer in the process takes 1s, so after 5 seconds, there should be 5 process IDs in the list. countDownListener.waitTillCompleted(); assertEquals(5, getNumberOfProcessInstances("Minimal")); } @Test public void testSignalToStartProcess() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-SignalStart.bpmn2", "BPMN2-IntermediateThrowEventSignal.bpmn2"); ksession = createKnowledgeSession(kbase); TestWorkItemHandler handler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", handler); final List<String> startedProcesses = new ArrayList<String>(); ksession.addEventListener(new DefaultProcessEventListener() { @Override public void beforeProcessStarted(ProcessStartedEvent event) { startedProcesses.add(event.getProcessInstance().getProcessId()); } }); ProcessInstance processInstance = ksession .startProcess("SignalIntermediateEvent"); assertProcessInstanceFinished(processInstance, ksession); assertEquals(1, getNumberOfProcessInstances("Minimal")); assertEquals(1, getNumberOfProcessInstances("SignalIntermediateEvent")); } @Test public void testSignalStart() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-SignalStart.bpmn2"); ksession = createKnowledgeSession(kbase); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); ksession.signalEvent("MySignal", "NewValue"); assertEquals(1, getNumberOfProcessInstances("Minimal")); } @Test public void testSignalStartDynamic() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-SignalStart.bpmn2"); ksession = createKnowledgeSession(kbase); // create KieContainer after session was created to make sure no runtime data // will be used during serialization (deep clone) KieServices ks = KieServices.Factory.get(); KieRepository kr = ks.getRepository(); KieContainer kContainer = ks.newKieContainer(kr.getDefaultReleaseId()); kContainer.getKieBase(); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { logger.info("{}", event.getProcessInstance().getId()); list.add(event.getProcessInstance().getId()); } }); ksession.signalEvent("MySignal", "NewValue"); assertEquals(1, getNumberOfProcessInstances("Minimal")); // now remove the process from kbase to make sure runtime based listeners are removed from signal manager kbase.removeProcess("Minimal"); try { ksession.signalEvent("MySignal", "NewValue"); } catch (IllegalArgumentException e) { assertEquals("Unknown process ID: Minimal", e.getMessage()); } // must be still one as the process was removed assertEquals(1, getNumberOfProcessInstances("Minimal")); } @Test public void testMessageStart() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-MessageStart.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.signalEvent("Message-HelloMessage", "NewValue"); assertEquals(1, getNumberOfProcessInstances("Minimal")); } @Test public void testMultipleStartEventsRegularStart() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-MultipleStartEventProcess.bpmn2"); ksession = createKnowledgeSession(kbase); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); ProcessInstance processInstance = ksession .startProcess("MultipleStartEvents"); assertProcessInstanceActive(processInstance); ksession = restoreSession(ksession, true); WorkItem workItem = workItemHandler.getWorkItem(); assertNotNull(workItem); assertEquals("john", workItem.getParameter("ActorId")); ksession.getWorkItemManager().completeWorkItem(workItem.getId(), null); assertProcessInstanceFinished(processInstance, ksession); } @Test(timeout=10000) public void testMultipleStartEventsStartOnTimer() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartTimer", 5); KieBase kbase = createKnowledgeBase("BPMN2-MultipleStartEventProcess.bpmn2"); ksession = createKnowledgeSession(kbase); try { ksession.addEventListener(countDownListener); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); // Timer in the process takes 500ms, so after 2.5 seconds, there should be 5 process IDs in the list. countDownListener.waitTillCompleted(); assertEquals(5, getNumberOfProcessInstances("MultipleStartEvents")); } finally { abortProcessInstances(ksession); } } @Test public void testMultipleEventBasedStartEventsSignalStart() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-MultipleEventBasedStartEventProcess.bpmn2"); ksession = createKnowledgeSession(kbase); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void afterProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); ksession.signalEvent("startSignal", null); assertEquals(1, list.size()); WorkItem workItem = workItemHandler.getWorkItem(); long processInstanceId = ((WorkItemImpl) workItem) .getProcessInstanceId(); ProcessInstance processInstance = ksession .getProcessInstance(processInstanceId); ksession = restoreSession(ksession, true); assertNotNull(workItem); assertEquals("john", workItem.getParameter("ActorId")); ksession.getWorkItemManager().completeWorkItem(workItem.getId(), null); assertProcessInstanceFinished(processInstance, ksession); } @Test public void testMultipleEventBasedStartEventsDifferentPaths() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-MultipleStartEventProcessDifferentPaths.bpmn2"); ksession = createKnowledgeSession(kbase); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void afterProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); ksession.startProcess("muliplestartevents", null); assertEquals(1, list.size()); WorkItem workItem = workItemHandler.getWorkItem(); long processInstanceId = ((WorkItemImpl) workItem) .getProcessInstanceId(); ProcessInstance processInstance = ksession .getProcessInstance(processInstanceId); ksession = restoreSession(ksession, true); assertNotNull(workItem); assertEquals("john", workItem.getParameter("ActorId")); ksession.getWorkItemManager().completeWorkItem(workItem.getId(), null); assertProcessInstanceFinished(processInstance, ksession); assertNodeTriggered(processInstanceId, "Start", "Script 1", "User task", "End"); } @Test(timeout=10000) public void testMultipleEventBasedStartEventsTimerDifferentPaths() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartTimer", 2); KieBase kbase = createKnowledgeBase("BPMN2-MultipleStartEventProcessDifferentPaths.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); // Timer in the process takes 1000ms, so after 2 seconds, there should be 2 process IDs in the list. countDownListener.waitTillCompleted(); assertEquals(2, list.size()); List<WorkItem> workItems = workItemHandler.getWorkItems(); for (WorkItem workItem : workItems) { long processInstanceId = ((WorkItemImpl) workItem).getProcessInstanceId(); ProcessInstance processInstance = ksession .getProcessInstance(processInstanceId); ksession = restoreSession(ksession, true); assertNotNull(workItem); assertEquals("john", workItem.getParameter("ActorId")); ksession.getWorkItemManager().completeWorkItem(workItem.getId(), null); assertProcessInstanceFinished(processInstance, ksession); assertNodeTriggered(processInstanceId, "StartTimer", "Script 2", "User task", "End"); } } @Test public void testMultipleEventBasedStartEventsSignalDifferentPaths() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-MultipleStartEventProcessDifferentPaths.bpmn2"); ksession = createKnowledgeSession(kbase); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void afterProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); ksession.signalEvent("startSignal", null); assertEquals(1, list.size()); WorkItem workItem = workItemHandler.getWorkItem(); long processInstanceId = ((WorkItemImpl) workItem) .getProcessInstanceId(); ProcessInstance processInstance = ksession .getProcessInstance(processInstanceId); ksession = restoreSession(ksession, true); assertNotNull(workItem); assertEquals("john", workItem.getParameter("ActorId")); ksession.getWorkItemManager().completeWorkItem(workItem.getId(), null); assertProcessInstanceFinished(processInstance, ksession); assertNodeTriggered(processInstanceId, "StartSignal", "Script 3", "User task", "End"); } @Test(timeout=10000) public void testMultipleEventBasedStartEventsStartOnTimer() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartTimer", 5); KieBase kbase = createKnowledgeBase("BPMN2-MultipleEventBasedStartEventProcess.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler); final List<Long> list = new ArrayList<Long>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance().getId()); } }); assertEquals(0, list.size()); // Timer in the process takes 500ms, so after 2.5 seconds, there should be 5 process IDs in the list. countDownListener.waitTillCompleted(); assertEquals(5, getNumberOfProcessInstances("MultipleStartEvents")); } @Test(timeout=10000) public void testTimerCycle() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("start", 5); KieBase kbase = createKnowledgeBase("timer/BPMN2-StartTimerCycle.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); StartCountingListener listener = new StartCountingListener(); ksession.addEventListener(listener); countDownListener.waitTillCompleted(); assertEquals(5, listener.getCount("start.cycle")); } @Test(timeout=10000) public void testSignalStartWithTransformation() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 1); KieBase kbase = createKnowledgeBaseWithoutDumper("BPMN2-SignalStartWithTransformation.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); final List<ProcessInstance> list = new ArrayList<ProcessInstance>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance()); } }); ksession.signalEvent("MySignal", "NewValue"); countDownListener.waitTillCompleted(); assertEquals(1, getNumberOfProcessInstances("Minimal")); assertNotNull(list); assertEquals(1, list.size()); String var = getProcessVarValue(list.get(0), "x"); assertEquals("NEWVALUE", var); } /** * This is how I would expect the start event to work (same as the recurring event) */ @Test(timeout=10000) public void testTimerDelay() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("start", 1); KieBase kbase = createKnowledgeBase("timer/BPMN2-StartTimerDuration.bpmn2"); ksession = createKnowledgeSession(kbase); ksession.addEventListener(countDownListener); StartCountingListener listener = new StartCountingListener(); ksession.addEventListener(listener); countDownListener.waitTillCompleted(); assertEquals(1, listener.getCount("start.delaying")); } @Test public void testSignalStartWithCustomEvent() throws Exception { KieBase kbase = createKnowledgeBase("BPMN2-SingalStartWithCustomEvent.bpmn2"); ksession = createKnowledgeSession(kbase); final List<ProcessInstance> list = new ArrayList<ProcessInstance>(); ksession.addEventListener(new DefaultProcessEventListener() { public void beforeProcessStarted(ProcessStartedEvent event) { list.add(event.getProcessInstance()); } }); NotAvailableGoodsReport report = new NotAvailableGoodsReport("test"); ksession.signalEvent("SignalNotAvailableGoods", report); assertEquals(1, getNumberOfProcessInstances("org.jbpm.example.SignalObjectProcess")); assertEquals(1, list.size()); assertProcessVarValue(list.get(0), "report", "NotAvailableGoodsReport{type:test}"); } @Test public void testInvalidDateTimerStart() throws Exception { try { createKnowledgeBase("timer/BPMN2-StartTimerDateInvalid.bpmn2"); fail("Should fail as timer expression is not valid"); } catch (RuntimeException e) { assertTrue(e.getMessage().contains("Could not parse date 'abcdef'")); } } @Test public void testInvalidDurationTimerStart() throws Exception { try { createKnowledgeBase("timer/BPMN2-StartTimerDurationInvalid.bpmn2"); fail("Should fail as timer expression is not valid"); } catch (Exception e) { assertTrue(e.getMessage().contains("Could not parse delay 'abcdef'")); } } @Test public void testInvalidCycleTimerStart() throws Exception { try { createKnowledgeBase("timer/BPMN2-StartTimerCycleInvalid.bpmn2"); fail("Should fail as timer expression is not valid"); } catch (Exception e) { assertTrue(e.getMessage().contains("Could not parse delay 'abcdef'")); } } private static class StartCountingListener extends DefaultProcessEventListener { private Map<String, Integer> map = new HashMap<String, Integer>(); public void beforeProcessStarted(ProcessStartedEvent event) { String processId = event.getProcessInstance().getProcessId(); Integer count = map.get(processId); if (count == null) { map.put(processId, 1); } else { map.put(processId, count + 1); } } public int getCount(String processId) { Integer count = map.get(processId); return (count == null) ? 0 : count; } } }