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.transport;
8   
9   import static org.apache.cxf.message.Message.DECOUPLED_CHANNEL_MESSAGE;
10  
11  import org.mule.DefaultMuleEvent;
12  import org.mule.DefaultMuleMessage;
13  import org.mule.RequestContext;
14  import org.mule.api.MuleContext;
15  import org.mule.api.MuleEvent;
16  import org.mule.api.MuleException;
17  import org.mule.api.MuleMessage;
18  import org.mule.api.MuleSession;
19  import org.mule.api.endpoint.OutboundEndpoint;
20  import org.mule.api.transformer.TransformerException;
21  import org.mule.api.transport.OutputHandler;
22  import org.mule.config.i18n.MessageFactory;
23  import org.mule.module.cxf.CxfConfiguration;
24  import org.mule.module.cxf.CxfConstants;
25  import org.mule.module.cxf.CxfOutboundMessageProcessor;
26  import org.mule.module.cxf.support.DelegatingOutputStream;
27  import org.mule.session.DefaultMuleSession;
28  import org.mule.transformer.types.DataTypeFactory;
29  import org.mule.transport.NullPayload;
30  import org.mule.transport.http.HttpConnector;
31  import org.mule.transport.http.HttpConstants;
32  import org.mule.api.DefaultMuleException;
33  
34  import java.io.ByteArrayOutputStream;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStream;
38  import java.io.PushbackInputStream;
39  import java.net.HttpURLConnection;
40  import java.net.MalformedURLException;
41  import java.util.HashMap;
42  import java.util.Map;
43  import java.util.logging.Logger;
44  
45  import javax.xml.ws.BindingProvider;
46  import javax.xml.ws.Holder;
47  
48  import org.apache.cxf.common.logging.LogUtils;
49  import org.apache.cxf.endpoint.ClientImpl;
50  import org.apache.cxf.interceptor.Fault;
51  import org.apache.cxf.message.Exchange;
52  import org.apache.cxf.message.ExchangeImpl;
53  import org.apache.cxf.message.Message;
54  import org.apache.cxf.message.MessageImpl;
55  import org.apache.cxf.phase.AbstractPhaseInterceptor;
56  import org.apache.cxf.phase.Phase;
57  import org.apache.cxf.service.model.EndpointInfo;
58  import org.apache.cxf.transport.AbstractConduit;
59  import org.apache.cxf.transport.MessageObserver;
60  import org.apache.cxf.ws.addressing.AttributedURIType;
61  import org.apache.cxf.ws.addressing.EndpointReferenceType;
62  import org.apache.cxf.wsdl.EndpointReferenceUtils;
63  
64  /**
65   * A Conduit is primarily responsible for sending messages from CXF to somewhere
66   * else. This conduit takes messages which are being written and sends them to the
67   * Mule bus.
68   */
69  public class MuleUniversalConduit extends AbstractConduit
70  {
71  
72      private static final Logger LOGGER = LogUtils.getL7dLogger(MuleUniversalConduit.class);
73  
74      private EndpointInfo endpoint;
75  
76      private CxfConfiguration configuration;
77  
78      private MuleUniversalTransport transport;
79  
80      private boolean closeInput = true;
81  
82      private Map<String,OutboundEndpoint> endpoints = new HashMap<String, OutboundEndpoint>();
83      
84      /**
85       * @param ei The Endpoint being invoked by this destination.
86       * @param t The EPR associated with this Conduit - i.e. the reply destination.
87       */
88      public MuleUniversalConduit(MuleUniversalTransport transport,
89                                  CxfConfiguration configuration,
90                                  EndpointInfo ei,
91                                  EndpointReferenceType t)
92      {
93          super(getTargetReference(ei, t));
94          this.transport = transport;
95          this.endpoint = ei;
96          this.configuration = configuration;
97      }
98      
99      @Override
100     public void close(Message msg) throws IOException
101     {
102         OutputStream os = msg.getContent(OutputStream.class);
103         if (os != null)
104         {
105             os.close();
106         }
107         
108         if (closeInput)
109         {
110             InputStream in = msg.getContent(InputStream.class);
111             if (in != null)
112             {
113                 in.close();
114             }
115         }
116     }
117 
118     @Override
119     protected Logger getLogger()
120     {
121         return LOGGER;
122     }
123 
124     /**
125      * Prepare the message for writing.
126      */
127     public void prepare(final Message message) throws IOException
128     {
129         // save in a separate place in case we need to resend the request
130         final ByteArrayOutputStream cache = new ByteArrayOutputStream();
131         final DelegatingOutputStream delegating = new DelegatingOutputStream(cache);
132         message.setContent(OutputStream.class, delegating);
133         message.setContent(DelegatingOutputStream.class, delegating);
134         
135         OutputHandler handler = new OutputHandler()
136         {
137             public void write(MuleEvent event, OutputStream out) throws IOException
138             {
139                 out.write(cache.toByteArray());
140                 
141                 delegating.setOutputStream(out);
142                 
143                 // resume writing!
144                 message.getInterceptorChain().doIntercept(message);
145             }
146         };
147 
148         MuleEvent event = (MuleEvent) message.getExchange().get(CxfConstants.MULE_EVENT);
149         // are we sending an out of band response for a server side request?
150         boolean decoupled = event != null && message.getExchange().getInMessage() != null;
151         
152         if (event == null || decoupled)
153         {
154             // we've got an out of band WS-RM message or a message from a standalone client
155             MuleContext muleContext = configuration.getMuleContext();
156             MuleMessage muleMsg = new DefaultMuleMessage(handler, muleContext);
157             MuleSession session = new DefaultMuleSession(muleContext);
158             
159             String url = setupURL(message);
160             
161             try
162             {
163                 OutboundEndpoint ep = getEndpoint(muleContext, url);
164                 event = new DefaultMuleEvent(muleMsg, ep, session);
165             }
166             catch (Exception e)
167             {
168                 throw new Fault(e);
169             }
170             event.setTimeout(MuleEvent.TIMEOUT_NOT_SET_VALUE);
171         }
172         else 
173         {
174             event.getMessage().setPayload(handler);
175         }
176 
177         if (!decoupled)
178         {
179             message.getExchange().put(CxfConstants.MULE_EVENT, event);
180         }
181         message.put(CxfConstants.MULE_EVENT, event);
182         
183         final MuleEvent finalEvent = event;
184         AbstractPhaseInterceptor<Message> i = new AbstractPhaseInterceptor<Message>(Phase.PRE_STREAM)
185         {
186             public void handleMessage(Message m) throws Fault
187             {
188                 try
189                 {
190                     dispatchMuleMessage(m, finalEvent);
191                 }
192                 catch (MuleException e)
193                 {
194                     throw new Fault(e);
195                 }
196             }
197         };
198         message.getInterceptorChain().add(i);
199     }
200 
201     protected synchronized OutboundEndpoint getEndpoint(MuleContext muleContext, String uri) throws MuleException
202     {
203         if (endpoints.get(uri) != null)
204         {
205             return endpoints.get(uri);
206         }
207 
208         OutboundEndpoint endpoint = muleContext.getEndpointFactory().getOutboundEndpoint(uri);
209         endpoints.put(uri, endpoint);
210         return endpoint;
211     }
212     
213     public String setupURL(Message message) throws MalformedURLException
214     {
215         String value = (String) message.get(Message.ENDPOINT_ADDRESS);
216         String pathInfo = (String) message.get(Message.PATH_INFO);
217         String queryString = (String) message.get(Message.QUERY_STRING);
218         String username = (String) message.get(BindingProvider.USERNAME_PROPERTY);
219         String password = (String) message.get(BindingProvider.PASSWORD_PROPERTY);
220 
221         String result = value != null ? value : getTargetOrEndpoint();
222 
223         if (username != null) {
224              int slashIdx = result.indexOf("//");
225              if (slashIdx != -1) {
226                  result = result.substring(0, slashIdx + 2) + username + ":" + password + "@" + result.substring(slashIdx+2);
227              }
228         }
229 
230         // REVISIT: is this really correct?
231         if (null != pathInfo && !result.endsWith(pathInfo))
232         {
233             result = result + pathInfo;
234         }
235         if (queryString != null)
236         {
237             result = result + "?" + queryString;
238         }
239         return result;
240     }
241     
242     protected void dispatchMuleMessage(Message m, MuleEvent reqEvent) throws MuleException {
243         try
244         {   
245             MuleMessage req = reqEvent.getMessage();
246             req.setOutboundProperty(HttpConnector.HTTP_DISABLE_STATUS_CODE_EXCEPTION_CHECK, Boolean.TRUE.toString());
247 
248             MuleEvent resEvent = processNext(reqEvent, m.getExchange());
249 
250             if (resEvent == null)
251             {
252                 m.getExchange().put(ClientImpl.FINISHED, Boolean.TRUE);
253                 return;
254             }
255 
256             m.getExchange().put(CxfConstants.MULE_EVENT, resEvent);
257             
258             // If we have a result, send it back to CXF
259             MuleMessage result = resEvent.getMessage();
260             InputStream is = getResponseBody(m, result);
261             if (is != null)
262             {
263                 Message inMessage = new MessageImpl();
264 
265                 String encoding = result.getEncoding();
266                 inMessage.put(Message.ENCODING, encoding);
267                 
268                 String contentType = result.getInboundProperty(HttpConstants.HEADER_CONTENT_TYPE, "text/xml");
269                 if (encoding != null && contentType.indexOf("charset") < 0)
270                 {
271                     contentType += "; charset=" + result.getEncoding();
272                 }
273                 inMessage.put(Message.CONTENT_TYPE, contentType);
274                 inMessage.setContent(InputStream.class, is);
275                 inMessage.setExchange(m.getExchange());
276                 getMessageObserver().onMessage(inMessage);
277             }
278         }
279         catch(MuleException me)
280         {
281             throw me;
282         }
283         catch (Exception e)
284         {
285             throw new DefaultMuleException(MessageFactory.createStaticMessage("Could not send message to Mule."), e);
286         }
287     }
288 
289     protected InputStream getResponseBody(Message m, MuleMessage result) throws TransformerException, IOException
290     {
291         boolean response = result != null
292             && !NullPayload.getInstance().equals(result.getPayload())
293             && !isOneway(m.getExchange());
294         
295         if (response)
296         {
297             // Sometimes there may not actually be a body, in which case
298             // we want to act appropriately. E.g. one way invocations over a proxy
299             InputStream is = result.getPayload(DataTypeFactory.create(InputStream.class));
300             PushbackInputStream pb = new PushbackInputStream(is);
301             result.setPayload(pb);
302             
303             int b = pb.read();
304             if (b != -1)
305             {
306                 pb.unread(b);
307                 return pb;
308             }
309         }
310         
311         return null;
312     }
313 
314     protected boolean isOneway(Exchange exchange)
315     {
316         return exchange != null && exchange.isOneWay();
317     }
318     
319     protected String getTargetOrEndpoint()
320     {
321         if (target != null)
322         {
323             return target.getAddress().getValue();
324         }
325 
326         return endpoint.getAddress().toString();
327     }
328 
329     public void onClose(final Message m) throws IOException
330     {
331         // template method
332     }
333     
334     protected MuleEvent processNext(MuleEvent event,
335                                     Exchange exchange) throws MuleException
336     {
337         CxfOutboundMessageProcessor processor = (CxfOutboundMessageProcessor) exchange.get(CxfConstants.CXF_OUTBOUND_MESSAGE_PROCESSOR);
338         MuleEvent response;
339         if (processor == null)
340         {
341             // we're sending from a CXF client, not from mule
342             OutboundEndpoint ep = (OutboundEndpoint) event.getEndpoint();
343             RequestContext.setEvent(event);
344             response = ep.process(event);
345         }
346         else
347         {
348            response = processor.processNext(event);
349            
350            Holder<MuleEvent> holder = (Holder<MuleEvent>) exchange.get("holder");
351            holder.value = response;
352         }
353         
354         return response;
355     }
356 
357     @Override
358     public void close()
359     {
360     }
361 
362     /**
363      * Get the target endpoint reference.
364      * 
365      * @param ei the corresponding EndpointInfo
366      * @param t the given target EPR if available
367      * @return the actual target
368      */
369     protected static EndpointReferenceType getTargetReference(EndpointInfo ei, EndpointReferenceType t)
370     {
371         EndpointReferenceType ref = null;
372         if (null == t)
373         {
374             ref = new EndpointReferenceType();
375             AttributedURIType address = new AttributedURIType();
376             address.setValue(ei.getAddress());
377             ref.setAddress(address);
378             if (ei.getService() != null)
379             {
380                 EndpointReferenceUtils.setServiceAndPortName(ref, ei.getService().getName(), ei.getName()
381                     .getLocalPart());
382             }
383         }
384         else
385         {
386             ref = t;
387         }
388         return ref;
389     }
390 
391     /**
392      * Used to set appropriate message properties, exchange etc. as required for an
393      * incoming decoupled response (as opposed what's normally set by the Destination
394      * for an incoming request).
395      */
396     protected class InterposedMessageObserver implements MessageObserver
397     {
398         /**
399          * Called for an incoming message.
400          * 
401          * @param inMessage
402          */
403         public void onMessage(Message inMessage)
404         {
405             // disposable exchange, swapped with real Exchange on correlation
406             inMessage.setExchange(new ExchangeImpl());
407             inMessage.put(DECOUPLED_CHANNEL_MESSAGE, Boolean.TRUE);
408             inMessage.put(Message.RESPONSE_CODE, HttpURLConnection.HTTP_OK);
409             inMessage.remove(Message.ASYNC_POST_RESPONSE_DISPATCH);
410 
411             incomingObserver.onMessage(inMessage);
412         }
413     }
414     
415     public void setCloseInput(boolean closeInput)
416     {
417         this.closeInput = closeInput;
418     }
419 
420     protected CxfConfiguration getConnector()
421     {
422         return configuration;
423     }
424 
425     protected EndpointInfo getEndpoint()
426     {
427         return endpoint;
428     }
429 
430     protected MuleUniversalTransport getTransport()
431     {
432         return transport;
433     }
434 }