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