From 6f0773bee53166fd83e44c148fbefeb4ff41e7c6 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 1 Dec 2006 11:42:49 +0000 Subject: [PATCH] Implemented #SWS-67: Dynamically create WSDL based on schema. Using it in the echo sample. --- .../http/MessageDispatcherServlet.java | 100 +++-- .../wsdl/wsdl11/DynamicWsdl11Definition.java | 84 ++++ .../wsdl/wsdl11/SimpleWsdl11Definition.java | 2 +- .../wsdl11/Wsdl4jDefinitionException.java | 34 ++ ...bstractBindingWsdl4jDefinitionBuilder.java | 250 +++++++++++ ...AbstractSoap11Wsdl4jDefinitionBuilder.java | 241 +++++++++++ .../AbstractWsdl4jDefinitionBuilder.java | 195 +++++++++ .../builder/Wsdl11DefinitionBuilder.java | 89 ++++ ...XsdBasedSoap11Wsdl4jDefinitionBuilder.java | 404 ++++++++++++++++++ .../ws/wsdl/wsdl11/builder/package.html | 5 + .../ws/wsdl/wsdl11/package.html | 2 +- .../http/MessageDispatcherServletTest.java | 39 +- .../wsdl11/DynamicWsdl11DefinitionTest.java | 76 ++++ ...asedSoap11Wsdl4jDefinitionBuilderTest.java | 80 ++++ .../ws/wsdl/wsdl11/builder/expected.wsdl | 204 +++++++++ .../ws/wsdl/wsdl11/builder/schema.xsd | 118 +++++ .../src/main/webapp/{ => WEB-INF}/echo.xsd | 0 .../main/webapp/WEB-INF/spring-ws-servlet.xml | 21 +- samples/echo/src/main/webapp/WEB-INF/web.xml | 2 +- samples/echo/src/main/webapp/echo.wsdl | 48 --- 20 files changed, 1912 insertions(+), 82 deletions(-) create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11Definition.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl4jDefinitionException.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractBindingWsdl4jDefinitionBuilder.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractSoap11Wsdl4jDefinitionBuilder.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractWsdl4jDefinitionBuilder.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/Wsdl11DefinitionBuilder.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilder.java create mode 100644 core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/package.html create mode 100644 core/src/test/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11DefinitionTest.java create mode 100644 core/src/test/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilderTest.java create mode 100644 core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/expected.wsdl create mode 100644 core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/schema.xsd rename samples/echo/src/main/webapp/{ => WEB-INF}/echo.xsd (100%) mode change 100755 => 100644 delete mode 100644 samples/echo/src/main/webapp/echo.wsdl diff --git a/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java b/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java index f87b61dc..77f60941 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java +++ b/core/src/main/java/org/springframework/ws/transport/http/MessageDispatcherServlet.java @@ -22,11 +22,13 @@ import org.springframework.core.OrderComparator; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StringUtils; import org.springframework.web.servlet.FrameworkServlet; +import org.springframework.web.util.WebUtils; import org.springframework.ws.EndpointAdapter; import org.springframework.ws.EndpointExceptionResolver; import org.springframework.ws.EndpointMapping; import org.springframework.ws.MessageDispatcher; import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.wsdl.WsdlDefinition; /** * Servlet for simplified dispatching of Web service messages. Delegates to a MessageDispatcher and a @@ -74,14 +76,10 @@ public class MessageDispatcherServlet extends FrameworkServlet { */ public static final String ENDPOINT_MAPPING_BEAN_NAME = "endpointMapping"; - /** - * Well-known name for the WebServiceMessageFactory object in the bean factory for this namespace. - */ + /** Well-known name for the WebServiceMessageFactory object in the bean factory for this namespace. */ public static final String WEB_SERVICE_MESSAGE_FACTORY_BEAN_NAME = "messageFactory"; - /** - * Well-known name for the MessageDispatcher object in the bean factory for this namespace. - */ + /** Well-known name for the MessageDispatcher object in the bean factory for this namespace. */ public static final String MESSAGE_DISPATCHER_BEAN_NAME = "messageDispatcher"; /** @@ -90,33 +88,35 @@ public class MessageDispatcherServlet extends FrameworkServlet { */ private static final String DEFAULT_STRATEGIES_PATH = "MessageDispatcherServlet.properties"; + /** Suffix of a WSDL request uri. */ + private static final String WSDL_SUFFIX_NAME = ".wsdl"; + private static final Properties defaultStrategies = new Properties(); - /** - * Detect all EndpointAdapters or just expect "endpointAdapter" bean? - */ + /** Detect all EndpointAdapters or just expect "endpointAdapter" bean? */ private boolean detectAllEndpointAdapters = true; - /** - * Detect all EndpointExceptionResolvers or just expect "endpointExceptionResolver" bean? - */ + /** Detect all EndpointExceptionResolvers or just expect "endpointExceptionResolver" bean? */ private boolean detectAllEndpointExceptionResolvers = true; - /** - * Detect all EndpointMappings or just expect "endpointMapping" bean? - */ + /** Detect all EndpointMappings or just expect "endpointMapping" bean? */ private boolean detectAllEndpointMappings = true; - /** - * The MessageEndpointHandlerAdapter used by this servlet. - */ - private MessageEndpointHandlerAdapter handlerAdapter = new MessageEndpointHandlerAdapter(); + /** Detect all WsdlDefinitions? */ + private boolean detectAllWsdlDefinitions = true; - /** - * The MessageDispatcher used by this servlet. - */ + /** The MessageEndpointHandlerAdapter used by this servlet. */ + private MessageEndpointHandlerAdapter messageEndpointHandlerAdapter = new MessageEndpointHandlerAdapter(); + + /** The WsdlDefinitionHandlerAdapter used by this servlet. */ + private WsdlDefinitionHandlerAdapter wsdlDefinitionHandlerAdapter = new WsdlDefinitionHandlerAdapter(); + + /** The MessageDispatcher used by this servlet. */ private MessageDispatcher messageDispatcher; + /** Keys are beans names, values are WsdlDefinitions. */ + private Map wsdlDefinitions; + static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized @@ -136,9 +136,7 @@ public class MessageDispatcherServlet extends FrameworkServlet { } } - /** - * Returns the MessageDispatcher used by this servlet. - */ + /** Returns the MessageDispatcher used by this servlet. */ protected MessageDispatcher getMessageDispatcher() { return messageDispatcher; } @@ -182,18 +180,56 @@ public class MessageDispatcherServlet extends FrameworkServlet { this.detectAllEndpointMappings = detectAllEndpointMappings; } + /** + * Set whether to detect all WsdlDefinition beans in this servlet's context. + *

