From 26f0f5a7426a0780dc88ecfc3546c29d265a722d Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 25 Sep 2012 13:29:24 +0000 Subject: [PATCH] SWS-797 - XsdSchemaHandlerAdapter does not transform schema locations --- .../LocationTransformerObjectSupport.java | 111 ++++++++++++++++++ .../http/WsdlDefinitionHandlerAdapter.java | 71 +---------- .../http/XsdSchemaHandlerAdapter.java | 82 +++++++++++-- .../http/XsdSchemaHandlerAdapterTest.java | 54 +++++++-- .../ws/transport/http/importing-expected.xsd | 7 ++ .../ws/transport/http/importing-input.xsd | 7 ++ 6 files changed, 248 insertions(+), 84 deletions(-) create mode 100644 core/src/main/java/org/springframework/ws/transport/http/LocationTransformerObjectSupport.java create mode 100644 core/src/test/resources/org/springframework/ws/transport/http/importing-expected.xsd create mode 100644 core/src/test/resources/org/springframework/ws/transport/http/importing-input.xsd diff --git a/core/src/main/java/org/springframework/ws/transport/http/LocationTransformerObjectSupport.java b/core/src/main/java/org/springframework/ws/transport/http/LocationTransformerObjectSupport.java new file mode 100644 index 00000000..868925c2 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/LocationTransformerObjectSupport.java @@ -0,0 +1,111 @@ +/* + * Copyright 2005-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.transport.http; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.xml.transform.TransformerObjectSupport; +import org.springframework.xml.xpath.XPathExpression; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * Abstract base class for {@link WsdlDefinitionHandlerAdapter} and {@link XsdSchemaHandlerAdapter} that transforms + * XSD and WSDL location attributes. + * + * @author Arjen Poutsma + * @since 2.1.2 + */ +public abstract class LocationTransformerObjectSupport extends TransformerObjectSupport { + + /** Logger available to subclasses. */ + private final Log logger = LogFactory.getLog(getClass()); + + /** + * Transforms the locations of the given definition document using the given XPath expression. + * @param xPathExpression the XPath expression + * @param definitionDocument the definition document + * @param request the request, used to determine the location to transform to + */ + protected void transformLocations(XPathExpression xPathExpression, + Document definitionDocument, + HttpServletRequest request) { + Assert.notNull(xPathExpression, "'xPathExpression' must not be null"); + Assert.notNull(definitionDocument, "'definitionDocument' must not be null"); + Assert.notNull(request, "'request' must not be null"); + + List locationNodes = xPathExpression.evaluateAsNodeList(definitionDocument); + for (Node locationNode : locationNodes) { + if (locationNode instanceof Attr) { + Attr location = (Attr) locationNode; + if (StringUtils.hasLength(location.getValue())) { + String newLocation = transformLocation(location.getValue(), request); + if (logger.isDebugEnabled()) { + logger.debug("Transforming [" + location.getValue() + "] to [" + newLocation + "]"); + } + location.setValue(newLocation); + } + } + } + } + + /** + * Transform the given location string to reflect the given request. If the given location is a full url, the + * scheme, server name, and port are changed. If it is a relative url, the scheme, server name, and port are + * prepended. Can be overridden in subclasses to change this behavior. + *

+ * For instance, if the location attribute defined in the WSDL is {@code http://localhost:8080/context/services/myService}, + * and the request URI for the WSDL is {@code http://example.com:80/context/myService.wsdl}, the location + * will be changed to {@code http://example.com:80/context/services/myService}. + *

+ * If the location attribute defined in the WSDL is {@code /services/myService}, and the request URI for the + * WSDL is {@code http://example.com:8080/context/myService.wsdl}, the location will be changed to + * {@code http://example.com:8080/context/services/myService}. + *

+ * This method is only called when the {@code transformLocations} property is true. + */ + protected String transformLocation(String location, HttpServletRequest request) { + StringBuilder url = new StringBuilder(request.getScheme()); + url.append("://").append(request.getServerName()).append(':').append(request.getServerPort()); + if (location.startsWith("/")) { + // a relative path, prepend the context path + url.append(request.getContextPath()).append(location); + return url.toString(); + } + else { + int idx = location.indexOf("://"); + if (idx != -1) { + // a full url + idx = location.indexOf('/', idx + 3); + if (idx != -1) { + String path = location.substring(idx); + url.append(path); + return url.toString(); + } + } + } + // unknown location, return the original + return location; + } +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java b/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java index a50893d0..aec08d7b 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java +++ b/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java @@ -17,7 +17,6 @@ package org.springframework.ws.transport.http; import java.util.HashMap; -import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -28,19 +27,13 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.ModelAndView; import org.springframework.ws.wsdl.WsdlDefinition; -import org.springframework.xml.transform.TransformerObjectSupport; import org.springframework.xml.xpath.XPathExpression; import org.springframework.xml.xpath.XPathExpressionFactory; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.w3c.dom.Attr; import org.w3c.dom.Document; -import org.w3c.dom.Node; /** * Adapter to use the {@code WsdlDefinition} interface with the generic {@code DispatcherServlet}. @@ -73,7 +66,7 @@ import org.w3c.dom.Node; * @see #transformLocation(String,javax.servlet.http.HttpServletRequest) * @since 1.0.0 */ -public class WsdlDefinitionHandlerAdapter extends TransformerObjectSupport implements HandlerAdapter, InitializingBean { +public class WsdlDefinitionHandlerAdapter extends LocationTransformerObjectSupport implements HandlerAdapter, InitializingBean { /** Default XPath expression used for extracting all {@code location} attributes from the WSDL definition. */ public static final String DEFAULT_LOCATION_EXPRESSION = "//@location"; @@ -83,8 +76,6 @@ public class WsdlDefinitionHandlerAdapter extends TransformerObjectSupport imple private static final String CONTENT_TYPE = "text/xml"; - private static final Log logger = LogFactory.getLog(WsdlDefinitionHandlerAdapter.class); - private Map expressionNamespaces = new HashMap(); private String locationExpression = DEFAULT_LOCATION_EXPRESSION; @@ -192,7 +183,7 @@ public class WsdlDefinitionHandlerAdapter extends TransformerObjectSupport imple * @see #transformLocation(String,javax.servlet.http.HttpServletRequest) */ protected void transformLocations(Document definitionDocument, HttpServletRequest request) throws Exception { - transformLocationsInternal(locationXPathExpression, definitionDocument, request); + transformLocations(locationXPathExpression, definitionDocument, request); } /** @@ -207,63 +198,7 @@ public class WsdlDefinitionHandlerAdapter extends TransformerObjectSupport imple * @see #transformLocation(String,javax.servlet.http.HttpServletRequest) */ protected void transformSchemaLocations(Document definitionDocument, HttpServletRequest request) throws Exception { - transformLocationsInternal(schemaLocationXPathExpression, definitionDocument, request); + transformLocations(schemaLocationXPathExpression, definitionDocument, request); } - private void transformLocationsInternal(XPathExpression xPathExpression, - Document definitionDocument, - HttpServletRequest request) throws Exception { - List locationNodes = xPathExpression.evaluateAsNodeList(definitionDocument); - for (Node locationNode : locationNodes) { - if (locationNode instanceof Attr) { - Attr location = (Attr) locationNode; - if (StringUtils.hasLength(location.getValue())) { - String newLocation = transformLocation(location.getValue(), request); - if (logger.isDebugEnabled()) { - logger.debug("Transforming [" + location.getValue() + "] to [" + newLocation + "]"); - } - location.setValue(newLocation); - } - } - } - } - - /** - * Transform the given location string to reflect the given request. If the given location is a full url, the - * scheme, server name, and port are changed. If it is a relative url, the scheme, server name, and port are - * prepended. Can be overridden in subclasses to change this behavior. - *

- * For instance, if the location attribute defined in the WSDL is {@code http://localhost:8080/context/services/myService}, - * and the request URI for the WSDL is {@code http://example.com:80/context/myService.wsdl}, the location - * will be changed to {@code http://example.com:80/context/services/myService}. - *

- * If the location attribute defined in the WSDL is {@code /services/myService}, and the request URI for the - * WSDL is {@code http://example.com:8080/context/myService.wsdl}, the location will be changed to - * {@code http://example.com:8080/context/services/myService}. - *

- * This method is only called when the {@code transformLocations} property is true. - */ - protected String transformLocation(String location, HttpServletRequest request) { - StringBuilder url = new StringBuilder(request.getScheme()); - url.append("://").append(request.getServerName()).append(':').append(request.getServerPort()); - if (location.startsWith("/")) { - // a relative path, prepend the context path - url.append(request.getContextPath()).append(location); - return url.toString(); - } - else { - int idx = location.indexOf("://"); - if (idx != -1) { - // a full url - idx = location.indexOf('/', idx + 3); - if (idx != -1) { - String path = location.substring(idx); - url.append(path); - return url.toString(); - } - } - } - // unknown location, return the original - return location; - } } diff --git a/core/src/main/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapter.java b/core/src/main/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapter.java index 4f1a6d9f..e8c84cb2 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapter.java +++ b/core/src/main/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapter.java @@ -1,11 +1,11 @@ /* - * Copyright 2005-2010 the original author or authors. + * Copyright 2005-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,17 +16,25 @@ package org.springframework.ws.transport.http; +import java.util.HashMap; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Source; import javax.xml.transform.Transformer; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.springframework.beans.factory.InitializingBean; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.ModelAndView; -import org.springframework.xml.transform.TransformerObjectSupport; +import org.springframework.xml.xpath.XPathExpression; +import org.springframework.xml.xpath.XPathExpressionFactory; import org.springframework.xml.xsd.XsdSchema; +import org.w3c.dom.Document; + /** * Adapter to use the {@link XsdSchema} interface with the generic DispatcherServlet. *

@@ -38,12 +46,39 @@ import org.springframework.xml.xsd.XsdSchema; * @see #getSchemaSource(XsdSchema) * @since 1.5.3 */ -public class XsdSchemaHandlerAdapter extends TransformerObjectSupport implements HandlerAdapter { +public class XsdSchemaHandlerAdapter extends LocationTransformerObjectSupport + implements HandlerAdapter, InitializingBean { + + /** + * Default XPath expression used for extracting all {@code schemaLocation} attributes from the WSDL definition. + */ + public static final String DEFAULT_SCHEMA_LOCATION_EXPRESSION = "//@schemaLocation"; private static final String CONTENT_TYPE = "text/xml"; - public boolean supports(Object handler) { - return handler instanceof XsdSchema; + private Map expressionNamespaces = new HashMap(); + + private String schemaLocationExpression = DEFAULT_SCHEMA_LOCATION_EXPRESSION; + + private XPathExpression schemaLocationXPathExpression; + + private boolean transformSchemaLocations = false; + + /** + * Sets the XPath expression used for extracting the {@code schemaLocation} attributes from the WSDL 1.1 definition. + *

+ * Defaults to {@code DEFAULT_SCHEMA_LOCATION_EXPRESSION}. + */ + public void setSchemaLocationExpression(String schemaLocationExpression) { + this.schemaLocationExpression = schemaLocationExpression; + } + + /** + * Sets whether relative address schema locations in the WSDL are to be transformed using the request URI of the + * incoming {@code HttpServletRequest}. Defaults to {@code false}. + */ + public void setTransformSchemaLocations(boolean transformSchemaLocations) { + this.transformSchemaLocations = transformSchemaLocations; } public long getLastModified(HttpServletRequest request, Object handler) { @@ -54,9 +89,18 @@ public class XsdSchemaHandlerAdapter extends TransformerObjectSupport implements public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (HttpTransportConstants.METHOD_GET.equals(request.getMethod())) { - response.setContentType(CONTENT_TYPE); Transformer transformer = createTransformer(); Source schemaSource = getSchemaSource((XsdSchema) handler); + + if (transformSchemaLocations) { + DOMResult domResult = new DOMResult(); + transformer.transform(schemaSource, domResult); + Document schemaDocument = (Document) domResult.getNode(); + transformSchemaLocations(schemaDocument, request); + schemaSource = new DOMSource(schemaDocument); + } + + response.setContentType(CONTENT_TYPE); StreamResult responseResult = new StreamResult(response.getOutputStream()); transformer.transform(schemaSource, responseResult); } @@ -66,6 +110,15 @@ public class XsdSchemaHandlerAdapter extends TransformerObjectSupport implements return null; } + public boolean supports(Object handler) { + return handler instanceof XsdSchema; + } + + public void afterPropertiesSet() throws Exception { + schemaLocationXPathExpression = + XPathExpressionFactory.createXPathExpression(schemaLocationExpression, expressionNamespaces); + } + /** * Returns the {@link Source} of the given schema. Allows for post-processing and transformation of the schema in * sub-classes. @@ -80,4 +133,19 @@ public class XsdSchemaHandlerAdapter extends TransformerObjectSupport implements return schema.getSource(); } + /** + * Transforms all {@code schemaLocation} attributes to reflect the server name given {@code HttpServletRequest}. + * Determines the suitable attributes by evaluating the defined XPath expression, and delegates to {@code + * transformLocation} to do the transformation for all attributes that match. + *

+ * This method is only called when the {@code transformSchemaLocations} property is true. + * + * @see #setSchemaLocationExpression(String) + * @see #transformLocation(String, javax.servlet.http.HttpServletRequest) + */ + protected void transformSchemaLocations(Document definitionDocument, HttpServletRequest request) throws Exception { + transformLocations(schemaLocationXPathExpression, definitionDocument, request); + } + + } \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapterTest.java b/core/src/test/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapterTest.java index efed9266..95cfb21a 100644 --- a/core/src/test/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapterTest.java +++ b/core/src/test/java/org/springframework/ws/transport/http/XsdSchemaHandlerAdapterTest.java @@ -1,11 +1,11 @@ /* - * Copyright 2005-2010 the original author or authors. + * Copyright 2005-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,7 +16,11 @@ package org.springframework.ws.transport.http; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -25,11 +29,12 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.util.FileCopyUtils; import org.springframework.xml.xsd.SimpleXsdSchema; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.w3c.dom.Document; import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; +import static org.junit.Assert.assertEquals; public class XsdSchemaHandlerAdapterTest { @@ -42,21 +47,22 @@ public class XsdSchemaHandlerAdapterTest { @Before public void setUp() throws Exception { adapter = new XsdSchemaHandlerAdapter(); + adapter.afterPropertiesSet(); request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); } @Test - public void testGetLastModified() throws Exception { + public void getLastModified() throws Exception { Resource single = new ClassPathResource("single.xsd", getClass()); SimpleXsdSchema schema = new SimpleXsdSchema(single); schema.afterPropertiesSet(); long lastModified = single.getFile().lastModified(); - Assert.assertEquals("Invalid last modified", lastModified, adapter.getLastModified(null, schema)); + assertEquals("Invalid last modified", lastModified, adapter.getLastModified(null, schema)); } @Test - public void testHandleGet() throws Exception { + public void handleGet() throws Exception { request.setMethod(HttpTransportConstants.METHOD_GET); Resource single = new ClassPathResource("single.xsd", getClass()); SimpleXsdSchema schema = new SimpleXsdSchema(single); @@ -67,10 +73,40 @@ public class XsdSchemaHandlerAdapterTest { } @Test - public void testHandleNonGet() throws Exception { + public void handleNonGet() throws Exception { request.setMethod(HttpTransportConstants.METHOD_POST); adapter.handle(request, response, null); - Assert.assertEquals("METHOD_NOT_ALLOWED expected", HttpServletResponse.SC_METHOD_NOT_ALLOWED, - response.getStatus()); + assertEquals("METHOD_NOT_ALLOWED expected", HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus()); } + + @Test + public void handleGetWithTransformLocation() throws Exception { + adapter.setTransformSchemaLocations(true); + + request.setMethod(HttpTransportConstants.METHOD_GET); + request.setScheme("http"); + request.setServerName("example.com"); + request.setServerPort(80); + request.setContextPath("/context"); + request.setServletPath("/service.xsd"); + request.setPathInfo(null); + request.setRequestURI("/context/service.xsd"); + + Resource importing = new ClassPathResource("importing-input.xsd", getClass()); + SimpleXsdSchema schema = new SimpleXsdSchema(importing); + schema.afterPropertiesSet(); + + adapter.handle(request, response, schema); + + InputStream inputStream = new ByteArrayInputStream(response.getContentAsByteArray()); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document resultingDocument = documentBuilder.parse(inputStream); + + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document expectedDocument = documentBuilder.parse(getClass().getResourceAsStream("importing-expected.xsd")); + assertXMLEqual("Invalid WSDL returned", expectedDocument, resultingDocument); + } + } \ No newline at end of file diff --git a/core/src/test/resources/org/springframework/ws/transport/http/importing-expected.xsd b/core/src/test/resources/org/springframework/ws/transport/http/importing-expected.xsd new file mode 100644 index 00000000..072789a8 --- /dev/null +++ b/core/src/test/resources/org/springframework/ws/transport/http/importing-expected.xsd @@ -0,0 +1,7 @@ + + + + diff --git a/core/src/test/resources/org/springframework/ws/transport/http/importing-input.xsd b/core/src/test/resources/org/springframework/ws/transport/http/importing-input.xsd new file mode 100644 index 00000000..265ccd1c --- /dev/null +++ b/core/src/test/resources/org/springframework/ws/transport/http/importing-input.xsd @@ -0,0 +1,7 @@ + + + +