diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilder.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilder.java index d2164f75..37bdd637 100644 --- a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilder.java +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilder.java @@ -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 WsdlDefinition with a SOAP 1.1 binding based on an XSD schema. This builder iterates over all * elements found in the schema, and creates a message for those elements that end with the * request or response suffix. It combines these messages into operations, and builds a - * portType based on the operations. The schema itself is inlined in a types block. + * portType based on the operations. + *

+ * By default, the schema file is inlined in a types block. However, if the schemaLocation + * property is set, an XSD import is used instead. As such, the imported schema file can contain further + * imports, which will be resolved correctly in accordance with the schema location. + *

+ * Typically used within a {@link DynamicWsdl11Definition}, like so: + *

+ * <bean id="airline" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
+ *   <property name="builder">
+ *     <bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder">
+ *     <property name="schema" value="/WEB-INF/airline.xsd"/>
+ *     <property name="portTypeName" value="Airline"/>
+ *     <property name="locationUri" value="http://localhost:8080/airline/services"/>
+ *     </bean>
+ *   </property>
+ * </bean>
+ * 
*

* Requires the schema and portTypeName 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 schema element in the + * generated WSDL will only contain an import, 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 Types 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 schema + * property will be inlined into this type. If the schemaLocation is set, object + * that is populated with the types found in the schema. * * @param definition the WSDL4J Definition * @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(); diff --git a/core/src/test/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilderTest.java b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilderTest.java index 49c30b28..4c676727 100644 --- a/core/src/test/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilderTest.java +++ b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilderTest.java @@ -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); } diff --git a/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/import.wsdl b/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/import.wsdl new file mode 100644 index 00000000..0fc12e14 --- /dev/null +++ b/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/import.wsdl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/expected.wsdl b/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/inline.wsdl similarity index 100% rename from core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/expected.wsdl rename to core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/inline.wsdl diff --git a/xml/src/main/java/org/springframework/xml/transform/TransformerObjectSupport.java b/xml/src/main/java/org/springframework/xml/transform/TransformerObjectSupport.java index 41884613..ce1cc670 100644 --- a/xml/src/main/java/org/springframework/xml/transform/TransformerObjectSupport.java +++ b/xml/src/main/java/org/springframework/xml/transform/TransformerObjectSupport.java @@ -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 TransformerFactory. */ protected TransformerFactory getTransformerFactory() {