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