View Javadoc

1   /*
2    * $Id: CxfOutboundMessageProcessor.java 22156 2011-06-08 21:36:30Z dfeist $
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.transformer.TransformerException;
20  import org.mule.api.transport.DispatchException;
21  import org.mule.message.DefaultExceptionPayload;
22  import org.mule.module.cxf.i18n.CxfMessages;
23  import org.mule.module.cxf.security.WebServiceSecurityException;
24  import org.mule.processor.AbstractInterceptingMessageProcessor;
25  import org.mule.transport.http.HttpConnector;
26  import org.mule.transport.http.HttpConstants;
27  import org.mule.util.TemplateParser;
28  
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  import java.util.regex.Pattern;
38  
39  import javax.activation.DataHandler;
40  import javax.xml.namespace.QName;
41  import javax.xml.ws.BindingProvider;
42  import javax.xml.ws.Holder;
43  
44  import org.apache.commons.httpclient.HttpException;
45  import org.apache.cxf.endpoint.Client;
46  import org.apache.cxf.endpoint.Endpoint;
47  import org.apache.cxf.frontend.MethodDispatcher;
48  import org.apache.cxf.service.model.BindingOperationInfo;
49  import org.apache.cxf.ws.addressing.WSAContextUtils;
50  
51  /**
52   * The CxfOutboundMessageProcessor performs outbound CXF processing, sending an event
53   * through the CXF client, then on to the next MessageProcessor.
54   */
55  public class CxfOutboundMessageProcessor extends AbstractInterceptingMessageProcessor
56  {
57  
58      private static final String URI_REGEX = "cxf:\\[(.+?)\\]:(.+?)/\\[(.+?)\\]:(.+?)";
59      Pattern URI_PATTERN = Pattern.compile(URI_REGEX);
60  
61      private final TemplateParser soapActionTemplateParser = TemplateParser.createMuleStyleParser();
62      private CxfPayloadToArguments payloadToArguments = CxfPayloadToArguments.NULL_PAYLOAD_AS_PARAMETER;
63      private Client client;
64      private boolean proxy;
65      private String operation;
66      private BindingProvider clientProxy;
67      private String decoupledEndpoint;
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 super.processNext(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 
161         // Set custom soap action if set on the event or endpoint
162         String soapAction = event.getMessage().getOutboundProperty(SoapConstants.SOAP_ACTION_PROPERTY);
163         if (soapAction != null)
164         {
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         Map<String, Object> ctx = new HashMap<String, Object>();
206         ctx.put(Client.REQUEST_CONTEXT, props);
207         ctx.put(Client.RESPONSE_CONTEXT, props);
208 
209         // Set Custom Headers on the client
210         Object[] arr = event.getMessage().getPropertyNames().toArray();
211         String head;
212 
213         for (int i = 0; i < arr.length; i++)
214         {
215             head = (String)arr[i];
216             if ((head != null) && (!head.startsWith("MULE")))
217             {
218                 props.put((String)arr[i], event.getMessage().getProperty((String)arr[i]));
219             }
220         }
221 
222         Object[] response = client.invoke(bop, getArgs(event), ctx);
223 
224         return buildResponseMessage(event, responseHolder.value, response);
225     }
226 
227     public Method getMethod(MuleEvent event) throws Exception
228     {
229         Method method = null;
230         if (method == null)
231         {
232             String opName = (String)event.getMessage().getProperty(CxfConstants.OPERATION);
233             if (opName != null)
234             {
235                 method = getMethodFromOperation(opName);
236             }
237 
238             if (method == null)
239             {
240                 opName = operation;
241                 if (opName != null)
242                 {
243                     method = getMethodFromOperation(opName);
244                 }
245             }
246         }
247 
248         if (method == null)
249         {
250             throw new MessagingException(CxfMessages.noOperationWasFoundOrSpecified(), event);
251         }
252         return method;
253     }
254 
255     protected BindingOperationInfo getOperation(final String opName) throws Exception
256     {
257         // Normally its not this hard to invoke the CXF Client, but we're
258         // sending along some exchange properties, so we need to use a more advanced
259         // method
260         Endpoint ep = client.getEndpoint();
261         BindingOperationInfo bop = getBindingOperationFromEndpoint(ep, opName);
262         if (bop == null)
263         {
264             bop = tryToGetTheOperationInDotNetNamingConvention(ep, opName);
265             if (bop == null)
266             {
267                 throw new Exception("No such operation: " + opName);
268             }
269         }
270 
271         if (bop.isUnwrappedCapable())
272         {
273             bop = bop.getUnwrappedOperation();
274         }
275         return bop;
276     }
277 
278     /**
279      * <p>
280      * This method tries to call
281      * {@link #getBindingOperationFromEndpoint(Endpoint, String)} with the .NET
282      * naming convention for .NET webservices (method names start with a capital
283      * letter).
284      * </p>
285      * <p>
286      * CXF generates method names compliant with Java naming so if the WSDL operation
287      * names starts with uppercase letter, matching with method name does not work -
288      * thus the work around.
289      * </p>
290      */
291     protected BindingOperationInfo tryToGetTheOperationInDotNetNamingConvention(Endpoint ep,
292                                                                                 final String opName)
293     {
294         final String capitalizedOpName = opName.substring(0, 1).toUpperCase() + opName.substring(1);
295         return getBindingOperationFromEndpoint(ep, capitalizedOpName);
296     }
297 
298     protected BindingOperationInfo getBindingOperationFromEndpoint(Endpoint ep, final String operationName)
299     {
300         QName q = new QName(ep.getService().getName().getNamespaceURI(), operationName);
301         BindingOperationInfo bop = ep.getBinding().getBindingInfo().getOperation(q);
302         return bop;
303     }
304 
305     private Method getMethodFromOperation(String op) throws Exception
306     {
307         BindingOperationInfo bop = getOperation(op);
308         MethodDispatcher md = (MethodDispatcher)client.getEndpoint()
309             .getService()
310             .get(MethodDispatcher.class.getName());
311         return md.getMethod(bop);
312     }
313 
314     protected String getMethodOrOperationName(MuleEvent event) throws DispatchException
315     {
316         // People can specify a CXF operation, which may in fact be different
317         // than the method name. If that's not found, we'll default back to the
318         // mule method property.
319         String method = event.getMessage().getInvocationProperty(CxfConstants.OPERATION);
320 
321         if (method == null)
322         {
323             method = event.getMessage().getInvocationProperty(MuleProperties.MULE_METHOD_PROPERTY);
324         }
325 
326         if (method == null)
327         {
328             method = operation;
329         }
330 
331         if (method == null && proxy)
332         {
333             return "invoke";
334         }
335 
336         return method;
337     }
338 
339     public BindingOperationInfo getOperation(MuleEvent event) throws Exception
340     {
341         String opName = getMethodOrOperationName(event);
342 
343         if (opName == null)
344         {
345             opName = operation;
346         }
347 
348         return getOperation(opName);
349     }
350 
351     private Map<String, Object> getInovcationProperties(MuleEvent event)
352     {
353         Map<String, Object> props = new HashMap<String, Object>();
354         props.put(CxfConstants.MULE_EVENT, event);
355         props.put(CxfConstants.CXF_OUTBOUND_MESSAGE_PROCESSOR, this);
356         //props.put(org.apache.cxf.message.Message.ENDPOINT_ADDRESS, endpoint.getEndpointURI().toString());
357 
358         if (decoupledEndpoint != null)
359         {
360             props.put(WSAContextUtils.REPLYTO_PROPERTY, decoupledEndpoint);
361         }
362 
363         return props;
364     }
365 
366     protected MuleEvent buildResponseMessage(MuleEvent request, MuleEvent transportResponse, Object[] response)
367     {
368         // One way dispatches over an async transport result in this
369         if (transportResponse == null)
370         {
371             return null;
372         }
373 
374         // Otherwise we may have a response!
375         Object payload;
376         if (response == null || response.length == 0)
377         {
378             payload = null;
379         }
380         else if (response.length == 1)
381         {
382             payload = response[0];
383         }
384         else
385         {
386             payload = response;
387         }
388 
389         MuleMessage message = transportResponse.getMessage();
390         String statusCode = message.getInboundProperty(HttpConnector.HTTP_STATUS_PROPERTY);
391         if (statusCode != null && Integer.parseInt(statusCode) != HttpConstants.SC_OK)
392         {
393             String exPayload;
394             try
395             {
396                 exPayload = message.getPayloadAsString();
397             }
398             catch (Exception e)
399             {
400                 exPayload = "Invalid status code: " + statusCode;
401             }
402             message.setExceptionPayload(new DefaultExceptionPayload(new HttpException(exPayload)));
403         }
404         else
405         {
406             message.setPayload(payload);
407         }
408 
409         return transportResponse;
410     }
411 
412 //    public String parseSoapAction(String soapAction, QName method, MuleEvent event)
413 //    {
414 //        EndpointURI endpointURI = endpoint.getEndpointURI();
415 //        Map<String, String> properties = new HashMap<String, String>();
416 //        MuleMessage msg = event.getMessage();
417 //        // propagate only invocation- and outbound-scoped properties
418 //        for (String name : msg.getInvocationPropertyNames())
419 //        {
420 //            final String value = msg.getInvocationProperty(name, StringUtils.EMPTY);
421 //            properties.put(name, value);
422 //        }
423 //        for (String name : msg.getOutboundPropertyNames())
424 //        {
425 //            final String value = msg.getOutboundProperty(name, StringUtils.EMPTY);
426 //            properties.put(name, value);
427 //        }
428 //        properties.put(MuleProperties.MULE_METHOD_PROPERTY, method.getLocalPart());
429 //        properties.put("methodNamespace", method.getNamespaceURI());
430 //        properties.put("address", endpointURI.getAddress());
431 //        properties.put("scheme", endpointURI.getScheme());
432 //        properties.put("host", endpointURI.getHost());
433 //        properties.put("port", String.valueOf(endpointURI.getPort()));
434 //        properties.put("path", endpointURI.getPath());
435 //        properties.put("hostInfo",
436 //            endpointURI.getScheme() + "://" + endpointURI.getHost()
437 //                            + (endpointURI.getPort() > -1 ? ":" + String.valueOf(endpointURI.getPort()) : ""));
438 //        if (event.getFlowConstruct() != null)
439 //        {
440 //            properties.put("serviceName", event.getFlowConstruct().getName());
441 //        }
442 //
443 //        soapAction = soapActionTemplateParser.parse(properties, soapAction);
444 //
445 //        if (logger.isDebugEnabled())
446 //        {
447 //            logger.debug("SoapAction for this call is: " + soapAction);
448 //        }
449 //
450 //        return soapAction;
451 //    }
452 
453     public void setPayloadToArguments(CxfPayloadToArguments payloadToArguments)
454     {
455         this.payloadToArguments = payloadToArguments;
456     }
457 
458     protected boolean isClientProxyAvailable()
459     {
460         return clientProxy != null;
461     }
462 
463     public boolean isProxy()
464     {
465         return proxy;
466     }
467 
468     public void setProxy(boolean proxy)
469     {
470         this.proxy = proxy;
471     }
472 
473     public String getOperation()
474     {
475         return operation;
476     }
477 
478     public void setOperation(String operation)
479     {
480         this.operation = operation;
481     }
482 
483     public void setClientProxy(BindingProvider clientProxy)
484     {
485         this.clientProxy = clientProxy;
486     }
487 
488     public CxfPayloadToArguments getPayloadToArguments()
489     {
490         return payloadToArguments;
491     }
492 
493     public Client getClient()
494     {
495         return client;
496     }
497 
498     public void setDecoupledEndpoint(String decoupledEndpoint)
499     {
500         this.decoupledEndpoint = decoupledEndpoint;
501     }
502     
503 }