View Javadoc

1   /*
2    * Copyright 2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.transport.http;
18  
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Properties;
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  import javax.xml.transform.Source;
25  import javax.xml.transform.Transformer;
26  import javax.xml.transform.dom.DOMResult;
27  import javax.xml.transform.dom.DOMSource;
28  import javax.xml.transform.stream.StreamResult;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.w3c.dom.Attr;
33  import org.w3c.dom.Document;
34  
35  import org.springframework.beans.factory.InitializingBean;
36  import org.springframework.util.StringUtils;
37  import org.springframework.web.servlet.HandlerAdapter;
38  import org.springframework.web.servlet.ModelAndView;
39  import org.springframework.ws.wsdl.WsdlDefinition;
40  import org.springframework.xml.transform.TransformerObjectSupport;
41  import org.springframework.xml.xpath.XPathExpression;
42  import org.springframework.xml.xpath.XPathExpressionFactory;
43  
44  /**
45   * Adapter to use the <code>WsdlDefinition</code> interface with the generic <code>DispatcherServlet</code>.
46   * <p/>
47   * Reads the source from the mapped <code>WsdlDefinition</code> implementation, and writes that as the result to the
48   * <code>HttpServletResponse</code>.
49   * <p/>
50   * If the property <code>transformLocations</code> is set to <code>true</code>, this adapter will change
51   * <code>location</code> attributes in the WSDL definition to reflect the URL of the incoming request. If the location
52   * field in the original WSDL is an absolute path, the scheme, hostname, and port will be changed. If the location is a
53   * relative path, the scheme, hostname, port, and context path will be prepended. This behavior can be customized by
54   * overriding the <code>transformLocation()</code> method.
55   * <p/>
56   * For instance, if the location attribute defined in the WSDL is <code>http://localhost:8080/context/services/myService</code>,
57   * and the request URI for the WSDL is <code>http://example.com/context/myService.wsdl</code>, the location will be
58   * changed to <code>http://example.com/context/services/myService</code>.
59   * <p/>
60   * If the location attribute defined in the WSDL is <code>/services/myService</code>, and the request URI for the WSDL
61   * is <code>http://example.com:8080/context/myService.wsdl</code>, the location will be changed to
62   * <code>http://example.com:8080/context/services/myService</code>.
63   * <p/>
64   * When <code>transformLocations</code> is enabled, all <code>location</code> attributes found in the WSDL definition
65   * are changed by default. This behavior can be customized by changing the <code>locationExpression</code> property,
66   * which is an XPath expression that matches the attributes to change.
67   *
68   * @author Arjen Poutsma
69   * @see WsdlDefinition
70   * @see #setTransformLocations(boolean)
71   * @see #setLocationExpression(String)
72   * @see #transformLocation(String,javax.servlet.http.HttpServletRequest)
73   * @since 1.0.0
74   */
75  public class WsdlDefinitionHandlerAdapter extends TransformerObjectSupport implements HandlerAdapter, InitializingBean {
76  
77      /** Default XPath expression used for extracting all <code>location</code> attributes from the WSDL definition. */
78      public static final String DEFAULT_LOCATION_EXPRESSION = "//@location";
79  
80      private static final String CONTENT_TYPE = "text/xml";
81  
82      private static final Log logger = LogFactory.getLog(WsdlDefinitionHandlerAdapter.class);
83  
84      private Properties expressionNamespaces = new Properties();
85  
86      private String locationExpression = DEFAULT_LOCATION_EXPRESSION;
87  
88      private XPathExpression locationXPathExpression;
89  
90      private boolean transformLocations = false;
91  
92      /**
93       * Sets the XPath expression used for extracting the <code>location</code> attributes from the WSDL 1.1 definition.
94       * <p/>
95       * Defaults to <code>DEFAULT_LOCATION_EXPRESSION</code>.
96       *
97       * @see #DEFAULT_LOCATION_EXPRESSION
98       */
99      public void setLocationExpression(String locationExpression) {
100         this.locationExpression = locationExpression;
101     }
102 
103     /**
104      * Sets whether relative address locations in the WSDL are to be transformed using the request URI of the incoming
105      * <code>HttpServletRequest</code>. Defaults to <code>false</code>.
106      */
107     public void setTransformLocations(boolean transformLocations) {
108         this.transformLocations = transformLocations;
109     }
110 
111     public long getLastModified(HttpServletRequest request, Object handler) {
112         Source definitionSource = ((WsdlDefinition) handler).getSource();
113         return LastModifiedHelper.getLastModified(definitionSource);
114     }
115 
116     public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
117             throws Exception {
118         if (HttpTransportConstants.METHOD_GET.equals(request.getMethod())) {
119             response.setContentType(CONTENT_TYPE);
120             Transformer transformer = createTransformer();
121             WsdlDefinition definition = (WsdlDefinition) handler;
122             Source definitionSource = definition.getSource();
123             if (transformLocations) {
124                 DOMResult domResult = new DOMResult();
125                 transformer.transform(definitionSource, domResult);
126                 Document definitionDocument = (Document) domResult.getNode();
127                 transformLocations(definitionDocument, request);
128                 definitionSource = new DOMSource(definitionDocument);
129             }
130             StreamResult responseResult = new StreamResult(response.getOutputStream());
131             transformer.transform(definitionSource, responseResult);
132         }
133         else {
134             response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
135         }
136         return null;
137     }
138 
139     public boolean supports(Object handler) {
140         return handler instanceof WsdlDefinition;
141     }
142 
143     public void afterPropertiesSet() throws Exception {
144         locationXPathExpression =
145                 XPathExpressionFactory.createXPathExpression(locationExpression, expressionNamespaces);
146     }
147 
148     /**
149      * Transform the given location string to reflect the given request. If the given location is a full url, the
150      * scheme, server name, and port are changed. If it is a relative url, the scheme, server name, and port are
151      * prepended. Can be overridden in subclasses to change this behavior.
152      * <p/>
153      * For instance, if the location attribute defined in the WSDL is <code>http://localhost:8080/context/services/myService</code>,
154      * and the request URI for the WSDL is <code>http://example.com:8080/context/myService.wsdl</code>, the location
155      * will be changed to <code>http://example.com:8080/context/services/myService</code>.
156      * <p/>
157      * If the location attribute defined in the WSDL is <code>/services/myService</code>, and the request URI for the
158      * WSDL is <code>http://example.com:8080/context/myService.wsdl</code>, the location will be changed to
159      * <code>http://example.com:8080/context/services/myService</code>.
160      * <p/>
161      * This method is only called when the <code>transformLocations</code> property is true.
162      */
163     protected String transformLocation(String location, HttpServletRequest request) {
164         StringBuffer url = new StringBuffer(request.getScheme());
165         url.append("://").append(request.getServerName()).append(':').append(request.getServerPort());
166         if (location.startsWith("/")) {
167             // a relative path, prepend the context path
168             url.append(request.getContextPath()).append(location);
169             return url.toString();
170         }
171         else {
172             int idx = location.indexOf("://");
173             if (idx != -1) {
174                 // a full url
175                 idx = location.indexOf('/', idx + 3);
176                 if (idx != -1) {
177                     String path = location.substring(idx);
178                     url.append(path);
179                     return url.toString();
180                 }
181             }
182         }
183         // unknown location, return the original
184         return location;
185     }
186 
187     /**
188      * Transforms all <code>location</code> attributes to reflect the server name given <code>HttpServletRequest</code>.
189      * Determines the suitable attributes by evaluating the defined XPath expression, and delegates to
190      * <code>transformLocation</code> to do the transformation for all attributes that match.
191      * <p/>
192      * This method is only called when the <code>transformLocations</code> property is true.
193      *
194      * @see #setLocationExpression(String)
195      * @see #setTransformLocations(boolean)
196      * @see #transformLocation(String,javax.servlet.http.HttpServletRequest)
197      */
198     protected void transformLocations(Document definitionDocument, HttpServletRequest request) throws Exception {
199         List locationNodes = locationXPathExpression.evaluateAsNodeList(definitionDocument);
200         for (Iterator iterator = locationNodes.iterator(); iterator.hasNext();) {
201             Attr location = (Attr) iterator.next();
202             if (location != null && StringUtils.hasLength(location.getValue())) {
203                 String newLocation = transformLocation(location.getValue(), request);
204                 if (logger.isDebugEnabled()) {
205                     logger.debug("Transforming [" + location.getValue() + "] to [" + newLocation + "]");
206                 }
207                 location.setValue(newLocation);
208             }
209         }
210     }
211 }