/*
* 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.util.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.drools.SystemEventListenerFactory;
import org.drools.KnowledgeBaseFactory;
import org.drools.command.SingleSessionCommandService;
import org.drools.command.impl.CommandBasedStatefulKnowledgeSession;
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.KnowledgeRuntimeLoggerFactory;
import org.drools.persistence.info.SessionInfo;
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.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.instance.context.variable.VariableScopeInstance;
import org.jbpm.workflow.instance.WorkflowProcessInstanceUpgrader;
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.jboss.processFlow.knowledgeService.IKnowledgeSession;
/**
*<pre>
*architecture
* - this singleton implements a 'single knowledgeSession for all pInstances' architecture
* - 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 ignored
*
* - this implementation is ideal in a multi-threaded, concurrent client environment where the following is either met or is acceptable :
* 1) process definitions do not include rule data
* 2) process instance lifecycle functions are executed synchroneously, not in parallel
* NOTE: see org.drools.persistence.SingleSessionCommandService.execute(...) function
*
*</pre>
*/
@ApplicationScoped
@Alternative
public class SingleSessionBean extends BaseKnowledgeSessionBean implements IKnowledgeSession {
private Logger log = Logger.getLogger(SingleSessionBean.class);
private StatefulKnowledgeSession ksession;
private Object lockObj = new Object();
@Inject
private AsyncBAMProducerPool bamProducerPool;
/******************************************************************************
************** Singleton Lifecycle Management *********/
@PostConstruct
public void start() throws Exception {
super.start();
}
@PreDestroy
public void stop() throws Exception{
// 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);
if(bamProducerPool != null)
bamProducerPool.close();
ksession.dispose();
}
/******************************************************************************
************* StatefulKnowledgeSession Management *********/
private void getStatefulKnowledgeSession() {
synchronized(lockObj) {
if(ksession != null)
return;
if(!useInMemoryKnowledgeSession){
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("FROM SessionInfo p ");
EntityManager eManager = jbpmCoreEMF.createEntityManager();
Query processInstanceQuery = eManager.createQuery(sqlBuilder.toString());
List<SessionInfo> results = processInstanceQuery.getResultList();
if(results.size() == 0){
ksession = makeStatefulKnowledgeSession();
}else if(results.size() > 1){
throw new RuntimeException("start() currently " +results.size()+" # of sessionInfo records when only 1 is allowed");
}else{
SessionInfo sInfoObj = (SessionInfo)results.get(0);
loadStatefulKnowledgeSession(sInfoObj.getId());
}
}else{
createOrRebuildKnowledgeBaseViaKnowledgeAgentOrBuilder();
ksession = kbase.newStatefulKnowledgeSession();
}
addExtrasToStatefulKnowledgeSession();
}
}
/*
-- this method is invoked by numerous methods such as 'completeWorkItem' and 'abortProcessInstance'
-- seems that there needs to be verification that StatefuleKnowledgeSession object corresponding to the ksession isn't already in use
-- without verification, there is a possibility that ksession corresponding to this ksessionId could be involved in processing of some other operation
-- optimistic lock exception could ensue
-- the kWrapperHash datastructure is a good candidate to use
*/
private void loadStatefulKnowledgeSession(Integer sessionId) {
//0) initialise knowledge base if it hasn't already been done so
if(kbase == null){
createKnowledgeBaseViaKnowledgeAgentOrBuilder();
}
//1) very important that a unique 'Environment' is created per StatefulKnowledgeSession
Environment ksEnv = createKnowledgeSessionEnvironment();
KnowledgeSessionConfiguration ksConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(ksconfigProperties);
// 2) instantiate new StatefulKnowledgeSession from old sessioninfo
ksession = JPAKnowledgeService.loadStatefulKnowledgeSession(sessionId, kbase, ksConfig, ksEnv);
}
private void addExtrasToStatefulKnowledgeSession() {
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();
log.info("afterProcessCompleted()\tsessionId : "+ksession.getId()+" : process : "+pInstance+" : pDefVersion = "+droolsProcess.getVersion());
}
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);
}
// set an AsyncBamProducer that works in a multithreaded environment
MultiThreadedAsyncBAMProducer bamProducer = new MultiThreadedAsyncBAMProducer();
ksession.addEventListener(bamProducer);
// 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
if(processEventListeners != null) {
for(String peString : processEventListeners) {
try {
if(StringUtils.isNotEmpty(peString)){
Class peClass = Class.forName(peString);
ProcessEventListener peListener = (ProcessEventListener)peClass.newInstance();
ksession.addEventListener(peListener);
}
} catch(Exception x) {
throw new RuntimeException(x);
}
}
}
// 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());
KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, sBuilder.toString());
}
}
public String dumpSessionStatusInfo() {
return "Not Applicable";
}
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
*- bean managed transaction demarcation is used by this method IOT dispose of the ksession *AFTER* the transaction has committed
*- otherwise, this method will fail due to implementation of JBRULES-1880
*</pre>
*/
public Map<String, Object> startProcessAndReturnId(String processId, Map<String, Object> parameters) {
StringBuilder sBuilder = new StringBuilder();
if(ksession == null)
this.getStatefulKnowledgeSession();
Integer ksessionId = ksession.getId();
Map<String, Object> returnMap = new HashMap<String, Object>();
try {
sBuilder.append("startProcessAndReturnId()\tsessionId : "+ksessionId+" : process = "+processId);
ProcessInstance pInstance = null;
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.KSESSION_ID, ksessionId);
sBuilder.append(" : pInstanceId = "+pInstance.getId()+" : now completed");
log.info(sBuilder.toString());
return returnMap;
} catch(Throwable x) {
x.printStackTrace();
return null;
}
}
/**
*completeWorkItem
*<pre>
*- notifies process engine to complete a work item and continue execution of next node in process instance
*- this method operates within scope of container managed transaction
*
*- NOTE: in this implementation, both pInstanceId and ksessionId can be null
*</pre>
*/
public void completeWorkItem(Long workItemId, Map<String, Object> pInstanceVariables, Long pInstanceId, Integer ksessionId) {
try {
if(ksession == null)
this.getStatefulKnowledgeSession();
ksession.getWorkItemManager().completeWorkItem(workItemId, pInstanceVariables);
} catch(RuntimeException x) {
throw x;
}catch(Exception x) {
throw new RuntimeException(x);
}
}
public int signalEvent(String signalType, Object signalValue, Long processInstanceId, Integer ksessionId) {
try {
if(ksession == null)
this.getStatefulKnowledgeSession();
if(enableLog)
log.info("signalEvent() \n\tksession = "+ksessionId+"\n\tprocessInstanceId = "+processInstanceId+"\n\tsignalType="+signalType+"\n\tsignalValue="+signalValue);
ProcessInstance pInstance = ksession.getProcessInstance(processInstanceId);
pInstance.signalEvent(signalType, signalValue);
return pInstance.getState();
} catch(RuntimeException x) {
rollbackTrnx();
throw x;
}catch(Exception x) {
rollbackTrnx();
throw new RuntimeException(x);
}
}
public void abortProcessInstance(Long processInstanceId, Integer ksessionId) {
try{
if(ksession == null)
this.getStatefulKnowledgeSession();
uTrnx.begin();
ksession.abortProcessInstance(processInstanceId);
uTrnx.commit();
}catch(Exception x) {
rollbackTrnx();
throw new RuntimeException(x);
} finally {
}
}
public String printActiveProcessInstanceVariables(Long processInstanceId, Integer ksessionId) {
Map<String,Object> vHash = getActiveProcessInstanceVariables(processInstanceId, ksessionId);
StringBuilder sBuilder = new StringBuilder();
if(vHash.size() == 0){
sBuilder.append("no process instance variables for :\n\tprocessInstanceId = ");
sBuilder.append(processInstanceId);
}
for (Map.Entry<?, ?> entry: vHash.entrySet()) {
sBuilder.append("\n");
sBuilder.append(entry.getKey());
sBuilder.append(" : ");
sBuilder.append(entry.getValue());
}
return sBuilder.toString();
}
public Map<String, Object> getActiveProcessInstanceVariables(Long processInstanceId, Integer ksessionId) {
try {
if(ksession == null)
this.getStatefulKnowledgeSession();
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
Map<String, Object> result = new HashMap<String, Object>();
for (Map.Entry<String, Object> entry: variables.entrySet()) {
if (entry.getValue() != null) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
} else {
throw new IllegalArgumentException("Could not find process instance " + processInstanceId);
}
} catch(Exception x) {
throw new RuntimeException(x);
} finally {
}
}
public void setProcessInstanceVariables(Long processInstanceId, Map<String, Object> variables, Integer ksessionId) {
try {
if(ksession == null)
this.getStatefulKnowledgeSession();
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 {
}
}
public void upgradeProcessInstance(long processInstanceId, String processId, Map<String, Long> nodeMapping) {
WorkflowProcessInstanceUpgrader.upgradeProcessInstance(ksession, processInstanceId, processId, nodeMapping);
}
public Map<String, Object> beanManagedGetActiveProcessInstanceVariables(Long processInstanceId, Integer ksessionId) {
return this.getActiveProcessInstanceVariables(processInstanceId, ksessionId);
}
public void beanManagedSetProcessInstanceVariables(Long processInstanceId, Map<String, Object> variables, Integer ksessionId) {
this.setProcessInstanceVariables(processInstanceId, variables, ksessionId);
}
public void beanManagedCompleteWorkItem(Long workItemId, Map<String, Object> pInstanceVariables, Long pInstanceId, Integer ksessionId) {
this.completeWorkItem(workItemId, pInstanceVariables, pInstanceId, ksessionId);
}
@Override
public int processJobExecutionContext(Serializable jobExectionContext) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getCurrentTimerJobsAsJson(String jobGroup) {
// TODO Auto-generated method stub
return null;
}
@Override
public int purgeCurrentTimerJobs(String jobGroup) {
// TODO Auto-generated method stub
return 0;
}
}