/*
* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.annotation.PreDestroy;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import javax.transaction.UserTransaction;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.SessionConfiguration;
import org.drools.SystemEventListener;
import org.drools.SystemEventListenerFactory;
import org.drools.agent.KnowledgeAgent;
import org.drools.agent.KnowledgeAgentConfiguration;
import org.drools.agent.KnowledgeAgentFactory;
import org.drools.agent.impl.PrintStreamSystemEventListener;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.command.SingleSessionCommandService;
import org.drools.common.InternalKnowledgeRuntime;
import org.drools.compiler.PackageBuilder;
import org.drools.core.util.DelegatingSystemEventListener;
import org.drools.definition.KnowledgePackage;
import org.drools.definition.process.Node;
import org.drools.definition.process.Process;
import org.drools.definition.process.WorkflowProcess;
import org.drools.definitions.impl.KnowledgePackageImp;
import org.drools.event.process.ProcessEventListener;
import org.drools.event.rule.AgendaEventListener;
import org.drools.event.rule.DefaultAgendaEventListener;
import org.drools.event.rule.RuleFlowGroupActivatedEvent;
import org.drools.event.rule.WorkingMemoryEventListener;
import org.drools.io.Resource;
import org.drools.io.ResourceChangeScannerConfiguration;
import org.drools.io.ResourceFactory;
import org.drools.io.impl.InputStreamResource;
import org.drools.management.DroolsManagementAgent;
import org.drools.marshalling.ObjectMarshallingStrategy;
import org.drools.marshalling.impl.ClassObjectMarshallingStrategyAcceptor;
import org.drools.marshalling.impl.SerializablePlaceholderResolverStrategy;
import org.drools.persistence.jpa.JPAKnowledgeService;
import org.drools.persistence.jpa.JpaJDKTimerService;
import org.drools.persistence.jpa.marshaller.JPAPlaceholderResolverStrategy;
import org.drools.persistence.jpa.processinstance.JPAWorkItemManagerFactory;
import org.drools.runtime.Environment;
import org.drools.runtime.EnvironmentName;
import org.drools.runtime.KnowledgeRuntime;
import org.drools.runtime.KnowledgeSessionConfiguration;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.process.WorkItemHandler;
import org.jboss.processFlow.tasks.ITaskService;
import org.jboss.processFlow.util.LogSystemEventListener;
import org.jboss.processFlow.workItem.WorkItemHandlerLifecycle;
import org.jbpm.compiler.ProcessBuilderImpl;
import org.jbpm.integration.console.shared.GuvnorConnectionUtils;
import org.jbpm.persistence.processinstance.ProcessInstanceInfo;
import org.jbpm.task.service.TaskService;
import org.jbpm.workflow.core.NodeContainer;
import org.mvel2.MVEL;
import org.mvel2.ParserConfiguration;
import org.mvel2.ParserContext;
/**
*<pre>
*architecture
* Drools knowledgeBase management
* - this implementation instantiates a single instance of org.drools.KnowledgeBase
* - this KnowledgeBase is kept current by interacting with a remote BRMS guvnor service
* - note: this KnowledgeBase instance is instantiated the first time any IKnowledgeSession operation is invoked
* - the KnowledgeBase is not instantiated in a start() method because the BRMS guvnor may be co-located on the same jvm
* as this KnowledgeSessionService and may not yet be available (depending on boot-loader order)
*
*
* WorkItemHandler Management
* - Creating & configuring custom work item handlers in PFP is almost identical to creating custom work item handlers in stock BRMS
* - Background Documentation : 12.1.3 Registering your own service handlers
* - The following are a few processFlowProvision additions :
*
* 1) programmatically registered work item handlers
* -- every StatefulKnowledgeSession managed by the processFlowProvision knowledgeSessionService is automatically registered with
*
* the following workItemHandlers :
* 1) "Human Task" : org.jboss.processFlow.tasks.handlers.PFPAddHumanTaskHandler
* 2) "Skip Task" : org.jboss.processFlow.tasks.handlers.PFPSkipTaskHandler
* 3) "Fail Task" : org.jboss.processFlow.tasks.handlers.PFPFailTaskHandler
* 4) "Email" : org.jboss.processFlow.tasks.handlers.PFPEmailWorkItemHandler
*
* 2) defining configurable work item handlers
* -- jbpm5 allows for more than one META-INF/drools.session.conf in the runtime classpath
* -- subsequently, there is the potential for mulitple locations that define custom work item handlers
* -- the ability to have multiple META-INF/drools.session.conf files on the runtime classpath most likely will lead to
* increased difficulty isolating problems encountered with defining and registering custom work item handlers
* -- processFlowProvision/build.properties includes the following property: space.delimited.workItemHandler.configs
* -- rather than allowing for multiple locations to define custom work item handlers,
* use of the 'space.delimited.workItemHandler.configs' property centralalizes where to define additional custom workItemHandlers
* -- please see documentation provided for that property in the build.properties
*
* processEventListeners
* - ProcessEventListeners get registered with the knowledgeSession/processEngine
* - when any of the corresponding events occurs in the lifecycle of a process instance, those processevent listeners get invoked
* - a configurable list of process event listeners can be registered with the process engine via the following system prroperty:
* IKnowledgeSession.SPACE_DELIMITED_PROCESS_EVENT_LISTENERS
*
* - in processFlowProvision, we have two classes that implement org.drools.event.process.ProcessEventListener :
* 1) the 'busySessionsListener' inner class constructed in this knowledgeSessionService
* -- used to help maintain our ksessionid state
* -- a new instance is automatically registered with a ksession with new ksession creation or ksession re-load
* 2) org.jboss.processFlow.bam.AsyncBAMProducer
* -- sends BAM events to a hornetq queue
* -- registered by including it in IKnowledgeSession.SPACE_DELIMITED_PROCESS_EVENT_LISTENERS system property
*
*
* BAM audit logging
* - this implementation leverages a pool of JMS producers to send BAM events to a JMS provider
* - a corresponding BAM consumer receives those BAM events and persists to the BRMS BAM database
* - it is possible to disable the production of BAM events by NOT including 'org.jboss.processFlow.bam.AsyncBAMProducer' as a value
* in the IKnowledgeSession.SPACE_DELIMITED_PROCESS_EVENT_LISTENERS property
* - note: if 'org.jboss.processFlow.bam.AsyncBAMProducer' is not included, then any clients that query the BRMS BAM database will be affected
* - an example is the BRMS gwt-console-server
* the gwt-console-server queries the BRMS BAM database for listing of active process instances
*
*
* 22 Jan 2013: various performance optimizations and general cleanup contributed by Michal Valach. thank you!
*/
public class BaseKnowledgeSessionBean {
public static final String EMF_NAME = "org.jbpm.persistence.jpa";
public static final String DROOLS_SESSION_CONF_PATH="/META-INF/drools.session.conf";
public static final String DROOLS_SESSION_TEMPLATE_PATH="drools.session.template.path";
public static final String DROOLS_WORK_ITEM_HANDLERS = "drools.workItemHandlers";
public static final String USE_IN_MEMORY_KNOWLEDGE_SESSION = "use.in.memory.knowledge.session";
protected Logger log = Logger.getLogger(BaseKnowledgeSessionBean.class);
protected String droolsResourceScannerInterval = "30";
protected boolean enableLog = false;
protected boolean enableKnowledgeRuntimeLogger = true;
protected Map<String, Class<?>> programmaticallyLoadedWorkItemHandlers = new HashMap<String, Class<?>>();
protected KnowledgeBase kbase = null;
protected KnowledgeAgent kagent = null;
protected boolean kAgentMonitor = true;
protected long lastKAgentRefresh;
protected int kAgentRefreshHours = 12;
protected SystemEventListener originalSystemEventListener = null;
protected DroolsManagementAgent kmanagement = null;
protected GuvnorConnectionUtils guvnorUtils = null;
protected Properties ksconfigProperties;
protected String[] processEventListeners;
protected String guvnorChangeSet;
protected Properties guvnorProps;
protected String taskCleanUpImpl;
protected String templateString;
private int sessionTemplateInstantiationAttempts = 0;
private Object templateStringLockObj = new Object();
private Object sessionTemplateInstantiationLock = new Object();
protected boolean useInMemoryKnowledgeSession = Boolean.parseBoolean(System.getProperty(USE_IN_MEMORY_KNOWLEDGE_SESSION, "FALSE"));
/* static variable because :
* 1) TaskService is a thread-safe object
* 2) TaskService is needed for both :
* - PFP HumanTaskService : functions using a jta enable entity manager for human task functionality
* - PFP KnowledgeSessionService : needed to instantiate TasksAdmin object and register with knowledgeSession
*/
protected static TaskService jtaTaskService;
protected @PersistenceUnit(unitName=EMF_NAME) EntityManagerFactory jbpmCoreEMF;
protected @javax.annotation.Resource UserTransaction uTrnx;
private boolean useJPAPlaceholderResolverStrategy;
protected void start() throws Exception{
/* - set KnowledgeBase properties
* - the alternative to this programmatic approach is a 'META-INF/drools.session.conf' on the classpath
*/
ksconfigProperties = new Properties();
ksconfigProperties.put("drools.commandService", SingleSessionCommandService.class.getName());
ksconfigProperties.put("drools.processInstanceManagerFactory", "org.jbpm.persistence.processinstance.JPAProcessInstanceManagerFactory");
ksconfigProperties.setProperty( "drools.workItemManagerFactory", JPAWorkItemManagerFactory.class.getName() );
ksconfigProperties.put("drools.processSignalManagerFactory", "org.jbpm.persistence.processinstance.JPASignalManagerFactory");
if(System.getProperty("org.jboss.processFlow.drools.resource.scanner.interval") != null)
droolsResourceScannerInterval = System.getProperty("org.jboss.processFlow.drools.resource.scanner.interval");
taskCleanUpImpl = System.getProperty(IKnowledgeSession.TASK_CLEAN_UP_PROCESS_EVENT_LISTENER_IMPL);
useJPAPlaceholderResolverStrategy = Boolean.parseBoolean(System.getProperty(IKnowledgeSession.USE_JPA_PLACEHOLDER_RESOLVER_STRATEGY, "FALSE"));
String timerService = System.getProperty("drools.timerService", JpaJDKTimerService.class.getName());
ksconfigProperties.setProperty( "drools.timerService", timerService);
guvnorUtils = new GuvnorConnectionUtils();
enableLog = Boolean.parseBoolean(System.getProperty("org.jboss.enableLog", "TRUE"));
if(System.getProperty(IKnowledgeSession.SPACE_DELIMITED_PROCESS_EVENT_LISTENERS) != null)
processEventListeners = System.getProperty(IKnowledgeSession.SPACE_DELIMITED_PROCESS_EVENT_LISTENERS).split("\\s");
kAgentRefreshHours = Integer.parseInt(System.getProperty("org.jboss.processFlow.kAgentRefreshHours", "12"));
kAgentMonitor = Boolean.parseBoolean(System.getProperty("org.jboss.processFlow.kAgentMonitor", "TRUE"));
enableKnowledgeRuntimeLogger = Boolean.parseBoolean(System.getProperty("org.jboss.processFlow.statefulKnowledge.enableKnowledgeRuntimeLogger", "TRUE"));
// 2) set the Drools system event listener to our implementation ...
originalSystemEventListener = SystemEventListenerFactory.getSystemEventListener();
if (originalSystemEventListener == null || originalSystemEventListener instanceof DelegatingSystemEventListener) {
// We need to check for DelegatingSystemEventListener so we don't get a
// StackOverflowError when we set it back. If it is a DelegatingSystemEventListener,
// we instead use what drools wraps by default, which is PrintStreamSystemEventListener.
// Refer to org.drools.impl.SystemEventListenerServiceImpl for more information.
originalSystemEventListener = new PrintStreamSystemEventListener();
}
SystemEventListenerFactory.setSystemEventListener(new LogSystemEventListener());
programmaticallyLoadedWorkItemHandlers.put(ITaskService.HUMAN_TASK, Class.forName("org.jboss.processFlow.tasks.handlers.PFPAddHumanTaskHandler"));
programmaticallyLoadedWorkItemHandlers.put(ITaskService.SKIP_TASK, Class.forName("org.jboss.processFlow.tasks.handlers.PFPSkipTaskHandler"));
programmaticallyLoadedWorkItemHandlers.put(ITaskService.FAIL_TASK, Class.forName("org.jboss.processFlow.tasks.handlers.PFPFailTaskHandler"));
programmaticallyLoadedWorkItemHandlers.put(IKnowledgeSession.EMAIL, Class.forName("org.jboss.processFlow.email.PFPEmailWorkItemHandler"));
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("start() ksession props as follows :\n\tdrools guvnor scanner interval = ");
logBuilder.append(droolsResourceScannerInterval);
logBuilder.append("\n\ttimerService = ");
logBuilder.append(timerService);
logBuilder.append("\n\ttaskCleanUpImpl = ");
logBuilder.append(taskCleanUpImpl);
logBuilder.append("\n\tenableLog = ");
logBuilder.append(enableLog);
logBuilder.append("\n\tkAgentMonitor = ");
logBuilder.append(kAgentMonitor);
logBuilder.append("\n\tkAgentRefreshHours = ");
logBuilder.append(kAgentRefreshHours);
logBuilder.append("\n\tuseJPAPlaceholderResolverStrategy = ");
logBuilder.append(useJPAPlaceholderResolverStrategy);
logBuilder.append("\n\tuseInMemoryKnowledgeSession = ");
logBuilder.append(useInMemoryKnowledgeSession);
log.info(logBuilder.toString());
}
@PreDestroy
public void destroy() throws Exception {
if (kagent != null) {
kagent.dispose();
kagent = null;
}
}
/******************************************************************************
* ************* Drools KnowledgeBase Management *********/
// critical that each StatefulKnowledgeSession have its own JPA 'Environment'
protected Environment createKnowledgeSessionEnvironment() {
Environment env = KnowledgeBaseFactory.newEnvironment();
env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, jbpmCoreEMF);
if(useJPAPlaceholderResolverStrategy) {
env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, new ObjectMarshallingStrategy[] {
new JPAPlaceholderResolverStrategy(env),
new SerializablePlaceholderResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT) }
);
}
return env;
}
protected synchronized void checkKAgentAndBaseHealth() {
long kAgentRefreshLapsedTime = System.currentTimeMillis() - lastKAgentRefresh;
long kAgentRefreshMillis = 1000*60*kAgentRefreshHours;
if(kbase == null || (kAgentMonitor && (kAgentRefreshLapsedTime > kAgentRefreshMillis))){
log.info("checkKAgentAndBaseHealth() will now refresh kbase and kagent. lapsed time "+kAgentRefreshLapsedTime+" is greater than "+kAgentRefreshMillis);
//ddoyle: Setting the kbase to null seems to cause issues in
//kbase = null;
createKnowledgeBaseViaKnowledgeAgentOrBuilder();
}
}
public synchronized void createKnowledgeBaseViaKnowledgeAgentOrBuilder() {
try {
this.createKnowledgeBaseViaKnowledgeAgent();
}catch(ConnectException x){
log.warn("createKnowledgeBaseViaKnowledgeAgentOrBuilder() can not create a kbase via a kagent due to a connection problem with guvnor ... will now create kbase via knowledgeBuilder");
rebuildKnowledgeBaseViaKnowledgeBuilder();
}
}
public synchronized void createOrRebuildKnowledgeBaseViaKnowledgeAgentOrBuilder() {
try {
this.createKnowledgeBaseViaKnowledgeAgent(true);
}catch(ConnectException x){
log.warn("createOrRebuildKnowledgeBaseViaKnowledgeAgentOrBuilder() can not create a kbase via a kagent due to a connection problem with guvnor ... will now create kbase via knowledgeBuilder");
rebuildKnowledgeBaseViaKnowledgeBuilder();
}
}
public synchronized void rebuildKnowledgeBaseViaKnowledgeAgent() throws ConnectException{
this.createKnowledgeBaseViaKnowledgeAgent(true);
}
protected synchronized void createKnowledgeBaseViaKnowledgeAgent() throws ConnectException{
this.createKnowledgeBaseViaKnowledgeAgent(false);
}
// only one knowledgeBase object is needed and is shared amongst all StatefulKnowledgeSessions
// needs to be invoked AFTER guvnor is available (obviously)
// setting 'force' parameter to true re-creates an existing kbase
protected synchronized void createKnowledgeBaseViaKnowledgeAgent(boolean forceRefresh) throws ConnectException{
log.info("createKnowledgeBaseViaKnowledgeAgent() forceRefresh = "+forceRefresh);
if(kbase != null && !forceRefresh)
return;
if(kagent != null) {
kagent.dispose();
kagent = null;
}
// investigate: List<String> guvnorPackages = guvnorUtils.getBuiltPackageNames();
// http://ratwateribm:8080/jboss-brms/org.drools.guvnor.Guvnor/package/org.jboss.processFlow/test-pfp-snapshot
if(!guvnorUtils.guvnorExists()) {
StringBuilder sBuilder = new StringBuilder();
sBuilder.append(guvnorUtils.getGuvnorProtocol());
sBuilder.append("://");
sBuilder.append(guvnorUtils.getGuvnorHost());
sBuilder.append("/");
sBuilder.append(guvnorUtils.getGuvnorSubdomain());
sBuilder.append("/rest/packages/");
throw new ConnectException("createKnowledgeBase() cannot connect to guvnor at URL : "+sBuilder.toString());
}
// for polling of guvnor to occur, the polling and notifier services must be started
ResourceChangeScannerConfiguration sconf = ResourceFactory.getResourceChangeScannerService().newResourceChangeScannerConfiguration();
// Do not start change set notifications
Integer droolsResourceScannerIntervalValue = -1;
try {
droolsResourceScannerIntervalValue = Integer.valueOf(droolsResourceScannerInterval);
} catch (NumberFormatException nfe) {
log.error("DroolsResourceScannerInterval is not an integer: " + droolsResourceScannerInterval, nfe);
}
if (droolsResourceScannerIntervalValue > 0) {
sconf.setProperty( "drools.resource.scanner.interval", droolsResourceScannerInterval);
ResourceFactory.getResourceChangeScannerService().configure( sconf );
ResourceFactory.getResourceChangeScannerService().start();
ResourceFactory.getResourceChangeNotifierService().start();
}
KnowledgeAgentConfiguration aconf = KnowledgeAgentFactory.newKnowledgeAgentConfiguration(); // implementation = org.drools.agent.impl.KnowledgeAgentConfigurationImpl
/* - incremental change set processing enabled
- will create a single KnowledgeBase and always refresh that same instance
*/
aconf.setProperty("drools.agent.newInstance", "false");
if (droolsResourceScannerIntervalValue < 0) {
aconf.setProperty("drools.agent.scanResources", Boolean.FALSE.toString());
aconf.setProperty("drools.agent.scanDirectories", Boolean.FALSE.toString());
aconf.setProperty("drools.agent.monitorChangeSetEvents", Boolean.FALSE.toString());
}
/* -- Knowledge Agent provides automatic loading, caching and re-loading of resources
-- the knowledge agent can update or rebuild this knowledge base as the resources it uses are changed
*/
kagent = KnowledgeAgentFactory.newKnowledgeAgent("Guvnor default", aconf);
StringReader sReader = guvnorUtils.createChangeSet();
try {
guvnorChangeSet = IOUtils.toString(sReader);
sReader.close();
}catch(Exception x){
x.printStackTrace();
}
kagent.applyChangeSet(ResourceFactory.newByteArrayResource(guvnorChangeSet.getBytes()));
/* - set KnowledgeBase as instance variable to this mbean for use throughout all functionality of this service
- a knowledge base is a collection of compiled definitions, such as rules and processes, which are compiled using the KnowledgeBuilder
- the knowledge base itself does not contain instance data, known as facts
- instead, sessions are created from the knowledge base into which data can be inserted and where process instances may be started
- creating the knowledge base can be heavy, whereas session creation is very light : http://blog.athico.com/2011/09/small-efforts-big-improvements.html
- a knowledge base is also serializable, allowing for it to be stored
*/
kbase = kagent.getKnowledgeBase();
lastKAgentRefresh = System.currentTimeMillis();
log.info("createKnowledgeBaseViaKnowledgeAgent() just refreshed kBase via knowledgeAgent");
}
/*
* intention of this function is to create a knowledgeBase without a strict dependency on guvnor
* will still query guvnor for packages but will continue on even if problems communicating with guvnor exists
* this function could be of use in those scenarios where guvnor is not accessible
* knowledgeBase can subsequently be populated via one of the addProcessToKnowledgeBase(....) functions
* in all cases, the knowledgeBase created by this function will NOT be registered with a knowledgeAgent that receives updates from guvnor
*/
public synchronized void rebuildKnowledgeBaseViaKnowledgeBuilder() {
guvnorProps = new Properties();
try {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
if(guvnorUtils.guvnorExists()) {
guvnorProps.load(BaseKnowledgeSessionBean.class.getResourceAsStream("/jbpm.console.properties"));
StringBuilder guvnorSBuilder = new StringBuilder();
guvnorSBuilder.append(guvnorProps.getProperty(GuvnorConnectionUtils.GUVNOR_PROTOCOL_KEY));
guvnorSBuilder.append("://");
guvnorSBuilder.append(guvnorProps.getProperty(GuvnorConnectionUtils.GUVNOR_HOST_KEY));
guvnorSBuilder.append("/");
guvnorSBuilder.append(guvnorProps.getProperty(GuvnorConnectionUtils.GUVNOR_SUBDOMAIN_KEY));
String guvnorURI = guvnorSBuilder.toString();
List<String> packages = guvnorUtils.getPackageNames();
if(packages.size() > 0){
for(String pkg : packages){
GuvnorRestApi guvnorRestApi = new GuvnorRestApi(guvnorURI);
try {
InputStream binaryPackage = guvnorRestApi.getBinaryPackage(pkg);
kbuilder.add(new InputStreamResource(binaryPackage), ResourceType.PKG);
guvnorRestApi.close();
} catch(java.io.IOException y) {
log.error("rebuildKnowledgeBaseViaKnowledgeBuilder() returned following exception when querying package = "+pkg+" : "+y);
}
}
}else {
log.warn("rebuildKnowledgeBaseViaKnowledgeBuilder() no packages returned from Guvnor");
}
}else if(StringUtils.isNotEmpty(System.getProperty(IKnowledgeSession.CHANGE_SET_URLS))){
processEventListeners = System.getProperty(IKnowledgeSession.SPACE_DELIMITED_PROCESS_EVENT_LISTENERS).split("\\s");
String[] changeSetUrls = System.getProperty(IKnowledgeSession.CHANGE_SET_URLS).split("\\s");
for(String changeSetUrl : changeSetUrls){
InputStream iStream = null;
try{
iStream = new FileInputStream(changeSetUrl);
Resource rObj = new InputStreamResource(iStream);
kbuilder.add(rObj, ResourceType.PKG);
}finally{
if(iStream != null)
iStream.close();
}
}
}else {
log.warn("rebuildKnowledgeBaseViaKnowledgeBuilder() guvnor does not exist and the following property is null: "+IKnowledgeSession.CHANGE_SET_URLS);
}
kbase = kbuilder.newKnowledgeBase();
log.info("rebuildKnowledgeBaseViaKnowledgeBuilder() just created kbase via KnowledgeBase");
}catch(RuntimeException x){
throw x;
}catch(Exception x){
throw new RuntimeException(x);
}
}
// compile a process into a package and add it to the knowledge base
public void addProcessToKnowledgeBase(Process processObj, Resource resourceObj) {
checkKAgentAndBaseHealth();
PackageBuilder packageBuilder = new PackageBuilder();
ProcessBuilderImpl processBuilder = new ProcessBuilderImpl( packageBuilder );
processBuilder.buildProcess( processObj, resourceObj);
List<KnowledgePackage> kpackages = new ArrayList<KnowledgePackage>();
kpackages.add( new KnowledgePackageImp( packageBuilder.getPackage() ) );
kbase.addKnowledgePackages(kpackages);
log.info("addProcessToKnowledgeBase() just added process obj to the kbase with Id : "+processObj.getId());
}
public void addProcessToKnowledgeBase(File bpmnFile) {
checkKAgentAndBaseHealth();
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newFileResource(bpmnFile), ResourceType.BPMN2);
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
if( !kbuilder.hasErrors() ) {
log.info("addProcessToKnowledgeBase() just added the following bpmn2 process definition to the kbase: "+bpmnFile.getName());
} else {
log.error("addProcessToKnowledgeBase() following errors occurred when adding bpmn2 process definition : "+bpmnFile.getName()+"\n\t"+kbuilder.getErrors().toString() );
}
}
public String getAllProcessesInPackage(String pkgName) throws ConnectException{
if(!guvnorUtils.guvnorExists()) {
StringBuilder sBuilder = new StringBuilder();
sBuilder.append(guvnorUtils.getGuvnorProtocol());
sBuilder.append("://");
sBuilder.append(guvnorUtils.getGuvnorHost());
sBuilder.append("/");
sBuilder.append(guvnorUtils.getGuvnorSubdomain());
sBuilder.append("/rest/packages/");
throw new ConnectException("createKnowledgeBase() cannot connect to guvnor at URL : "+sBuilder.toString());
}
List<String> processes = guvnorUtils.getAllProcessesInPackage(pkgName);
StringBuilder sBuilder = new StringBuilder("getAllProcessesInPackage() pkgName = "+pkgName);
if(processes.isEmpty()){
sBuilder.append("\n\n\t : not processes found");
return sBuilder.toString();
}
for(String pDef : processes){
sBuilder.append("\n\t");
sBuilder.append(pDef);
}
return sBuilder.toString();
}
public String printKnowledgeBaseContent() {
checkKAgentAndBaseHealth();
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("guvnor changesets:\n\t");
if(guvnorChangeSet != null)
sBuilder.append(guvnorChangeSet);
else
sBuilder.append("not yet created by knowledgeAgent");
Collection<KnowledgePackage> kPackages = kbase.getKnowledgePackages();
if(kPackages != null && kPackages.size() > 0) {
sBuilder.append("\nprintKnowledgeBaseContent()\n\t");
for(KnowledgePackage kPackage : kPackages){
Collection<Process> processes = kPackage.getProcesses();
if(processes.size() == 0){
sBuilder.append("\n\tpackage = "+kPackage.getName()+" : no process definitions found ");
}else {
for (Process process : processes) {
sBuilder.append("\n\tpackage = "+kPackage.getName()+" : pDef= " + process.getId()+" : pDefVersion= "+process.getVersion());
}
}
}
} else {
sBuilder.append("\n\nNo Packages found in kbase");
}
sBuilder.append("\n");
return sBuilder.toString();
}
protected SessionTemplate newSessionTempate() {
return newSessionTemplate(null);
}
protected SessionTemplate newSessionTemplate(StatefulKnowledgeSession ksession) {
//looking for session.templte on local file system
if(templateString == null){
synchronized(templateStringLockObj){
if(templateString == null){
String droolsSessionTemplatePath = System.getProperty(DROOLS_SESSION_TEMPLATE_PATH);
if(StringUtils.isNotEmpty(droolsSessionTemplatePath)){
File droolsSessionTemplate = new File(droolsSessionTemplatePath);
if(!droolsSessionTemplate.exists()) {
log.error("newSessionTemplate() drools session template not found at : "+droolsSessionTemplatePath);
sessionTemplateInstantiationAttempts = -1;
}else {
FileInputStream fStream = null;
try {
fStream = new FileInputStream(droolsSessionTemplate);
templateString = IOUtils.toString(fStream);
}catch(IOException x){
x.printStackTrace();
}finally {
if(fStream != null) {
try {fStream.close(); }catch(Exception x){x.printStackTrace();}
}
}
}
}else {
throw new RuntimeException("newSessionTemplate() following property must be defined : "+DROOLS_SESSION_TEMPLATE_PATH);
}
}
}
}
if(sessionTemplateInstantiationAttempts == -1)
return null;
if(sessionTemplateInstantiationAttempts == 0){
synchronized(sessionTemplateInstantiationLock){
if(sessionTemplateInstantiationAttempts == -1)
return null;
return parseSessionTemplateString(ksession);
}
}
return parseSessionTemplateString(ksession);
}
// TO-DO: somehow add ksession via mvel to workItemhandlers defined in session template
private SessionTemplate parseSessionTemplateString(StatefulKnowledgeSession ksession) {
ParserConfiguration pconf = new ParserConfiguration();
pconf.addImport("SessionTemplate", SessionTemplate.class);
ParserContext context = new ParserContext(pconf);
Serializable s = MVEL.compileExpression(templateString.trim(), context);
try {
Map vars = new HashMap();
vars.put(IKnowledgeSession.KSESSION, ksession);
SessionTemplate sTemplate = (SessionTemplate)MVEL.executeExpression(s, vars);
sessionTemplateInstantiationAttempts = 1;
return sTemplate;
}catch(Throwable x){
sessionTemplateInstantiationAttempts = -1;
x.printStackTrace();
log.error("newSessionTemplate() following exception thrown \n\t"+x.getLocalizedMessage()+"\n : with session template string = \n\n"+templateString);
return null;
}
}
/******************************************************************************
* ************* WorkItemHandler Management *********/
public String printWorkItemHandlers() {
StringBuilder sBuilder = new StringBuilder("Programmatically Loaded Work Item Handlers :");
for(String name : programmaticallyLoadedWorkItemHandlers.keySet()){
sBuilder.append("\n\t");
sBuilder.append(name);
sBuilder.append(" : ");
sBuilder.append(programmaticallyLoadedWorkItemHandlers.get(name));
}
sBuilder.append("\nWork Item Handlers loaded from drools session template:");
SessionTemplate sTemplate = newSessionTemplate(null);
if(sTemplate != null && (sTemplate.getWorkItemHandlers() != null)){
for(Map.Entry<?, ?> entry : sTemplate.getWorkItemHandlers().entrySet()){
Class wiClass = entry.getValue().getClass();
sBuilder.append("\n\t");
sBuilder.append(entry.getKey());
sBuilder.append(" : ");
sBuilder.append(wiClass.getClass());
}
}else {
sBuilder.append("\n\tsessionTemplate not instantiated or is empty... check previous exceptions");
}
sBuilder.append("\nConfiguration Loaded Work Item Handlers :");
SessionConfiguration ksConfig = (SessionConfiguration)KnowledgeBaseFactory.newKnowledgeSessionConfiguration(ksconfigProperties);
try {
Map<String, WorkItemHandler> wiHandlers = ksConfig.getWorkItemHandlers();
if(wiHandlers.size() == 0) {
sBuilder.append("\n\t no work item handlers defined");
Properties badProps = createPropsFromDroolsSessionConf();
if(badProps == null)
sBuilder.append("\n\tunable to locate "+DROOLS_SESSION_CONF_PATH);
else
sBuilder.append("\n\tlocated"+DROOLS_SESSION_CONF_PATH);
} else {
for(String name : wiHandlers.keySet()){
sBuilder.append("\n\t");
sBuilder.append(name);
sBuilder.append(" : ");
Class wiClass = wiHandlers.get(name).getClass();
sBuilder.append(wiClass);
}
}
}catch(NullPointerException x){
sBuilder.append("\n\tError intializing at least one of the configured work item handlers via drools.session.conf.\n\tEnsure all space delimited work item handlers listed in drools.session.conf exist on the classpath");
Properties badProps = createPropsFromDroolsSessionConf();
if(badProps == null){
sBuilder.append("\n\tunable to locate "+DROOLS_SESSION_CONF_PATH);
} else {
try {
Enumeration badEnums = badProps.propertyNames();
while (badEnums.hasMoreElements()) {
String handlerConfName = (String) badEnums.nextElement();
if(DROOLS_WORK_ITEM_HANDLERS.equals(handlerConfName)) {
String[] badHandlerNames = ((String)badProps.get(handlerConfName)).split("\\s");
for(String badHandlerName : badHandlerNames){
sBuilder.append("\n\t\t");
sBuilder.append(badHandlerName);
InputStream iStream = this.getClass().getResourceAsStream("/META-INF/"+badHandlerName);
if(iStream != null){
sBuilder.append("\t : found on classpath");
iStream.close();
} else {
sBuilder.append("\t : NOT FOUND on classpath !!!!! ");
}
}
}
}
} catch (Exception y) {
y.printStackTrace();
}
}
}catch(org.mvel2.CompileException x) {
sBuilder.append("\n\t located "+DROOLS_SESSION_CONF_PATH);
sBuilder.append("\n\t however, following ClassNotFoundException encountered when instantiating defined work item handlers : \n\t\t");
sBuilder.append(x.getLocalizedMessage());
}
sBuilder.append("\n");
return sBuilder.toString();
}
private Properties createPropsFromDroolsSessionConf() {
Properties badProps = null;
InputStream iStream = null;
try {
iStream = this.getClass().getResourceAsStream(DROOLS_SESSION_CONF_PATH);
if(iStream != null){
badProps = new Properties();
badProps.load(iStream);
iStream.close();
}
} catch(Exception x) {
x.printStackTrace();
}
return badProps;
}
protected void registerWorkItemHandler(StatefulKnowledgeSession ksession, String serviceTaskName, WorkItemHandlerLifecycle handler) {
try {
ksession.getWorkItemManager().registerWorkItemHandler(serviceTaskName, handler);
} catch(NullPointerException x) {
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("registerHumanTaskWorkItemHandler() ********* NullPointerException when attempting to programmatically register workItemHander of type: "+serviceTaskName);
sBuilder.append("\nthe following is a report of your work item situation: \n\n");
sBuilder.append(printWorkItemHandlers());
sBuilder.append("\n");
log.error(sBuilder);
throw x;
}
}
protected void registerAddHumanTaskWorkItemHandler(StatefulKnowledgeSession ksession) {
try {
// 1. instantiate an object and register with this session workItemManager
Class workItemHandlerClass = programmaticallyLoadedWorkItemHandlers.get(ITaskService.HUMAN_TASK);
WorkItemHandlerLifecycle handler = (WorkItemHandlerLifecycle)workItemHandlerClass.newInstance();
// 2. register workItemHandler with workItemManager
registerWorkItemHandler(ksession, ITaskService.HUMAN_TASK, handler);
// 3). call init() on newly instantiated WorkItemHandlerLifecycle
handler.init(ksession);
}catch(Exception x) {
throw new RuntimeException(x);
}
}
protected void registerSkipHumanTaskWorkItemHandler(StatefulKnowledgeSession ksession){
try {
Class workItemHandlerClass = programmaticallyLoadedWorkItemHandlers.get(ITaskService.SKIP_TASK);
WorkItemHandlerLifecycle handler = (WorkItemHandlerLifecycle)workItemHandlerClass.newInstance();
registerWorkItemHandler(ksession, ITaskService.SKIP_TASK, handler);
handler.init(ksession);
}catch(Exception x) {
throw new RuntimeException(x);
}
}
protected void registerFailHumanTaskWorkItemHandler(StatefulKnowledgeSession ksession){
try {
Class workItemHandlerClass = programmaticallyLoadedWorkItemHandlers.get(ITaskService.FAIL_TASK);
WorkItemHandlerLifecycle handler = (WorkItemHandlerLifecycle)workItemHandlerClass.newInstance();
registerWorkItemHandler(ksession, ITaskService.FAIL_TASK, handler);
handler.init(ksession);
}catch(Exception x) {
throw new RuntimeException(x);
}
}
protected void registerEmailWorkItemHandler(StatefulKnowledgeSession ksession) {
String address = System.getProperty("org.jbpm.workItemHandler.mail.address");
String port = System.getProperty("org.jbpm.workItemHandler.mail.port");
String userId = System.getProperty("org.jbpm.workItemHandler.mail.userId");
String password = System.getProperty("org.jbpm.workItemHandler.mail.password");
WorkItemHandlerLifecycle handler = null;
try {
Class workItemHandlerClass = programmaticallyLoadedWorkItemHandlers.get(IKnowledgeSession.EMAIL);
Class[] classParams = new Class[] {String.class, String.class, String.class, String.class};
Object[] objParams = new Object[] {address, port, userId, password};
Constructor cObj = workItemHandlerClass.getConstructor(classParams);
handler = (WorkItemHandlerLifecycle)cObj.newInstance(objParams);
registerWorkItemHandler(ksession, IKnowledgeSession.EMAIL, handler);
}catch(Exception x) {
throw new RuntimeException(x);
}
}
/******************************************************************************
* ************* ProcessEventListener Management *********/
// listens for agenda changes like rules being activated, fired, cancelled, etc
protected void addAgendaEventListener(StatefulKnowledgeSession ksession) {
final AgendaEventListener agendaEventListener = new DefaultAgendaEventListener() {
@Override
public void afterRuleFlowGroupActivated(RuleFlowGroupActivatedEvent event) {
KnowledgeRuntime kRuntime = event.getKnowledgeRuntime();
if (kRuntime instanceof StatefulKnowledgeSession) {
((StatefulKnowledgeSession) kRuntime).fireAllRules();
}
}
};
ksession.addEventListener(agendaEventListener);
}
/******************************************************************************
************* StatefulKnowledgeSession Management *********/
protected StatefulKnowledgeSession makeStatefulKnowledgeSession() {
// 1) instantiate a KnowledgeBase via query to guvnor or kbuilder
createKnowledgeBaseViaKnowledgeAgentOrBuilder();
// 2) very important that a unique 'Environment' is created per StatefulKnowledgeSession
Environment ksEnv = createKnowledgeSessionEnvironment();
// Nick: always instantiate new ksconfig to make it threadlocal to bypass the ConcurrentModificationExcepotion
KnowledgeSessionConfiguration ksConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(ksconfigProperties);
// 3) instantiate StatefulKnowledgeSession
// make synchronize because under heavy load, appears that underlying SessionInfo.update() breaks with a NPE
StatefulKnowledgeSession ksession = JPAKnowledgeService.newStatefulKnowledgeSession(kbase, ksConfig, ksEnv);
return ksession;
}
protected void addExtrasCommon(StatefulKnowledgeSession ksession) {
// 1) register a configurable WorkItemHandlers with StatefulKnowledgeSession
this.registerAddHumanTaskWorkItemHandler(ksession);
this.registerSkipHumanTaskWorkItemHandler(ksession);
this.registerFailHumanTaskWorkItemHandler(ksession);
this.registerEmailWorkItemHandler(ksession);
//1.5 register any addition workItemHandlers and eventListeners defined in drools.session.template
SessionTemplate sTemplate = newSessionTemplate(ksession);
if(sTemplate != null){
if(sTemplate.getWorkItemHandlers() != null){
for(Map.Entry<String, ?> entry : sTemplate.getWorkItemHandlers().entrySet()){
try {
WorkItemHandler wHandler = (WorkItemHandler)entry.getValue();
ksession.getWorkItemManager().registerWorkItemHandler(entry.getKey(), wHandler);
} catch(Exception x){
throw new RuntimeException("addExtrasToStatefulKnowledgeSession() following exception occurred when registering workItemId = "+entry.getKey()+" : "+x.getLocalizedMessage());
}
}
}
if(sTemplate.getEventListeners() != null){
List eListeners = sTemplate.getEventListeners();
for(Object eListener : eListeners) {
Object eObj = MVEL.eval((String)eListener);
if(eObj instanceof AgendaEventListener){
ksession.addEventListener((AgendaEventListener)eObj);
}else if(eObj instanceof WorkingMemoryEventListener) {
ksession.addEventListener((WorkingMemoryEventListener)eObj);
}else if(eObj instanceof ProcessEventListener) {
ksession.addEventListener((ProcessEventListener)eObj);
}else{
log.error("addExtrasToStatefulKnowledgeSession() invalid eventListener : "+eListener);
}
}
}
}
// 2) add agendaEventListener to knowledge session to notify knowledge session of various rules events
addAgendaEventListener(ksession);
}
/******************************************************************************
************* Process Definition Management *********/
public List<SerializableProcessMetaData> retrieveProcesses() throws Exception {
checkKAgentAndBaseHealth();
List<SerializableProcessMetaData> result = new ArrayList<SerializableProcessMetaData>();
for (KnowledgePackage kpackage: kbase.getKnowledgePackages()) {
for(Process processObj : kpackage.getProcesses()){
result.add(getProcess(processObj.getId()));
}
}
log.info("getProcesses() # of processes = "+result.size());
return result;
}
public SerializableProcessMetaData getProcess(String processId) {
checkKAgentAndBaseHealth();
Process processObj = kbase.getProcess(processId);
Long pVersion = 0L;
if(!StringUtils.isEmpty(processObj.getVersion())) {
try {
pVersion = Long.parseLong(processObj.getVersion());
} catch(NumberFormatException x) {
log.error("getProcess() processId = "+processId+" : process versions must be of type long. the following is invalid: "+processObj.getVersion());
}
}
SerializableProcessMetaData spObj = new SerializableProcessMetaData(processObj.getId(), processObj.getName(), pVersion, processObj.getPackageName());
if (processObj instanceof org.drools.definition.process.WorkflowProcess) {
Node[] nodes = ((WorkflowProcess)processObj).getNodes();
addNodesInfo(spObj.getNodes(), nodes, "id=");
}
return spObj;
}
private void addNodesInfo(List<SerializableNodeMetaData> snList, Node[] nodes, String prefix) {
for(Node nodeObj : nodes) {
// JA Bride: AsyncBAMProducer has been modified from stock jbpm5 to persist the "uniqueNodeId" in the jbpm_bam database
// (as opposed to persisting just the simplistic nodeId)
// will need to invoke same functionality here to calculate 'uniqueNodeId'
String uniqueId = org.jbpm.bpmn2.xml.XmlBPMNProcessDumper.getUniqueNodeId(nodeObj);
SerializableNodeMetaData snObj = new SerializableNodeMetaData(
(Integer)nodeObj.getMetaData().get(SerializableNodeMetaData.X),
(Integer)nodeObj.getMetaData().get(SerializableNodeMetaData.Y),
(Integer)nodeObj.getMetaData().get(SerializableNodeMetaData.HEIGHT),
(Integer)nodeObj.getMetaData().get(SerializableNodeMetaData.WIDTH),
uniqueId
);
snList.add(snObj);
if (nodeObj instanceof NodeContainer) {
addNodesInfo(snObj.getNodes(), ((NodeContainer)nodeObj).getNodes(), prefix + nodeObj.getId() + ":");
}
}
}
public void removeProcess(String processId) {
throw new UnsupportedOperationException();
}
public List<ProcessInstanceInfo> getActiveProcessInstances(Map<String, Object> queryCriteria) {
EntityManager psqlEm = null;
List<ProcessInstanceInfo> results = null;
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("FROM ProcessInstanceInfo p ");
if(queryCriteria != null && queryCriteria.size() > 0){
sqlBuilder.append("WHERE ");
if(queryCriteria.containsKey(IKnowledgeSession.PROCESS_ID)){
sqlBuilder.append("p.processid = :processId");
}
}
try {
psqlEm = jbpmCoreEMF.createEntityManager();
Query processInstanceQuery = psqlEm.createQuery(sqlBuilder.toString());
if(queryCriteria != null && queryCriteria.size() > 0){
if(queryCriteria.containsKey(IKnowledgeSession.PROCESS_ID)){
processInstanceQuery = processInstanceQuery.setParameter(IKnowledgeSession.PROCESS_ID, queryCriteria.get(IKnowledgeSession.PROCESS_ID));
}
}
results = processInstanceQuery.getResultList();
return results;
}catch(Exception x) {
throw new RuntimeException(x);
}
}
public String printActiveProcessInstances(Map<String,Object> queryCriteria){
List<ProcessInstanceInfo> pInstances = getActiveProcessInstances(queryCriteria);
StringBuffer sBuffer = new StringBuffer();
if(pInstances != null){
sBuffer.append("\npInstanceId\tprocessId");
for(ProcessInstanceInfo pInstance: pInstances){
sBuffer.append("\n"+pInstance.getId()+"\t"+pInstance.getProcessId());
}
sBuffer.append("\n");
}else{
sBuffer.append("\nno active process instances found\n");
}
return sBuffer.toString();
}
private static final List<Integer> NON_ROLLBACK_TX = Arrays.asList(new Integer[]{
javax.transaction.Status.STATUS_NO_TRANSACTION,
javax.transaction.Status.STATUS_ROLLEDBACK
});
protected void rollbackTrnx() {
try {
if(!NON_ROLLBACK_TX.contains(uTrnx.getStatus())) {
uTrnx.rollback();
}
} catch(Exception e) {
log.error(e.getMessage() + " - at: " + (e.getStackTrace() == null ? null : e.getStackTrace()[0]));
}
}
}