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