SWS-797 - XsdSchemaHandlerAdapter does not transform schema locations

This commit is contained in:
Arjen Poutsma
2012-09-25 13:29:24 +00:00
parent d5107c5b00
commit 26f0f5a742
6 changed files with 248 additions and 84 deletions

View File

@@ -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<Node> 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.
* <p/>
* 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}.
* <p/>
* 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}.
* <p/>
* 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;
}
}

View File

@@ -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<String, String> expressionNamespaces = new HashMap<String, String>();
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<Node> 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.
* <p/>
* 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}.
* <p/>
* 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}.
* <p/>
* 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;
}
}

View File

@@ -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 <code>DispatcherServlet</code>.
* <p/>
@@ -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<String, String> expressionNamespaces = new HashMap<String, String>();
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.
* <p/>
* 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.
* <p/>
* 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);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified"
elementFormDefault="qualified" targetNamespace="http://www.springframework.org/spring-ws/samples/echo">
<import namespace="http://www.springframework.org/spring-ws/samples/echo/imported"
schemaLocation="http://example.com:80/echo/services/imported.xsd"/>
</schema>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified"
elementFormDefault="qualified" targetNamespace="http://www.springframework.org/spring-ws/samples/echo">
<import namespace="http://www.springframework.org/spring-ws/samples/echo/imported"
schemaLocation="http://localhost:8080/echo/services/imported.xsd"/>
</schema>