View Javadoc

1   /*
2    * $Id: Jbpm.java 20121 2010-11-08 17:33:48Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.module.jbpm;
12  
13  import org.mule.api.NamedObject;
14  import org.mule.api.lifecycle.Disposable;
15  import org.mule.api.lifecycle.Initialisable;
16  import org.mule.module.bpm.BPMS;
17  import org.mule.module.bpm.MessageService;
18  import org.mule.util.IOUtils;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.util.Map;
23  import java.util.Properties;
24  import java.util.Set;
25  
26  import org.jbpm.api.Configuration;
27  import org.jbpm.api.Execution;
28  import org.jbpm.api.ProcessEngine;
29  import org.jbpm.api.ProcessInstance;
30  import org.jbpm.api.task.Task;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  /**
35   * An implementation of Mule's generic {@link BPMS} interface for JBoss jBPM. 
36   */
37  public class Jbpm implements BPMS, Initialisable, Disposable, NamedObject
38  {
39      /** 
40       * The initialized jBPM ProcessEngine. 
41       */
42      protected ProcessEngine processEngine = null;
43  
44      /** 
45       * The configuration file for jBPM, default is "jbpm.cfg.xml" if not specified. 
46       */
47      private String configurationResource;
48      
49      /** 
50       * Process definitions to be loaded into jBPM at startup. 
51       */
52      private Properties processDefinitions;
53      
54      /** 
55       * An optional logical name for the BPMS. 
56       */
57      private String name;
58      
59      /**
60       * Indicates whether jBPM has been instantiated by the connector (false) or was
61       * passed in from somewhere else (true).
62       */
63      protected boolean containerManaged = true;
64  
65      public static final String PROCESS_ENDED = "Process has ended";
66      
67      /**
68       * Given the multi-threaded nature of Mule, sometimes a response message will arrive to advance the 
69       * process before the creation of the process has fully terminated (e.g., during in-memory unit tests).
70       * After this amount of time (in ms), we stop waiting and assume there must be some other problem.  
71       */
72      public static final int PROCESS_CREATION_WAIT = 3000;
73      
74      protected static final Logger log = LoggerFactory.getLogger(Jbpm.class);
75  
76      // ///////////////////////////////////////////////////////////////////////////
77      // Lifecycle methods
78      // ///////////////////////////////////////////////////////////////////////////
79  
80      /**
81       * Creates the Mule wrapper for jBPM
82       */
83      public Jbpm()
84      {
85          // empty
86      }
87  
88      /**
89       * Creates the Mule wrapper for jBPM
90       * @param configurationResource - The configuration file for jBPM, default is "jbpm.cfg.xml" if not specified.
91       * @param processDefinitions - A list of process definitions to load into jBPM upon initialization.
92       */
93      public Jbpm(String configurationResource, Properties processDefinitions)
94      {
95          this.configurationResource = configurationResource;
96          this.processDefinitions = processDefinitions;
97      }
98  
99      /**
100      * Creates the Mule wrapper for jBPM
101      * @param The already-initialized jBPM ProcessEngine.  This is useful if you use Spring to configure your jBPM instance.
102      */
103     public Jbpm(ProcessEngine processEngine, Properties processDefinitions)
104     {
105         this.processEngine = processEngine;
106         this.processDefinitions = processDefinitions;
107     }
108 
109     public void initialise()
110     {
111         if (processEngine == null)
112         {
113             Configuration config = new Configuration();
114             if (configurationResource != null)
115             {
116                 config.setResource(configurationResource);
117             }
118             setProcessEngine(config.buildProcessEngine());  
119             containerManaged = false;
120         }
121         if (processDefinitions != null)
122         {
123             for (Object def : processDefinitions.values())
124             {
125                 try
126                 {
127                     deployProcess((String) def);
128                 }
129                 catch (IOException e)
130                 {
131                     log.error("Unable to deploy process definition: " + e.getMessage());
132                 }
133             }
134         }
135     }
136     
137     public void dispose()
138     {
139         if (!containerManaged && processEngine != null)
140         {
141             processEngine.close();
142             processEngine = null;
143         }
144     }
145 
146     public void setMessageService(MessageService msgService)
147     {
148         MuleMessageService serviceProxy = processEngine.get(MuleMessageService.class);
149         serviceProxy.setMessageService(msgService);
150     }
151 
152     // ///////////////////////////////////////////////////////////////////////////
153     // Process manipulation
154     // ///////////////////////////////////////////////////////////////////////////
155 
156     /**
157      * Start a new process.
158      * 
159      * @return the newly-created ProcessInstance
160      */
161     public Object startProcess(Object processDefinitionKey) throws Exception
162     {
163         return startProcess(processDefinitionKey, null, null);
164     }
165     
166     /**
167      * Start a new process.
168      * 
169      * @return the newly-created ProcessInstance
170      */
171     public Object startProcess(Object processDefinitionKey, Object signalName, Map variables) throws Exception
172     {
173         ProcessInstance processInstance = 
174             processEngine.getExecutionService().startProcessInstanceByKey((String) processDefinitionKey, (Map) variables);
175 
176         if (processInstance == null)
177         {
178             throw new IllegalArgumentException("No process definition found for process " + processDefinitionKey);
179         }
180 
181         return processInstance;
182     }
183 
184     /**
185      * Advance a process instance one step.
186      * 
187      * @return the updated ProcessInstance
188      */
189     public Object advanceProcess(Object executionId) throws Exception
190     {
191         return advanceProcess(executionId, null, null);
192     }
193 
194     /**
195      * Advance a process instance one step.
196      * 
197      * @param processId - if no transition value is provided, this is assumed to be
198      *            execution id
199      * @param transition - the state of the execution we're looking for
200      * @param processVariables - optional process variables/parameters to set
201      * @return the updated ProcessInstance
202      */
203     public Object advanceProcess(Object executionId, Object signalName, Map variables) throws Exception
204     {
205         int waitTime = 0;
206         Execution execution = processEngine.getExecutionService().findExecutionById((String) executionId);
207         while (execution == null && waitTime < PROCESS_CREATION_WAIT)
208         {
209             // Given the multi-threaded nature of Mule, sometimes a response message will arrive to advance the 
210             // process before the creation of the process has fully terminated (e.g., during in-memory unit tests).  
211             // We delay for awhile to make sure this is not the case before giving up and throwing an exception.
212             Thread.sleep(PROCESS_CREATION_WAIT / 10);
213             waitTime += (PROCESS_CREATION_WAIT / 10);
214             execution = processEngine.getExecutionService().findExecutionById((String) executionId);
215         }
216         if (execution == null)
217         {
218             throw new IllegalArgumentException("No process execution found with id = " + executionId + " (it may have already terminated)");
219         }
220         
221         String processId;
222         if (execution.getProcessInstance() != null)
223         {
224             processId = execution.getProcessInstance().getId();
225         }
226         else
227         {
228             processId = execution.getId();
229         }
230 
231         // Set any process variables.
232         if (variables != null && !variables.isEmpty())
233         {
234             processEngine.getExecutionService().setVariables((String) executionId, variables);
235         }
236 
237         // MULE-1690
238         synchronized (this)
239         {
240             processEngine.getExecutionService().signalExecutionById((String) executionId, (String) signalName, variables);
241         }
242 
243         // Refresh process info. from the DB
244         ProcessInstance process = processEngine.getExecutionService().findProcessInstanceById(processId);
245         if (process == null)
246         {
247             // The process has already ended, so we return a mock/skeleton ProcessInstance with the expected ID and state = "ended"
248             process = new EndedProcess(processId);
249         }
250         return process;
251     }
252 
253     /**
254      * Update the variables for an execution.
255      * 
256      * @return the updated ProcessInstance
257      */
258     public Object updateProcess(Object executionId, Map variables) throws Exception
259     {
260         // Get Process ID
261         String processId;
262         Execution execution = processEngine.getExecutionService().findExecutionById((String) executionId);
263         if (execution == null)
264         {
265             throw new IllegalArgumentException("No process execution found with id = " + executionId + " (it may have already terminated)");
266         }
267         if (execution.getProcessInstance() != null)
268         {
269             processId = execution.getProcessInstance().getId();
270         }
271         else
272         {
273             processId = execution.getId();
274         }
275 
276         // Set any process variables.
277         if (variables != null && !variables.isEmpty())
278         {
279             processEngine.getExecutionService().setVariables((String) executionId, variables);
280         }
281 
282         // Refresh process info. from the DB
283         ProcessInstance process = processEngine.getExecutionService().findProcessInstanceById(processId);
284         if (process == null)
285         {
286             // The process has already ended, so we return a mock/skeleton ProcessInstance with the expected ID and state = "ended"
287             process = new EndedProcess(processId);
288         }
289         return process;
290     }
291 
292     /**
293      * Delete a process instance.
294      */
295     public void abortProcess(Object processInstanceId) throws Exception
296     {
297         processEngine.getExecutionService().endProcessInstance((String) processInstanceId, Execution.STATE_ENDED);
298     }
299 
300     // ///////////////////////////////////////////////////////////////////////////
301     // Process status / lookup
302     // ///////////////////////////////////////////////////////////////////////////
303 
304     public boolean isProcess(Object obj) throws Exception
305     {
306         return (obj instanceof ProcessInstance);
307     }
308 
309     public Object getId(Object process) throws Exception
310     {
311         return ((ProcessInstance) process).getId();
312     }
313 
314     public Object getState(Object process) throws Exception
315     {
316         return getState((ProcessInstance) process);
317     }
318     
319     public static String getState(ProcessInstance processInstance) throws Exception
320     {
321         if (processInstance == null || processInstance.isEnded())
322         {
323             return ProcessInstance.STATE_ENDED;
324         }
325         
326         Set activities = processInstance.findActiveActivityNames();
327         String state = null;
328         // Separate concurrent paths of execution with a "/"
329         for (Object activityName : activities)
330         {
331             if (state == null)
332             {
333                 state = (String) activityName;
334             }
335             else
336             {
337                 state += " / " + activityName;
338             }
339         }
340         return state;
341     }
342 
343     public boolean hasEnded(Object process) throws Exception
344     {
345         return process == null ? true : ((ProcessInstance) process).isEnded();
346     }
347 
348     /**
349      * Look up an already-running process instance.
350      * 
351      * @return the ProcessInstance
352      */
353     public Object lookupProcess(Object processId) throws Exception
354     {
355         return processEngine.getExecutionService().findProcessInstanceById((String) processId);
356     }
357 
358     // ///////////////////////////////////////////////////////////////////////////
359     // Miscellaneous
360     // ///////////////////////////////////////////////////////////////////////////
361 
362     public void deployProcess(String processDefinitionFile) throws IOException
363     {
364         deployProcessFromStream(processDefinitionFile, IOUtils.getResourceAsStream(processDefinitionFile,
365             getClass()));
366     }
367 
368     public void deployProcessFromStream(String resourceName, InputStream processDefinition)
369         throws IOException
370     {
371         processEngine.getRepositoryService().createDeployment()
372             .addResourceFromInputStream(resourceName, processDefinition)
373             .deploy();
374     }
375 
376     public void undeployProcess(String resource) throws Exception
377     {
378         // empty
379     }
380     
381     public void completeTask(Task task)
382     {
383         completeTask(task, null, null);
384     }
385 
386     public void completeTask(Task task, String outcome, Map variables)
387     {
388         processEngine.getTaskService().completeTask(task.getId(), outcome, variables);
389     }
390 
391     // ///////////////////////////////////////////////////////////////////////////
392     // Getters and setters
393     // ///////////////////////////////////////////////////////////////////////////
394 
395     public ProcessEngine getProcessEngine()
396     {
397         return processEngine;
398     }
399 
400     public void setProcessEngine(ProcessEngine processEngine)
401     {
402         this.processEngine = processEngine;
403     }
404 
405     public String getConfigurationResource()
406     {
407         return configurationResource;
408     }
409 
410     public void setConfigurationResource(String configurationResource)
411     {
412         this.configurationResource = configurationResource;
413     }
414 
415     public Properties getProcessDefinitions()
416     {
417         return processDefinitions;
418     }
419 
420     public void setProcessDefinitions(Properties processDefinitions)
421     {
422         this.processDefinitions = processDefinitions;
423     }
424 
425     public void setName(String name)
426     {
427         this.name = name;
428     }
429 
430     public String getName()
431     {
432         return name;
433     }
434 }