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.client.remoting;
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.DefaultMuleException;
14  import org.mule.api.MuleContext;
15  import org.mule.api.MuleEvent;
16  import org.mule.api.MuleEventContext;
17  import org.mule.api.MuleException;
18  import org.mule.api.MuleMessage;
19  import org.mule.api.MuleSession;
20  import org.mule.api.config.MuleProperties;
21  import org.mule.api.construct.FlowConstruct;
22  import org.mule.api.endpoint.EndpointBuilder;
23  import org.mule.api.endpoint.EndpointFactory;
24  import org.mule.api.endpoint.ImmutableEndpoint;
25  import org.mule.api.endpoint.InboundEndpoint;
26  import org.mule.api.endpoint.OutboundEndpoint;
27  import org.mule.api.lifecycle.Callable;
28  import org.mule.api.lifecycle.Initialisable;
29  import org.mule.api.lifecycle.InitialisationException;
30  import org.mule.api.service.Service;
31  import org.mule.api.source.CompositeMessageSource;
32  import org.mule.api.transformer.DataType;
33  import org.mule.api.transformer.TransformerException;
34  import org.mule.api.transformer.wire.WireFormat;
35  import org.mule.component.SimpleCallableJavaComponent;
36  import org.mule.config.i18n.CoreMessages;
37  import org.mule.endpoint.EndpointURIEndpointBuilder;
38  import org.mule.message.DefaultExceptionPayload;
39  import org.mule.model.seda.SedaService;
40  import org.mule.module.client.i18n.ClientMessages;
41  import org.mule.module.client.remoting.notification.RemoteDispatcherNotification;
42  import org.mule.object.PrototypeObjectFactory;
43  import org.mule.session.DefaultMuleSession;
44  import org.mule.transport.AbstractConnector;
45  import org.mule.transport.NullPayload;
46  import org.mule.util.MapUtils;
47  
48  import java.io.ByteArrayInputStream;
49  import java.lang.reflect.Method;
50  import java.util.HashMap;
51  import java.util.LinkedList;
52  import java.util.List;
53  import java.util.Map;
54  
55  import org.apache.commons.io.output.ByteArrayOutputStream;
56  import org.apache.commons.logging.Log;
57  import org.apache.commons.logging.LogFactory;
58  
59  /**
60   * <code>RemoteDispatcherComponent</code> is a MuleManager interal server component
61   * responsible for receiving remote requests and dispatching them locally. This
62   * allows developer to tunnel requests through http ssl to a Mule instance behind a
63   * firewall
64   */
65  
66  public class RemoteDispatcherComponent implements Callable, Initialisable
67  {
68      /**
69       * logger used by this class
70       */
71      protected static final Log logger = LogFactory.getLog(RemoteDispatcherComponent.class);
72  
73      public static final String MANAGER_COMPONENT_NAME = "_muleManagerComponent";
74  
75      /**
76       * Use Serialization by default
77       */
78      protected WireFormat wireFormat;
79  
80      protected String encoding;
81  
82      protected int synchronousEventTimeout = 5000;
83  
84      protected MuleContext muleContext;
85  
86      public void initialise() throws InitialisationException
87      {
88          if (wireFormat == null)
89          {
90              throw new InitialisationException(CoreMessages.objectIsNull("wireFormat"), this);
91          }
92      }
93  
94      public Object onCall(MuleEventContext context) throws Exception
95      {
96          muleContext = context.getMuleContext();
97          byte[] messageBytes = (byte[]) context.transformMessage(byte[].class);
98          if(new String(messageBytes).equals(ServerHandshake.SERVER_HANDSHAKE_PROPERTY))
99          {
100             return doHandshake(context);
101         }
102 
103         Object result;
104         logger.debug("Message received by RemoteDispatcherComponent");
105         ByteArrayInputStream in = new ByteArrayInputStream((byte[]) context.transformMessage(DataType.BYTE_ARRAY_DATA_TYPE));
106         RemoteDispatcherNotification action = (RemoteDispatcherNotification) ((MuleMessage)wireFormat.read(in)).getPayload();
107 
108         // because we serialized a message inside a message, we need to inject the the muleContext ourselves
109         //TODO review the serialization format for RemoteDispatching
110         if(action.getMessage()!=null)
111         {
112             Method m = action.getMessage().getClass().getDeclaredMethod("initAfterDeserialisation", MuleContext.class);
113             m.setAccessible(true);
114             m.invoke(action.getMessage(), muleContext);
115         }
116 
117         if (RemoteDispatcherNotification.ACTION_INVOKE == action.getAction())
118         {
119             result = invokeAction(action, context);
120         }
121         else if (RemoteDispatcherNotification.ACTION_SEND == action.getAction() ||
122                  RemoteDispatcherNotification.ACTION_DISPATCH == action.getAction())
123         {
124             result = sendAction(action, context);
125         }
126         else if (RemoteDispatcherNotification.ACTION_RECEIVE == action.getAction())
127         {
128             result = receiveAction(action, context);
129         }
130         else
131         {
132             result = handleException(null, new DefaultMuleException(
133                 CoreMessages.eventTypeNotRecognised("RemoteDispatcherNotification:" + action.getAction())));
134         }
135         return result;
136     }
137 
138     protected ServerHandshake doHandshake(MuleEventContext context) throws TransformerException
139     {
140         ServerHandshake handshake  = new ServerHandshake();
141         handshake.setWireFormatClass(wireFormat.getClass().getName());
142         return handshake;
143     }
144 
145     protected Object invokeAction(RemoteDispatcherNotification action, MuleEventContext context) throws MuleException
146     {
147         String destComponent;
148         MuleMessage result = null;
149         String endpoint = action.getResourceIdentifier();
150         if (action.getResourceIdentifier().startsWith("mule:"))
151         {
152             destComponent = endpoint.substring(endpoint.lastIndexOf("/") + 1);
153         }
154         else
155         {
156             destComponent = endpoint;
157         }
158 
159         if (destComponent != null)
160         {
161             Object fc = muleContext.getRegistry().lookupObject(destComponent);
162             if (!(fc instanceof FlowConstruct))
163             {
164                 return handleException(null, new DefaultMuleException(ClientMessages.noSuchFlowConstruct(destComponent)));
165             }
166             FlowConstruct flowConstruct = (FlowConstruct) fc;
167             MuleSession session = new DefaultMuleSession(flowConstruct, muleContext);
168             // Need to do this otherise when the event is invoked the
169             // transformer associated with the Mule Admin queue will be invoked, but
170             // the message will not be of expected type
171 
172             EndpointBuilder builder = new EndpointURIEndpointBuilder(RequestContext.getEvent().getEndpoint());
173             // TODO - is this correct? it stops any other transformer from being set
174             builder.setTransformers(new LinkedList());
175             ImmutableEndpoint ep = muleContext.getEndpointFactory().getInboundEndpoint(builder);
176             MuleEvent event = new DefaultMuleEvent(action.getMessage(), ep, context.getSession());
177             event = RequestContext.setEvent(event);
178 
179             if (context.getExchangePattern().hasResponse())
180             {
181                 MuleEvent resultEvent = flowConstruct.getMessageProcessorChain().process(event);
182                 result = resultEvent == null ? null : resultEvent.getMessage();
183                 if (result == null)
184                 {
185                     return null;
186                 }
187                 else
188                 {
189                     ByteArrayOutputStream out = new ByteArrayOutputStream();
190                     wireFormat.write(out, result, getEncoding());
191                     return out.toByteArray();
192                 }
193             }
194             else
195             {
196                 flowConstruct.getMessageProcessorChain().process(event);
197                 return null;
198             }
199         }
200         else
201         {
202             return handleException(result, new DefaultMuleException(
203                 CoreMessages.couldNotDetermineDestinationComponentFromEndpoint(endpoint)));
204         }
205     }
206 
207     protected Object sendAction(RemoteDispatcherNotification action, MuleEventContext context) throws MuleException
208     {
209         MuleMessage result = null;
210         OutboundEndpoint endpoint = null;
211         MuleContext managementContext = context.getMuleContext();
212         try
213         {
214             if (RemoteDispatcherNotification.ACTION_DISPATCH == action.getAction())
215             {
216                 endpoint = managementContext.getEndpointFactory().getOutboundEndpoint(
217                     action.getResourceIdentifier());
218                 context.dispatchEvent(action.getMessage(), endpoint);
219                 return null;
220             }
221             else
222             {
223                 EndpointFactory endpointFactory = managementContext.getEndpointFactory();
224 
225                 EndpointBuilder endpointBuilder = endpointFactory.getEndpointBuilder(action.getResourceIdentifier());
226                 endpointBuilder.setExchangePattern(MessageExchangePattern.REQUEST_RESPONSE);
227 
228                 endpoint = managementContext.getEndpointFactory().getOutboundEndpoint(endpointBuilder);
229                 result = context.sendEvent(action.getMessage(), endpoint);
230                 if (result == null)
231                 {
232                     return null;
233                 }
234                 else
235                 {
236                     ByteArrayOutputStream out = new ByteArrayOutputStream();
237                     wireFormat.write(out, result, getEncoding());
238                     return out.toByteArray();
239                 }
240             }
241         }
242         catch (Exception e)
243         {
244             return handleException(result, e);
245         }
246     }
247 
248     protected Object receiveAction(RemoteDispatcherNotification action, MuleEventContext context) throws MuleException
249     {
250         MuleMessage result = null;
251         try
252         {
253             ImmutableEndpoint endpoint = context.getMuleContext().getEndpointFactory()
254                 .getOutboundEndpoint(action.getResourceIdentifier());
255 
256             long timeout = MapUtils.getLongValue(action.getProperties(),
257                 MuleProperties.MULE_EVENT_TIMEOUT_PROPERTY, getSynchronousEventTimeout());
258 
259             result = endpoint.getConnector().request(action.getResourceIdentifier(), timeout);
260             if (result != null)
261             {
262                 // See if there is a default transformer on the connector
263                 List transformers = ((AbstractConnector) endpoint.getConnector()).getDefaultInboundTransformers(endpoint);
264                 if (transformers != null)
265                 {
266                     result.applyTransformers(null, transformers);
267                 }
268                 ByteArrayOutputStream out = new ByteArrayOutputStream();
269                 wireFormat.write(out, result, getEncoding());
270                 return out.toByteArray();
271             }
272             else
273             {
274                 return null;
275             }
276         }
277         catch (Exception e)
278         {
279             return handleException(result, e);
280         }
281 
282     }
283 
284 
285     public static Service getSerivce(InboundEndpoint endpoint,
286                                                     WireFormat wireFormat,
287                                                     String encoding,
288                                                     int eventTimeout,
289                                                     MuleContext muleContext) throws MuleException
290     {
291         try
292         {
293             Service service = new SedaService(muleContext);
294             service.setName(MANAGER_COMPONENT_NAME);
295             service.setModel(muleContext.getRegistry().lookupSystemModel());
296 
297             Map props = new HashMap();
298             props.put("wireFormat", wireFormat);
299             props.put("encoding", encoding);
300             props.put("synchronousEventTimeout", new Integer(eventTimeout));
301             final SimpleCallableJavaComponent component = new SimpleCallableJavaComponent(new PrototypeObjectFactory(RemoteDispatcherComponent.class, props));
302             component.setMuleContext(muleContext);
303             service.setComponent(component);
304 
305 
306             if (!(service.getMessageSource() instanceof CompositeMessageSource))
307             {
308                 throw new IllegalStateException("Only 'CompositeMessageSource' is supported with RemoteDispatcherService");
309             }
310 
311             ((CompositeMessageSource) service.getMessageSource()).addSource(endpoint);
312 
313             return service;
314         }
315         catch (Exception e)
316         {
317             throw new InitialisationException(e, null);
318         }
319     }
320 
321     /**
322      * Wraps an exception into a MuleMessage with an Exception payload and returns
323      * the Xml representation of it
324      *
325      * @param result the result of the invocation or null if the exception occurred
326      *            before or during the invocation
327      * @param e the Exception thrown
328      * @return an Xml String message result
329      */
330     protected Object handleException(MuleMessage result, Throwable e)
331     {
332         logger.error("Failed to process admin request: " + e.getMessage(), e);
333         if (result == null)
334         {
335             result = new DefaultMuleMessage(NullPayload.getInstance(), (Map) null, muleContext);
336         }
337         result.setExceptionPayload(new DefaultExceptionPayload(e));
338         try
339         {
340             ByteArrayOutputStream out = new ByteArrayOutputStream();
341             wireFormat.write(out, result, getEncoding());
342             return out.toByteArray();
343         }
344         catch (Exception e1)
345         {
346             // TODO MULE-863: Is this sufficient?
347             // log the inner exception here since the earlier exception was logged earlier
348             logger.error("Failed to format message, using direct string (details at debug level): " + e1.getMessage());
349             logger.debug(e1.toString(), e1);
350             return e.getMessage();
351         }
352     }
353 
354     public WireFormat getWireFormat()
355     {
356         return wireFormat;
357     }
358 
359     public void setWireFormat(WireFormat wireFormat)
360     {
361         this.wireFormat = wireFormat;
362     }
363 
364     public String getEncoding()
365     {
366         return encoding;
367     }
368 
369     public void setEncoding(String encoding)
370     {
371         this.encoding = encoding;
372     }
373 
374     public int getSynchronousEventTimeout()
375     {
376         return synchronousEventTimeout;
377     }
378 
379     public void setSynchronousEventTimeout(int synchronousEventTimeout)
380     {
381         this.synchronousEventTimeout = synchronousEventTimeout;
382     }
383 }