+ * Default is true. + * + * @see org.springframework.ws.wsdl.WsdlDefinition + */ + public void setDetectAllWsdlDefinitions(boolean detectAllWsdlDefinitions) { + this.detectAllWsdlDefinitions = detectAllWsdlDefinitions; + } + protected long getLastModified(HttpServletRequest req) { - return handlerAdapter.getLastModified(req, messageDispatcher); + return messageEndpointHandlerAdapter.getLastModified(req, messageDispatcher); } protected void initFrameworkServlet() throws ServletException, BeansException { initWebServiceMessageFactory(); initMessageDispatcher(); + initWsdlDefinitions(); } protected void doService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { - handlerAdapter.handle(httpServletRequest, httpServletResponse, messageDispatcher); + WsdlDefinition definition = getWsdlDefinition(httpServletRequest); + if (definition != null) { + wsdlDefinitionHandlerAdapter.handle(httpServletRequest, httpServletResponse, definition); + } + else { + messageEndpointHandlerAdapter.handle(httpServletRequest, httpServletResponse, messageDispatcher); + } + } + + /** + * Determines the WsdlDefinition for a given request, or null if none is found. + *

+ * Default implementation checks whether the request method is GET, whether the request uri ends with ".wsdl", and + * if there is a WsdlDefinition with the same name as the filename in the request uri. + * + * @param request the HttpServletRequest + * @return a definition, or null + * @throws Exception in case of errors + */ + protected WsdlDefinition getWsdlDefinition(HttpServletRequest request) throws Exception { + if ("GET".equals(request.getMethod()) && request.getRequestURI().endsWith(WSDL_SUFFIX_NAME)) { + String fileName = WebUtils.extractFilenameFromUrlPath(request.getRequestURI()); + return (WsdlDefinition) wsdlDefinitions.get(fileName); + } + else { + return null; + } } /** @@ -365,7 +401,7 @@ public class MessageDispatcherServlet extends FrameworkServlet { } } } - handlerAdapter.setMessageFactory(messageFactory); + messageEndpointHandlerAdapter.setMessageFactory(messageFactory); } private void initMessageDispatcher() { @@ -387,4 +423,12 @@ public class MessageDispatcherServlet extends FrameworkServlet { initEndpointExceptionResolvers(); } } + + private void initWsdlDefinitions() { + if (detectAllWsdlDefinitions) { + // Find all WsdlDefinitions in the ApplicationContext, incuding ancestor contexts. + wsdlDefinitions = BeanFactoryUtils + .beansOfTypeIncludingAncestors(getWebApplicationContext(), WsdlDefinition.class, true, false); + } + } } diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11Definition.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11Definition.java new file mode 100644 index 00000000..b144ac51 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11Definition.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 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.wsdl.wsdl11; + +import javax.xml.transform.Source; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.wsdl.wsdl11.builder.Wsdl11DefinitionBuilder; + +/** + * Wsdl11Definition that creates a WSDL definition at runtime, using a {@link Wsdl11DefinitionBuilder}. Can + * be instructed to build abstract or concrete parts of the definition, by setting the buildAbstractPart + * and buildConcretePart properties. + * + * @author Arjen Poutsma + * @see #setBuilder(org.springframework.ws.wsdl.wsdl11.builder.Wsdl11DefinitionBuilder) + * @see #setBuildAbstractPart(boolean) + * @see #setBuildConcretePart(boolean) + */ +public class DynamicWsdl11Definition implements Wsdl11Definition, InitializingBean { + + private Wsdl11DefinitionBuilder builder; + + private Wsdl11Definition definition; + + private boolean buildAbstractPart = true; + + private boolean buildConcretePart = true; + + public void setBuilder(Wsdl11DefinitionBuilder builder) { + this.builder = builder; + } + + /** + * Indicates whether the built definition should contain an abstract part. If set to true (the default) + * the definition will contain types, messages, and portTypes; if false, it will not. + */ + public void setBuildAbstractPart(boolean buildAbstractPart) { + this.buildAbstractPart = buildAbstractPart; + } + + /** + * Indicates whether the built definition should contain an concrete part. If set to true (the default) + * the definition will contain bindings, and services; if false, it will not. + */ + public void setBuildConcretePart(boolean buildConcretePart) { + this.buildConcretePart = buildConcretePart; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(builder, "builder is required"); + builder.buildDefinition(); + builder.buildImports(); + if (buildAbstractPart) { + builder.buildTypes(); + builder.buildMessages(); + builder.buildPortTypes(); + } + if (buildConcretePart) { + builder.buildBindings(); + builder.buildServices(); + } + definition = builder.getDefinition(); + } + + public Source getSource() { + return definition.getSource(); + } +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java index ef6ea872..e819a0ef 100644 --- a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java @@ -52,7 +52,7 @@ public class SimpleWsdl11Definition implements Wsdl11Definition, InitializingBea } public void afterPropertiesSet() throws Exception { - Assert.notNull(wsdlResource, "wsdlResource is required"); + Assert.notNull(wsdlResource, "wsdl is required"); Assert.isTrue(wsdlResource.exists(), "wsdl \"" + wsdlResource + "\" does not exit"); } diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl4jDefinitionException.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl4jDefinitionException.java new file mode 100644 index 00000000..c88ef600 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl4jDefinitionException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2006 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.wsdl.wsdl11; + +import javax.wsdl.WSDLException; + +import org.springframework.ws.wsdl.WsdlDefinitionException; + +/** + * Subclass of WsdlDefinitionException that wraps WSDLExceptions. + * + * @author Arjen Poutsma + */ +public class Wsdl4jDefinitionException extends WsdlDefinitionException { + + public Wsdl4jDefinitionException(WSDLException ex) { + super(ex.getMessage(), ex); + } + +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractBindingWsdl4jDefinitionBuilder.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractBindingWsdl4jDefinitionBuilder.java new file mode 100644 index 00000000..a3de76b1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractBindingWsdl4jDefinitionBuilder.java @@ -0,0 +1,250 @@ +/* + * Copyright 2006 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.wsdl.wsdl11.builder; + +import java.util.Iterator; +import javax.wsdl.Binding; +import javax.wsdl.BindingFault; +import javax.wsdl.BindingInput; +import javax.wsdl.BindingOperation; +import javax.wsdl.BindingOutput; +import javax.wsdl.Definition; +import javax.wsdl.Fault; +import javax.wsdl.Input; +import javax.wsdl.Operation; +import javax.wsdl.Output; +import javax.wsdl.Port; +import javax.wsdl.PortType; +import javax.wsdl.Service; +import javax.wsdl.WSDLException; +import javax.xml.namespace.QName; + +import org.springframework.ws.wsdl.wsdl11.Wsdl4jDefinitionException; + +/** + * Abstract base class for Wsdl11DefinitionBuilder implementations that use WSDL4J and contain a concrete + * part. Creates a binding that matches any present portType, and a service containing + * ports that match the bindings. Lets subclasses populate these through template methods. + * + * @author Arjen Poutsma + */ +public abstract class AbstractBindingWsdl4jDefinitionBuilder extends AbstractWsdl4jDefinitionBuilder { + + /** The suffix used to create a binding name from a port type name. */ + private static final String BINDING_SUFFIX = "Binding"; + + /** The suffix used to create a binding name from a port type name. */ + private static final String PORT_SUFFIX = "Port"; + + /** + * Creates a Binding for each PortType in the definition, and calls + * populateBinding with it. Creates a BindingOperation for each Operation in + * the port type, a BindingInput for each Input in the operation, etc. + *

+ * Calls the various populate methods with the created WSDL4J objects. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + * @see javax.wsdl.Binding + * @see javax.wsdl.extensions.soap.SOAPBinding + * @see #populateBinding(javax.wsdl.Binding,javax.wsdl.PortType) + * @see javax.wsdl.BindingOperation + * @see #populateBindingOperation(javax.wsdl.BindingOperation,javax.wsdl.Operation) + * @see javax.wsdl.BindingInput + * @see #populateBindingInput(javax.wsdl.BindingInput,javax.wsdl.Input) + * @see javax.wsdl.BindingOutput + * @see #populateBindingOutput(javax.wsdl.BindingOutput,javax.wsdl.Output) + * @see javax.wsdl.BindingFault + * @see #populateBindingFault(javax.wsdl.BindingFault,javax.wsdl.Fault) + */ + public void buildBindings(Definition definition) throws WSDLException { + try { + for (Iterator iterator = definition.getPortTypes().values().iterator(); iterator.hasNext();) { + PortType portType = (PortType) iterator.next(); + Binding binding = definition.createBinding(); + binding.setPortType(portType); + populateBinding(binding, portType); + createBindingOperations(definition, binding, portType); + binding.setUndefined(false); + definition.addBinding(binding); + } + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Called after the Binding has been created, but before any sub-elements are added. Subclasses can + * implement this method to define the binding name, or add extensions to it. + *

+ * Default implementation sets the binding name to the port type name with the suffix Binding appended + * to it. + * + * @param binding the WSDL4J Binding + * @param portType the corresponding PortType + * @throws WSDLException in case of errors + */ + protected void populateBinding(Binding binding, PortType portType) throws WSDLException { + QName portTypeName = portType.getQName(); + if (portTypeName != null) { + binding.setQName(new QName(portTypeName.getNamespaceURI(), portTypeName.getLocalPart() + BINDING_SUFFIX)); + } + } + + private void createBindingOperations(Definition definition, Binding binding, PortType portType) + throws WSDLException { + for (Iterator operationIterator = portType.getOperations().iterator(); operationIterator.hasNext();) { + Operation operation = (Operation) operationIterator.next(); + BindingOperation bindingOperation = definition.createBindingOperation(); + bindingOperation.setOperation(operation); + populateBindingOperation(bindingOperation, operation); + if (operation.getInput() != null) { + BindingInput bindingInput = definition.createBindingInput(); + populateBindingInput(bindingInput, operation.getInput()); + bindingOperation.setBindingInput(bindingInput); + } + if (operation.getOutput() != null) { + BindingOutput bindingOutput = definition.createBindingOutput(); + populateBindingOutput(bindingOutput, operation.getOutput()); + bindingOperation.setBindingOutput(bindingOutput); + } + for (Iterator faultIterator = operation.getFaults().values().iterator(); faultIterator.hasNext();) { + Fault fault = (Fault) faultIterator.next(); + BindingFault bindingFault = definition.createBindingFault(); + populateBindingFault(bindingFault, fault); + bindingOperation.addBindingFault(bindingFault); + } + binding.addBindingOperation(bindingOperation); + } + } + + /** + * Called after the BindingOperation has been created, but before any sub-elements are added. + * Subclasses can implement this method to define the binding name, or add extensions to it. + *

+ * Default implementation sets the name of the binding operation to the name of the operation. + * + * @param bindingOperation the WSDL4J BindingOperation + * @param operation the corresponding WSDL4J Operation + * @throws WSDLException in case of errors + */ + protected void populateBindingOperation(BindingOperation bindingOperation, Operation operation) + throws WSDLException { + bindingOperation.setName(operation.getName()); + } + + /** + * Called after the BindingInput has been created. Subclasses can implement this method to define the + * name, or add extensions to it. + *

+ * Default implementation set the name of the binding input to the name of the input. + * + * @param bindingInput the WSDL4J BindingInput + * @param input the corresponding WSDL4J Input + * @throws WSDLException in case of errors + */ + protected void populateBindingInput(BindingInput bindingInput, Input input) throws WSDLException { + bindingInput.setName(input.getName()); + } + + /** + * Called after the BindingOutput has been created. Subclasses can implement this method to define the + * name, or add extensions to it. + *

+ * Default implementation set the name of the binding output to the name of the output. + * + * @param bindingOutput the WSDL4J BindingOutput + * @param output the corresponding WSDL4J Output + * @throws WSDLException in case of errors + */ + protected void populateBindingOutput(BindingOutput bindingOutput, Output output) throws WSDLException { + bindingOutput.setName(output.getName()); + } + + /** + * Called after the BindingFault has been created. Subclasses can implement this method to define the + * name, or add extensions to it. + *

+ * Default implementation set the name of the binding fault to the name of the fault. + * + * @param bindingFault the WSDL4J BindingFault + * @param fault the corresponding WSDL4J Fault + * @throws WSDLException in case of errors + */ + protected void populateBindingFault(BindingFault bindingFault, Fault fault) throws WSDLException { + bindingFault.setName(fault.getName()); + } + + /** + * Creates a single Service, and calls populateService() with it. Creates a corresponding + * Port for each Binding, which is passed to populatePort(). + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + * @see javax.wsdl.Service + * @see javax.wsdl.Port + * @see #populatePort(javax.wsdl.Port,javax.wsdl.Binding) + */ + public void buildServices(Definition definition) throws WSDLException { + Service service = definition.createService(); + populateService(service); + createPorts(definition, service); + definition.addService(service); + } + + /** + * Called after the Binding has been created, but before any sub-elements are added. Subclasses can + * implement this method to define the binding name, or add extensions to it. + *

+ * Default implementation is empty. + * + * @param service the WSDL4J Service + * @throws WSDLException in case of errors + */ + protected void populateService(Service service) throws WSDLException { + } + + private void createPorts(Definition definition, Service service) throws WSDLException { + for (Iterator iterator = definition.getBindings().values().iterator(); iterator.hasNext();) { + Binding binding = (Binding) iterator.next(); + Port port = definition.createPort(); + port.setBinding(binding); + populatePort(port, binding); + service.addPort(port); + } + } + + /** + * Called after the Port has been created, but before any sub-elements are added. Subclasses can + * implement this method to define the port name, or add extensions to it. + *

+ *

+ * Default implementation sets the port name to the port type name with the suffix Port appended to + * it. + * + * @param port the WSDL4J Port + * @param binding the corresponding WSDL4J Binding + * @throws WSDLException in case of errors + */ + protected void populatePort(Port port, Binding binding) throws WSDLException { + if (binding.getPortType() != null && binding.getPortType().getQName() != null) { + port.setName(binding.getPortType().getQName().getLocalPart() + PORT_SUFFIX); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractSoap11Wsdl4jDefinitionBuilder.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractSoap11Wsdl4jDefinitionBuilder.java new file mode 100644 index 00000000..810b33b1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractSoap11Wsdl4jDefinitionBuilder.java @@ -0,0 +1,241 @@ +/* + * Copyright 2006 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.wsdl.wsdl11.builder; + +import javax.wsdl.Binding; +import javax.wsdl.BindingFault; +import javax.wsdl.BindingInput; +import javax.wsdl.BindingOperation; +import javax.wsdl.BindingOutput; +import javax.wsdl.Definition; +import javax.wsdl.Fault; +import javax.wsdl.Input; +import javax.wsdl.Operation; +import javax.wsdl.Output; +import javax.wsdl.Port; +import javax.wsdl.PortType; +import javax.wsdl.WSDLException; +import javax.wsdl.extensions.ExtensibilityElement; +import javax.wsdl.extensions.soap.SOAPAddress; +import javax.wsdl.extensions.soap.SOAPBinding; +import javax.wsdl.extensions.soap.SOAPBody; +import javax.wsdl.extensions.soap.SOAPOperation; +import javax.xml.namespace.QName; + +/** + * Abstract base class for Wsdl11DefinitionBuilder implementations that use WSDL4J and contain a SOAP 1.1 + * binding. Requires the locationUri property to be set before use. + * + * @author Arjen Poutsma + * @see #setLocationUri(String) + */ +public abstract class AbstractSoap11Wsdl4jDefinitionBuilder extends AbstractBindingWsdl4jDefinitionBuilder { + + private static final String WSDL_SOAP_NAMESPACE_URI = "http://schemas.xmlsoap.org/wsdl/soap/"; + + private static final String WSDL_SOAP_PREFIX = "soap"; + + /** The default soap:binding transport attribute value. */ + public static final String DEFAULT_TRANSPORT_URI = "http://schemas.xmlsoap.org/soap/http"; + + private String transportUri = DEFAULT_TRANSPORT_URI; + + private String locationUri; + + /** + * Sets the value used for the soap:binding transport attribute value. + * + * @see javax.wsdl.extensions.soap.SOAPBinding#setTransportURI(String) + * @see #DEFAULT_TRANSPORT_URI + */ + public void setTransportUri(String transportUri) { + this.transportUri = transportUri; + } + + /** Sets the value used for the soap:address location attribute value. */ + public void setLocationUri(String locationUri) { + this.locationUri = locationUri; + } + + /** Adds the WSDL SOAP namespace to the definition. */ + protected void populateDefinition(Definition definition) throws WSDLException { + definition.addNamespace(WSDL_SOAP_PREFIX, WSDL_SOAP_NAMESPACE_URI); + } + + /** + * Calls populateBindingInternal(), creates SOAPBinding, and calls + * populateSoapBinding(). + * + * @param binding the WSDL4J Binding + * @param portType the corresponding PortType + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPBinding + * @see #populateSoapBinding(javax.wsdl.extensions.soap.SOAPBinding) + */ + protected void populateBinding(Binding binding, PortType portType) throws WSDLException { + super.populateBinding(binding, portType); + SOAPBinding soapBinding = (SOAPBinding) createSoapExtension(Binding.class, "binding"); + populateSoapBinding(soapBinding); + binding.addExtensibilityElement(soapBinding); + } + + /** + * Called after the SOAPBinding has been created. Default implementation sets the binding style to + * "document", and set the transport URI to the value set on this builder. Subclasses can override this + * behavior. + * + * @param soapBinding the WSDL4J SOAPBinding + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPBinding#setStyle(String) + * @see javax.wsdl.extensions.soap.SOAPBinding#setTransportURI(String) + * @see #setTransportUri(String) + * @see #DEFAULT_TRANSPORT_URI + */ + protected void populateSoapBinding(SOAPBinding soapBinding) throws WSDLException { + soapBinding.setStyle("document"); + soapBinding.setTransportURI(transportUri); + } + + /** + * Calls getBindingOperationName(), creates a SOAPOperation, and calls + * populateSoapOperation(). + * + * @param bindingOperation the WSDL4J BindingOperation + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPOperation + * @see #populateSoapOperation(javax.wsdl.extensions.soap.SOAPOperation) + */ + protected void populateBindingOperation(BindingOperation bindingOperation, Operation operation) + throws WSDLException { + super.populateBindingOperation(bindingOperation, operation); + SOAPOperation soapOperation = (SOAPOperation) createSoapExtension(BindingOperation.class, "operation"); + populateSoapOperation(soapOperation); + bindingOperation.addExtensibilityElement(soapOperation); + } + + /** + * Called after the SOAPOperation has been created. + *

+ * Default implementation set the SOAPAction uri to an empty string. + * + * @param soapOperation the WSDL4J SOAPOperation + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPOperation#setSoapActionURI(String) + */ + protected void populateSoapOperation(SOAPOperation soapOperation) throws WSDLException { + soapOperation.setSoapActionURI(""); + } + + /** + * Creates a SOAPBody, and calls populateSoapBody(). + * + * @param bindingInput the WSDL4J BindingInput + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPOperation + * @see #populateSoapBody(javax.wsdl.extensions.soap.SOAPBody) + */ + protected void populateBindingInput(BindingInput bindingInput, Input input) throws WSDLException { + super.populateBindingInput(bindingInput, input); + SOAPBody soapBody = (SOAPBody) createSoapExtension(BindingInput.class, "body"); + populateSoapBody(soapBody); + bindingInput.addExtensibilityElement(soapBody); + } + + /** + * Creates a SOAPBody, and calls populateSoapBody(). + * + * @param bindingOutput the WSDL4J BindingOutput + * @throws javax.wsdl.WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPOperation + * @see #populateSoapBody(javax.wsdl.extensions.soap.SOAPBody) + */ + protected void populateBindingOutput(BindingOutput bindingOutput, Output output) throws WSDLException { + super.populateBindingOutput(bindingOutput, output); + SOAPBody soapBody = (SOAPBody) createSoapExtension(BindingOutput.class, "body"); + populateSoapBody(soapBody); + bindingOutput.addExtensibilityElement(soapBody); + } + + /** + * Creates a SOAPBody, and calls populateSoapBody(). + * + * @param bindingFault the WSDL4J BindingFault + * @throws javax.wsdl.WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPOperation + * @see #populateSoapBody(javax.wsdl.extensions.soap.SOAPBody) + */ + protected void populateBindingFault(BindingFault bindingFault, Fault fault) throws WSDLException { + super.populateBindingFault(bindingFault, fault); + SOAPBody soapBody = (SOAPBody) createSoapExtension(BindingOutput.class, "body"); + populateSoapBody(soapBody); + bindingFault.addExtensibilityElement(soapBody); + } + + /** + * Called after the SOAPBody has been created. Default implementation sets the use style to + * "literal". Subclasses can override this behavior. + * + * @param soapBody the WSDL4J SOAPBody + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPBody#setUse(String) + */ + protected void populateSoapBody(SOAPBody soapBody) throws WSDLException { + soapBody.setUse("literal"); + } + + /** + * Creates a SOAPAddress, and calls populateSoapAddress(). + * + * @param port the WSDL4J Port + * @throws javax.wsdl.WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPAddress + * @see #populateSoapBody(javax.wsdl.extensions.soap.SOAPBody) + */ + protected void populatePort(Port port, Binding binding) throws WSDLException { + super.populatePort(port, binding); + SOAPAddress soapAddress = (SOAPAddress) createSoapExtension(Port.class, "address"); + populateSoapAddress(soapAddress); + port.addExtensibilityElement(soapAddress); + } + + /** + * Called after the SOAPAddress has been created. Default implementation sets the location URI to the + * value set on this builder. Subclasses can override this behavior. + * + * @param soapAddress the WSDL4J SOAPAddress + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.soap.SOAPAddress#setLocationURI(String) + * @see #setLocationUri(String) + */ + protected void populateSoapAddress(SOAPAddress soapAddress) throws WSDLException { + soapAddress.setLocationURI(locationUri); + } + + /** + * Creates a SOAP extensibility element. + * + * @param parentType a class object indicating where in the WSDL definition this extension will exist + * @param localName the local name of the extensibility element + * @return the extensibility element + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.ExtensionRegistry#createExtension(Class,javax.xml.namespace.QName) + */ + protected ExtensibilityElement createSoapExtension(Class parentType, String localName) throws WSDLException { + return createExtension(parentType, new QName(WSDL_SOAP_NAMESPACE_URI, localName)); + } + +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractWsdl4jDefinitionBuilder.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractWsdl4jDefinitionBuilder.java new file mode 100644 index 00000000..9c1e6afe --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/AbstractWsdl4jDefinitionBuilder.java @@ -0,0 +1,195 @@ +/* + * Copyright 2006 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.wsdl.wsdl11.builder; + +import javax.wsdl.Definition; +import javax.wsdl.WSDLException; +import javax.wsdl.extensions.ExtensibilityElement; +import javax.wsdl.extensions.ExtensionRegistry; +import javax.wsdl.factory.WSDLFactory; +import javax.xml.namespace.QName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ws.wsdl.WsdlDefinitionException; +import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition; +import org.springframework.ws.wsdl.wsdl11.Wsdl4jDefinition; +import org.springframework.ws.wsdl.wsdl11.Wsdl4jDefinitionException; + +/** + * Abstract base class for Wsdl11DefinitionBuilder implementations that use WSDL4J. Creates a base {@link + * Definition}, and passes that to subclass template methods. + * + * @author Arjen Poutsma + */ +public abstract class AbstractWsdl4jDefinitionBuilder implements Wsdl11DefinitionBuilder { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** WSDL4J extension registry. Lazily created in createExtension(). */ + private ExtensionRegistry extensionRegistry; + + /** The WSDL4J Definition created by buildDefinition(). */ + private Definition definition; + + public final void buildDefinition() throws WsdlDefinitionException { + try { + WSDLFactory wsdlFactory = WSDLFactory.newInstance(); + definition = wsdlFactory.newDefinition(); + populateDefinition(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Called after the Definition has been created, but before any sub-elements are added. Default + * implementation is empty. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + * @see #buildDefinition() + */ + protected void populateDefinition(Definition definition) throws WSDLException { + } + + public final void buildImports() throws WsdlDefinitionException { + try { + buildImports(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Adds imports to the definition. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + */ + protected abstract void buildImports(Definition definition) throws WSDLException; + + public final void buildTypes() throws WsdlDefinitionException { + try { + buildTypes(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Adds types to the definition. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + */ + protected abstract void buildTypes(Definition definition) throws WSDLException; + + public final void buildMessages() throws WsdlDefinitionException { + try { + buildMessages(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Adds messages to the definition. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + */ + protected abstract void buildMessages(Definition definition) throws WSDLException; + + public final void buildPortTypes() throws WsdlDefinitionException { + try { + buildPortTypes(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Adds port types to the definition. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + */ + protected abstract void buildPortTypes(Definition definition) throws WSDLException; + + public final void buildBindings() throws WsdlDefinitionException { + try { + buildBindings(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Adds bindings to the definition. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + */ + protected abstract void buildBindings(Definition definition) throws WSDLException; + + public final void buildServices() throws WsdlDefinitionException { + try { + buildServices(definition); + } + catch (WSDLException ex) { + throw new Wsdl4jDefinitionException(ex); + } + } + + /** + * Adds services to the definition. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + */ + protected abstract void buildServices(Definition definition) throws WSDLException; + + public final Wsdl11Definition getDefinition() throws WsdlDefinitionException { + return new Wsdl4jDefinition(definition); + } + + /** + * Creates a WSDL4J extensibility element. + * + * @param parentType a class object indicating where in the WSDL definition this extension will exist + * @param elementType the qname of the extensibility element + * @return the extensibility element + * @throws WSDLException in case of errors + * @see javax.wsdl.extensions.ExtensionRegistry#createExtension(Class,javax.xml.namespace.QName) + */ + protected ExtensibilityElement createExtension(Class parentType, QName elementType) throws WSDLException { + if (extensionRegistry == null) { + WSDLFactory wsdlFactory = WSDLFactory.newInstance(); + extensionRegistry = wsdlFactory.newPopulatedExtensionRegistry(); + } + return extensionRegistry.createExtension(parentType, elementType); + } + +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/Wsdl11DefinitionBuilder.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/Wsdl11DefinitionBuilder.java new file mode 100644 index 00000000..2df8e540 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/Wsdl11DefinitionBuilder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2006 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.wsdl.wsdl11.builder; + +import org.springframework.ws.wsdl.WsdlDefinitionException; +import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition; + +/** + * Defines the contract for classes that can create a {@link Wsdl11Definition} at runtime. + *

+ * Used by {@link org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition} to generate the WSDL based on a schema, a + * class, etc. + * + * @author Arjen Poutsma + */ +public interface Wsdl11DefinitionBuilder { + + /** + * Builds a new, empty definition. This method should be called before all others. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildDefinition() throws WsdlDefinitionException; + + /** + * Adds imports to the definition. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildImports() throws WsdlDefinitionException; + + /** + * Adds types to the definition. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildTypes() throws WsdlDefinitionException; + + /** + * Adds messages to the definition. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildMessages() throws WsdlDefinitionException; + + /** + * Adds portTypes to the definition. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildPortTypes() throws WsdlDefinitionException; + + /** + * Adds bindings to the definition. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildBindings() throws WsdlDefinitionException; + + /** + * Adds services to the definition. + * + * @throws WsdlDefinitionException in case of errors + */ + void buildServices() throws WsdlDefinitionException; + + /** + * Returns the built Wsdl11Definition. + * + * @return the WSDL definition, or null if {@link #buildDefinition()} has not been called + * @throws WsdlDefinitionException in case of errors + */ + Wsdl11Definition getDefinition() throws WsdlDefinitionException; + +} 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 new file mode 100644 index 00000000..48be899d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilder.java @@ -0,0 +1,404 @@ +/* + * Copyright 2006 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.wsdl.wsdl11.builder; + +import java.io.IOException; +import java.util.Iterator; +import javax.wsdl.Definition; +import javax.wsdl.Input; +import javax.wsdl.Message; +import javax.wsdl.Operation; +import javax.wsdl.Output; +import javax.wsdl.Part; +import javax.wsdl.PortType; +import javax.wsdl.Service; +import javax.wsdl.Types; +import javax.wsdl.WSDLException; +import javax.wsdl.extensions.schema.Schema; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +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.xml.namespace.QNameUtils; +import org.springframework.xml.sax.SaxUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +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. + *

+ * Requires the schema and portTypeName properties to be set. + * + * @author Arjen Poutsma + * @see #setSchema(org.springframework.core.io.Resource) + * @see #setPortTypeName(String) + * @see #setRequestSuffix(String) + * @see #setResponseSuffix(String) + */ +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 default suffix used to detect request elements in the schema. */ + public static final String DEFAULT_REQUEST_SUFFIX = "Request"; + + /** The default suffix used to detect response elements in the schema. */ + public static final String DEFAULT_RESPONSE_SUFFIX = "Response"; + + /** The default prefix used to register the schema namespace in the WSDL. */ + public static final String DEFAULT_SCHEMA_PREFIX = "schema"; + + /** The default prefix used to register the target namespace in the WSDL. */ + public static final String DEFAULT_PREFIX = "tns"; + + /** The suffix used to create a service name from a port type name. */ + public static final String SERVICE_SUFFIX = "Service"; + + private Resource schema; + + private Element schemaElement; + + private String targetNamespace; + + private String portTypeName; + + private String schemaPrefix = DEFAULT_SCHEMA_PREFIX; + + private String prefix = DEFAULT_PREFIX; + + private String requestSuffix = DEFAULT_REQUEST_SUFFIX; + + private String responseSuffix = DEFAULT_RESPONSE_SUFFIX; + + /** + * Sets the suffix used to detect request elements in the schema. + * + * @see #DEFAULT_REQUEST_SUFFIX + */ + public void setRequestSuffix(String requestSuffix) { + this.requestSuffix = requestSuffix; + } + + /** + * Sets the suffix used to detect response elements in the schema. + * + * @see #DEFAULT_RESPONSE_SUFFIX + */ + public void setResponseSuffix(String responseSuffix) { + this.responseSuffix = responseSuffix; + } + + /** Sets the port type name used for this definition. Required. */ + public void setPortTypeName(String portTypeName) { + this.portTypeName = portTypeName; + } + + /** Sets the target namespace used for this definition. */ + public void setTargetNamespace(String targetNamespace) { + this.targetNamespace = targetNamespace; + } + + /** + * Sets the prefix used to declare the schema target namespace. + * + * @see #DEFAULT_SCHEMA_PREFIX + */ + public void setSchemaPrefix(String schemaPrefix) { + this.schemaPrefix = schemaPrefix; + } + + /** + * Sets the prefix used to declare the target namespace. + * + * @see #DEFAULT_PREFIX + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** Sets the XSD schema to use for generating the WSDL. */ + public void setSchema(Resource schema) { + Assert.notNull(schema, "schema must not be empty or null"); + Assert.isTrue(schema.exists(), "schema \"" + schema + "\" does not exit"); + this.schema = schema; + } + + public final void afterPropertiesSet() throws IOException, ParserConfigurationException, SAXException { + Assert.notNull(schema, "schema is required"); + Assert.notNull(portTypeName, "portTypeName is required"); + parseSchema(); + } + + 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()), + "schema document root element has invalid local name : [" + schemaElement.getLocalName() + + "] instead of [schema]"); + Assert.isTrue(SCHEMA_NAMESPACE_URI.equals(schemaElement.getNamespaceURI()), + "schema document root element has invalid namespace uri: [" + schemaElement.getNamespaceURI() + + "] instead of [" + SCHEMA_NAMESPACE_URI + "]"); + String schemaTargetNamespace = getSchemaTargetNamespace(); + Assert.hasLength(schemaTargetNamespace, "schema document has no targetNamespace"); + if (!StringUtils.hasLength(targetNamespace)) { + targetNamespace = schemaTargetNamespace; + } + } + + private String getSchemaTargetNamespace() { + return schemaElement.getAttribute("targetNamespace"); + } + + /** Adds the target namespace and schema namespace to the definition. */ + protected void populateDefinition(Definition definition) throws WSDLException { + super.populateDefinition(definition); + definition.setTargetNamespace(targetNamespace); + definition.addNamespace(schemaPrefix, getSchemaTargetNamespace()); + if (!targetNamespace.equals(getSchemaTargetNamespace())) { + definition.addNamespace(prefix, targetNamespace); + } + } + + /** Does nothing. */ + protected void buildImports(Definition definition) throws WSDLException { + } + + /** + * Creates a Types 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); + types.addExtensibilityElement(schema); + definition.setTypes(types); + } + + /** + * Creates messages for each element found in the schema for which isRequestMessage() or + * isResponseMessage() is true. + * + * @param definition the WSDL4J Definition + * @throws WSDLException in case of errors + * @see #isRequestMessage(javax.xml.namespace.QName) + * @see #isResponseMessage(javax.xml.namespace.QName) + */ + protected void buildMessages(Definition definition) throws WSDLException { + NodeList elements = schemaElement.getElementsByTagNameNS(SCHEMA_NAMESPACE_URI, "element"); + for (int i = 0; i < elements.getLength(); i++) { + Element element = (Element) elements.item(i); + QName elementName = getSchemaElementName(element); + if (isRequestMessage(elementName) || isResponseMessage(elementName)) { + Message message = definition.createMessage(); + populateMessage(message, element); + Part part = definition.createPart(); + populatePart(part, elementName); + message.addPart(part); + message.setUndefined(false); + definition.addMessage(message); + } + } + } + + /** + * Indicates whether the given name name should be included as request Message in the definition. + * Default implementation checks whether the local part ends with the request suffix. + * + * @param name the name of the element elligable for being a message + * @return true if to be included as message; false otherwise + * @see #setRequestSuffix(String) + */ + protected boolean isRequestMessage(QName name) { + return name.getLocalPart().endsWith(requestSuffix); + } + + /** + * Indicates whether the given name should be included as Message in the definition. Default + * implementation checks whether the local part ends with the response suffix. + * + * @param name the name of the element elligable for being a message + * @return true if to be included as message; false otherwise + * @see #setResponseSuffix(String) + */ + protected boolean isResponseMessage(QName name) { + return name.getLocalPart().endsWith(responseSuffix); + } + + /** + * Called after the Message has been created. + *

+ * Default implementation sets the name of the message to the element name. + * + * @param message the WSDL4J Message + * @param element the element + * @throws WSDLException in case of errors + */ + protected void populateMessage(Message message, Element element) { + message.setQName(new QName(targetNamespace, element.getAttribute("name"))); + } + + /** + * Called after the Part has been created. + *

+ * Default implementation sets the element name of the part. + * + * @param part the WSDL4J Part + * @param elementName the elementName + * @throws WSDLException in case of errors + * @see Part#setElementName(javax.xml.namespace.QName) + */ + protected void populatePart(Part part, QName elementName) { + part.setElementName(elementName); + part.setName(elementName.getLocalPart()); + } + + protected void buildPortTypes(Definition definition) throws WSDLException { + PortType portType = definition.createPortType(); + populatePortType(portType); + createOperations(definition, portType); + portType.setUndefined(false); + definition.addPortType(portType); + } + + /** + * Called after the PortType has been created. + *

+ * Default implementation sets the name of the port type to the defined value. + * + * @param portType the WSDL4J PortType + * @throws WSDLException in case of errors + * @see #setPortTypeName(String) + */ + protected void populatePortType(PortType portType) throws WSDLException { + portType.setQName(new QName(targetNamespace, portTypeName)); + } + + private void createOperations(Definition definition, PortType portType) throws WSDLException { + for (Iterator messageIterator = definition.getMessages().values().iterator(); messageIterator.hasNext();) { + Message message = (Message) messageIterator.next(); + for (Iterator partIterator = message.getParts().values().iterator(); partIterator.hasNext();) { + Part part = (Part) partIterator.next(); + if (isRequestMessage(part.getElementName())) { + Message responseMessage = definition.getMessage(getResponseMessageName(message.getQName())); + Operation operation = definition.createOperation(); + populateOperation(operation, message, responseMessage); + if (message != null) { + Input input = definition.createInput(); + input.setMessage(message); + input.setName(message.getQName().getLocalPart()); + operation.setInput(input); + } + if (responseMessage != null) { + Output output = definition.createOutput(); + output.setMessage(responseMessage); + output.setName(responseMessage.getQName().getLocalPart()); + operation.setOutput(output); + } + operation.setUndefined(false); + portType.addOperation(operation); + } + } + } + } + + /** + * Given an request message name, return the corresponding response message name. + *

+ * Default implementation removes the request suffix, and appends the response suffix. + * + * @param requestMessageName the name of the request message + * @return the name of the corresponding response message, or null + */ + protected QName getResponseMessageName(QName requestMessageName) { + String localPart = requestMessageName.getLocalPart(); + if (localPart.endsWith(requestSuffix)) { + String prefix = localPart.substring(0, localPart.length() - requestSuffix.length()); + return new QName(requestMessageName.getNamespaceURI(), prefix + responseSuffix); + } + else { + return null; + } + } + + /** + * Called after the Operation has been created. + *

+ * Default implementation sets the name of the operation to name of the messages, without suffix. + * + * @param operation the WSDL4J Operation + * @param requestMessage the WSDL4J request Message + * @param responseMessage the WSDL4J response Message + * @throws WSDLException in case of errors + * @see #setPortTypeName(String) + */ + protected void populateOperation(Operation operation, Message requestMessage, Message responseMessage) + throws WSDLException { + String localPart = requestMessage.getQName().getLocalPart(); + String operationName = null; + if (localPart.endsWith(requestSuffix)) { + operationName = localPart.substring(0, localPart.length() - requestSuffix.length()); + } + else { + localPart = responseMessage.getQName().getLocalPart(); + if (localPart.endsWith(responseSuffix)) { + operationName = localPart.substring(0, localPart.length() - responseSuffix.length()); + operationName = localPart; + } + } + operation.setName(operationName); + } + + /** Sets the name of the service to the name of the port type, with "Service" appended to it. */ + protected void populateService(Service service) throws WSDLException { + service.setQName(new QName(targetNamespace, portTypeName + SERVICE_SUFFIX)); + } + + /** + * Returns the qualified name of the element. This is a combination of schema target namespace and the value of the + * "name" attribute value. + * + * @param element an element + * @return the value of the name attribute + */ + private QName getSchemaElementName(Element element) { + String attributeValue = element.getAttribute("name"); + if (StringUtils.hasLength(attributeValue)) { + return new QName(getSchemaTargetNamespace(), attributeValue); + } + else { + return null; + } + } +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/package.html b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/package.html new file mode 100644 index 00000000..f618f3b6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/builder/package.html @@ -0,0 +1,5 @@ + + +Provides a strategy for WSDL building. Used by DynamicWsdl11Definition to generate WSDL definitions at runtime. + + diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html index 9774cc37..a1ccb4a4 100644 --- a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html @@ -1,5 +1,5 @@ -Contains interfaces specific to WSDL 1.1. +Contains interfaces and classes specific to WSDL 1.1. diff --git a/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java b/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java index 8e3ad1ef..3ff0e01b 100644 --- a/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java +++ b/core/src/test/java/org/springframework/ws/transport/http/MessageDispatcherServletTest.java @@ -1,11 +1,19 @@ package org.springframework.ws.transport.http; +import java.io.ByteArrayInputStream; import java.util.List; import javax.servlet.ServletConfig; import javax.servlet.ServletException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; -import junit.framework.TestCase; +import org.custommonkey.xmlunit.XMLTestCase; +import org.custommonkey.xmlunit.XMLUnit; import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.support.StaticWebApplicationContext; @@ -13,8 +21,10 @@ import org.springframework.ws.MessageDispatcher; import org.springframework.ws.endpoint.PayloadEndpointAdapter; import org.springframework.ws.endpoint.mapping.PayloadRootQNameEndpointMapping; import org.springframework.ws.soap.endpoint.SimpleSoapExceptionResolver; +import org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition; +import org.w3c.dom.Document; -public class MessageDispatcherServletTest extends TestCase { +public class MessageDispatcherServletTest extends XMLTestCase { private ServletConfig config; @@ -70,6 +80,21 @@ public class MessageDispatcherServletTest extends TestCase { assertStrategies(SimpleSoapExceptionResolver.class, messageDispatcher.getEndpointExceptionResolvers()); } + public void testDetectWsdlDefinitions() throws Exception { + servlet.setContextClass(WsdlDefinitionWebApplicationContext.class); + servlet.init(config); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/definition.wsdl"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document result = documentBuilder.parse(new ByteArrayInputStream(response.getContentAsByteArray())); + Document expected = documentBuilder.parse(getClass().getResourceAsStream("wsdl11-input.wsdl")); + XMLUnit.setIgnoreWhitespace(true); + assertXMLEqual("Invalid WSDL written", expected, result); + } + private static class DetectWebApplicationContext extends StaticWebApplicationContext { public void refresh() throws BeansException, IllegalStateException { @@ -104,4 +129,14 @@ public class MessageDispatcherServletTest extends TestCase { super.refresh(); } } + + private static class WsdlDefinitionWebApplicationContext extends StaticWebApplicationContext { + + public void refresh() throws BeansException, IllegalStateException { + MutablePropertyValues mpv = new MutablePropertyValues(); + mpv.addPropertyValue("wsdl", new ClassPathResource("wsdl11-input.wsdl", getClass())); + registerSingleton("definition", SimpleWsdl11Definition.class, mpv); + super.refresh(); + } + } } \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11DefinitionTest.java b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11DefinitionTest.java new file mode 100644 index 00000000..ba5e6525 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/DynamicWsdl11DefinitionTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2006 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.wsdl.wsdl11; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.springframework.ws.wsdl.wsdl11.builder.Wsdl11DefinitionBuilder; + +public class DynamicWsdl11DefinitionTest extends TestCase { + + private DynamicWsdl11Definition definition; + + private MockControl builderControl; + + private Wsdl11DefinitionBuilder builderMock; + + protected void setUp() throws Exception { + definition = new DynamicWsdl11Definition(); + builderControl = MockControl.createControl(Wsdl11DefinitionBuilder.class); + builderMock = (Wsdl11DefinitionBuilder) builderControl.getMock(); + definition.setBuilder(builderMock); + } + + public void testComplete() throws Exception { + builderMock.buildDefinition(); + builderMock.buildImports(); + builderMock.buildTypes(); + builderMock.buildMessages(); + builderMock.buildPortTypes(); + builderMock.buildBindings(); + builderMock.buildServices(); + builderControl.expectAndReturn(builderMock.getDefinition(), null); + builderControl.replay(); + definition.afterPropertiesSet(); + builderControl.verify(); + } + + public void testAbstract() throws Exception { + definition.setBuildConcretePart(false); + builderMock.buildDefinition(); + builderMock.buildImports(); + builderMock.buildTypes(); + builderMock.buildMessages(); + builderMock.buildPortTypes(); + builderControl.expectAndReturn(builderMock.getDefinition(), null); + builderControl.replay(); + definition.afterPropertiesSet(); + builderControl.verify(); + } + + public void testConcrete() throws Exception { + definition.setBuildAbstractPart(false); + builderMock.buildDefinition(); + builderMock.buildImports(); + builderMock.buildBindings(); + builderMock.buildServices(); + builderControl.expectAndReturn(builderMock.getDefinition(), null); + builderControl.replay(); + definition.afterPropertiesSet(); + builderControl.verify(); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..49c30b28 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/builder/XsdBasedSoap11Wsdl4jDefinitionBuilderTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2006 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.wsdl.wsdl11.builder; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.custommonkey.xmlunit.XMLUnit; +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition; +import org.w3c.dom.Document; + +public class XsdBasedSoap11Wsdl4jDefinitionBuilderTest extends XMLTestCase { + + private XsdBasedSoap11Wsdl4jDefinitionBuilder builder; + + private Transformer transformer; + + protected void setUp() throws Exception { + builder = new XsdBasedSoap11Wsdl4jDefinitionBuilder(); + builder.setSchema(new ClassPathResource("schema.xsd", getClass())); + builder.setPortTypeName("Airline"); + builder.setTargetNamespace("http://www.springframework.org/spring-ws/wsdl/definitions"); + builder.setLocationUri("http://localhost:8080/"); + builder.afterPropertiesSet(); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + } + + public void testNonExistingSchema() throws Exception { + try { + builder.setSchema(new ClassPathResource("bla")); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException ex) { + // expected + } + } + + public void testBuilder() throws Exception { + 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("expected.wsdl")); + XMLUnit.setIgnoreWhitespace(true); + assertXMLEqual("Invalid WSDL built", expected, result); + } + +} \ 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/expected.wsdl new file mode 100644 index 00000000..d15fbb70 --- /dev/null +++ b/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/expected.wsdl @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/schema.xsd b/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/schema.xsd new file mode 100644 index 00000000..2a9dddc1 --- /dev/null +++ b/core/src/test/resources/org/springframework/ws/wsdl/wsdl11/builder/schema.xsd @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/echo/src/main/webapp/echo.xsd b/samples/echo/src/main/webapp/WEB-INF/echo.xsd old mode 100755 new mode 100644 similarity index 100% rename from samples/echo/src/main/webapp/echo.xsd rename to samples/echo/src/main/webapp/WEB-INF/echo.xsd diff --git a/samples/echo/src/main/webapp/WEB-INF/spring-ws-servlet.xml b/samples/echo/src/main/webapp/WEB-INF/spring-ws-servlet.xml index f635b402..53f3822a 100755 --- a/samples/echo/src/main/webapp/WEB-INF/spring-ws-servlet.xml +++ b/samples/echo/src/main/webapp/WEB-INF/spring-ws-servlet.xml @@ -28,7 +28,7 @@ This interceptor validates both incoming and outgoing message contents according to the 'echo.xsd' XML Schema file. - + @@ -46,6 +46,25 @@ + + + This bean definition represents a WSDL definition that is generated at runtime, based on the builder defined + below. It can be retrieved by going to /echo/echo.wsdl (i.e. the bean name corresponds to the filename). + + + + The builder creates a WSDL from the 'echo.xsd' schema. It detects all elements that ends with 'Request', + finds corresponding 'Response' messages, and creates an operation based on that. All operations are put + in a portType with name 'Echo', and binding and service are created. + + + + + + + + + This bean is our "business" service. diff --git a/samples/echo/src/main/webapp/WEB-INF/web.xml b/samples/echo/src/main/webapp/WEB-INF/web.xml index ac936d8b..0c86bcc6 100755 --- a/samples/echo/src/main/webapp/WEB-INF/web.xml +++ b/samples/echo/src/main/webapp/WEB-INF/web.xml @@ -19,7 +19,7 @@ spring-ws - /services + /* diff --git a/samples/echo/src/main/webapp/echo.wsdl b/samples/echo/src/main/webapp/echo.wsdl deleted file mode 100644 index c893406b..00000000 --- a/samples/echo/src/main/webapp/echo.wsdl +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -