package org.atricore.idbus.kernel.planning.jbpm; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.atricore.idbus.kernel.planning.IdentityArtifact; import org.atricore.idbus.kernel.planning.IdentityPlanExecutionExchange; import org.atricore.idbus.kernel.planning.IdentityPlanExecutionStatus; import org.atricore.idbus.kernel.planning.IdentityPlanningException; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import org.jbpm.context.exe.ContextInstance; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.instantiation.ProcessClassLoaderFactory; import org.jbpm.taskmgmt.exe.TaskInstance; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.xml.sax.InputSource; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * */ public class StatelessJbpmManager implements BPMSManager, Constants, InitializingBean, ApplicationContextAware { protected static transient Log logger = LogFactory.getLog(StatelessJbpmManager.class); protected JbpmConfiguration jbpmConfiguration = null; protected ProcessFragmentRegistry processFragmentRegistry; protected ApplicationContext applicationContext; private Map<String, ProcessDefinition> processDefinitions = new ConcurrentHashMap<String, ProcessDefinition>(); // /////////////////////////////////////////////////////////////////////////// // Property accessor and setter methods // /////////////////////////////////////////////////////////////////////////// public void setProcessFragmentRegistry(ProcessFragmentRegistry processFragmentRegistry) { this.processFragmentRegistry = processFragmentRegistry; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } // /////////////////////////////////////////////////////////////////////////// // Lifecycle methods // /////////////////////////////////////////////////////////////////////////// public StatelessJbpmManager() throws Exception { jbpmConfiguration = JbpmConfiguration.getInstance("org/atricore/idbus/kernel/planning/jbpm/jbpm.cfg.xml"); } public StatelessJbpmManager(JbpmConfiguration jbpmConfiguration) { setJbpmConfiguration(jbpmConfiguration); } public void afterPropertiesSet() throws Exception { // Enable OSGi-based process fragment resolution ProcessFragmentState.setDefaultProcessFragmentResolver( new SpringProcessFragmentResolver(processFragmentRegistry) ); ProcessFragmentState.setBpmsManager(this); // Enable OSGi-based Jbpm action class resolution OsgiProcessClassLoader.setProcessRegistry(processFragmentRegistry); } // /////////////////////////////////////////////////////////////////////////// // Process status / lookup // /////////////////////////////////////////////////////////////////////////// public boolean isProcess(Object obj) throws Exception { return (obj instanceof ProcessInstance); } public Object getId(Object process) throws Exception { return new Long(((ProcessInstance) process).getId()); } public Object getState(Object process) throws Exception { return ((ProcessInstance) process).getRootToken().getNode().getName(); } public boolean hasEnded(Object process) throws Exception { return ((ProcessInstance) process).hasEnded(); } /** * Look up an already-running process instance. * * @return the ProcessInstance */ public Object lookupProcess(Object processId) throws Exception { ProcessInstance processInstance = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { // Look up the process instance from the database. processInstance = jbpmContext.getGraphSession().loadProcessInstance(toLong(processId)); } finally { jbpmContext.close(); } return processInstance; } // /////////////////////////////////////////////////////////////////////////// // Process manipulation // /////////////////////////////////////////////////////////////////////////// /** * Start a new process. * * @return the newly-created ProcessInstance */ public Object startProcess(Object processType) throws Exception { return startProcess(processType, /* processVariables */null, null); } /** * Start a new process. * * @return the newly-created ProcessInstance */ public Object startProcess(Object processType, Map processVariables, Map transientVariables) throws Exception { ProcessInstance processInstance = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { // Some access needs to be serialized: ProcessDefinition processDefinition = null; processDefinition = jbpmContext.getGraphSession().findLatestProcessDefinition( (String) processType); if (processDefinition == null) throw new IllegalArgumentException("No process definition found for process " + processType); processInstance = new ProcessInstance(processDefinition); // Set any process variables. if (processVariables != null && !processVariables.isEmpty()) { processInstance.getContextInstance().addVariables(processVariables); } if (transientVariables != null && !transientVariables.isEmpty()) { processInstance.getContextInstance().setTransientVariables(transientVariables); } // Leave the start state. processInstance.signal(); //jbpmContext.save(processInstance); } catch (Exception e) { jbpmContext.setRollbackOnly(); throw e; } finally { jbpmContext.close(); } return processInstance; } /** * Advance a process instance one step. * * @return the updated ProcessInstance */ public Object advanceProcess(Object processId) throws Exception { return advanceProcess(processId, /* transition */null, /* processVariables */null, /* transient variables */ null); } /** * Advance a process instance one step. * * @return the updated ProcessInstance */ public Object advanceProcess(Object processId, Object transition, Map processVariables, Map transientVariables) throws Exception { ProcessInstance processInstance = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { // Look up the process instance from the database. processInstance = jbpmContext.getGraphSession().loadProcessInstance(toLong(processId)); if (processInstance.hasEnded()) { throw new IllegalStateException( "Process cannot be advanced because it has already terminated, processId = " + processId); } // Set any process variables. // Note: addVariables() will replace the old value of a variable if it // already exists. if (processVariables != null && !processVariables.isEmpty()) { processInstance.getContextInstance().addVariables(processVariables); } if (transientVariables != null && !transientVariables.isEmpty()) { processInstance.getContextInstance().setTransientVariables(transientVariables); } // Advance the workflow. if (transition != null) { processInstance.signal((String) transition); } else { processInstance.signal(); } // Save the process state back to the database. jbpmContext.save(processInstance); } catch (Exception e) { jbpmContext.setRollbackOnly(); throw e; } finally { jbpmContext.close(); } return processInstance; } /** * Update the variables for a process instance. * * @return the updated ProcessInstance */ public Object updateProcess(Object processId, Map processVariables, Map transientVariables) throws Exception { ProcessInstance processInstance = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { // Look up the process instance from the database. processInstance = jbpmContext.getGraphSession().loadProcessInstance(toLong(processId)); // Set any process variables. // Note: addVariables() will replace the old value of a variable if it // already exists. if (processVariables != null && !processVariables.isEmpty()) { processInstance.getContextInstance().addVariables(processVariables); } if (transientVariables != null && !transientVariables.isEmpty()) { processInstance.getContextInstance().setTransientVariables(processVariables); } // Save the process state back to the database. jbpmContext.save(processInstance); } catch (Exception e) { jbpmContext.setRollbackOnly(); throw e; } finally { jbpmContext.close(); } return processInstance; } /** * Delete a process instance. */ public void abortProcess(Object processId) throws Exception { JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { jbpmContext.getGraphSession().deleteProcessInstance(toLong(processId)); } catch (Exception e) { jbpmContext.setRollbackOnly(); throw e; } finally { jbpmContext.close(); } } public void destroyProcess(Object processId) { JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { jbpmContext.getGraphSession().deleteProcessInstance(toLong(processId)); } catch (Exception e) { jbpmContext.setRollbackOnly(); logger.error(e.getMessage(), e); } finally { jbpmContext.close(); } } /** * Returns the variables for given a process. * * @param processId * @return * @throws Exception */ public Map getProcessVariables(Object processId) throws Exception { Map processVariables = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { ProcessInstance processInstance = null; // Look up the process instance from the database. processInstance = jbpmContext.getGraphSession().loadProcessInstance(toLong(processId)); processVariables = processInstance.getContextInstance().getVariables(); } finally { jbpmContext.close(); } return processVariables; } // /////////////////////////////////////////////////////////////////////////// // Miscellaneous // /////////////////////////////////////////////////////////////////////////// /** * Deploy a new process definition. */ public String deployProcessFromStream(InputStream processDefinitionIs) throws FileNotFoundException, IOException { throw new UnsupportedOperationException("Not supported by this Manager"); /* JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); ProcessDefinition processDefinition; processDefinition = parseXmlInputStream(processDefinitionIs); String processType = processDefinition.getName(); try { jbpmContext.deployProcessDefinition(processDefinition); } finally { jbpmContext.close(); } return processType; */ } protected ProcessDefinition parseXmlInputStream(ProcessDescriptor pd, InputStream procesDefinitionIs) { JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { StatelessJpdlXmlReader jpdlReader = new StatelessJpdlXmlReader(new InputSource(procesDefinitionIs)); jpdlReader.setFragmentResolver(ProcessFragmentState.getDefaultProcessFragmentResolver()); jpdlReader.setProcessDescriptor(pd); return jpdlReader.readProcessDefinition(); } finally { jbpmContext.close(); } } public String deployProcessDefinition(String processDescriptorName) throws IdentityPlanningException { String processType = null; InputStream descriptorIs = null; // JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { ProcessDescriptor pd; String bootstrapFragmentName; ProcessFragment bootstrapProcessFragment; if (logger.isDebugEnabled()) logger.debug("Deploying process definition " + processDescriptorName); pd = processFragmentRegistry.lookupProcessDescriptor(processDescriptorName); if (pd == null) { if (logger.isDebugEnabled()) { // Information about the problem Collection<ProcessDescriptor> frags = processFragmentRegistry.listProcessDescriptors(); StringBuffer descriptors = new StringBuffer(); for (ProcessDescriptor desc : frags) { descriptors.append(desc.getName()).append(","); } logger.error("No process descriptor found for '" + processDescriptorName + "'. Registered processes are :" + descriptors); } throw new IdentityPlanningException("No process definition found for '"+processDescriptorName+"'"); } bootstrapFragmentName = pd.getBootstrapProcessFragmentName(); if (bootstrapFragmentName == null) { throw new IdentityPlanningException("No bootstrap fragment specified for process descriptor "); } if (logger.isDebugEnabled()) { logger.debug("Process definition " + processDescriptorName + " uses bootstrap process fragment " + bootstrapFragmentName); } // This is the main process descriptor: bootstrapProcessFragment = processFragmentRegistry.lookupProcessFragment(bootstrapFragmentName); if (bootstrapProcessFragment != null) { descriptorIs = bootstrapProcessFragment.getProcessFragmentDescriptor().getInputStream(); ProcessDefinition processDefinition = parseXmlInputStream(pd, descriptorIs); processType = processDefinition.getName(); // jbpmContext.deployProcessDefinition(processDefinition); processDefinitions.put(pd.getName(), processDefinition); logger.info("Deployed Process Definition " + processType + " backed by " + pd.getBootstrapProcessFragmentName() + " boostrap process fragment"); } else { // Dump some info about the problem Collection<ProcessFragment> frags = processFragmentRegistry.listProcessFragments(); StringBuffer descriptors = new StringBuffer(); for (ProcessFragment frag : frags) { descriptors.append(frag.getName()).append(","); } logger.error("No bootstrap process fragment definition found for '" + processDescriptorName + "'. Registered fragments are :" + descriptors); throw new IdentityPlanningException("No bootstrap fragment registered at [" + bootstrapFragmentName + "] Verify definition [" + pd + "]"); } } catch (Exception e) { throw new IdentityPlanningException(e); } finally { // Close descriptors, just in case try { if (descriptorIs != null) descriptorIs.close(); } catch (Exception e) { /**/ } // jbpmContext.close(); } return processType; } public void perform(String processType, String processDescriptorName, IdentityPlanExecutionExchange ex) throws IdentityPlanningException { Map<String, Object> transientVariables = new HashMap<String, Object>(); Map<String, Object> processVariables = new HashMap<String, Object>(); logger.debug("IdentityArtifact IN : ["+ex.getIn() + "]"); logger.debug("IdentityArtifact OUT: ["+ex.getOut() + "]"); processVariables.put(VAR_IN_IDENTITY_ARTIFACT, ex.getIn()); processVariables.put(VAR_OUT_IDENTITY_ARTIFACT, ex.getOut()); // Publish exchange properties as process variables for (String key : ex.getPropertyNames()) { logger.debug("Copying IDPlan Execution Exchange property '" + key + "' as process variable"); processVariables.put(key, ex.getProperty(key)); } // Execution context information is available as transient variables transientVariables.put(Constants.VAR_PFR, processFragmentRegistry); transientVariables.put(Constants.VAR_PDN, processDescriptorName); transientVariables.put(Constants.VAR_APP_CTX, applicationContext); transientVariables.put(Constants.VAR_IPEE, ex); // Other transient variables for (String transientVar : ex.getTransientPropertyNames()) { transientVariables.put(transientVar, ex.getTransientProperty(transientVar)); } ClassLoader cl = Thread.currentThread().getContextClassLoader(); JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { logger.debug("Starting process '" + processType + "'"); // process = startProcess(processType, processVariables, transientVariables); //processId = getId(process); ProcessDefinition processDefinition = processDefinitions.get(processDescriptorName); ProcessInstance processInstance = new ProcessInstance(processDefinition); ContextInstance contextInstance = processInstance.getContextInstance(); // Set any process variables. if (processVariables != null && !processVariables.isEmpty()) { contextInstance.addVariables(processVariables); } if (transientVariables != null && !transientVariables.isEmpty()) { contextInstance.setTransientVariables(transientVariables); } processInstance.signal(); } catch (Exception e) { ex.setStatus(IdentityPlanExecutionStatus.ERROR); throw new IdentityPlanningException(e); } finally { Thread.currentThread().setContextClassLoader(cl); jbpmContext.close(); } // TODO : Can be replaced the out/in ? IdentityArtifact outIdentityArtifact = (IdentityArtifact) processVariables.get(VAR_OUT_IDENTITY_ARTIFACT); ex.setOut(outIdentityArtifact); ex.setStatus(IdentityPlanExecutionStatus.SUCCESS); } public void performOld(String processType, String processDescriptorName, IdentityPlanExecutionExchange ex) throws IdentityPlanningException { Map<String, Object> transientVariables = new HashMap<String, Object>(); Map<String, Object> processVariables = new HashMap<String, Object>(); logger.debug("IdentityArtifact IN : ["+ex.getIn() + "]"); logger.debug("IdentityArtifact OUT: ["+ex.getOut() + "]"); processVariables.put(VAR_IN_IDENTITY_ARTIFACT, ex.getIn()); processVariables.put(VAR_OUT_IDENTITY_ARTIFACT, ex.getOut()); // Publish exchange properties as process variables for (String key : ex.getPropertyNames()) { logger.debug("Copying IDPlan Execution Exchange property '" + key + "' as process variable"); processVariables.put(key, ex.getProperty(key)); } // Execution context information is available as transient variables transientVariables.put(Constants.VAR_PFR, processFragmentRegistry); transientVariables.put(Constants.VAR_PDN, processDescriptorName); transientVariables.put(Constants.VAR_APP_CTX, applicationContext); transientVariables.put(Constants.VAR_IPEE, ex); // Other transient variables for (String transientVar : ex.getTransientPropertyNames()) { transientVariables.put(transientVar, ex.getTransientProperty(transientVar)); } ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { ProcessClassLoaderFactory f = new OsgiProcessClassLoaderFactory(); ProcessDefinition processDefinition = this.processDefinitions.get(processDescriptorName); ClassLoader pcl = f.getProcessClassLoader(processDefinition); Thread.currentThread().setContextClassLoader(pcl); ProcessInstance processInstance = new ProcessInstance(processDefinition); ContextInstance contextInstance = processInstance.getContextInstance(); // Set any process variables. if (processVariables != null && !processVariables.isEmpty()) { contextInstance.addVariables(processVariables); } if (transientVariables != null && !transientVariables.isEmpty()) { contextInstance.setTransientVariables(transientVariables); } processInstance.signal(); } catch (Exception e) { ex.setStatus(IdentityPlanExecutionStatus.ERROR); throw new IdentityPlanningException(e); } finally { Thread.currentThread().setContextClassLoader(cl); } // TODO : Can be replaced the out/in ? IdentityArtifact outIdentityArtifact = (IdentityArtifact) processVariables.get(VAR_OUT_IDENTITY_ARTIFACT); ex.setOut(outIdentityArtifact); ex.setStatus(IdentityPlanExecutionStatus.SUCCESS); } public List/* <TaskInstance> */loadTasks(ProcessInstance process) { List/* <TaskInstance> */taskInstances = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { taskInstances = jbpmContext.getTaskMgmtSession().findTaskInstancesByToken( process.getRootToken().getId()); } finally { jbpmContext.close(); } return taskInstances; } public void completeTask(TaskInstance task) { completeTask(task, /* transition */null); } public void completeTask(TaskInstance task, String transition) { JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { task = jbpmContext.getTaskMgmtSession().loadTaskInstance(task.getId()); if (transition != null) { task.end(transition); } else { task.end(); } } finally { jbpmContext.close(); } } // /////////////////////////////////////////////////////////////////////////// // Getters and setters // /////////////////////////////////////////////////////////////////////////// public JbpmConfiguration getJbpmConfiguration() { return jbpmConfiguration; } public void setJbpmConfiguration(JbpmConfiguration jbpmConfiguration) { this.jbpmConfiguration = jbpmConfiguration; } private static long toLong(Object obj) { if (obj == null) { throw new IllegalArgumentException("Unable to convert null object to long"); } else if (obj instanceof String) { return Long.valueOf((String) obj).longValue(); } else if (obj instanceof Number) { return ((Number) obj).longValue(); } else { throw new IllegalArgumentException("Unable to convert object of type: " + obj.getClass().getName() + " to long."); } } }