View Javadoc

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