View Javadoc

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