/* * 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.test; import java.io.File; import java.io.FilenameFilter; import java.nio.channels.ClosedChannelException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.naming.InitialContext; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.sql.DataSource; import javax.transaction.Status; import javax.transaction.UserTransaction; import org.drools.core.audit.WorkingMemoryInMemoryLogger; import org.drools.core.audit.event.LogEvent; import org.drools.core.audit.event.RuleFlowNodeLogEvent; import org.drools.persistence.jta.JtaTransactionManager; import org.jbpm.process.audit.JPAAuditLogService; import org.jbpm.process.instance.event.DefaultSignalManagerFactory; import org.jbpm.process.instance.impl.DefaultProcessInstanceManagerFactory; import org.jbpm.runtime.manager.impl.DefaultRegisterableItemsFactory; import org.jbpm.runtime.manager.impl.SimpleRegisterableItemsFactory; import org.jbpm.runtime.manager.impl.jpa.EntityManagerFactoryManager; import org.jbpm.services.task.identity.JBossUserGroupCallbackImpl; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.kie.api.definition.process.Node; import org.kie.api.event.process.ProcessEventListener; import org.kie.api.event.rule.AgendaEventListener; import org.kie.api.io.ResourceType; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.manager.Context; 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.manager.audit.AuditService; import org.kie.api.runtime.manager.audit.NodeInstanceLog; import org.kie.api.runtime.manager.audit.ProcessInstanceLog; import org.kie.api.runtime.process.NodeInstance; import org.kie.api.runtime.process.NodeInstanceContainer; import org.kie.api.runtime.process.ProcessInstance; import org.kie.api.runtime.process.WorkItem; import org.kie.api.runtime.process.WorkItemHandler; import org.kie.api.runtime.process.WorkItemManager; import org.kie.api.runtime.process.WorkflowProcessInstance; import org.kie.api.task.TaskLifeCycleEventListener; import org.kie.api.task.UserGroupCallback; import org.kie.internal.io.ResourceFactory; import org.kie.internal.runtime.StatefulKnowledgeSession; import org.kie.internal.runtime.manager.context.EmptyContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import bitronix.tm.TransactionManagerServices; import bitronix.tm.internal.BitronixSystemException; import bitronix.tm.resource.ResourceRegistrar; import bitronix.tm.resource.common.XAResourceProducer; import bitronix.tm.resource.jdbc.PoolingDataSource; /** * Base test case class that shall be used for jBPM related tests. It provides four sections: * <ul> * <li>JUnit life cycle methods</li> * <li>Knowledge Base and KnowledgeSession management methods</li> * <li>Assertions</li> * <li>Helper methods</li> * </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 Assert { /** * Currently supported RuntimeEngine strategies */ public enum Strategy { SINGLETON, REQUEST, PROCESS_INSTANCE; } private static final Logger logger = LoggerFactory.getLogger(JbpmJUnitBaseTestCase.class); protected boolean setupDataSource = false; protected boolean sessionPersistence = false; private String persistenceUnitName; private EntityManagerFactory emf; private PoolingDataSource ds; private TestWorkItemHandler workItemHandler = new TestWorkItemHandler(); private RuntimeManagerFactory managerFactory = RuntimeManagerFactory.Factory.get(); protected RuntimeManager manager; private AuditService logService; private WorkingMemoryInMemoryLogger inMemoryLogger; protected UserGroupCallback userGroupCallback = new JBossUserGroupCallbackImpl("classpath:/usergroups.properties"); protected Set<RuntimeEngine> activeEngines = new HashSet<RuntimeEngine>(); protected Map<String, WorkItemHandler> customHandlers = new HashMap<String, WorkItemHandler>(); protected List<ProcessEventListener> customProcessListeners = new ArrayList<ProcessEventListener>(); protected List<AgendaEventListener> customAgendaListeners = new ArrayList<AgendaEventListener>(); protected List<TaskLifeCycleEventListener> customTaskListeners = new ArrayList<TaskLifeCycleEventListener>(); protected Map<String, Object> customEnvironmentEntries = new HashMap<String, Object>(); private final Map<String, Object> persistenceProperties = new HashMap<String, Object>(); /** * The most simple test case configuration: * <ul> * <li>does NOT initialize data source</li> * <li>does NOT configure session persistence</li> * </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.debug("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, persistenceProperties); } cleanupSingletonSessionId(); } @After public void tearDown() throws Exception { try { clearCustomRegistry(); disposeRuntimeManager(); clearHistory(); } finally { if (setupDataSource) { try { InitialContext context = new InitialContext(); UserTransaction ut = (UserTransaction) context.lookup( JtaTransactionManager.DEFAULT_USER_TRANSACTION_NAME ); if( ut.getStatus() != Status.STATUS_NO_TRANSACTION ) { ut.setRollbackOnly(); ut.rollback(); } } catch( Exception e ) { // do nothing } if (emf != null) { emf.close(); emf = null; EntityManagerFactoryManager.get().clear(); } if (ds != null) { ds.close(); ds = null; } persistenceProperties.clear(); } } } /** * 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, null, 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 identifier - identifies the runtime manager * @param process - processes that shall be added to knowledge base * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(Strategy strategy, String identifier, String... process) { Map<String, ResourceType> resources = new HashMap<String, ResourceType>(); for (String p : process) { resources.put(p, ResourceType.BPMN2); } return createRuntimeManager(strategy, resources, identifier); } /** * 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, null); } /** * 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 * @param identifier - identifies the runtime manager * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(Map<String, ResourceType> resources, String identifier) { return createRuntimeManager(Strategy.SINGLETON, resources, identifier); } /** * 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) { return createRuntimeManager(strategy, resources, null); } /** * 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 * @param identifier - identifies the runtime manager * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(Strategy strategy, Map<String, ResourceType> resources, String identifier) { if (manager != null) { throw new IllegalStateException("There is already one RuntimeManager active"); } RuntimeEnvironmentBuilder builder = null; if (!setupDataSource){ builder = RuntimeEnvironmentBuilder.Factory.get() .newEmptyBuilder() .addConfiguration("drools.processSignalManagerFactory", DefaultSignalManagerFactory.class.getName()) .addConfiguration("drools.processInstanceManagerFactory", DefaultProcessInstanceManagerFactory.class.getName()) .registerableItemsFactory(new SimpleRegisterableItemsFactory() { @Override public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) { Map<String, WorkItemHandler> handlers = new HashMap<String, WorkItemHandler>(); handlers.putAll(super.getWorkItemHandlers(runtime)); handlers.putAll(customHandlers); return handlers; } @Override public List<ProcessEventListener> getProcessEventListeners(RuntimeEngine runtime) { List<ProcessEventListener> listeners = super.getProcessEventListeners(runtime); listeners.addAll(customProcessListeners); return listeners; } @Override public List<AgendaEventListener> getAgendaEventListeners( RuntimeEngine runtime) { List<AgendaEventListener> listeners = super.getAgendaEventListeners(runtime); listeners.addAll(customAgendaListeners); return listeners; } @Override public List<TaskLifeCycleEventListener> getTaskListeners() { List<TaskLifeCycleEventListener> listeners = super.getTaskListeners(); listeners.addAll(customTaskListeners); return listeners; } }); } else if (sessionPersistence) { builder = RuntimeEnvironmentBuilder.Factory.get() .newDefaultBuilder() .entityManagerFactory(emf) .registerableItemsFactory(new DefaultRegisterableItemsFactory() { @Override public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) { Map<String, WorkItemHandler> handlers = new HashMap<String, WorkItemHandler>(); handlers.putAll(super.getWorkItemHandlers(runtime)); handlers.putAll(customHandlers); return handlers; } @Override public List<ProcessEventListener> getProcessEventListeners(RuntimeEngine runtime) { List<ProcessEventListener> listeners = super.getProcessEventListeners(runtime); listeners.addAll(customProcessListeners); return listeners; } @Override public List<AgendaEventListener> getAgendaEventListeners( RuntimeEngine runtime) { List<AgendaEventListener> listeners = super.getAgendaEventListeners(runtime); listeners.addAll(customAgendaListeners); return listeners; } @Override public List<TaskLifeCycleEventListener> getTaskListeners() { List<TaskLifeCycleEventListener> listeners = super.getTaskListeners(); listeners.addAll(customTaskListeners); return listeners; } }); } else { builder = RuntimeEnvironmentBuilder.Factory.get() .newDefaultInMemoryBuilder() .entityManagerFactory(emf) .registerableItemsFactory(new DefaultRegisterableItemsFactory() { @Override public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) { Map<String, WorkItemHandler> handlers = new HashMap<String, WorkItemHandler>(); handlers.putAll(super.getWorkItemHandlers(runtime)); handlers.putAll(customHandlers); return handlers; } @Override public List<ProcessEventListener> getProcessEventListeners(RuntimeEngine runtime) { List<ProcessEventListener> listeners = super.getProcessEventListeners(runtime); listeners.addAll(customProcessListeners); return listeners; } @Override public List<AgendaEventListener> getAgendaEventListeners( RuntimeEngine runtime) { List<AgendaEventListener> listeners = super.getAgendaEventListeners(runtime); listeners.addAll(customAgendaListeners); return listeners; } @Override public List<TaskLifeCycleEventListener> getTaskListeners() { List<TaskLifeCycleEventListener> listeners = super.getTaskListeners(); listeners.addAll(customTaskListeners); return listeners; } }); } builder.userGroupCallback(userGroupCallback); for (Entry<String, Object> envEntry : customEnvironmentEntries.entrySet()) { builder.addEnvironmentEntry(envEntry.getKey(), envEntry.getValue()); } for (Map.Entry<String, ResourceType> entry : resources.entrySet()) { builder.addAsset(ResourceFactory.newClassPathResource(entry.getKey()), entry.getValue()); } return createRuntimeManager(strategy, resources, builder.get(), identifier); } /** * 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 * @param identifier - identifies the runtime manager * @return new instance of RuntimeManager */ protected RuntimeManager createRuntimeManager(Strategy strategy, Map<String, ResourceType> resources, RuntimeEnvironment environment, String identifier) { if (manager != null) { throw new IllegalStateException("There is already one RuntimeManager active"); } try { switch (strategy) { case SINGLETON: if (identifier == null) { manager = managerFactory.newSingletonRuntimeManager(environment); } else { manager = managerFactory.newSingletonRuntimeManager(environment, identifier); } break; case REQUEST: if (identifier == null) { manager = managerFactory.newPerRequestRuntimeManager(environment); } else { manager = managerFactory.newPerRequestRuntimeManager(environment, identifier); } break; case PROCESS_INSTANCE: if (identifier == null) { manager = managerFactory.newPerProcessInstanceRuntimeManager(environment); } else { manager = managerFactory.newPerProcessInstanceRuntimeManager(environment, identifier); } break; default: if (identifier == null) { manager = managerFactory.newSingletonRuntimeManager(environment); } else { manager = managerFactory.newSingletonRuntimeManager(environment, identifier); } break; } return manager; } catch (Exception e) { if (e instanceof BitronixSystemException || e instanceof ClosedChannelException) { TransactionManagerServices.getTransactionManager().shutdown(); } throw new RuntimeException(e); } } /** * 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) { try { manager.disposeRuntimeEngine(engine); } catch (Exception e) { logger.debug("Exception during dipose of runtime engine, might be already disposed - {}", e.getMessage()); } } 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> * <li>Request</li> * </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 = runtimeEngine.getAuditService(); } 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 ************* * **************************************** */ /** * @deprecated This method does not check the actual state of process instance. Use either * {@link #assertProcessInstanceCompleted(long)} if session persistence is enabled or * {@link #assertProcessInstanceNotActive(long, KieSession)} otherwise. */ @Deprecated public void assertProcessInstanceCompleted(long processInstanceId, KieSession ksession) { assertNull(ksession.getProcessInstance(processInstanceId)); } /** * @deprecated This method does not check the actual state of process instance. Use either * {@link #assertProcessInstanceAborted(long)} if session persistence is enabled or * {@link #assertProcessInstanceNotActive(long, KieSession)} otherwise. */ @Deprecated public void assertProcessInstanceAborted(long processInstanceId, KieSession ksession) { assertNull(ksession.getProcessInstance(processInstanceId)); } /** * Asserts that process instance is active. * Does not require session persistence to be enabled. * * @param processInstanceId id of process instance * @param ksession */ public void assertProcessInstanceActive(long processInstanceId, KieSession ksession) { assertNotNull(ksession.getProcessInstance(processInstanceId)); } /** * Asserts that process instance is not active. * Does not require session persistence to be enabled. * * @param processInstanceId id of process instance * @param ksession */ public void assertProcessInstanceNotActive(long processInstanceId, KieSession ksession) { assertNull(ksession.getProcessInstance(processInstanceId)); } private void assertProcessInstanceState(long processInstanceId, Integer expectedState, String message) { if (logService == null) { throw new IllegalStateException("Audit service has not been initialized. Session persistence is probably " + "not enabled or you are not getting runtime engine using methods from JbpmJUnitBaseTestCase"); } ProcessInstanceLog log = logService.findProcessInstance(processInstanceId); assertNotNull("Process instance has not been found", log); assertEquals(message, expectedState, log.getStatus()); } /** * Asserts that process instance is active. * Makes use of AuditService which requires persistence to be enabled. * * @param processInstanceId id of process instance */ public void assertProcessInstanceActive(long processInstanceId) { assertProcessInstanceState(processInstanceId, ProcessInstance.STATE_ACTIVE, "Process instance is not active"); } /** * Asserts that process instance is completed. * Makes use of AuditService which requires persistence to be enabled. * * @param processInstanceId id of process instance */ public void assertProcessInstanceCompleted(long processInstanceId) { assertProcessInstanceState(processInstanceId, ProcessInstance.STATE_COMPLETED, "Process instance is not completed"); } /** * Asserts that process instance is aborted. * Makes use of AuditService which requires persistence to be enabled. * * @param processInstanceId id of process instance */ public void assertProcessInstanceAborted(long processInstanceId) { assertProcessInstanceState(processInstanceId, ProcessInstance.STATE_ABORTED, "Process instance is not aborted"); } 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) { if (sessionPersistence) { List<? extends NodeInstanceLog> logs = logService.findNodeInstances(processInstanceId); // ENTER -> EXIT is correctly ordered if (logs != null) { List<String> activeNodes = new ArrayList<String>(); for (NodeInstanceLog l : logs) { String nodeName = l.getNodeName(); if (l.getType() == NodeInstanceLog.TYPE_ENTER && names.contains(nodeName)) { activeNodes.add(nodeName); } if (l.getType() == NodeInstanceLog.TYPE_EXIT && names.contains(nodeName)) { activeNodes.remove(nodeName); } } names.removeAll(activeNodes); } } else { 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<? extends 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:mem:jbpm-db;MVCC=true"); pds.getDriverProperties().put("driverClassName", "org.h2.Driver"); try { pds.init(); } catch (Exception e) { logger.warn("DBPOOL_MGR:Looks like there is an issue with creating db pool because of " + e.getMessage() + " cleaing up..."); Set<String> resources = ResourceRegistrar.getResourcesUniqueNames(); for (String resource : resources) { XAResourceProducer producer = ResourceRegistrar.get(resource); producer.close(); ResourceRegistrar.unregister(producer); logger.debug("DBPOOL_MGR:Removed resource " + resource); } logger.debug("DBPOOL_MGR: attempting to create db pool again..."); 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:mem:jbpm-db;MVCC=true"); pds.getDriverProperties().put("driverClassName", "org.h2.Driver"); pds.init(); logger.debug("DBPOOL_MGR:Pool created after cleanup of leftover resources"); } return pds; } protected void clearHistory() { if (sessionPersistence && logService != null) { // RuntimeManager manager = createRuntimeManager(); // RuntimeEngine engine = manager.getRuntimeEngine(null); // engine.getAuditService().clear(); // manager.disposeRuntimeEngine(engine); // manager.close(); JPAAuditLogService service = new JPAAuditLogService(emf); service.clear(); service.dispose(); } else if (inMemoryLogger != null) { inMemoryLogger.clear(); } } protected void clearCustomRegistry() { this.customAgendaListeners.clear(); this.customHandlers.clear(); this.customProcessListeners.clear(); this.customTaskListeners.clear(); } protected TestWorkItemHandler getTestWorkItemHandler() { return workItemHandler; } protected AuditService getLogService() { return logService; } protected WorkingMemoryInMemoryLogger getInMemoryLogger() { return inMemoryLogger; } public void addProcessEventListener(ProcessEventListener listener) { customProcessListeners.add(listener); } public void addAgendaEventListener(AgendaEventListener listener) { customAgendaListeners.add(listener); } public void addTaskEventListener(TaskLifeCycleEventListener listener) { customTaskListeners.add(listener); } public void addWorkItemHandler(String name, WorkItemHandler handler) { customHandlers.put(name, handler); } public void addEnvironmentEntry(String name, Object value) { customEnvironmentEntries.put(name, value); } public void setPersistenceProperty(String name, Object value) { persistenceProperties.put(name, value); } protected static class TestWorkItemHandler implements WorkItemHandler { public TestWorkItemHandler() { } 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(); } } } }