/* * 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.timer; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.naming.InitialContext; import javax.persistence.Persistence; import javax.sql.DataSource; import org.drools.core.time.TimerService; import org.jbpm.process.core.timer.TimerServiceRegistry; import org.jbpm.process.core.timer.impl.GlobalTimerService; import org.jbpm.process.core.timer.impl.QuartzSchedulerService; import org.jbpm.runtime.manager.impl.AbstractRuntimeManager; import org.jbpm.services.task.identity.JBossUserGroupCallbackImpl; import org.jbpm.test.listener.CountDownProcessEventListener; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; 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.ProcessEventListener; import org.kie.api.event.process.ProcessNodeLeftEvent; 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.runtime.process.ProcessInstance; import org.kie.api.task.UserGroupCallback; import org.kie.internal.io.ResourceFactory; import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext; @RunWith(Parameterized.class) public class GlobalQuartzDBTimerServiceTest extends GlobalTimerServiceBaseTest { private int managerType; @Parameters public static Collection<Object[]> persistence() { Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 } }; return Arrays.asList(data); }; public GlobalQuartzDBTimerServiceTest(int managerType) { this.managerType = managerType; } @Before public void setUp() { cleanupSingletonSessionId(); emf = Persistence.createEntityManagerFactory("org.jbpm.test.persistence"); System.setProperty("org.quartz.properties", "quartz-db.properties"); testCreateQuartzSchema(); globalScheduler = new QuartzSchedulerService(); ((QuartzSchedulerService)globalScheduler).forceShutdown(); } @After public void tearDown() { try { globalScheduler.shutdown(); } catch (Exception e) { } cleanup(); System.clearProperty("org.quartz.properties"); } @Override protected RuntimeManager getManager(RuntimeEnvironment environment, boolean waitOnStart) { RuntimeManager manager = null; if (managerType ==1) { manager = RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment); } else if (managerType == 2) { manager = RuntimeManagerFactory.Factory.get().newPerRequestRuntimeManager(environment); } else if (managerType == 3) { manager = RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager(environment); } else { throw new IllegalArgumentException("Invalid runtime maanger type"); } if (waitOnStart) { // wait for the 2 seconds (default startup delay for quartz) try { Thread.sleep(2000); } catch (InterruptedException e) { // do nothing } } return manager; } @Test(timeout=20000) public void testTimerStartManagerClose() throws Exception { CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 3); QuartzSchedulerService additionalCopy = new QuartzSchedulerService(); additionalCopy.initScheduler(null); // prepare listener to assert results final List<Long> timerExporations = new ArrayList<Long>(); ProcessEventListener listener = new DefaultProcessEventListener(){ @Override public void beforeProcessStarted(ProcessStartedEvent event) { timerExporations.add(event.getProcessInstance().getId()); } }; environment = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/TimerStart2.bpmn2"), ResourceType.BPMN2) .schedulerService(globalScheduler) .registerableItemsFactory(new TestRegisterableItemsFactory(listener, countDownListener)) .get(); manager = getManager(environment, false); RuntimeEngine runtime = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession = runtime.getKieSession(); assertEquals(0, timerExporations.size()); countDownListener.waitTillCompleted(); manager.disposeRuntimeEngine(runtime); int atDispose = timerExporations.size(); assertTrue(atDispose > 0); ((AbstractRuntimeManager)manager).close(true); countDownListener.reset(1); countDownListener.waitTillCompleted(3000); assertEquals(atDispose, timerExporations.size()); additionalCopy.shutdown(); } /** * Test that illustrates that jobs are persisted and survives server restart * and as soon as GlobalTimerService is active jobs are fired and it loads and aborts the * process instance to illustrate jobs are properly removed when isntance is aborted * NOTE: this test is disabled by default as it requires real db (not in memory) * and test to be executed separately each with new jvm process */ @Test @Ignore public void testAbortGlobalTestService() throws Exception { RuntimeEnvironment environment = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/IntermediateCatchEventTimerCycle3.bpmn2"), ResourceType.BPMN2) .addConfiguration("drools.timerService", "org.jbpm.process.core.timer.impl.RegisteredTimerServiceDelegate") .get(); RuntimeManager manger = RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment); // build GlobalTimerService instance TimerService globalTs = new GlobalTimerService(manger, globalScheduler); // and register it in the registry under 'default' key TimerServiceRegistry.getInstance().registerTimerService("default", globalTs); // prepare listener to assert results final List<Long> timerExporations = new ArrayList<Long>(); ProcessEventListener listener = new DefaultProcessEventListener(){ @Override public void afterNodeLeft(ProcessNodeLeftEvent event) { if (event.getNodeInstance().getNodeName().equals("timer")) { timerExporations.add(event.getProcessInstance().getId()); } } }; long id = -1; Thread.sleep(5000); RuntimeEngine runtime = manger.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession = runtime.getKieSession(); ksession.addEventListener(listener); ksession.abortProcessInstance(id); ProcessInstance processInstance = ksession.getProcessInstance(id); assertNull(processInstance); // let's wait to ensure no more timers are expired and triggered Thread.sleep(3000); ksession.dispose(); } /** * Test that illustrates that jobs are persisted and survives server restart * and as soon as GlobalTimerService is active jobs are fired * NOTE: this test is disabled by default as it requires real db (not in memory) * and test to be executed separately each with new jvm process */ @Test @Ignore public void testContinueGlobalTestService() throws Exception { RuntimeEnvironment environment = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/IntermediateCatchEventTimerCycle2.bpmn2"), ResourceType.BPMN2) .addConfiguration("drools.timerService", "org.jbpm.process.core.timer.impl.RegisteredTimerServiceDelegate") .get(); RuntimeManager manger = RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment); // build GlobalTimerService instance TimerService globalTs = new GlobalTimerService(manger, globalScheduler); // and register it in the registry under 'default' key TimerServiceRegistry.getInstance().registerTimerService("default", globalTs); // prepare listener to assert results final List<Long> timerExporations = new ArrayList<Long>(); ProcessEventListener listener = new DefaultProcessEventListener(){ @Override public void afterNodeLeft(ProcessNodeLeftEvent event) { if (event.getNodeInstance().getNodeName().equals("timer")) { timerExporations.add(event.getProcessInstance().getId()); } } }; Thread.sleep(5000); } @Test(timeout=20000) public void testContinueTimer() throws Exception { // JBPM-4443 CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("timer", 2); // prepare listener to assert results final List<Long> timerExporations = new ArrayList<Long>(); ProcessEventListener listener = new DefaultProcessEventListener(){ @Override public void afterNodeLeft(ProcessNodeLeftEvent event) { if (event.getNodeInstance().getNodeName().equals("timer")) { timerExporations.add(event.getProcessInstance().getId()); } } }; // No special configuration for TimerService in order to test RuntimeManager default environment = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/IntermediateCatchEventTimerCycle4.bpmn2"), ResourceType.BPMN2) .registerableItemsFactory(new TestRegisterableItemsFactory(listener, countDownListener)) .get(); manager = getManager(environment, true); RuntimeEngine runtime = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession = runtime.getKieSession(); ProcessInstance processInstance = ksession.startProcess("IntermediateCatchEvent"); manager.disposeRuntimeEngine(runtime); countDownListener.waitTillCompleted(); manager.close(); countDownListener.reset(1); // ---- restart ---- environment = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/IntermediateCatchEventTimerCycle4.bpmn2"), ResourceType.BPMN2) .registerableItemsFactory(new TestRegisterableItemsFactory(listener)) .get(); manager = getManager(environment, true); manager.disposeRuntimeEngine(runtime); countDownListener.waitTillCompleted(3000); assertEquals(2, timerExporations.size()); manager.close(); } @Test(timeout=20000) public void testTimerRequiresRecoveryFlagSet() throws Exception { Properties properties= new Properties(); properties.setProperty("mary", "HR"); properties.setProperty("john", "HR"); UserGroupCallback userGroupCallback = new JBossUserGroupCallbackImpl(properties); environment = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/HumanTaskWithBoundaryTimer.bpmn"), ResourceType.BPMN2) .schedulerService(globalScheduler) .userGroupCallback(userGroupCallback) .get(); manager = getManager(environment, true); RuntimeEngine runtime = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); KieSession ksession = runtime.getKieSession(); Map<String, Object> params = new HashMap<String, Object>(); params.put("test", "john"); ProcessInstance processInstance = ksession.startProcess("PROCESS_1", params); Connection connection = null; Statement stmt = null; try { connection = ((DataSource)InitialContext.doLookup("jdbc/jbpm-ds")).getConnection(); stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery("select REQUESTS_RECOVERY from QRTZ_JOB_DETAILS"); while(resultSet.next()) { boolean requestsRecovery = resultSet.getBoolean(1); assertEquals("Requests recovery must be set to true", true, requestsRecovery); } } finally { if(stmt != null) { stmt.close(); } if(connection != null) { connection.close(); } } ksession.abortProcessInstance(processInstance.getId()); manager.disposeRuntimeEngine(runtime); } }