@Override protected RuntimeManager getManager(RuntimeEnvironment environment) { if (managerType == 1) { return RuntimeManagerFactory.Factory.get().newSingletonRuntimeManager(environment); } else if (managerType == 2) { return RuntimeManagerFactory.Factory.get().newPerRequestRuntimeManager(environment); } else if (managerType == 3) { return RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager(environment); } else { throw new IllegalArgumentException("Invalid runtime maanger type"); } }
/** * 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 { SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment(); environment.addAsset( ResourceFactory.newClassPathResource("BPMN2-IntermediateCatchEventTimerCycle2.bpmn2"), ResourceType.BPMN2); environment.addToConfiguration( "drools.timerService", "org.jbpm.process.core.timer.impl.RegisteredTimerServiceDelegate"); 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 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 { SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment(); environment.addAsset( ResourceFactory.newClassPathResource("BPMN2-IntermediateCatchEventTimerCycle3.bpmn2"), ResourceType.BPMN2); environment.addToConfiguration( "drools.timerService", "org.jbpm.process.core.timer.impl.RegisteredTimerServiceDelegate"); 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); Runtime runtime = manger.getRuntime(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(); }
/** * Base test case class that shall be used for jBPM related tests. It provides four sections: * * <ul> * <li>JUnit life cycle methods * <li>Knowledge Base and KnowledgeSession management methods * <li>Assertions * <li>Helper methods * </ul> * * <b>JUnit life cycle methods</b>:<br> * * setUp: executed @Before and configures data source and <code>EntityManagerFactory</code>, * cleans up Singleton's session id<br> * * tearDown: executed @After and clears out history, closes <code>EntityManagerFactory</code> and * data source, disposes <code>RuntimeEngine</code>'s and <code>RuntimeManager</code><br> * <br> * <b>KnowledgeBase and KnowledgeSession management methods</b> * createRuntimeManager creates * <code>RuntimeManager</code> for gives set of assets and selected strategy <br> * * disposeRuntimeManager disposes <code>RuntimeManager</code> currently active in the scope of * test <br> * * getRuntimeEngine creates new <code>RuntimeEngine</code> for given context<br> * <br> * <b>Assertions</b><br> * Set of useful methods to assert process instance at various stages. <br> * <b>Helper methods</b><br> * * getDs - returns currently configured data source<br> * * getEmf - returns currently configured <code>EntityManagerFactory</code><br> * * getTestWorkItemHandler - returns test work item handler that might be registered in addition to * what is registered by default<br> * * clearHistory - clears history log<br> * * setupPoolingDataSource - sets up data source<br> */ public abstract class JbpmJUnitBaseTestCase extends AbstractBaseTest { /** Currently supported RuntimeEngine strategies */ public enum Strategy { SINGLETON, REQUEST, PROCESS_INSTANCE; } private static final Logger logger = LoggerFactory.getLogger(JbpmJUnitBaseTestCase.class); private boolean setupDataSource = false; private boolean sessionPersistence = false; private String persistenceUnitName; private EntityManagerFactory emf; private PoolingDataSource ds; private TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); private RuntimeManagerFactory managerFactory = RuntimeManagerFactory.Factory.get(); private RuntimeManager manager; private AuditLogService logService; private WorkingMemoryInMemoryLogger inMemoryLogger; private List<RuntimeEngine> activeEngines = new ArrayList<RuntimeEngine>(); /** * The most simple test case configuration: * * <ul> * <li>does NOT initialize data source * <li>does NOT configure session persistence * </ul> * * This is usually used for in memory process management, without human task interaction. */ public JbpmJUnitBaseTestCase() { this(false, false, "org.jbpm.persistence.jpa"); } /** * Allows to explicitly configure persistence and data source. This is the most common way of * bootstrapping test cases for jBPM.<br> * Use following configuration to execute in memory process management with human tasks * persistence <br> * <code> * super(true, false); * </code> <br> * Use following configuration to execute in persistent process management with human tasks * persistence <br> * <code> * super(true, true); * </code> <br> * This will use default persistence unit name <code>org.jbpm.persistence.jpa</code> * * @param setupDataSource - true to configure data source under JNDI name: jdbc/jbpm-ds * @param sessionPersistence - configures RuntimeEngine to be with JPA persistence enabled */ public JbpmJUnitBaseTestCase(boolean setupDataSource, boolean sessionPersistence) { this(setupDataSource, sessionPersistence, "org.jbpm.persistence.jpa"); } /** * Same as {@link #JbpmJUnitBaseTestCase(boolean, boolean)} but allows to use another persistence * unit name. * * @param setupDataSource - true to configure data source under JNDI name: jdbc/jbpm-ds * @param sessionPersistence - configures RuntimeEngine to be with JPA persistence enabled * @param persistenceUnitName - custom persistence unit name */ public JbpmJUnitBaseTestCase( boolean setupDataSource, boolean sessionPersistence, String persistenceUnitName) { this.setupDataSource = setupDataSource; this.sessionPersistence = sessionPersistence; this.persistenceUnitName = persistenceUnitName; if (!this.setupDataSource && this.sessionPersistence) { throw new IllegalArgumentException( "Unsupported configuration, cannot enable sessionPersistence when setupDataSource is disabled"); } logger.info( "Configuring entire test case to have data source enabled {} and session persistence enabled {} with persistence unit name {}", this.setupDataSource, this.sessionPersistence, this.persistenceUnitName); } @Before public void setUp() throws Exception { if (setupDataSource) { ds = setupPoolingDataSource(); logger.debug("Data source configured with unique id {}", ds.getUniqueName()); emf = Persistence.createEntityManagerFactory(persistenceUnitName); } cleanupSingletonSessionId(); } @After public void tearDown() throws Exception { clearHistory(); if (setupDataSource) { if (emf != null) { emf.close(); emf = null; } if (ds != null) { ds.close(); ds = null; } } if (!activeEngines.isEmpty()) { for (RuntimeEngine engine : activeEngines) { manager.disposeRuntimeEngine(engine); } } if (manager != null) { manager.close(); manager = null; } } /** * Creates default configuration of <code>RuntimeManager</code> with SINGLETON strategy and all * <code>processes</code> being added to knowledge base. <br> * There should be only one <code>RuntimeManager</code> created during single test. * * @param process - processes that shall be added to knowledge base * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(String... process) { return createRuntimeManager(Strategy.SINGLETON, process); } /** * Creates default configuration of <code>RuntimeManager</code> with given <code>strategy</code> * and all <code>processes</code> being added to knowledge base. <br> * There should be only one <code>RuntimeManager</code> created during single test. * * @param strategy - selected strategy of those that are supported * @param process - processes that shall be added to knowledge base * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(Strategy strategy, String... process) { Map<String, ResourceType> resources = new HashMap<String, ResourceType>(); for (String p : process) { resources.put(p, ResourceType.BPMN2); } return createRuntimeManager(strategy, resources); } /** * Creates default configuration of <code>RuntimeManager</code> with SINGLETON strategy and all * <code>resources</code> being added to knowledge base. <br> * There should be only one <code>RuntimeManager</code> created during single test. * * @param resources - resources (processes, rules, etc) that shall be added to knowledge base * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(Map<String, ResourceType> resources) { return createRuntimeManager(Strategy.SINGLETON, resources); } /** * Creates default configuration of <code>RuntimeManager</code> with given <code>strategy</code> * and all <code>resources</code> being added to knowledge base. <br> * There should be only one <code>RuntimeManager</code> created during single test. * * @param strategy - selected strategy of those that are supported * @param resources - resources that shall be added to knowledge base * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager( Strategy strategy, Map<String, ResourceType> resources) { if (manager != null) { throw new IllegalStateException("There is already one RuntimeManager active"); } RuntimeEnvironmentBuilder builder = null; if (!setupDataSource) { builder = RuntimeEnvironmentBuilder.getEmpty() .addConfiguration( "drools.processSignalManagerFactory", DefaultSignalManagerFactory.class.getName()) .addConfiguration( "drools.processInstanceManagerFactory", DefaultProcessInstanceManagerFactory.class.getName()); } else if (sessionPersistence) { builder = RuntimeEnvironmentBuilder.getDefault().entityManagerFactory(emf); } else { builder = RuntimeEnvironmentBuilder.getDefaultInMemory(); } builder.userGroupCallback(new JBossUserGroupCallbackImpl("classpath:/usergroups.properties")); for (Map.Entry<String, ResourceType> entry : resources.entrySet()) { builder.addAsset(ResourceFactory.newClassPathResource(entry.getKey()), entry.getValue()); } switch (strategy) { case SINGLETON: manager = managerFactory.newSingletonRuntimeManager(builder.get()); break; case REQUEST: manager = managerFactory.newPerRequestRuntimeManager(builder.get()); break; case PROCESS_INSTANCE: manager = managerFactory.newPerProcessInstanceRuntimeManager(builder.get()); break; default: manager = managerFactory.newSingletonRuntimeManager(builder.get()); break; } return manager; } /** * The lowest level of creation of <code>RuntimeManager</code> that expects to get <code> * RuntimeEnvironment</code> to be given as argument. It does not assume any particular * configuration as it's considered manual creation that allows to configure every single piece of * <code>RuntimeManager</code>. <br> * Use this only when you know what you do! * * @param strategy - selected strategy of those that are supported * @param resources - resources that shall be added to knowledge base * @param environment - runtime environment used for <code>RuntimeManager</code> creation * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager( Strategy strategy, Map<String, ResourceType> resources, RuntimeEnvironment environment) { if (manager != null) { throw new IllegalStateException("There is already one RuntimeManager active"); } switch (strategy) { case SINGLETON: manager = managerFactory.newSingletonRuntimeManager(environment); break; case REQUEST: manager = managerFactory.newPerRequestRuntimeManager(environment); break; case PROCESS_INSTANCE: manager = managerFactory.newPerProcessInstanceRuntimeManager(environment); break; default: manager = managerFactory.newSingletonRuntimeManager(environment); break; } return manager; } /** * Disposes currently active (in scope of a test) <code>RuntimeManager</code> together with all * active <code>RuntimeEngine</code>'s that were created (in scope of a test). Usual use case is * to simulate system shutdown. */ protected void disposeRuntimeManager() { if (!activeEngines.isEmpty()) { for (RuntimeEngine engine : activeEngines) { manager.disposeRuntimeEngine(engine); } activeEngines.clear(); } if (manager != null) { manager.close(); manager = null; } } /** * Returns new <code>RuntimeEngine</code> built from the manager of this test case. It uses <code> * EmptyContext</code> that is suitable for following strategies: * * <ul> * <li>Singleton * <li>Request * </ul> * * @see #getRuntimeEngine(Context) * @return new RuntimeEngine instance */ protected RuntimeEngine getRuntimeEngine() { return getRuntimeEngine(EmptyContext.get()); } /** * Returns new <code>RuntimeEngine</code> built from the manager of this test case. Common use * case is to maintain same session for process instance and thus <code>ProcessInstanceIdContext * </code> shall be used. * * @param context - instance of the context that shall be used to create <code>RuntimeManager * </code> * @return new RuntimeEngine instance */ protected RuntimeEngine getRuntimeEngine(Context<?> context) { if (manager == null) { throw new IllegalStateException( "RuntimeManager is not initialized, did you forgot to create it?"); } RuntimeEngine runtimeEngine = manager.getRuntimeEngine(context); activeEngines.add(runtimeEngine); if (sessionPersistence) { logService = new JPAAuditLogService(runtimeEngine.getKieSession().getEnvironment()); } else { inMemoryLogger = new WorkingMemoryInMemoryLogger((StatefulKnowledgeSession) runtimeEngine.getKieSession()); } return runtimeEngine; } /** * Retrieves value of the variable given by <code>name</code> from process instance given by * <code>processInstanceId</code> using given session. * * @param name - name of the variable * @param processInstanceId - id of process instance * @param ksession - ksession used to retrieve the value * @return returns variable value or null if there is no such variable */ public Object getVariableValue(String name, long processInstanceId, KieSession ksession) { return ((WorkflowProcessInstance) ksession.getProcessInstance(processInstanceId)) .getVariable(name); } /* * **************************************** * *********** assert methods ************* * **************************************** */ public void assertProcessInstanceCompleted(long processInstanceId, KieSession ksession) { assertNull(ksession.getProcessInstance(processInstanceId)); } public void assertProcessInstanceAborted(long processInstanceId, KieSession ksession) { assertNull(ksession.getProcessInstance(processInstanceId)); } public void assertProcessInstanceActive(long processInstanceId, KieSession ksession) { assertNotNull(ksession.getProcessInstance(processInstanceId)); } public void assertNodeActive(long processInstanceId, KieSession ksession, String... name) { List<String> names = new ArrayList<String>(); for (String n : name) { names.add(n); } ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); if (processInstance instanceof WorkflowProcessInstance) { assertNodeActive((WorkflowProcessInstance) processInstance, names); } if (!names.isEmpty()) { String s = names.get(0); for (int i = 1; i < names.size(); i++) { s += ", " + names.get(i); } fail("Node(s) not active: " + s); } } private void assertNodeActive(NodeInstanceContainer container, List<String> names) { for (NodeInstance nodeInstance : container.getNodeInstances()) { String nodeName = nodeInstance.getNodeName(); if (names.contains(nodeName)) { names.remove(nodeName); } if (nodeInstance instanceof NodeInstanceContainer) { assertNodeActive((NodeInstanceContainer) nodeInstance, names); } } } public void assertNodeTriggered(long processInstanceId, String... nodeNames) { List<String> names = new ArrayList<String>(); for (String nodeName : nodeNames) { names.add(nodeName); } if (sessionPersistence) { List<NodeInstanceLog> logs = logService.findNodeInstances(processInstanceId); if (logs != null) { for (NodeInstanceLog l : logs) { String nodeName = l.getNodeName(); if ((l.getType() == NodeInstanceLog.TYPE_ENTER || l.getType() == NodeInstanceLog.TYPE_EXIT) && names.contains(nodeName)) { names.remove(nodeName); } } } } else { for (LogEvent event : inMemoryLogger.getLogEvents()) { if (event instanceof RuleFlowNodeLogEvent) { String nodeName = ((RuleFlowNodeLogEvent) event).getNodeName(); if (names.contains(nodeName)) { names.remove(nodeName); } } } } if (!names.isEmpty()) { String s = names.get(0); for (int i = 1; i < names.size(); i++) { s += ", " + names.get(i); } fail("Node(s) not executed: " + s); } } public void assertProcessVarExists(ProcessInstance process, String... processVarNames) { WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; List<String> names = new ArrayList<String>(); for (String nodeName : processVarNames) { names.add(nodeName); } for (String pvar : instance.getVariables().keySet()) { if (names.contains(pvar)) { names.remove(pvar); } } if (!names.isEmpty()) { String s = names.get(0); for (int i = 1; i < names.size(); i++) { s += ", " + names.get(i); } fail("Process Variable(s) do not exist: " + s); } } public void assertNodeExists(ProcessInstance process, String... nodeNames) { WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; List<String> names = new ArrayList<String>(); for (String nodeName : nodeNames) { names.add(nodeName); } for (Node node : instance.getNodeContainer().getNodes()) { if (names.contains(node.getName())) { names.remove(node.getName()); } } if (!names.isEmpty()) { String s = names.get(0); for (int i = 1; i < names.size(); i++) { s += ", " + names.get(i); } fail("Node(s) do not exist: " + s); } } public void assertNumOfIncommingConnections(ProcessInstance process, String nodeName, int num) { assertNodeExists(process, nodeName); WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; for (Node node : instance.getNodeContainer().getNodes()) { if (node.getName().equals(nodeName)) { if (node.getIncomingConnections().size() != num) { fail( "Expected incomming connections: " + num + " - found " + node.getIncomingConnections().size()); } else { break; } } } } public void assertNumOfOutgoingConnections(ProcessInstance process, String nodeName, int num) { assertNodeExists(process, nodeName); WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; for (Node node : instance.getNodeContainer().getNodes()) { if (node.getName().equals(nodeName)) { if (node.getOutgoingConnections().size() != num) { fail( "Expected outgoing connections: " + num + " - found " + node.getOutgoingConnections().size()); } else { break; } } } } public void assertVersionEquals(ProcessInstance process, String version) { WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; if (!instance.getWorkflowProcess().getVersion().equals(version)) { fail( "Expected version: " + version + " - found " + instance.getWorkflowProcess().getVersion()); } } public void assertProcessNameEquals(ProcessInstance process, String name) { WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; if (!instance.getWorkflowProcess().getName().equals(name)) { fail("Expected name: " + name + " - found " + instance.getWorkflowProcess().getName()); } } public void assertPackageNameEquals(ProcessInstance process, String packageName) { WorkflowProcessInstanceImpl instance = (WorkflowProcessInstanceImpl) process; if (!instance.getWorkflowProcess().getPackageName().equals(packageName)) { fail( "Expected package name: " + packageName + " - found " + instance.getWorkflowProcess().getPackageName()); } } /* * **************************************** * *********** helper methods ************* * **************************************** */ protected EntityManagerFactory getEmf() { return this.emf; } protected DataSource getDs() { return this.ds; } protected PoolingDataSource setupPoolingDataSource() { PoolingDataSource pds = new PoolingDataSource(); pds.setUniqueName("jdbc/jbpm-ds"); pds.setClassName("bitronix.tm.resource.jdbc.lrc.LrcXADataSource"); pds.setMaxPoolSize(5); pds.setAllowLocalTransactions(true); pds.getDriverProperties().put("user", "sa"); pds.getDriverProperties().put("password", ""); pds.getDriverProperties().put("url", "jdbc:h2:inmemory:jbpm-db;MVCC=true"); pds.getDriverProperties().put("driverClassName", "org.h2.Driver"); pds.init(); return pds; } protected void clearHistory() { if (sessionPersistence) { logService.clear(); } else { inMemoryLogger.clear(); } } protected TestWorkItemHandler getTestWorkItemHandler() { return workItemHandler; } protected static class TestWorkItemHandler implements WorkItemHandler { private List<WorkItem> workItems = new ArrayList<WorkItem>(); public void executeWorkItem(WorkItem workItem, WorkItemManager manager) { workItems.add(workItem); } public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {} public WorkItem getWorkItem() { if (workItems.size() == 0) { return null; } if (workItems.size() == 1) { WorkItem result = workItems.get(0); this.workItems.clear(); return result; } else { throw new IllegalArgumentException("More than one work item active"); } } public List<WorkItem> getWorkItems() { List<WorkItem> result = new ArrayList<WorkItem>(workItems); workItems.clear(); return result; } } protected static void cleanupSingletonSessionId() { File tempDir = new File(System.getProperty("java.io.tmpdir")); if (tempDir.exists()) { String[] jbpmSerFiles = tempDir.list( new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith("-jbpmSessionId.ser"); } }); for (String file : jbpmSerFiles) { new File(tempDir, file).delete(); } } } }