View Javadoc

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