View Javadoc

1   /*
2    * $Id: CxfOutboundMessageProcessor.java 20375 2010-11-28 22:00:45Z dandiep $
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.cxf;
12  
13  import org.mule.api.DefaultMuleException;
14  import org.mule.api.MessagingException;
15  import org.mule.api.MuleEvent;
16  import org.mule.api.MuleException;
17  import org.mule.api.MuleMessage;
18  import org.mule.api.config.MuleProperties;
19  import org.mule.api.endpoint.EndpointURI;
20  import org.mule.api.transformer.TransformerException;
21  import org.mule.api.transport.DispatchException;
22  import org.mule.message.DefaultExceptionPayload;
23  import org.mule.module.cxf.i18n.CxfMessages;
24  import org.mule.module.cxf.security.WebServiceSecurityException;
25  import org.mule.processor.AbstractInterceptingMessageProcessor;
26  import org.mule.transport.http.HttpConnector;
27  import org.mule.transport.http.HttpConstants;
28  import org.mule.util.StringUtils;
29  import org.mule.util.TemplateParser;
30  
31  import java.lang.reflect.InvocationTargetException;
32  import java.lang.reflect.Method;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.regex.Pattern;
40  
41  import javax.activation.DataHandler;
42  import javax.xml.namespace.QName;
43  import javax.xml.ws.BindingProvider;
44  import javax.xml.ws.Holder;
45  
46  import org.apache.commons.httpclient.HttpException;
47  import org.apache.cxf.endpoint.Client;
48  import org.apache.cxf.endpoint.Endpoint;
49  import org.apache.cxf.frontend.MethodDispatcher;
50  import org.apache.cxf.service.model.BindingOperationInfo;
51  import org.apache.cxf.ws.addressing.WSAContextUtils;
52  
53  /**
54   * The CxfOutboundMessageProcessor performs outbound CXF processing, sending an event
55   * through the CXF client, then on to the next MessageProcessor.
56   */
57  public class CxfOutboundMessageProcessor extends AbstractInterceptingMessageProcessor
58  {
59  
60      private static final String URI_REGEX = "cxf:\\[(.+?)\\]:(.+?)/\\[(.+?)\\]:(.+?)";
61      Pattern URI_PATTERN = Pattern.compile(URI_REGEX);
62  
63      private final TemplateParser soapActionTemplateParser = TemplateParser.createMuleStyleParser();
64      private CxfPayloadToArguments payloadToArguments = CxfPayloadToArguments.NULL_PAYLOAD_AS_PARAMETER;
65      private Client client;
66      private boolean proxy;
67      private String operation;
68      private BindingProvider clientProxy;
69      private String decoupledEndpoint;
70  
71      public CxfOutboundMessageProcessor(Client client)
72      {
73          this.client = client;
74      }
75  
76      protected void cleanup()
77      {
78          // MULE-4899: cleans up client's request and response context to avoid a
79          // memory leak.
80          Map<String, Object> requestContext = client.getRequestContext();
81          requestContext.clear();
82          Map<String, Object> responseContext = client.getResponseContext();
83          responseContext.clear();
84      }
85  
86      protected Object[] getArgs(MuleEvent event) throws TransformerException
87      {
88          Object payload;
89  
90          payload = event.getMessage().getPayload();
91  
92          if (proxy)
93          {
94              return new Object[]{ event.getMessage() };
95          }
96  
97          Object[] args = payloadToArguments.payloadToArrayOfArguments(payload);
98  
99          MuleMessage message = event.getMessage();
100         Set<?> attachmentNames = message.getInboundAttachmentNames();
101         if (attachmentNames != null && !attachmentNames.isEmpty())
102         {
103             List<DataHandler> attachments = new ArrayList<DataHandler>();
104             for (Object attachmentName : attachmentNames)
105             {
106                 attachments.add(message.getInboundAttachment((String)attachmentName));
107             }
108             List<Object> temp = new ArrayList<Object>(Arrays.asList(args));
109             temp.add(attachments.toArray(new DataHandler[attachments.size()]));
110             args = temp.toArray();
111         }
112 
113         if (args.length == 0)
114         {
115             return null;
116         }
117         return args;
118     }
119 
120     public MuleEvent process(MuleEvent event) throws MuleException
121     {
122         try
123         {
124             MuleEvent res;
125             if (!isClientProxyAvailable())
126             {
127                 res = doSendWithClient(event);
128             }
129             else
130             {
131                 res = doSendWithProxy(event);
132             }
133             return res;
134         }
135         catch (MuleException e)
136         {
137             throw e;
138         }
139         catch (Exception e)
140         {
141             throw new DefaultMuleException(e);
142         }
143     }
144 
145     /**
146      * This method is public so it can be invoked from the MuleUniversalConduit.
147      */
148     @Override
149     public MuleEvent processNext(MuleEvent event) throws MuleException
150     {
151         return next.process(event);
152     }
153 
154     protected MuleEvent doSendWithProxy(MuleEvent event) throws Exception
155     {
156         Method method = getMethod(event);
157 
158         Map<String, Object> props = getInovcationProperties(event);
159 
160         Holder<MuleEvent> responseHolder = new Holder<MuleEvent>();
161         props.put("holder", responseHolder);
162         
163         // Set custom soap action if set on the event or endpoint
164         String soapAction = event.getMessage().getOutboundProperty(SoapConstants.SOAP_ACTION_PROPERTY);
165         if (soapAction != null)
166         {
167             soapAction = parseSoapAction(soapAction, new QName(method.getName()), event);
168             props.put(org.apache.cxf.binding.soap.SoapBindingConstants.SOAP_ACTION, soapAction);
169         }   
170 
171         clientProxy.getRequestContext().putAll(props);
172 
173         Object response;
174         try
175         {
176             Object[] args = getArgs(event);
177             response = method.invoke(clientProxy, args);
178         }
179         catch (InvocationTargetException e)
180         {
181             Throwable ex = e.getTargetException();
182 
183             if (ex != null && ex.getMessage().contains("Security"))
184             {
185                 throw new WebServiceSecurityException(event, e);
186             }
187             else
188             {
189                 throw e;
190             }
191         }
192 
193         // TODO: handle holders
194         MuleEvent muleRes = responseHolder.value;
195         return buildResponseMessage(event, muleRes, new Object[]{ response });
196     }
197 
198     protected MuleEvent doSendWithClient(MuleEvent event) throws Exception
199     {
200         BindingOperationInfo bop = getOperation(event);
201 
202         Map<String, Object> props = getInovcationProperties(event);
203 
204         // Holds the response from the transport
205         Holder<MuleEvent> responseHolder = new Holder<MuleEvent>();
206         props.put("holder", responseHolder);
207         
208         // Set custom soap action if set on the event or endpoint
209         String soapAction = event.getMessage().getOutboundProperty(SoapConstants.SOAP_ACTION_PROPERTY);
210         if (soapAction != null)
211         {
212             soapAction = parseSoapAction(soapAction, bop.getName(), event);
213             props.put(org.apache.cxf.binding.soap.SoapBindingConstants.SOAP_ACTION, soapAction);
214             event.getMessage().setProperty(SoapConstants.SOAP_ACTION_PROPERTY, soapAction);
215         }
216 
217         Map<String, Object> ctx = new HashMap<String, Object>();
218         ctx.put(Client.REQUEST_CONTEXT, props);
219         ctx.put(Client.RESPONSE_CONTEXT, props);
220 
221         // Set Custom Headers on the client
222         Object[] arr = event.getMessage().getPropertyNames().toArray();
223         String head;
224 
225         for (int i = 0; i < arr.length; i++)
226         {
227             head = (String)arr[i];
228             if ((head != null) && (!head.startsWith("MULE")))
229             {
230                 props.put((String)arr[i], event.getMessage().getProperty((String)arr[i]));
231             }
232         }
233 
234         Object[] response = client.invoke(bop, getArgs(event), ctx);
235 
236         return buildResponseMessage(event, responseHolder.value, response);
237     }
238 
239     public Method getMethod(MuleEvent event) throws Exception
240     {
241         Method method = null;
242         if (method == null)
243         {
244             String opName = (String)event.getMessage().getProperty(CxfConstants.OPERATION);
245             if (opName != null)
246             {
247                 method = getMethodFromOperation(opName);
248             }
249 
250             if (method == null)
251             {
252                 opName = operation;
253                 if (opName != null)
254                 {
255                     method = getMethodFromOperation(opName);
256                 }
257             }
258         }
259 
260         if (method == null)
261         {
262             throw new MessagingException(CxfMessages.noOperationWasFoundOrSpecified(), event);
263         }
264         return method;
265     }
266 
267     protected BindingOperationInfo getOperation(final String opName) throws Exception
268     {
269         // Normally its not this hard to invoke the CXF Client, but we're
270         // sending along some exchange properties, so we need to use a more advanced
271         // method
272         Endpoint ep = client.getEndpoint();
273         BindingOperationInfo bop = getBindingOperationFromEndpoint(ep, opName);
274         if (bop == null)
275         {
276             bop = tryToGetTheOperationInDotNetNamingConvention(ep, opName);
277             if (bop == null)
278             {
279                 throw new Exception("No such operation: " + opName);
280             }
281         }
282 
283         if (bop.isUnwrappedCapable())
284         {
285             bop = bop.getUnwrappedOperation();
286         }
287         return bop;
288     }
289 
290     /**
291      * This method tries to call
292      * {@link #getBindingOperationFromEndpoint(Endpoint, String)} with the .net
293      * naming convention for .net webservices (method names start with a capital
294      * letter).
295      * <p>
296      * CXF generates method names compliant with Java naming so if the WSDL operation
297      * names starts with uppercase letter, matching with method name does not work -
298      * thus the work around.
299      * 
300      * @param opName
301      * @param ep
302      * @return
303      */
304     protected BindingOperationInfo tryToGetTheOperationInDotNetNamingConvention(Endpoint ep,
305                                                                                 final String opName)
306     {
307         final String capitalizedOpName = opName.substring(0, 1).toUpperCase() + opName.substring(1);
308         return getBindingOperationFromEndpoint(ep, capitalizedOpName);
309     }
310 
311     protected BindingOperationInfo getBindingOperationFromEndpoint(Endpoint ep, final String operationName)
312     {
313         QName q = new QName(ep.getService().getName().getNamespaceURI(), operationName);
314         BindingOperationInfo bop = ep.getBinding().getBindingInfo().getOperation(q);
315         return bop;
316     }
317 
318     private Method getMethodFromOperation(String op) throws Exception
319     {
320         BindingOperationInfo bop = getOperation(op);
321         MethodDispatcher md = (MethodDispatcher)client.getEndpoint()
322             .getService()
323             .get(MethodDispatcher.class.getName());
324         return md.getMethod(bop);
325     }
326 
327     protected String getMethodOrOperationName(MuleEvent event) throws DispatchException
328     {
329         // People can specify a CXF operation, which may in fact be different
330         // than the method name. If that's not found, we'll default back to the
331         // mule method property.
332         String method = event.getMessage().getInvocationProperty(CxfConstants.OPERATION);
333 
334         if (method == null)
335         {
336             method = event.getMessage().getInvocationProperty(MuleProperties.MULE_METHOD_PROPERTY);
337         }
338 
339         if (method == null)
340         {
341             method = operation;
342         }
343 
344         if (method == null && proxy)
345         {
346             return "invoke";
347         }
348 
349         return method;
350     }
351 
352     public BindingOperationInfo getOperation(MuleEvent event) throws Exception
353     {
354         String opName = getMethodOrOperationName(event);
355 
356         if (opName == null)
357         {
358             opName = operation;
359         }
360 
361         return getOperation(opName);
362     }
363 
364     private Map<String, Object> getInovcationProperties(MuleEvent event)
365     {
366         Map<String, Object> props = new HashMap<String, Object>();
367         props.put(MuleProperties.MULE_EVENT_PROPERTY, event);
368         props.put(CxfConstants.CXF_OUTBOUND_MESSAGE_PROCESSOR, this);
369         props.put(org.apache.cxf.message.Message.ENDPOINT_ADDRESS, event.getEndpoint().getEndpointURI().toString());
370         
371         if (decoupledEndpoint != null)
372         {
373             props.put(WSAContextUtils.REPLYTO_PROPERTY, decoupledEndpoint);
374         }
375         
376         return props;
377     }
378 
379     protected MuleEvent buildResponseMessage(MuleEvent request, MuleEvent transportResponse, Object[] response)
380     {
381         // One way dispatches over an async transport result in this
382         if (transportResponse == null)
383         {
384             return null;
385         }
386 
387         // Otherwise we may have a response!
388         Object payload;
389         if (response == null || response.length == 0)
390         {
391             payload = null;
392         }
393         else if (response.length == 1)
394         {
395             payload = response[0];
396         }
397         else
398         {
399             payload = response;
400         }
401 
402         MuleMessage message = transportResponse.getMessage();
403         String statusCode = message.getInboundProperty(HttpConnector.HTTP_STATUS_PROPERTY);
404         if (statusCode != null && Integer.parseInt(statusCode) != HttpConstants.SC_OK)
405         {
406             String exPayload;
407             try
408             {
409                 exPayload = message.getPayloadAsString();
410             }
411             catch (Exception e)
412             {
413                 exPayload = "Invalid status code: " + statusCode;
414             }
415             message.setExceptionPayload(new DefaultExceptionPayload(new HttpException(exPayload)));
416         }
417         else
418         {
419             message.setPayload(payload);
420         }
421 
422         return transportResponse;
423     }
424 
425     public String parseSoapAction(String soapAction, QName method, MuleEvent event)
426     {
427         EndpointURI endpointURI = event.getEndpoint().getEndpointURI();
428         Map<String, String> properties = new HashMap<String, String>();
429         MuleMessage msg = event.getMessage();
430         // propagate only invocation- and outbound-scoped properties
431         for (String name : msg.getInvocationPropertyNames())
432         {
433             final String value = msg.getInvocationProperty(name, StringUtils.EMPTY);
434             properties.put(name, value);
435         }
436         for (String name : msg.getOutboundPropertyNames())
437         {
438             final String value = msg.getOutboundProperty(name, StringUtils.EMPTY);
439             properties.put(name, value);
440         }
441         properties.put(MuleProperties.MULE_METHOD_PROPERTY, method.getLocalPart());
442         properties.put("methodNamespace", method.getNamespaceURI());
443         properties.put("address", endpointURI.getAddress());
444         properties.put("scheme", endpointURI.getScheme());
445         properties.put("host", endpointURI.getHost());
446         properties.put("port", String.valueOf(endpointURI.getPort()));
447         properties.put("path", endpointURI.getPath());
448         properties.put("hostInfo",
449             endpointURI.getScheme() + "://" + endpointURI.getHost()
450                             + (endpointURI.getPort() > -1 ? ":" + String.valueOf(endpointURI.getPort()) : ""));
451         if (event.getFlowConstruct() != null)
452         {
453             properties.put("serviceName", event.getFlowConstruct().getName());
454         }
455 
456         soapAction = soapActionTemplateParser.parse(properties, soapAction);
457 
458         if (logger.isDebugEnabled())
459         {
460             logger.debug("SoapAction for this call is: " + soapAction);
461         }
462 
463         return soapAction;
464     }
465 
466     public void setPayloadToArguments(CxfPayloadToArguments payloadToArguments)
467     {
468         this.payloadToArguments = payloadToArguments;
469     }
470 
471     protected boolean isClientProxyAvailable()
472     {
473         return clientProxy != null;
474     }
475 
476     public boolean isProxy()
477     {
478         return proxy;
479     }
480 
481     public void setProxy(boolean proxy)
482     {
483         this.proxy = proxy;
484     }
485 
486     public String getOperation()
487     {
488         return operation;
489     }
490 
491     public void setOperation(String operation)
492     {
493         this.operation = operation;
494     }
495 
496     public void setClientProxy(BindingProvider clientProxy)
497     {
498         this.clientProxy = clientProxy;
499     }
500 
501     public CxfPayloadToArguments getPayloadToArguments()
502     {
503         return payloadToArguments;
504     }
505 
506     public Client getClient()
507     {
508         return client;
509     }
510 
511     public void setDecoupledEndpoint(String decoupledEndpoint)
512     {
513         this.decoupledEndpoint = decoupledEndpoint;
514     }
515 
516 }