SWS-104 and SWS-162

This commit is contained in:
Arjen Poutsma
2007-09-17 12:39:49 +00:00
parent 4f3ca4be9e
commit fcdcf61260
5 changed files with 188 additions and 21 deletions

View File

@@ -40,6 +40,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition;
import org.springframework.xml.namespace.QNameUtils;
import org.springframework.xml.sax.SaxUtils;
import org.w3c.dom.Document;
@@ -51,7 +52,24 @@ import org.xml.sax.SAXException;
* Builds a <code>WsdlDefinition</code> with a SOAP 1.1 binding based on an XSD schema. This builder iterates over all
* <code>element</code>s found in the schema, and creates a <code>message</code> for those elements that end with the
* request or response suffix. It combines these messages into <code>operation</code>s, and builds a
* <code>portType</code> based on the operations. The schema itself is inlined in a <code>types</code> block.
* <code>portType</code> based on the operations.
* <p/>
* By default, the schema file is inlined in a <code>types</code> block. However, if the <code>schemaLocation</code>
* property is set, an XSD <code>import</code> is used instead. As such, the imported schema file can contain further
* imports, which will be resolved correctly in accordance with the schema location.
* <p/>
* Typically used within a {@link DynamicWsdl11Definition}, like so:
* <pre>
* &lt;bean id=&quot;airline&quot; class=&quot;org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition&quot;&gt;
* &lt;property name=&quot;builder&quot;&gt;
* &lt;bean class=&quot;org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder&quot;&gt;
* &lt;property name=&quot;schema&quot; value=&quot;/WEB-INF/airline.xsd&quot;/&gt;
* &lt;property name=&quot;portTypeName&quot; value=&quot;Airline&quot;/&gt;
* &lt;property name=&quot;locationUri&quot; value=&quot;http://localhost:8080/airline/services&quot;/&gt;
* &lt;/bean&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* </pre>
* <p/>
* Requires the <code>schema</code> and <code>portTypeName</code> properties to be set.
*
@@ -65,8 +83,11 @@ import org.xml.sax.SAXException;
public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jDefinitionBuilder
implements InitializingBean {
/** The schema namespace URI. */
private static final String SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
/** The schema qualified name. */
private static final QName SCHEMA_NAME = new QName("http://www.w3.org/2001/XMLSchema", "schema", "xsd");
/** The schema import qualified name. */
private static final QName IMPORT_NAME = new QName("http://www.w3.org/2001/XMLSchema", "import", "xsd");
/** The default suffix used to detect request elements in the schema. */
public static final String DEFAULT_REQUEST_SUFFIX = "Request";
@@ -88,6 +109,8 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
private Resource schema;
private String schemaLocation;
private Element schemaElement;
private String targetNamespace;
@@ -104,6 +127,14 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
private String faultSuffix = DEFAULT_FAULT_SUFFIX;
private String schemaTargetNamespace;
private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
static {
documentBuilderFactory.setNamespaceAware(true);
}
/**
* Sets the suffix used to detect request elements in the schema.
*
@@ -166,6 +197,15 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
this.schema = schema;
}
/**
* Sets the location of the schema to import. If this property is set, the <code>schema</code> element in the
* generated WSDL will only contain an <code>import</code>, referring to the value of this property.
*/
public void setSchemaLocation(String schemaLocation) {
Assert.hasLength(schemaLocation, "'schemaLocation' must not be empty");
this.schemaLocation = schemaLocation;
}
public final void afterPropertiesSet() throws IOException, ParserConfigurationException, SAXException {
Assert.notNull(schema, "schema is required");
Assert.notNull(portTypeName, "portTypeName is required");
@@ -173,18 +213,16 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
}
private void parseSchema() throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document schemaDocument = documentBuilder.parse(SaxUtils.createInputSource(schema));
schemaElement = schemaDocument.getDocumentElement();
Assert.isTrue("schema".equals(schemaElement.getLocalName()),
Assert.isTrue(SCHEMA_NAME.getLocalPart().equals(schemaElement.getLocalName()),
"schema document root element has invalid local name : [" + schemaElement.getLocalName() +
"] instead of [schema]");
Assert.isTrue(SCHEMA_NAMESPACE_URI.equals(schemaElement.getNamespaceURI()),
Assert.isTrue(SCHEMA_NAME.getNamespaceURI().equals(schemaElement.getNamespaceURI()),
"schema document root element has invalid namespace uri: [" + schemaElement.getNamespaceURI() +
"] instead of [" + SCHEMA_NAMESPACE_URI + "]");
String schemaTargetNamespace = getSchemaTargetNamespace();
"] instead of [" + SCHEMA_NAME.getNamespaceURI() + "]");
schemaTargetNamespace = getSchemaTargetNamespace();
Assert.hasLength(schemaTargetNamespace, "schema document has no targetNamespace");
if (!StringUtils.hasLength(targetNamespace)) {
targetNamespace = schemaTargetNamespace;
@@ -210,15 +248,39 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
}
/**
* Creates a <code>Types</code> object that is populated with the types found in the schema.
* Creates a {@link Types} object containing a {@link Schema}. By default, the schema set by the <code>schema</code>
* property will be inlined into this <code>type</code>. If the <code>schemaLocation</code> is set, </code>object
* that is populated with the types found in the schema.
*
* @param definition the WSDL4J <code>Definition</code>
* @throws WSDLException in case of errors
*/
protected void buildTypes(Definition definition) throws WSDLException {
Types types = definition.createTypes();
Schema schema = (Schema) createExtension(Types.class, QNameUtils.getQNameForNode(schemaElement));
schema.setElement(schemaElement);
Schema schema = null;
if (!StringUtils.hasLength(schemaLocation)) {
schema = (Schema) createExtension(Types.class, QNameUtils.getQNameForNode(schemaElement));
schema.setElement(schemaElement);
}
else {
schema = (Schema) createExtension(Types.class, SCHEMA_NAME);
Document document = null;
try {
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.newDocument();
}
catch (ParserConfigurationException ex) {
throw new WSDLException(WSDLException.PARSER_ERROR, "Could not create DocumentBuilder", ex);
}
Element importingSchemaElement =
document.createElementNS(SCHEMA_NAME.getNamespaceURI(), QNameUtils.toQualifiedName(SCHEMA_NAME));
schema.setElement(importingSchemaElement);
Element importElement =
document.createElementNS(IMPORT_NAME.getNamespaceURI(), QNameUtils.toQualifiedName(IMPORT_NAME));
importingSchemaElement.appendChild(importElement);
importElement.setAttribute("namespace", schemaTargetNamespace);
importElement.setAttribute("schemaLocation", schemaLocation);
}
types.addExtensibilityElement(schema);
definition.setTypes(types);
}
@@ -234,7 +296,7 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
* @see #isFaultMessage(javax.xml.namespace.QName)
*/
protected void buildMessages(Definition definition) throws WSDLException {
NodeList elements = schemaElement.getElementsByTagNameNS(SCHEMA_NAMESPACE_URI, "element");
NodeList elements = schemaElement.getElementsByTagNameNS(SCHEMA_NAME.getNamespaceURI(), "element");
for (int i = 0; i < elements.getLength(); i++) {
Element element = (Element) elements.item(i);
QName elementName = getSchemaElementName(element);
@@ -336,7 +398,6 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jD
portType.setQName(new QName(targetNamespace, portTypeName));
}
/** @noinspection UnnecessaryLocalVariable */
private void createOperations(Definition definition, PortType portType) throws WSDLException {
for (Iterator messageIterator = definition.getMessages().values().iterator(); messageIterator.hasNext();) {
Message message = (Message) messageIterator.next();

View File

@@ -55,7 +55,7 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilderTest extends XMLTestCase {
}
}
public void testBuilder() throws Exception {
public void testInline() throws Exception {
builder.buildDefinition();
builder.buildDefinition();
builder.buildImports();
@@ -72,7 +72,30 @@ public class XsdBasedSoap11Wsdl4jDefinitionBuilderTest extends XMLTestCase {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document expected = documentBuilder.parse(getClass().getResourceAsStream("expected.wsdl"));
Document expected = documentBuilder.parse(getClass().getResourceAsStream("inline.wsdl"));
XMLUnit.setIgnoreWhitespace(true);
assertXMLEqual("Invalid WSDL built", expected, result);
}
public void testImport() throws Exception {
builder.setSchemaLocation("schema.xsd");
builder.buildDefinition();
builder.buildDefinition();
builder.buildImports();
builder.buildTypes();
builder.buildMessages();
builder.buildPortTypes();
builder.buildBindings();
builder.buildServices();
Wsdl11Definition definition = builder.getDefinition();
DOMResult domResult = new DOMResult();
transformer.transform(definition.getSource(), domResult);
Document result = (Document) domResult.getNode();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document expected = documentBuilder.parse(getClass().getResourceAsStream("import.wsdl"));
XMLUnit.setIgnoreWhitespace(true);
assertXMLEqual("Invalid WSDL built", expected, result);
}

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:schema="http://www.springframework.org/spring-ws/samples/airline/schemas"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://www.springframework.org/spring-ws/wsdl/definitions"
targetNamespace="http://www.springframework.org/spring-ws/wsdl/definitions">
<wsdl:types>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://www.springframework.org/spring-ws/samples/airline/schemas"
schemaLocation="schema.xsd"/>
</schema>
</wsdl:types>
<wsdl:message name="GetFrequentFlyerMileageResponse">
<wsdl:part element="schema:GetFrequentFlyerMileageResponse" name="GetFrequentFlyerMileageResponse"/>
</wsdl:message>
<wsdl:message name="BookFlightResponse">
<wsdl:part element="schema:BookFlightResponse" name="BookFlightResponse"/>
</wsdl:message>
<wsdl:message name="GetFlightsRequest">
<wsdl:part element="schema:GetFlightsRequest" name="GetFlightsRequest"/>
</wsdl:message>
<wsdl:message name="GetFlightsFault">
<wsdl:part element="schema:GetFlightsFault" name="GetFlightsFault"/>
</wsdl:message>
<wsdl:message name="GetFrequentFlyerMileageRequest">
<wsdl:part element="schema:GetFrequentFlyerMileageRequest" name="GetFrequentFlyerMileageRequest"/>
</wsdl:message>
<wsdl:message name="GetFlightsResponse">
<wsdl:part element="schema:GetFlightsResponse" name="GetFlightsResponse"/>
</wsdl:message>
<wsdl:message name="BookFlightRequest">
<wsdl:part element="schema:BookFlightRequest" name="BookFlightRequest"/>
</wsdl:message>
<wsdl:portType name="Airline">
<wsdl:operation name="GetFlights">
<wsdl:input message="tns:GetFlightsRequest" name="GetFlightsRequest"/>
<wsdl:output message="tns:GetFlightsResponse" name="GetFlightsResponse"/>
<wsdl:fault message="tns:GetFlightsFault" name="GetFlightsFault"/>
</wsdl:operation>
<wsdl:operation name="GetFrequentFlyerMileage">
<wsdl:input message="tns:GetFrequentFlyerMileageRequest" name="GetFrequentFlyerMileageRequest"/>
<wsdl:output message="tns:GetFrequentFlyerMileageResponse" name="GetFrequentFlyerMileageResponse"/>
</wsdl:operation>
<wsdl:operation name="BookFlight">
<wsdl:input message="tns:BookFlightRequest" name="BookFlightRequest"/>
<wsdl:output message="tns:BookFlightResponse" name="BookFlightResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="AirlineBinding" type="tns:Airline">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="GetFlights">
<soap:operation soapAction=""/>
<wsdl:input name="GetFlightsRequest">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="GetFlightsResponse">
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="GetFlightsFault">
<soap:fault name="GetFlightsFault" use="literal"/>
</wsdl:fault>
</wsdl:operation>
<wsdl:operation name="GetFrequentFlyerMileage">
<soap:operation soapAction=""/>
<wsdl:input name="GetFrequentFlyerMileageRequest">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="GetFrequentFlyerMileageResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="BookFlight">
<soap:operation soapAction=""/>
<wsdl:input name="BookFlightRequest">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="BookFlightResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="AirlineService">
<wsdl:port binding="tns:AirlineBinding" name="AirlinePort">
<soap:address location="http://localhost:8080/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

View File

@@ -41,11 +41,7 @@ public abstract class TransformerObjectSupport {
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
private static TransformerFactory transformerFactory;
static {
transformerFactory = TransformerFactory.newInstance();
}
private static TransformerFactory transformerFactory = TransformerFactory.newInstance();
/** Returns the <code>TransformerFactory</code>. */
protected TransformerFactory getTransformerFactory() {