View Javadoc

1   /*
2    * $Id: Process.java 22625 2011-08-09 16:21:33Z mike.schilling $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.bpm;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.DefaultMuleMessage;
15  import org.mule.MessageExchangePattern;
16  import org.mule.RequestContext;
17  import org.mule.api.MuleContext;
18  import org.mule.api.MuleEvent;
19  import org.mule.api.MuleException;
20  import org.mule.api.MuleMessage;
21  import org.mule.api.config.MuleProperties;
22  import org.mule.api.construct.FlowConstruct;
23  import org.mule.api.endpoint.EndpointCache;
24  import org.mule.api.endpoint.OutboundEndpoint;
25  import org.mule.api.lifecycle.Disposable;
26  import org.mule.api.lifecycle.Initialisable;
27  import org.mule.api.lifecycle.InitialisationException;
28  import org.mule.api.transport.DispatchException;
29  import org.mule.api.transport.PropertyScope;
30  import org.mule.config.i18n.MessageFactory;
31  import org.mule.endpoint.SimpleEndpointCache;
32  import org.mule.session.DefaultMuleSession;
33  import org.mule.transport.NullPayload;
34  import org.mule.util.StringUtils;
35  
36  import java.util.HashMap;
37  import java.util.Map;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  /**
43   * A business process definition.
44   */
45  public class Process implements Initialisable, Disposable, MessageService
46  {
47      /** The underlying BPMS */
48      private final BPMS bpms;
49  
50      /** The logical name of the process.  This is used to look up the running process instance from the BPMS. */
51      private final String name;
52  
53      /** The resource containing the process definition.  This will be used to deploy the process to the BPMS. */
54      private final String resource;
55  
56      /** This field will be used to correlate messages with processes. */
57      protected final String processIdField;
58  
59      protected MuleContext muleContext;
60  
61      /** Needed for exception handling. */
62      private FlowConstruct flowConstruct;
63  
64      public static final String BPM_PROPERTY_PREFIX = "BPM_";
65      
66      public static final String PROPERTY_ENDPOINT = 
67          MuleProperties.PROPERTY_PREFIX + BPM_PROPERTY_PREFIX + "ENDPOINT";
68      public static final String PROPERTY_PROCESS_TYPE = 
69          MuleProperties.PROPERTY_PREFIX + BPM_PROPERTY_PREFIX + "PROCESS_TYPE";
70      public static final String PROPERTY_PROCESS_ID = 
71          MuleProperties.PROPERTY_PREFIX + BPM_PROPERTY_PREFIX + "PROCESS_ID";
72      public static final String PROPERTY_ACTION = 
73          MuleProperties.PROPERTY_PREFIX + BPM_PROPERTY_PREFIX + "ACTION";
74      public static final String PROPERTY_TRANSITION = 
75          MuleProperties.PROPERTY_PREFIX + BPM_PROPERTY_PREFIX + "TRANSITION";
76      public static final String PROPERTY_PROCESS_STARTED = 
77          MuleProperties.PROPERTY_PREFIX + BPM_PROPERTY_PREFIX + "STARTED";
78      
79      public static final String ACTION_START = "start";
80      public static final String ACTION_ADVANCE = "advance";
81      public static final String ACTION_UPDATE = "update";
82      public static final String ACTION_ABORT = "abort";
83      
84      public static final String PROCESS_VARIABLE_INCOMING = "incoming";
85      public static final String PROCESS_VARIABLE_INCOMING_SOURCE = "incomingSource";
86      public static final String PROCESS_VARIABLE_DATA = "data";
87  
88      protected static final Log logger = LogFactory.getLog(Process.class);
89  
90      private final EndpointCache endpointCache;
91  
92      public Process(BPMS bpms, String name, String resource, FlowConstruct flowConstruct, MuleContext muleContext)
93      {
94          this(bpms, name, resource, null, flowConstruct, muleContext);
95      }
96  
97      public Process(BPMS bpms, String name, String resource, String processIdField, FlowConstruct flowConstruct, MuleContext muleContext)
98      {
99          this.bpms = bpms;
100         this.name = name;
101         this.resource = resource;
102         this.processIdField = processIdField;
103         this.flowConstruct = flowConstruct;
104         this.muleContext = muleContext;
105         this.endpointCache = new SimpleEndpointCache(muleContext);
106     }
107 
108     public void initialise() throws InitialisationException
109     {
110         try
111         {
112             bpms.deployProcess(resource);
113         }
114         catch (Exception e)
115         {
116             throw new InitialisationException(e, this);
117         }
118     }
119 
120     public void dispose()
121     {
122         try
123         {
124             bpms.undeployProcess(resource);
125         }
126         catch (Exception e)
127         {
128             logger.warn(e.getMessage());
129         }
130     }
131 
132     protected Object handleEvent(MuleEvent event) throws Exception
133     {
134         // An object representing the new state of the process
135         Object process;
136 
137         // Create a map of process variables based on the message properties.
138         Map processVariables = new HashMap();
139         if (event != null)
140         {
141             populateProcessVariables(event, processVariables, PropertyScope.INVOCATION);
142             populateProcessVariables(event, processVariables, PropertyScope.INBOUND);
143 
144             Object payload = event.getMessage().getPayload();
145             if (payload != null && !(payload instanceof NullPayload))
146             {
147                 // Store the message's payload as a process variable.
148                 processVariables.put(PROCESS_VARIABLE_INCOMING, payload);
149 
150                 // Store the endpoint on which the message was received as a process variable.
151                 String originatingEndpoint = event.getMessage().getInboundProperty(MuleProperties.MULE_ORIGINATING_ENDPOINT_PROPERTY);
152                 if (StringUtils.isNotEmpty(originatingEndpoint))
153                 {
154                     processVariables.put(PROCESS_VARIABLE_INCOMING_SOURCE, originatingEndpoint);
155                 }
156             }
157         }
158 
159         String processIdField = getProcessIdField();
160         if (StringUtils.isEmpty(processIdField))
161         {
162             processIdField = PROPERTY_PROCESS_ID;
163         }
164 
165         Object processId;
166         processId = event.getMessage().getSessionProperty(processIdField);
167         if (processId == null)
168         {
169             processId = event.getMessage().getInvocationProperty(processIdField); 
170         }
171         if (processId == null)
172         {
173             processId = event.getMessage().getInboundProperty(processIdField); 
174         }
175         processVariables.remove(processIdField);
176 
177         // Default action is "advance"
178         String action = event.getMessage().getInvocationProperty(PROPERTY_ACTION, ACTION_ADVANCE);
179         processVariables.remove(PROPERTY_ACTION);
180 
181         Object transition = event.getMessage().getInvocationProperty(PROPERTY_TRANSITION);
182         processVariables.remove(PROPERTY_TRANSITION);
183 
184         // //////////////////////////////////////////////////////////////////////
185 
186         logger.debug("Message received: payload = " + event.getMessage().getPayload().getClass().getName() + " processType = " + name + " processId = " + processId + " action = " + action);
187         
188         // Start a new process.
189         if (processId == null || action.equals(ACTION_START))
190         {
191             process = getBpms().startProcess(name, transition, processVariables);
192             if ((process != null) && logger.isInfoEnabled())
193             {
194                 logger.info("New process started, ID = " + getBpms().getId(process));
195             }
196         }
197 
198         // Don't advance the process, just update the process variables.
199         else if (action.equals(ACTION_UPDATE))
200         {
201             if (processId != null)
202             {
203                 process = getBpms().updateProcess(processId, processVariables);
204                 if ((process != null) && logger.isInfoEnabled())
205                 {
206                     logger.info("Process variables updated, ID = " + getBpms().getId(process));
207                 }
208             }
209             else
210             {
211                 throw new IllegalArgumentException("Process ID is missing, cannot update process.");
212             }
213         }
214 
215         // Abort the running process (end abnormally).
216         else if (action.equals(ACTION_ABORT))
217         {
218             if (processId != null)
219             {
220                 getBpms().abortProcess(processId);
221                 process = NullPayload.getInstance();
222                 logger.info("Process aborted, ID = " + processId);
223             }
224             else
225             {
226                 throw new IllegalArgumentException("Process ID is missing, cannot abort process.");
227             }
228         }
229 
230         // Advance the already-running process one step.
231         else
232         {
233             if (processId != null)
234             {
235                 process = getBpms().advanceProcess(processId, transition, processVariables);
236                 if ((process != null) && logger.isInfoEnabled())
237                 {
238                     logger.info("Process advanced, ID = " + getBpms().getId(process)
239                                     + ", new state = " + getBpms().getState(process));
240                 }
241             }
242             else
243             {
244                 throw new IllegalArgumentException("Process ID is missing, cannot advance process.");
245             }
246         }
247 
248         return process;
249     }
250 
251     protected void populateProcessVariables(MuleEvent event, Map processVariables, PropertyScope propertyScope)
252     {
253         for (String propertyName : event.getMessage().getPropertyNames(propertyScope))
254         {
255             // The session property can become rather large and causes problems with DB persistence.
256             if (!propertyName.equals(MuleProperties.MULE_SESSION_PROPERTY))
257             {
258                 processVariables.put(propertyName, event.getMessage().getProperty(propertyName, propertyScope));
259             }
260         }
261     }
262 
263     // TODO This method should probably use the LocalMuleClient instead of re-inventing the wheel
264     public MuleMessage generateMessage(String endpoint, Object payload, Map messageProperties, MessageExchangePattern exchangePattern) throws MuleException
265     {
266         MuleMessage message;
267         if (payload instanceof MuleMessage)
268         {
269             message = (MuleMessage) payload;
270         }
271         else
272         {
273             message = new DefaultMuleMessage(payload, muleContext);
274         }
275         message.addProperties(messageProperties, PropertyScope.OUTBOUND);
276         message.addProperties(messageProperties, PropertyScope.INVOCATION);
277 
278         // Use an endpoint cache to prevent memory leaks (see MULE-5422)
279         OutboundEndpoint ep = endpointCache.getOutboundEndpoint(endpoint, exchangePattern, null);
280         DefaultMuleEvent event = new DefaultMuleEvent(message, ep.getExchangePattern(),
281             new DefaultMuleSession(flowConstruct, muleContext));
282         RequestContext.setEvent(event);
283 
284         // Set correlation properties in SESSION scope so that they get propagated to response messages.
285         if (messageProperties.get(PROPERTY_PROCESS_TYPE) != null)
286         {
287             event.getMessage().setSessionProperty(PROPERTY_PROCESS_TYPE, messageProperties.get(PROPERTY_PROCESS_TYPE));
288         }
289         if (messageProperties.get(PROPERTY_PROCESS_ID) != null)
290         {
291             event.getMessage().setSessionProperty(PROPERTY_PROCESS_ID, messageProperties.get(PROPERTY_PROCESS_ID));
292         }
293         
294         MuleEvent resultEvent = ep.process(event);
295         
296         MuleMessage response = null;
297         if (resultEvent != null)
298         {
299             response = resultEvent.getMessage();
300             if (response.getExceptionPayload() != null)
301             {
302                 throw new DispatchException(MessageFactory.createStaticMessage("Unable to send or route message"), event, ep, response.getExceptionPayload().getRootException());
303             }
304         }        
305         return response;
306     }
307 
308     public String getProcessIdField()
309     {
310         return processIdField;
311     }
312 
313     public BPMS getBpms()
314     {
315         return bpms;
316     }
317 
318     public String getResource()
319     {
320         return resource;
321     }
322 
323     public String getName()
324     {
325         return name;
326     }
327 }
328 
329