View Javadoc

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