View Javadoc

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