View Javadoc

1   /*
2    * $Id: WSProxy.java 19884 2010-10-12 19:23:36Z ddossot $
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.ws.construct;
12  
13  import java.net.InetAddress;
14  import java.net.URI;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  import org.mule.MessageExchangePattern;
19  import org.mule.api.MessagingException;
20  import org.mule.api.MuleContext;
21  import org.mule.api.MuleEvent;
22  import org.mule.api.MuleException;
23  import org.mule.api.MuleMessage;
24  import org.mule.api.construct.FlowConstructInvalidException;
25  import org.mule.api.endpoint.InboundEndpoint;
26  import org.mule.api.endpoint.OutboundEndpoint;
27  import org.mule.api.processor.MessageProcessor;
28  import org.mule.api.source.MessageSource;
29  import org.mule.config.i18n.MessageFactory;
30  import org.mule.construct.AbstractFlowConstruct;
31  import org.mule.construct.processor.FlowConstructStatisticsMessageObserver;
32  import org.mule.endpoint.DynamicOutboundEndpoint;
33  import org.mule.interceptor.LoggingInterceptor;
34  import org.mule.processor.StopFurtherMessageProcessingMessageProcessor;
35  import org.mule.processor.builder.InterceptingChainMessageProcessorBuilder;
36  import org.mule.transformer.TransformerTemplate;
37  import org.mule.transformer.TransformerTemplate.TransformerCallback;
38  import org.mule.util.ObjectUtils;
39  import org.mule.util.StringUtils;
40  
41  /**
42   * This class is implemented to act as a Proxy for a Web Service. It listens for
43   * requests on the inbound endpoint and if it encounters the "WSDL" property in the
44   * address, it will fetch the WSDL from the original web service and return it back.
45   * In case the wsdlFile property is set, when the WSProxyService encounters a request
46   * for the wsdl, instead of fetching the WSDL from the original web service, it will
47   * return back the file expressed in the property. When a normal SOAP request is
48   * encountered, it will forward the call to the web service with no modifications to
49   * the SOAP message. The outbound router of this class must include the address of
50   * the webservice to be proxied. No need to include the method name as a parameter in
51   * the address, since it will be in the SOAP message as well. Furthermore a property
52   * named uriWsdl can optionally be set which as the name suggests, indicate the URL
53   * of the WSDL for the service. If this property is not set, the address of the WSDL
54   * will be assumed to be the value of uriWebservice followed by "?WSDL".
55   */
56  public class WSProxy extends AbstractFlowConstruct
57  {
58      private final AbstractProxyRequestProcessor proxyMessageProcessor;
59      private final OutboundEndpoint outboundEndpoint;
60  
61      public WSProxy(String name,
62                     MuleContext muleContext,
63                     MessageSource messageSource,
64                     OutboundEndpoint outboundEndpoint) throws MuleException
65      {
66          this(name, muleContext, messageSource, outboundEndpoint, new DynamicWsdlProxyRequestProcessor(
67              outboundEndpoint));
68      }
69  
70      public WSProxy(String name,
71                     MuleContext muleContext,
72                     MessageSource messageSource,
73                     OutboundEndpoint outboundEndpoint,
74                     String wsdlContents) throws MuleException
75      {
76          this(name, muleContext, messageSource, outboundEndpoint, new StaticWsdlProxyRequestProcessor(
77              wsdlContents));
78      }
79  
80      public WSProxy(String name,
81                     MuleContext muleContext,
82                     MessageSource messageSource,
83                     OutboundEndpoint outboundEndpoint,
84                     URI wsdlUri) throws MuleException
85      {
86          this(name, muleContext, messageSource, outboundEndpoint,
87              new DynamicWsdlProxyRequestProcessor(wsdlUri));
88      }
89  
90      private WSProxy(String name,
91                      MuleContext muleContext,
92                      MessageSource messageSource,
93                      OutboundEndpoint outboundEndpoint,
94                      AbstractProxyRequestProcessor proxyMessageProcessor) throws MuleException
95      {
96          super(name, muleContext);
97  
98          if (messageSource == null)
99          {
100             throw new FlowConstructInvalidException(
101                 MessageFactory.createStaticMessage("messageSource can't be null on: " + this.toString()),
102                 this);
103         }
104 
105         super.setMessageSource(messageSource);
106 
107         if (outboundEndpoint == null)
108         {
109             throw new FlowConstructInvalidException(
110                 MessageFactory.createStaticMessage("outboundEndpoint can't be null on: " + this.toString()),
111                 this);
112         }
113 
114         this.outboundEndpoint = outboundEndpoint;
115 
116         this.proxyMessageProcessor = proxyMessageProcessor;
117     }
118 
119     @Override
120     protected void configureMessageProcessors(InterceptingChainMessageProcessorBuilder builder)
121     {
122         builder.chain(new LoggingInterceptor());
123         builder.chain(new FlowConstructStatisticsMessageObserver());
124         builder.chain(proxyMessageProcessor);
125         builder.chain(new StopFurtherMessageProcessingMessageProcessor());
126         builder.chain(new TransformerTemplate(new CopyInboundToOutboundPropertiesTransformerCallback()));
127         builder.chain(outboundEndpoint);
128     }
129 
130     @Override
131     protected void validateConstruct() throws FlowConstructInvalidException
132     {
133         super.validateConstruct();
134 
135         if ((messageSource instanceof InboundEndpoint)
136             && (!((InboundEndpoint) messageSource).getExchangePattern().equals(
137                 MessageExchangePattern.REQUEST_RESPONSE)))
138         {
139             throw new FlowConstructInvalidException(
140                 MessageFactory.createStaticMessage("WSProxy only works with a request-response inbound endpoint."),
141                 this);
142         }
143 
144         if (!outboundEndpoint.getExchangePattern().equals(MessageExchangePattern.REQUEST_RESPONSE))
145         {
146             throw new FlowConstructInvalidException(
147                 MessageFactory.createStaticMessage("WSProxy only works with a request-response outbound endpoint."),
148                 this);
149         }
150     }
151 
152     private static final class CopyInboundToOutboundPropertiesTransformerCallback
153         implements TransformerCallback
154     {
155         public Object doTransform(MuleMessage message) throws Exception
156         {
157             for (final String inboundPropertyName : message.getInboundPropertyNames())
158             {
159                 message.setOutboundProperty(inboundPropertyName,
160                     message.getInboundProperty(inboundPropertyName));
161             }
162 
163             return message;
164         }
165     }
166 
167     private static abstract class AbstractProxyRequestProcessor implements MessageProcessor
168     {
169         private static final String HTTP_REQUEST = "http.request";
170         private static final String WSDL_PARAM_1 = "?wsdl";
171         private static final String WSDL_PARAM_2 = "&wsdl";
172 
173         protected final Log logger = LogFactory.getLog(WSProxy.class);
174 
175         public MuleEvent process(MuleEvent event) throws MuleException
176         {
177             if (isWsdlRequest(event))
178             {
179                 return buildWsdlResult(event);
180             }
181 
182             if (logger.isDebugEnabled())
183             {
184                 logger.debug("Forwarding SOAP message");
185             }
186 
187             return event;
188         }
189 
190         private MuleEvent buildWsdlResult(MuleEvent event) throws MuleException
191         {
192             try
193             {
194                 final String wsdlContents = getWsdlContents(event);
195                 event.getMessage().setPayload(wsdlContents);
196 
197                 // the processing is stopped so that the result is not passed through
198                 // the outbound router but will be passed back as a result
199                 event.setStopFurtherProcessing(true);
200                 return event;
201             }
202             catch (final Exception e)
203             {
204                 throw new MessagingException(
205                     MessageFactory.createStaticMessage("Impossible to retrieve WSDL for proxied service"),
206                     event, e);
207             }
208         }
209 
210         private boolean isWsdlRequest(MuleEvent event) throws MuleException
211         {
212             // retrieve the original HTTP request. This will be used to check if the
213             // user asked for the WSDL or a service method.
214             final String httpRequest = event.getMessage().<String> getInboundProperty(HTTP_REQUEST);
215 
216             if (httpRequest == null)
217             {
218                 logger.warn("WS Proxy can't rewrite WSDL for non-HTTP " + event);
219                 return false;
220             }
221 
222             final String lowerHttpRequest = httpRequest.toLowerCase();
223 
224             // check if the inbound request contains the WSDL parameter
225             return (lowerHttpRequest.indexOf(WSDL_PARAM_1) != -1)
226                    || (lowerHttpRequest.indexOf(WSDL_PARAM_2) != -1);
227         }
228 
229         protected abstract String getWsdlContents(MuleEvent event) throws Exception;
230     }
231 
232     private static class StaticWsdlProxyRequestProcessor extends AbstractProxyRequestProcessor
233     {
234         private final String wsdlContents;
235 
236         /**
237          * Instantiates a request processor that returns a static WSDL contents when
238          * the proxy receives a WSDL request.
239          * 
240          * @param wsdlContents the WSDL contents to use.
241          * @throws FlowConstructInvalidException
242          */
243         StaticWsdlProxyRequestProcessor(String wsdlContents) throws FlowConstructInvalidException
244         {
245             if (StringUtils.isBlank(wsdlContents))
246             {
247                 throw new FlowConstructInvalidException(
248                     MessageFactory.createStaticMessage("wsdlContents can't be empty"));
249             }
250 
251             this.wsdlContents = wsdlContents;
252         }
253 
254         @Override
255         protected String getWsdlContents(MuleEvent event) throws Exception
256         {
257             if (logger.isDebugEnabled())
258             {
259                 logger.debug("Serving static WSDL");
260             }
261 
262             return wsdlContents;
263         }
264     }
265 
266     private static class DynamicWsdlProxyRequestProcessor extends AbstractProxyRequestProcessor
267     {
268         private interface WsdlAddressProvider
269         {
270             String get(MuleEvent event);
271         }
272 
273         private static final String LOCALHOST = "localhost";
274         private final WsdlAddressProvider wsdlAddressProvider;
275 
276         /**
277          * Instantiates a request processor that fetches and rewrites addresses of a
278          * remote WSDL when the proxy receives a WSDL request.
279          * 
280          * @param wsdlUri the URI to fetch the WSDL from.
281          * @throws FlowConstructInvalidException
282          */
283         DynamicWsdlProxyRequestProcessor(final URI wsdlUri) throws FlowConstructInvalidException
284         {
285             if (wsdlUri == null)
286             {
287                 throw new FlowConstructInvalidException(
288                     MessageFactory.createStaticMessage("wsdlUri can't be null"));
289             }
290 
291             final String wsdlAddress = wsdlUri.toString();
292 
293             wsdlAddressProvider = new WsdlAddressProvider()
294             {
295                 public String get(MuleEvent event)
296                 {
297                     return wsdlAddress;
298                 }
299             };
300 
301             logger.info("Using url " + wsdlAddress + " as WSDL");
302         }
303 
304         /**
305          * Instantiates a request processor that fetches and rewrites addresses of a
306          * remote WSDL when the proxy receives a WSDL request.
307          * 
308          * @param outboundEndpoint the endpoint to fetch the WSDL from.
309          * @throws FlowConstructInvalidException
310          */
311         DynamicWsdlProxyRequestProcessor(OutboundEndpoint outboundEndpoint)
312             throws FlowConstructInvalidException
313         {
314             if (outboundEndpoint == null)
315             {
316                 throw new FlowConstructInvalidException(
317                     MessageFactory.createStaticMessage("outboundEndpoint can't be null"));
318             }
319 
320             final String wsAddress = outboundEndpoint.getAddress();
321 
322             if (outboundEndpoint instanceof DynamicOutboundEndpoint)
323             {
324                 wsdlAddressProvider = new WsdlAddressProvider()
325                 {
326                     public String get(MuleEvent event)
327                     {
328                         final String resolvedWsAddress = event.getMuleContext().getExpressionManager().parse(
329                             wsAddress, event.getMessage(), true);
330 
331                         return makeWsdlAddress(resolvedWsAddress);
332                     }
333                 };
334 
335                 logger.info("Using dynamic WSDL with service address: " + wsAddress);
336             }
337             else
338             {
339                 final String wsdlAddress = makeWsdlAddress(wsAddress);
340 
341                 wsdlAddressProvider = new WsdlAddressProvider()
342                 {
343                     public String get(MuleEvent event)
344                     {
345                         return wsdlAddress;
346                     }
347                 };
348 
349                 logger.info("Setting WSDL address to: " + wsdlAddress);
350             }
351         }
352 
353         private static String makeWsdlAddress(String wsAddress)
354         {
355             return StringUtils.substringBefore(wsAddress, "?").concat("?wsdl");
356         }
357 
358         @Override
359         protected String getWsdlContents(MuleEvent event) throws Exception
360         {
361             final String wsdlAddress = wsdlAddressProvider.get(event);
362             String wsdlString;
363 
364             final MuleContext muleContext = event.getMuleContext();
365             final InboundEndpoint webServiceEndpoint = muleContext.getRegistry()
366                 .lookupEndpointFactory()
367                 .getInboundEndpoint(wsdlAddress);
368 
369             if (logger.isDebugEnabled())
370             {
371                 logger.debug("Retrieving WSDL from web service with: " + webServiceEndpoint);
372             }
373 
374             final MuleMessage replyWSDL = webServiceEndpoint.request(event.getTimeout());
375             wsdlString = replyWSDL.getPayloadAsString();
376 
377             // create a new mule message with the new WSDL
378             final String realWsdlAddress = wsdlAddress.split("\\?")[0];
379             final String proxyWsdlAddress = event.getEndpoint().getEndpointURI().getUri().toString();
380             wsdlString = wsdlString.replaceAll(realWsdlAddress, proxyWsdlAddress);
381 
382             if (wsdlString.indexOf(LOCALHOST) > -1)
383             {
384                 wsdlString = wsdlString.replaceAll(LOCALHOST, InetAddress.getLocalHost().getHostName());
385             }
386 
387             if (logger.isDebugEnabled())
388             {
389                 logger.debug("WSDL retrieved successfully");
390             }
391 
392             return wsdlString;
393         }
394     }
395 
396     @Override
397     public String toString()
398     {
399         return ObjectUtils.toString(this);
400     }
401 }