View Javadoc

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