View Javadoc

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