/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.processFlow.knowledgeService; import java.io.Serializable; import java.io.StringWriter; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Default; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; import org.quartz.JobExecutionContext; import org.drools.SystemEventListenerFactory; import org.drools.KnowledgeBaseFactory; import org.drools.base.MapGlobalResolver; import org.drools.command.SingleSessionCommandService; import org.drools.command.impl.CommandBasedStatefulKnowledgeSession; import org.drools.event.rule.AgendaEventListener; import org.drools.event.rule.WorkingMemoryEventListener; import org.drools.event.process.ProcessCompletedEvent; import org.drools.event.process.ProcessEventListener; import org.drools.event.process.ProcessNodeLeftEvent; import org.drools.event.process.ProcessNodeTriggeredEvent; import org.drools.event.process.ProcessStartedEvent; import org.drools.event.process.ProcessVariableChangedEvent; import org.drools.io.*; import org.drools.logger.KnowledgeRuntimeLogger; import org.drools.logger.KnowledgeRuntimeLoggerFactory; import org.drools.persistence.jpa.JPAKnowledgeService; import org.drools.runtime.KnowledgeSessionConfiguration; import org.drools.runtime.StatefulKnowledgeSession; import org.drools.runtime.Environment; import org.drools.runtime.process.ProcessInstance; import org.drools.runtime.process.WorkItemHandler; import org.drools.runtime.rule.FactHandle; import org.jbpm.process.core.context.variable.VariableScope; import org.jbpm.process.instance.context.variable.VariableScopeInstance; import org.jbpm.process.instance.timer.TimerInstance; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.jbpm.workflow.instance.node.SubProcessNodeInstance; import org.jbpm.task.admin.TaskCleanUpProcessEventListener; import org.jbpm.task.admin.TasksAdmin; import org.jbpm.workflow.instance.WorkflowProcessInstanceUpgrader; import org.jboss.processFlow.knowledgeService.IKnowledgeSession; import org.jboss.processFlow.util.CMTDisposeCommand; import org.jboss.processFlow.util.GlobalQuartzJobHandle; import org.mvel2.MVEL; /** *<pre> *architecture * - this singleton utilizes a 'processInstance per knowledgeSession' architecture * - although the jbpm5 API technically allows for a StatefulKnowledgeSession to manage the lifecycle of multiple process instances, * we choose not to have to deal with optimistic lock exception handling (in particular with the sessionInfo) during highly concurrent environments * *notes on Transactions * - most publicly exposed methods in this singleton assumes a container managed trnx demarcation of REQUIRED * - in some methods, bean managed transaction demarcation is used IOT dispose of the ksession *AFTER* the transaction has committed * - otherwise, the method will fail due to implementation of JBRULES-1880 * * *ksession management * - in this IKnowledgeSession implementation, a ksessionId is allocated to a process instance (and any subprocesses) for its entire lifecycle * - upon completion of a process instance, the ksessionId is made available again for a new process instance * - this singleton utilizes two data structures, busySessions & availableSessions, to maintain which ksessionIds are available for reuse * - a sessioninfo record in the jbpm database corresponds to a single StatefulKnowledgeSession * - a sessioninfo record typically includes the state of : * * timers * * business rule data * * business rule state * - a sessioninfo record is never purged from the database ... in this implementation it is simply re-cycled for use by a new process instance * - ksessionId state : * - some of the public methods implemented by this bean take both a 'processInstanceId' and a 'ksessionId' as a parameter * - for the purposes of this implementation, the 'ksessionId' is always optional * if null is passed to any of the methods accepting a ksessionid, then this implementation will query the jbpm5 task table * to determine the mapping between processInstanceId and ksessionId * - this implementation is ideal in a multi-thread, concurrent client environment where the following is either met or is acceptable: * 1) process definitions do include rule data * 2) from a performance perspective, it's critical that process instance lifecycle functions are executed in parallel rather than synchroneously * NOTE: see org.drools.persistence.SingleSessionCommandService.execute(...) function * *</pre> * * 22 Jan 2013: various performance optimizations and general cleanup contributed by Michal Valach. thank you! */ @ApplicationScoped @Alternative @Default public class SessionPerPInstanceBean extends BaseKnowledgeSessionBean implements IKnowledgeSession { private static final String DASH = "-"; private static final String TIMER_TRIGGERED="timerTriggered"; private ConcurrentMap<Integer, KnowledgeSessionWrapper> kWrapperHash = new ConcurrentHashMap<Integer, KnowledgeSessionWrapper>(); private Logger log = Logger.getLogger(SessionPerPInstanceBean.class); private IKnowledgeSessionPool sessionPool; @Inject private AsyncBAMProducerPool bamProducerPool; /****************************************************************************** ************** Singleton Lifecycle Management *********/ @PostConstruct public void start() throws Exception { super.start(); if (System.getProperty("org.jboss.processFlow.KnowledgeSessionPool") != null) { String clazzName = System.getProperty("org.jboss.processFlow.KnowledgeSessionPool"); sessionPool = (IKnowledgeSessionPool) Class.forName(clazzName).newInstance(); } else { sessionPool = new InMemoryKnowledgeSessionPool(); } QuartzSchedulerService.start(); } @PreDestroy public void stop() throws Exception { log.info("stop"); // JA Bride : completely plagarized from David Ward in his org.jboss.internal.soa.esb.services.rules.DroolsResourceChangeService implementation // ORDER IS IMPORTANT! // 1) stop the scanner ResourceFactory.getResourceChangeScannerService().stop(); // 2) stop the notifier //ResourceFactory.getResourceChangeNotifierService().stop(); // 3) set the system event listener back to the original implementation SystemEventListenerFactory.setSystemEventListener(originalSystemEventListener); QuartzSchedulerService.stop(); } /****************************************************************************** ************* StatefulKnowledgeSession Management *********/ /* - load a StatefulKnowledgeSession with an id recently freed during the 'after process completion' event - if no available sessions, then make a new StatefulKnowledgeSession */ private StatefulKnowledgeSession getStatefulKnowledgeSession(String processId) { StatefulKnowledgeSession ksession = null; if(processId != null) { int sessionId = sessionPool.getAvailableSessionId(); if(sessionId > 0) { ksession = loadStatefulKnowledgeSession(new Integer(sessionId)); } else { ksession = makeStatefulKnowledgeSession(); } sessionPool.markAsBorrowed(ksession.getId(), processId); } else { ksession = makeStatefulKnowledgeSession(); } return ksession; } private StatefulKnowledgeSession loadStatefulKnowledgeSession(Integer sessionId) { if(kWrapperHash.containsKey(sessionId)) { //log.info("loadStatefulKnowledgeSession() found ksession in cache for ksessionId = " +sessionId); return kWrapperHash.get(sessionId).ksession; } //0) initialise knowledge base if it hasn't already been done so checkKAgentAndBaseHealth(); //1) very important that a unique 'Environment' is created every time StatefulKnowledgeSession is loaded Environment ksEnv = createKnowledgeSessionEnvironment(); KnowledgeSessionConfiguration ksConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(ksconfigProperties); // 2) instantiate new StatefulKnowledgeSession from old sessioninfo StatefulKnowledgeSession ksession = JPAKnowledgeService.loadStatefulKnowledgeSession(sessionId, kbase, ksConfig, ksEnv); return ksession; } /* * disposeStatefulKnowledgeSessionAndExtras *<pre> *- disposes of a StatefulKnowledgeSession object currently in use *- NOTE: can no longer dispose knowledge session within scope of a transaction due to side effects from fix for JBRULES-1880 *</pre> */ public void disposeStatefulKnowledgeSessionAndExtras(Integer sessionId) { try { KnowledgeSessionWrapper kWrapper = ((KnowledgeSessionWrapper)kWrapperHash.get(sessionId)); if(kWrapper == null){ log.error("disposeStatefulKnowledgeSessionAndExtras() no ksessionWrapper found with sessionId = "+sessionId); return; } kWrapper.dispose(); kWrapperHash.remove(sessionId); } catch(RuntimeException x) { throw x; } catch(Exception x){ throw new RuntimeException(x); } } private void addExtrasToStatefulKnowledgeSession(StatefulKnowledgeSession ksession) { addExtrasCommon(ksession); // 3) add 'busySessions' ProcessEventListener to knowledgesession to assist in maintaining 'busySessions' state final ProcessEventListener busySessionsListener = new ProcessEventListener() { /* * these process events are implemented as a 'stack pattern' * ie: afterProcessStarted() event is the last event to be called * see org.jbpm.process.instance.ProcessRuntimeImpl.startProcessInstance(long processInstanceId) for details */ public void afterProcessCompleted(ProcessCompletedEvent event) { StatefulKnowledgeSession ksession = (StatefulKnowledgeSession)event.getKnowledgeRuntime(); ProcessInstance pInstance = event.getProcessInstance(); org.drools.definition.process.Process droolsProcess = event.getProcessInstance().getProcess(); if(sessionPool.isBorrowed(ksession.getId(), pInstance.getProcessId())) { log.info("afterProcessCompleted()\tsessionId : "+ksession.getId()+" : "+pInstance+" : pDefVersion = "+droolsProcess.getVersion()+" : session to be reused"); sessionPool.markAsReturned(ksession.getId()); } else { log.info("afterProcessCompleted()\tsessionId : "+ksession.getId()+" : process : "+pInstance+" : pDefVersion = "+droolsProcess.getVersion()); } // Thank you Duncan Doyle for the following: //Retract all the facts from the knowledge runtime. Collection<FactHandle> factHandles = ksession.getFactHandles(); for (FactHandle nextFactHandle : factHandles) { ksession.retract(nextFactHandle); } // Reset globals in the knowledge runtime. MapGlobalResolver globals = (MapGlobalResolver) ksession.getGlobals(); Entry<Object, Object>[] entries = globals.getGlobals(); for (Entry<Object, Object> nextEntry : entries) { nextEntry.setValue(null); } // Clear Agenda ksession.getAgenda().clear(); } public void beforeProcessStarted(ProcessStartedEvent event) { } /* with a process with no wait state, this call-back method will actually get invoked AFTER the 'afterProcessCompleted' call back - if parent process, state = 1 - if subprocess, state = 2 */ public void afterProcessStarted(ProcessStartedEvent event) { StatefulKnowledgeSession ksession = (StatefulKnowledgeSession)event.getKnowledgeRuntime(); ProcessInstance pInstance = event.getProcessInstance(); org.drools.definition.process.Process droolsProcess = event.getProcessInstance().getProcess(); log.info("afterProcessStarted()\tsessionId : "+ksession.getId()+" : "+pInstance+" : pDefVersion = "+droolsProcess.getVersion()); } public void beforeProcessCompleted(ProcessCompletedEvent event) { } public void beforeNodeTriggered(ProcessNodeTriggeredEvent event) { if (event.getNodeInstance() instanceof SubProcessNodeInstance) { StatefulKnowledgeSession ksession = (StatefulKnowledgeSession)event.getKnowledgeRuntime(); SubProcessNodeInstance spNode = (SubProcessNodeInstance)event.getNodeInstance(); org.drools.definition.process.Process droolsProcess = event.getProcessInstance().getProcess(); if(enableLog) log.info("beforeNodeTriggered()\tsessionId : "+ksession.getId()+" : sub-process : " + spNode.getNodeName()+" : pid: "+spNode.getProcessInstanceId()+" : pDefVersion = "+droolsProcess.getVersion()); } } public void afterNodeTriggered(ProcessNodeTriggeredEvent event) { if (event.getNodeInstance() instanceof SubProcessNodeInstance) { StatefulKnowledgeSession ksession = (StatefulKnowledgeSession)event.getKnowledgeRuntime(); org.drools.definition.process.Process droolsProcess = event.getProcessInstance().getProcess(); SubProcessNodeInstance spNode = (SubProcessNodeInstance)event.getNodeInstance(); if(enableLog) log.info("afterNodeTriggered()\tsessionId : "+ksession.getId()+" : sub-process : " + spNode.getNodeName()+" : pid: "+spNode.getProcessInstanceId()+" : pDefVersion = "+droolsProcess.getVersion()); } } public void beforeNodeLeft(ProcessNodeLeftEvent event) { } public void afterNodeLeft(ProcessNodeLeftEvent event) { } public void beforeVariableChanged(ProcessVariableChangedEvent event) { } public void afterVariableChanged(ProcessVariableChangedEvent event) { } }; ksession.addEventListener(busySessionsListener); // 4) register TaskCleanUpProcessEventListener // NOTE: need to ensure that task audit data has been pushed to BAM prior to this taskCleanUpProcessEventListener firing if(!StringUtils.isEmpty(taskCleanUpImpl) && taskCleanUpImpl.equals(TaskCleanUpProcessEventListener.class.getName())) { TasksAdmin adminObj = jtaTaskService.createTaskAdmin(); TaskCleanUpProcessEventListener taskCleanUpListener = new TaskCleanUpProcessEventListener(adminObj); ksession.addEventListener(taskCleanUpListener); } // 5) register any other process event listeners specified via configuration // TO_DO: refactor using mvel. ie: jbpm-gwt/jbpm-gwt-console-server/src/main/resources/default.session.template AsyncBAMProducer bamProducer= null; if(processEventListeners != null) { for(String peString : processEventListeners) { try { Class peClass = Class.forName(peString); ProcessEventListener peListener = (ProcessEventListener)peClass.newInstance(); if(IKnowledgeSession.ASYNC_BAM_PRODUCER.equals(peListener.getClass().getName())) bamProducer = (AsyncBAMProducer)peListener; ksession.addEventListener(peListener); } catch(Exception x) { throw new RuntimeException(x); } } } // 6) create a kWrapper object with optional bamProducer KnowledgeSessionWrapper kWrapper = new KnowledgeSessionWrapper(ksession, bamProducer); kWrapperHash.put(ksession.getId(), kWrapper); // 7) add KnowledgeRuntimeLogger as per section 4.1.3 of jbpm5 user manual if(enableKnowledgeRuntimeLogger) { StringBuilder sBuilder = new StringBuilder(); sBuilder.append(System.getProperty("jboss.server.log.dir")); sBuilder.append("/knowledgeRuntimeLogger-"); sBuilder.append(ksession.getId()); kWrapper.setKnowledgeRuntimeLogger(KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, sBuilder.toString())); } SingleSessionCommandService ssCommandService = (SingleSessionCommandService) ((CommandBasedStatefulKnowledgeSession)ksession).getCommandService(); } private StatefulKnowledgeSession loadStatefulKnowledgeSessionAndAddExtras(Integer sessionId) { StatefulKnowledgeSession ksession = loadStatefulKnowledgeSession(sessionId); addExtrasToStatefulKnowledgeSession(ksession); return ksession; } public String dumpSessionStatusInfo() { return sessionPool.dumpSessionStatusInfo(); } public String dumpBAMProducerPoolInfo() { StringBuilder sBuilder = new StringBuilder("dumpBAMProducerPoolInfo()\n\tNumber Active = "); if(bamProducerPool != null) { sBuilder.append(bamProducerPool.getNumActive()); sBuilder.append("\n\tNumber Idle = "); sBuilder.append(bamProducerPool.getNumIdle()); } else { sBuilder.append("bamProducerPool is null. most likely environment is not configured correctly for async logging of bam events from jbpm5 process engine"); } return sBuilder.toString(); } /****************************************************************************** ************* Process Instance Management *********/ /** *startProcessAndReturnId *<pre> *- this method will block until the newly created process instance either completes or arrives at a wait state *- at completion of the process instance (or arrival at a wait state), the StatefulKnowledgeSession will be disposed * - will return null if problems arise *</pre> */ public Map<String, Object> startProcessAndReturnId(String processId, Map<String, Object> parameters) { StatefulKnowledgeSession ksession = null; StringBuilder sBuilder = new StringBuilder(); Integer ksessionId = null; ProcessInstance pInstance = null; Map<String, Object> returnMap = new HashMap<String, Object>(); try { ksession = getStatefulKnowledgeSession(processId); ksessionId = ksession.getId(); addExtrasToStatefulKnowledgeSession(ksession); sBuilder.append("startProcessAndReturnId()\tsessionId : "+ksessionId+" : process = "+processId); if(parameters != null) { pInstance = ksession.startProcess(processId, parameters); } else { pInstance = ksession.startProcess(processId); } // now always return back to client the latest (possibly modified) pInstance variables // thank you Jano Kasarda Map<String, Object> variables = ((WorkflowProcessInstanceImpl) pInstance).getVariables(); for (String key : variables.keySet()) { returnMap.put(key, variables.get(key)); } returnMap.put(IKnowledgeSession.PROCESS_INSTANCE_ID, pInstance.getId()); returnMap.put(IKnowledgeSession.PROCESS_INSTANCE_STATE, pInstance.getState()); returnMap.put(IKnowledgeSession.KSESSION_ID, ksessionId); sessionPool.setProcessInstanceId(ksessionId, pInstance.getId()); }catch(Throwable x){ x.printStackTrace(); return null; }finally { if(ksession != null){ disposeStatefulKnowledgeSessionAndExtras(ksessionId); } } sBuilder.append(" : pInstanceId = "+pInstance.getId()+" : function (not necessarily pInstance) now completed. check the jbpm_bam db for status of pInstance"); log.info(sBuilder.toString()); return returnMap; } public int signalEvent(String signalType, Object signalValue, Long processInstanceId, Integer ksessionId) { StatefulKnowledgeSession ksession = null; try { try { // always go to the database to ensure row-level pessimistic lock for each process instance ksessionId = sessionPool.getSessionId(processInstanceId); //due to ksession.dispose() needing to be outside trnx, ksessionId could still be temporarily in kWrapperHash //want to avoid calling loadStatefulKnowledgeSessionAndExtras until ksessionId has been removed from kWrapperHash boolean goodToGo=true; for(int x=0; x < 10; x++){ if(kWrapperHash.containsKey(ksessionId)) { log.info("signalEvent() found ksession in cache for ksessionId = " +ksessionId+" : will sleep"); try {Thread.sleep(100);} catch(Exception t){t.printStackTrace();} goodToGo = false; }else { goodToGo = true; break; } } if(!goodToGo) throw new RuntimeException("signalEvent() the following ksession continues to be in use: "+ksessionId); ksession = this.loadStatefulKnowledgeSessionAndAddExtras(ksessionId); StringBuilder sBuilder = new StringBuilder("signalEvent() \n\tksession = "+ksessionId+"\n\tprocessInstanceId = "+processInstanceId+"\n\tsignalType="+signalType); // sometimes signalValue can be huge (as in if passing large JSON/xml strings ) if(enableLog) { sBuilder.append("\n\tsignalValue="+signalValue); } log.info(sBuilder.toString()); ProcessInstance pInstance = ksession.getProcessInstance(processInstanceId); if(pInstance == null){ log.warn("signalEvent() not able to locate pInstance with id = "+processInstanceId+" : for sessionId = "+ksessionId); return ProcessInstance.STATE_COMPLETED; }else { pInstance.signalEvent(signalType, signalValue); return pInstance.getState(); } }finally { if(ksession != null) disposeStatefulKnowledgeSessionAndExtras(ksessionId); } } catch(RuntimeException x) { log.error("signalEvent() exception thrown. signalType = "+signalType+" : pInstanceId = "+processInstanceId+" : ksessionId ="+ksessionId); throw x; }catch(Exception x) { log.error("signalEvent() exception thrown. signalType = "+signalType+" : pInstanceId = "+processInstanceId+" : ksessionId ="+ksessionId); throw new RuntimeException(x); } } public void abortProcessInstance(Long processInstanceId, Integer ksessionId) { StatefulKnowledgeSession ksession = null; try { try { if(ksessionId == null) ksessionId = sessionPool.getSessionId(processInstanceId); ksession = loadStatefulKnowledgeSessionAndAddExtras(ksessionId); ksession.abortProcessInstance(processInstanceId); }finally { if(ksession != null) disposeStatefulKnowledgeSessionAndExtras(ksessionId); } } catch(RuntimeException x) { throw x; }catch(Exception x) { throw new RuntimeException(x); } } public void upgradeProcessInstance(long processInstanceId, String processId, Map<String, Long> nodeMapping) { StatefulKnowledgeSession ksession = null; Integer ksessionId = 0; try { try { ksessionId = sessionPool.getSessionId(processInstanceId); ksession = loadStatefulKnowledgeSessionAndAddExtras(ksessionId); WorkflowProcessInstanceUpgrader.upgradeProcessInstance(ksession, processInstanceId, processId, nodeMapping); }finally { if(ksession != null) disposeStatefulKnowledgeSessionAndExtras(ksessionId); } } catch(Exception x) { throw new RuntimeException(x); } } public String printActiveProcessInstanceVariables(Long processInstanceId, Integer ksessionId) { Map<String,Object> vHash = null; try { try { if(ksessionId == null) ksessionId = sessionPool.getSessionId(processInstanceId); vHash = getActiveProcessInstanceVariables(processInstanceId, ksessionId); }finally { disposeStatefulKnowledgeSessionAndExtras(ksessionId); } }catch(Exception x) { throw new RuntimeException(x); } if(vHash.size() == 0) log.error("printActiveProcessInstanceVariables() no process instance variables for :\n\tprocessInstanceId = "+processInstanceId); StringWriter sWriter = null; try { sWriter = new StringWriter(); ObjectMapper jsonMapper = new ObjectMapper(); jsonMapper.writeValue(sWriter, vHash); return sWriter.toString(); }catch(Exception x){ throw new RuntimeException(x); }finally { if(sWriter != null) { try { sWriter.close(); }catch(Exception x){x.printStackTrace();} } } } public void setProcessInstanceVariables(Long processInstanceId, Map<String, Object> variables, Integer ksessionId) { try { try { if(ksessionId == null) ksessionId = sessionPool.getSessionId(processInstanceId); StatefulKnowledgeSession ksession = loadStatefulKnowledgeSessionAndAddExtras(ksessionId); ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); if (processInstance != null) { VariableScopeInstance variableScope = (VariableScopeInstance)((org.jbpm.process.instance.ProcessInstance) processInstance).getContextInstance(VariableScope.VARIABLE_SCOPE); if (variableScope == null) { throw new IllegalArgumentException("Could not find variable scope for process instance " + processInstanceId); } for (Map.Entry<String, Object> entry: variables.entrySet()) { variableScope.setVariable(entry.getKey(), entry.getValue()); } } else { throw new IllegalArgumentException("Could not find process instance " + processInstanceId); } }finally { disposeStatefulKnowledgeSessionAndExtras(ksessionId); } }catch(Exception x) { throw new RuntimeException(x); } } // notifies process engine to complete a work item and continue execution of next node in process instance // can no longer dispose knowledge session within scope of this transaction due to side effects from fix for JBRULES-1880 // subsequently, it's expected that a client will invoke 'disposeStatefulKnowledgeSessionAndExtras' after this JTA trnx has been committed public void completeWorkItem(Long workItemId, Map<String, Object> pInstanceVariables, Long pInstanceId, Integer ksessionId) { try { try { if(ksessionId == null) ksessionId = sessionPool.getSessionId(pInstanceId); StatefulKnowledgeSession ksession = loadStatefulKnowledgeSessionAndAddExtras(ksessionId); ksession.getWorkItemManager().completeWorkItem(workItemId, pInstanceVariables); }finally { disposeStatefulKnowledgeSessionAndExtras(ksessionId); } }catch(Exception x) { throw new RuntimeException(x); } } public Map<String, Object> getActiveProcessInstanceVariables(Long processInstanceId, Integer ksessionId) { if(ksessionId == null) ksessionId = sessionPool.getSessionId(processInstanceId); Map<String, Object> result = new HashMap<String, Object>(); try { StatefulKnowledgeSession ksession = this.loadStatefulKnowledgeSessionAndAddExtras(ksessionId); ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); if (processInstance != null) { Map<String, Object> variables = ((WorkflowProcessInstanceImpl) processInstance).getVariables(); if (variables == null) { return new HashMap<String, Object>(); } // filter out null values for (Map.Entry<String, Object> entry: variables.entrySet()) { if (entry.getValue() != null) { result.put(entry.getKey(), entry.getValue()); } } } else { log.error("getActiveProcessInstanceVariables() : Could not find process instance " + processInstanceId); } }finally{ this.disposeStatefulKnowledgeSessionAndExtras(ksessionId); } return result; } // implementation is expecting jContext parameter to be of type: org.quartz.JobExecutionContext public int processJobExecutionContext(Serializable jContext) { JobExecutionContext qContext = (JobExecutionContext)jContext; GlobalQuartzJobHandle jHandle = (GlobalQuartzJobHandle)(qContext.getMergedJobDataMap().get(QuartzSchedulerService.TIMER_JOB_HANDLE)); String jName = qContext.getJobDetail().getName(); String[] details = StringUtils.split(jName, DASH); int sessionId = jHandle.getSessionId(); String timerType = details[0]; long period = jHandle.getInterval(); try { if(QuartzSchedulerService.PROCESS_JOB.equals(timerType)){ long pInstanceId = Long.parseLong(details[1]); long timerId = Long.parseLong(details[2]); log.info("processJobExecution() sessionId = "+sessionId+" : pInstanceId = "+pInstanceId+" : timerId = "+timerId); TimerInstance jbpmTimerInstance = new TimerInstance(); jbpmTimerInstance.setId(timerId); /* A Timer node is set up with a delay and a period. * The delay specifies the amount of time to wait after node activation before triggering the timer the first time. * The period defines the time between subsequent trigger activations. * A period of 0 results in a one-shot timer. */ jbpmTimerInstance.setPeriod(period); jbpmTimerInstance.setProcessInstanceId(pInstanceId); // timerTriggered string constant is required to trigger a timer as per TimerNodeInstance.signalEvent(....) return this.signalEvent( TIMER_TRIGGERED, jbpmTimerInstance, pInstanceId, sessionId); }else if (QuartzSchedulerService.ACTIVATION_TIMER_JOB.equals(timerType)){ String processId = details[1]; // in ProcessRuntimeImpl.startProcessInstance(..), the actionQueue appears to be empty after the initial timer invocation // so, can continue to execute startProcessAndReturnId(...) with the cron trigger Map<String, Object> returnMap = this.startProcessAndReturnId(processId, null); return (Integer)returnMap.get(IKnowledgeSession.PROCESS_INSTANCE_STATE); }else { log.error("processJobExecution() TO-DO : need to figure out how to implement behavior associated with timer type = "+timerType); return ProcessInstance.STATE_PENDING; } } catch (Exception x) { throw new RuntimeException(x); } } class KnowledgeSessionWrapper { StatefulKnowledgeSession ksession; KnowledgeRuntimeLogger rLogger; BAMProducerWrapper pWrapper; public KnowledgeSessionWrapper(StatefulKnowledgeSession x, AsyncBAMProducer bamProducer) { ksession = x; try { if(bamProducer != null){ pWrapper = bamProducerPool.borrowObject(); bamProducer.setBAMProducerWrapper(pWrapper); } }catch(Exception e) { throw new RuntimeException(e); } } public void dispose() throws Exception { if(pWrapper != null) bamProducerPool.returnObject(pWrapper); if(rLogger != null) { rLogger.close(); } ksession.execute(new CMTDisposeCommand()); } public void setKnowledgeRuntimeLogger(KnowledgeRuntimeLogger x) { rLogger = x; } } public String getCurrentTimerJobsAsJson(String jobGroup) { try { return QuartzSchedulerService.getCurrentTimerJobsAsJson(jobGroup); }catch(Exception x){ throw new RuntimeException(x); } } @Override public int purgeCurrentTimerJobs(String jobGroup) { try { return QuartzSchedulerService.purgeCurrentTimerJobs(jobGroup); }catch(Exception x){ throw new RuntimeException(x); } } }