View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.module.ws.construct;
8   
9   import org.mule.MessageExchangePattern;
10  import org.mule.api.MessagingException;
11  import org.mule.api.MuleContext;
12  import org.mule.api.MuleEvent;
13  import org.mule.api.MuleException;
14  import org.mule.api.MuleMessage;
15  import org.mule.api.construct.FlowConstructInvalidException;
16  import org.mule.api.endpoint.InboundEndpoint;
17  import org.mule.api.endpoint.OutboundEndpoint;
18  import org.mule.api.expression.ExpressionManager;
19  import org.mule.api.expression.ExpressionRuntimeException;
20  import org.mule.api.processor.MessageProcessor;
21  import org.mule.api.processor.MessageProcessorChainBuilder;
22  import org.mule.api.source.MessageSource;
23  import org.mule.config.i18n.MessageFactory;
24  import org.mule.construct.AbstractFlowConstruct;
25  import org.mule.construct.processor.FlowConstructStatisticsMessageProcessor;
26  import org.mule.endpoint.DynamicOutboundEndpoint;
27  import org.mule.interceptor.LoggingInterceptor;
28  import org.mule.interceptor.ProcessingTimeInterceptor;
29  import org.mule.processor.ResponseMessageProcessorAdapter;
30  import org.mule.processor.StopFurtherMessageProcessingMessageProcessor;
31  import org.mule.transformer.TransformerTemplate;
32  import org.mule.transformer.TransformerTemplate.TransformerCallback;
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  
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         proxyMessageProcessor.setOutboundAddress((outboundEndpoint.getAddress()));
118 
119         this.proxyMessageProcessor = proxyMessageProcessor;
120     }
121 
122     @Override
123     protected void configureMessageProcessors(MessageProcessorChainBuilder builder)
124     {
125         builder.chain(new ProcessingTimeInterceptor());
126         builder.chain(new LoggingInterceptor());
127         builder.chain(new FlowConstructStatisticsMessageProcessor());
128         builder.chain(proxyMessageProcessor);
129         builder.chain(new StopFurtherMessageProcessingMessageProcessor());
130         final TransformerTemplate copyInboundToOutboundPropertiesTransformer = new TransformerTemplate(new CopyInboundToOutboundPropertiesTransformerCallback());
131         builder.chain(copyInboundToOutboundPropertiesTransformer);
132         builder.chain(new ResponseMessageProcessorAdapter(copyInboundToOutboundPropertiesTransformer));
133         builder.chain(outboundEndpoint);
134     }
135 
136     @Override
137     protected void validateConstruct() throws FlowConstructInvalidException
138     {
139         super.validateConstruct();
140 
141         if ((messageSource instanceof InboundEndpoint)
142             && (!((InboundEndpoint) messageSource).getExchangePattern().equals(
143                 MessageExchangePattern.REQUEST_RESPONSE)))
144         {
145             throw new FlowConstructInvalidException(
146                 MessageFactory.createStaticMessage("WSProxy only works with a request-response inbound endpoint."),
147                 this);
148         }
149 
150         if (!outboundEndpoint.getExchangePattern().equals(MessageExchangePattern.REQUEST_RESPONSE))
151         {
152             throw new FlowConstructInvalidException(
153                 MessageFactory.createStaticMessage("WSProxy only works with a request-response outbound endpoint."),
154                 this);
155         }
156     }
157 
158     private static final class CopyInboundToOutboundPropertiesTransformerCallback
159         implements TransformerCallback
160     {
161         public Object doTransform(MuleMessage message) throws Exception
162         {
163             for (final String inboundPropertyName : message.getInboundPropertyNames())
164             {
165                 message.setOutboundProperty(inboundPropertyName,
166                     message.getInboundProperty(inboundPropertyName));
167             }
168 
169             return message;
170         }
171     }
172 
173     private static abstract class AbstractProxyRequestProcessor implements MessageProcessor
174     {
175         private static final String HTTP_REQUEST = "http.request";
176         private static final String WSDL_PARAM_1 = "?wsdl";
177         private static final String WSDL_PARAM_2 = "&wsdl";
178         private static final String LOCALHOST = "localhost";
179 
180         protected final Log logger = LogFactory.getLog(WSProxy.class);
181 
182         private String outboundAddress;
183 
184         protected void setOutboundAddress(String outboundAddress)
185         {
186             this.outboundAddress = outboundAddress;
187         }
188 
189         public MuleEvent process(MuleEvent event) throws MuleException
190         {
191             if (isWsdlRequest(event))
192             {
193                 return buildWsdlResult(event);
194             }
195 
196             if (logger.isDebugEnabled())
197             {
198                 logger.debug("Forwarding SOAP message");
199             }
200 
201             return event;
202         }
203 
204         private MuleEvent buildWsdlResult(MuleEvent event) throws MuleException
205         {
206             try
207             {
208                 String wsdlContents = getWsdlContents(event);
209                 wsdlContents = modifyServiceAddress(wsdlContents, event);
210                 event.getMessage().setPayload(wsdlContents);
211 
212                 // the processing is stopped so that the result is not passed through
213                 // the outbound router but will be passed back as a result
214                 event.setStopFurtherProcessing(true);
215                 return event;
216             }
217             catch (final Exception e)
218             {
219                 throw new MessagingException(
220                     MessageFactory.createStaticMessage("Impossible to retrieve WSDL for proxied service"),
221                     event, e);
222             }
223         }
224 
225         private String modifyServiceAddress(String wsdlContents, MuleEvent event) throws UnknownHostException
226         {
227             // create a new mule message with the new WSDL
228             String inboundAddress = event.getEndpoint().getAddress();
229             try
230             {
231                 String substitutedAddress = outboundAddress;
232                 ExpressionManager expressionManager = event.getMuleContext().getExpressionManager();
233                 if (expressionManager.isValidExpression(outboundAddress))
234                 {                    
235                     substitutedAddress = expressionManager.parse(outboundAddress, event.getMessage(), true);
236                 }
237                 wsdlContents = wsdlContents.replaceAll(substitutedAddress, inboundAddress);
238             }
239             catch (ExpressionRuntimeException ex)
240             {
241                 logger.warn("Unable to construct outbound address for WSDL request to proxied dynamic endpoint " + outboundAddress);    
242             }
243 
244 
245             if (wsdlContents.indexOf(LOCALHOST) > -1)
246             {
247                 wsdlContents = wsdlContents.replaceAll(LOCALHOST, InetAddress.getLocalHost().getHostName());
248             }
249 
250             if (logger.isDebugEnabled())
251             {
252                 logger.debug("WSDL modified successfully");
253             }
254             
255             return wsdlContents;
256         }
257 
258         private boolean isWsdlRequest(MuleEvent event) throws MuleException
259         {
260             // retrieve the original HTTP request. This will be used to check if the
261             // user asked for the WSDL or a service method.
262             final String httpRequest = event.getMessage().<String> getInboundProperty(HTTP_REQUEST);
263 
264             if (httpRequest == null)
265             {
266                 logger.warn("WS Proxy can't rewrite WSDL for non-HTTP " + event);
267                 return false;
268             }
269 
270             final String lowerHttpRequest = httpRequest.toLowerCase();
271 
272             // check if the inbound request contains the WSDL parameter
273             return (lowerHttpRequest.indexOf(WSDL_PARAM_1) != -1)
274                    || (lowerHttpRequest.indexOf(WSDL_PARAM_2) != -1);
275         }
276 
277         protected abstract String getWsdlContents(MuleEvent event) throws Exception;
278     }
279 
280     private static class StaticWsdlProxyRequestProcessor extends AbstractProxyRequestProcessor
281     {
282         private final String wsdlContents;
283 
284         /**
285          * Instantiates a request processor that returns a static WSDL contents when
286          * the proxy receives a WSDL request.
287          *
288          * @param wsdlContents the WSDL contents to use.
289          * @throws FlowConstructInvalidException
290          */
291         StaticWsdlProxyRequestProcessor(String wsdlContents) throws FlowConstructInvalidException
292         {
293             if (StringUtils.isBlank(wsdlContents))
294             {
295                 throw new FlowConstructInvalidException(
296                     MessageFactory.createStaticMessage("wsdlContents can't be empty"));
297             }
298 
299             this.wsdlContents = wsdlContents;
300         }
301 
302         @Override
303         protected String getWsdlContents(MuleEvent event) throws Exception
304         {
305             if (logger.isDebugEnabled())
306             {
307                 logger.debug("Serving static WSDL");
308             }
309 
310             return wsdlContents;
311         }
312     }
313 
314     private static class DynamicWsdlProxyRequestProcessor extends AbstractProxyRequestProcessor
315     {
316         private interface WsdlAddressProvider
317         {
318             String get(MuleEvent event);
319         }
320 
321         private final WsdlAddressProvider wsdlAddressProvider;
322 
323         /**
324          * Instantiates a request processor that fetches and rewrites addresses of a
325          * remote WSDL when the proxy receives a WSDL request.
326          *
327          * @param wsdlUri the URI to fetch the WSDL from.
328          * @throws FlowConstructInvalidException
329          */
330         DynamicWsdlProxyRequestProcessor(final URI wsdlUri) throws FlowConstructInvalidException
331         {
332             if (wsdlUri == null)
333             {
334                 throw new FlowConstructInvalidException(
335                     MessageFactory.createStaticMessage("wsdlUri can't be null"));
336             }
337 
338             final String wsdlAddress = wsdlUri.toString();
339 
340             wsdlAddressProvider = new WsdlAddressProvider()
341             {
342                 public String get(MuleEvent event)
343                 {
344                     return wsdlAddress;
345                 }
346             };
347 
348             logger.info("Using url " + wsdlAddress + " as WSDL");
349         }
350 
351         /**
352          * Instantiates a request processor that fetches and rewrites addresses of a
353          * remote WSDL when the proxy receives a WSDL request.
354          *
355          * @param outboundEndpoint the endpoint to fetch the WSDL from.
356          * @throws FlowConstructInvalidException
357          */
358         DynamicWsdlProxyRequestProcessor(OutboundEndpoint outboundEndpoint)
359             throws FlowConstructInvalidException
360         {
361             if (outboundEndpoint == null)
362             {
363                 throw new FlowConstructInvalidException(
364                     MessageFactory.createStaticMessage("outboundEndpoint can't be null"));
365             }
366 
367             final String wsAddress = outboundEndpoint.getAddress();
368 
369             if (outboundEndpoint instanceof DynamicOutboundEndpoint)
370             {
371                 wsdlAddressProvider = new WsdlAddressProvider()
372                 {
373                     public String get(MuleEvent event)
374                     {
375                         final String resolvedWsAddress = event.getMuleContext().getExpressionManager().parse(
376                             wsAddress, event.getMessage(), true);
377 
378                         return makeWsdlAddress(resolvedWsAddress);
379                     }
380                 };
381 
382                 logger.info("Using dynamic WSDL with service address: " + wsAddress);
383             }
384             else
385             {
386                 final String wsdlAddress = makeWsdlAddress(wsAddress);
387 
388                 wsdlAddressProvider = new WsdlAddressProvider()
389                 {
390                     public String get(MuleEvent event)
391                     {
392                         return wsdlAddress;
393                     }
394                 };
395 
396                 logger.info("Setting WSDL address to: " + wsdlAddress);
397             }
398         }
399 
400         private static String makeWsdlAddress(String wsAddress)
401         {
402             return StringUtils.substringBefore(wsAddress, "?").concat("?wsdl");
403         }
404 
405         @Override
406         protected String getWsdlContents(MuleEvent event) throws Exception
407         {
408             final String wsdlAddress = wsdlAddressProvider.get(event);
409             String wsdlString;
410 
411             final MuleContext muleContext = event.getMuleContext();
412             final InboundEndpoint webServiceEndpoint =
413                 muleContext.getEndpointFactory().getInboundEndpoint(wsdlAddress);
414 
415             if (logger.isDebugEnabled())
416             {
417                 logger.debug("Retrieving WSDL from web service with: " + webServiceEndpoint);
418             }
419 
420             final MuleMessage replyWSDL = webServiceEndpoint.request(event.getTimeout());
421             wsdlString = replyWSDL.getPayloadAsString();
422 
423             return wsdlString;
424         }
425     }
426 
427     @Override
428     public String toString()
429     {
430         return ObjectUtils.toString(this);
431     }
432 
433     @Override
434     public String getConstructType()
435     {
436         return "Web-Service-Proxy";
437     }
438 }