/*
* 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);
}
}
}