001: /*
002: * Copyright 2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.ws.transport.http;
018:
019: import java.net.MalformedURLException;
020: import java.net.URL;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Properties;
024: import javax.servlet.ServletException;
025: import javax.servlet.http.HttpServletRequest;
026: import javax.servlet.http.HttpServletResponse;
027: import javax.xml.transform.Source;
028: import javax.xml.transform.Transformer;
029: import javax.xml.transform.dom.DOMResult;
030: import javax.xml.transform.dom.DOMSource;
031: import javax.xml.transform.stream.StreamResult;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.springframework.beans.factory.InitializingBean;
036: import org.springframework.util.StringUtils;
037: import org.springframework.web.servlet.HandlerAdapter;
038: import org.springframework.web.servlet.ModelAndView;
039: import org.springframework.ws.wsdl.WsdlDefinition;
040: import org.springframework.xml.transform.TransformerObjectSupport;
041: import org.springframework.xml.xpath.XPathExpression;
042: import org.springframework.xml.xpath.XPathExpressionFactory;
043: import org.w3c.dom.Attr;
044: import org.w3c.dom.Document;
045:
046: /**
047: * Adapter to use the <code>WsdlDefinition</code> interface with the generic <code>DispatcherServlet</code>.
048: * <p/>
049: * Reads the source from the mapped <code>WsdlDefinition</code> implementation, and writes that as the result to the
050: * <code>HttpServletResponse</code>.
051: * <p/>
052: * If the property <code>transformLocations</code> is set to <code>true</code>, this adapter will change
053: * <code>location</code> attributes in the WSDL definition to reflect the URL of the incoming request. If the location
054: * field in the original WSDL is an absolute path, the scheme, hostname, and port will be changed. If the location is a
055: * relative path, the scheme, hostname, port, and context path will be prepended. This behavior can be customized by
056: * overriding the <code>transformLocation()</code> method.
057: * <p/>
058: * For instance, if the location attribute defined in the WSDL is <code>http://localhost:8080/context/services/myService</code>,
059: * and the request URI for the WSDL is <code>http://example.com/context/myService.wsdl</code>, the location will be
060: * changed to <code>http://example.com/context/services/myService</code>.
061: * <p/>
062: * If the location attribute defined in the WSDL is <code>/services/myService</code>, and the request URI for the WSDL
063: * is <code>http://example.com:8080/context/myService.wsdl</code>, the location will be changed to
064: * <code>http://example.com:8080/context/services/myService</code>.
065: * <p/>
066: * When <code>transformLocations</code> is enabled, all <code>location</code> attributes found in the WSDL definition
067: * are changed by default. This behavior can be customized by changing the <code>locationExpression</code> property,
068: * which is an XPath expression that matches the attributes to change.
069: *
070: * @author Arjen Poutsma
071: * @see WsdlDefinition
072: * @see #setTransformLocations(boolean)
073: * @see #setLocationExpression(String)
074: * @see #transformLocation(String,javax.servlet.http.HttpServletRequest)
075: * @since 1.0.0
076: */
077: public class WsdlDefinitionHandlerAdapter extends
078: TransformerObjectSupport implements HandlerAdapter,
079: InitializingBean {
080:
081: /** Default XPath expression used for extracting all <code>location</code> attributes from the WSDL definition. */
082: public static final String DEFAULT_LOCATION_EXPRESSION = "//@location";
083:
084: private static final String CONTENT_TYPE = "text/xml";
085:
086: private static final Log logger = LogFactory
087: .getLog(WsdlDefinitionHandlerAdapter.class);
088:
089: private Properties expressionNamespaces = new Properties();
090:
091: private String locationExpression = DEFAULT_LOCATION_EXPRESSION;
092:
093: private XPathExpression locationXPathExpression;
094:
095: private boolean transformLocations = false;
096:
097: /**
098: * Sets the XPath expression used for extracting the <code>location</code> attributes from the WSDL 1.1 definition.
099: * <p/>
100: * Defaults to <code>DEFAULT_LOCATION_EXPRESSION</code>.
101: *
102: * @see #DEFAULT_LOCATION_EXPRESSION
103: */
104: public void setLocationExpression(String locationExpression) {
105: this .locationExpression = locationExpression;
106: }
107:
108: /**
109: * Sets whether relative address locations in the WSDL are to be transformed using the request URI of the incoming
110: * <code>HttpServletRequest</code>. Defaults to <code>false</code>.
111: */
112: public void setTransformLocations(boolean transformLocations) {
113: this .transformLocations = transformLocations;
114: }
115:
116: public long getLastModified(HttpServletRequest request,
117: Object handler) {
118: return -1;
119: }
120:
121: public ModelAndView handle(HttpServletRequest request,
122: HttpServletResponse response, Object handler)
123: throws Exception {
124: if (!"GET".equals(request.getMethod())) {
125: throw new ServletException("Request method '"
126: + request.getMethod() + "' not supported");
127: }
128: response.setContentType(CONTENT_TYPE);
129: Transformer transformer = createTransformer();
130: WsdlDefinition definition = (WsdlDefinition) handler;
131: Source definitionSource = definition.getSource();
132: if (transformLocations) {
133: DOMResult domResult = new DOMResult();
134: transformer.transform(definitionSource, domResult);
135: Document definitionDocument = (Document) domResult
136: .getNode();
137: transformLocations(definitionDocument, request);
138: definitionSource = new DOMSource(definitionDocument);
139: }
140: StreamResult responseResult = new StreamResult(response
141: .getOutputStream());
142: transformer.transform(definitionSource, responseResult);
143: return null;
144: }
145:
146: public boolean supports(Object handler) {
147: return handler instanceof WsdlDefinition;
148: }
149:
150: public void afterPropertiesSet() throws Exception {
151: locationXPathExpression = XPathExpressionFactory
152: .createXPathExpression(locationExpression,
153: expressionNamespaces);
154: }
155:
156: /**
157: * Transform the given location string to reflect the given request. If the given location is a full url, the
158: * scheme, server name, and port are changed. If it is a relative url, the scheme, server name, and port are
159: * prepended. Can be overridden in subclasses to change this behavior.
160: * <p/>
161: * For instance, if the location attribute defined in the WSDL is <code>http://localhost:8080/context/services/myService</code>,
162: * and the request URI for the WSDL is <code>http://example.com:8080/context/myService.wsdl</code>, the location
163: * will be changed to <code>http://example.com:8080/context/services/myService</code>.
164: * <p/>
165: * If the location attribute defined in the WSDL is <code>/services/myService</code>, and the request URI for the
166: * WSDL is <code>http://example.com:8080/context/myService.wsdl</code>, the location will be changed to
167: * <code>http://example.com:8080/context/services/myService</code>.
168: * <p/>
169: * This method is only called when the <code>transformLocations</code> property is true.
170: */
171: protected String transformLocation(String location,
172: HttpServletRequest request) {
173: try {
174: if (location.startsWith("/")) {
175: // a relative path, prepend the context path
176: URL newLocation = new URL(request.getScheme(), request
177: .getServerName(), request.getServerPort(),
178: request.getContextPath() + location);
179: return newLocation.toString();
180: } else {
181: // a full url
182: URL oldLocation = new URL(location);
183: URL newLocation = new URL(request.getScheme(), request
184: .getServerName(), request.getServerPort(),
185: oldLocation.getFile());
186: return newLocation.toString();
187: }
188: } catch (MalformedURLException e) {
189: return location;
190: // fall though to the default return value
191: }
192: }
193:
194: /**
195: * Transforms all <code>location</code> attributes to reflect the server name given <code>HttpServletRequest</code>.
196: * Determines the suitable attributes by evaluating the defined XPath expression, and delegates to
197: * <code>transformLocation</code> to do the transformation for all attributes that match.
198: * <p/>
199: * This method is only called when the <code>transformLocations</code> property is true.
200: *
201: * @see #setLocationExpression(String)
202: * @see #setTransformLocations(boolean)
203: * @see #transformLocation(String,javax.servlet.http.HttpServletRequest)
204: */
205: protected void transformLocations(Document definitionDocument,
206: HttpServletRequest request) throws Exception {
207: List locationNodes = locationXPathExpression
208: .evaluateAsNodeList(definitionDocument);
209: for (Iterator iterator = locationNodes.iterator(); iterator
210: .hasNext();) {
211: Attr location = (Attr) iterator.next();
212: if (location != null
213: && StringUtils.hasLength(location.getValue())) {
214: String newLocation = transformLocation(location
215: .getValue(), request);
216: logger.debug("Transforming [" + location.getValue()
217: + "] to [" + newLocation + "]");
218: location.setValue(newLocation);
219: }
220: }
221: }
222: }
|