diff --git a/build.gradle b/build.gradle index 5da3fc30..4e3bcc40 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ configure(allprojects) { ext.axiomVersion = "1.2.14" apply plugin: "java" + apply plugin: "maven" apply plugin: "propdeps-idea" apply plugin: "propdeps" @@ -55,7 +56,6 @@ configure(allprojects) { } repositories { - mavenLocal() maven { url 'http://repo.spring.io/libs-release' } } @@ -176,7 +176,7 @@ project('spring-ws-core') { optional("wsdl4j:wsdl4j:1.6.1") // Transport - provided("javax.servlet:javax.servlet-api:3.1.0") + provided("javax.servlet:javax.servlet-api:3.0.1") optional("org.apache.httpcomponents:httpclient:4.2.5") optional("commons-httpclient:commons-httpclient:3.1") testCompile("org.mortbay.jetty:jetty:6.1.26") diff --git a/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/DelegatingWsConfiguration.java b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/DelegatingWsConfiguration.java new file mode 100644 index 00000000..a8c9d167 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/DelegatingWsConfiguration.java @@ -0,0 +1,47 @@ +package org.springframework.ws.config.annotation; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; + +/** + * A sub-class of {@code WsConfigurationSupport} that detects and delegates + * to all beans of type {@link WsConfigurer} allowing them to customize the + * configuration provided by {@code WsConfigurationSupport}. This is the + * class actually imported by {@link EnableWs @EnableWs}. + * + * @author Arjen Poutsma + * @since 2.2 + */ +@Configuration +public class DelegatingWsConfiguration extends WsConfigurationSupport { + + private final WsConfigurerComposite configurers = new WsConfigurerComposite(); + + @Autowired(required = false) + public void setConfigurers(List configurers) { + if (configurers != null && !configurers.isEmpty()) { + this.configurers.addWsConfigurers(configurers); + } + } + + @Override + protected void addInterceptors(List interceptors) { + this.configurers.addInterceptors(interceptors); + } + + @Override + protected void addArgumentResolvers(List argumentResolvers) { + this.configurers.addArgumentResolvers(argumentResolvers); + } + + @Override + protected void addReturnValueHandlers( + List returnValueHandlers) { + this.configurers.addReturnValueHandlers(returnValueHandlers); + } +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/EnableWs.java b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/EnableWs.java new file mode 100644 index 00000000..69f9593d --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/EnableWs.java @@ -0,0 +1,102 @@ +/* + * Copyright 2005-2014 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.config.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * Add this annotation to an {@link Configuration @Configuration} class to have the Spring + * Web Services configuration defined in {@link WsConfigurationSupport} imported. For + * instance: + * + *
+ * @Configuration
+ * @EnableWs
+ * @ComponentScan(basePackageClasses = { MyConfiguration.class })
+ * public class MyWsConfiguration {
+ *
+ * }
+ * 
+ *

Customize the imported configuration by implementing the + * {@link WsConfigurer} interface or more likely by extending the + * {@link WsConfigurerAdapter} base class and overriding individual methods: + * + *

+ * @Configuration
+ * @EnableWs
+ * @ComponentScan(basePackageClasses = { MyConfiguration.class })
+ * public class MyConfiguration extends WsConfigurerAdapter {
+ *
+ * 	@Override
+ * 	public void addInterceptors(List<EndpointInterceptor> interceptors) {
+ * 	    interceptors.add(new MyInterceptor());
+ *  }
+ *
+ * 	@Override
+ * 	public void addArgumentResolvers(List<MethodArgumentResolver> argumentResolvers) {
+ * 	    argumentResolvers.add(new MyArgumentResolver());
+ * 	}
+ *
+ * 	// More overridden methods ...
+ * }
+ * 
+ * + *

If the customization options of {@link WsConfigurer} do not expose + * something you need to configure, consider removing the {@code @EnableWs} + * annotation and extending directly from {@link WsConfigurationSupport} + * overriding selected {@code @Bean} methods: + * + *

+ * @Configuration
+ * @ComponentScan(basePackageClasses = { MyConfiguration.class })
+ * public class MyConfiguration extends WsConfigurationSupport {
+ *
+ * 	@Override
+ * 	public void addInterceptors(List<EndpointInterceptor> interceptors) {
+ * 	    interceptors.add(new MyInterceptor());
+ *  }
+ *
+ *	@Bean
+ * 	@Override
+ *  public DefaultMethodEndpointAdapter defaultMethodEndpointAdapter() {
+ *		// Create or delegate to "super" to create and
+ *		// customize properties of DefaultMethodEndpointAdapter
+ *	}
+ * }
+ * 
+ * + * @see WsConfigurer + * @see WsConfigurerAdapter + * @see WsConfigurationSupport + * + * @author Arjen Poutsma + * @since 2.2 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Import(DelegatingWsConfiguration.class) +public @interface EnableWs { + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurationSupport.java b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurationSupport.java new file mode 100644 index 00000000..69522d6b --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurationSupport.java @@ -0,0 +1,217 @@ +/* + * Copyright 2005-2014 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.config.annotation; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.ws.server.EndpointAdapter; +import org.springframework.ws.server.EndpointExceptionResolver; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.EndpointMapping; +import org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; +import org.springframework.ws.server.endpoint.annotation.PayloadRoot; +import org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping; +import org.springframework.ws.soap.addressing.server.AnnotationActionEndpointMapping; +import org.springframework.ws.soap.addressing.server.annotation.Action; +import org.springframework.ws.soap.server.endpoint.SimpleSoapExceptionResolver; +import org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver; +import org.springframework.ws.soap.server.endpoint.annotation.SoapAction; +import org.springframework.ws.soap.server.endpoint.annotation.SoapFault; +import org.springframework.ws.soap.server.endpoint.mapping.SoapActionAnnotationMethodEndpointMapping; + +/** + * This is the main class providing the configuration behind the Spring Web Services Java + * config. It is typically imported by adding {@link EnableWs @EnableWs} to an + * application {@link Configuration @Configuration} class. An alternative, more + * advanced option is to extend directly from this class and override methods as + * necessary remembering to add {@link Configuration @Configuration} to the + * subclass and {@link Bean @Bean} to overridden {@link Bean @Bean} methods. + * For more details see the Javadoc of {@link EnableWs @EnableWs}. + * + *

This class registers the following {@link EndpointMapping}s: + *

    + *
  • {@link PayloadRootAnnotationMethodEndpointMapping} + * ordered at 0 for mapping requests to {@link PayloadRoot @PayloadRoot} annotated + * controller methods. + *
  • {@link SoapActionAnnotationMethodEndpointMapping} + * ordered at 1 for mapping requests to {@link SoapAction @SoapAction} annotated + * controller methods. + *
  • {@link AnnotationActionEndpointMapping} + * ordered at 2 for mapping requests to {@link Action @Action} annotated + * controller methods. + *
+ * + *

Registers one {@link EndpointAdapter}: + *

    + *
  • {@link DefaultMethodEndpointAdapter} + * for processing requests with annotated endpoint methods. + *
+ * + *

Registers the following {@link EndpointExceptionResolver}s: + *

    + *
  • {@link SoapFaultAnnotationExceptionResolver} for handling exceptions + * annotated with {@link SoapFault @SoapFault}. + *
  • {@link SimpleSoapExceptionResolver} for creating default exceptions. + *
+ * + * @see EnableWs + * @see WsConfigurer + * @see WsConfigurerAdapter + * + * @author Arjen Poutsma + * @since 2.2 + */ +public class WsConfigurationSupport { + + private List interceptors; + + /** + * Returns a {@link PayloadRootAnnotationMethodEndpointMapping} ordered at 0 for + * mapping requests to annotated endpoints. + */ + @Bean + public PayloadRootAnnotationMethodEndpointMapping payloadRootAnnotationMethodEndpointMapping() { + PayloadRootAnnotationMethodEndpointMapping endpointMapping = + new PayloadRootAnnotationMethodEndpointMapping(); + endpointMapping.setOrder(0); + endpointMapping.setInterceptors(getInterceptors()); + return endpointMapping; + } + + /** + * Returns a {@link SoapActionAnnotationMethodEndpointMapping} ordered at 1 for + * mapping requests to annotated endpoints. + */ + @Bean + public SoapActionAnnotationMethodEndpointMapping soapActionAnnotationMethodEndpointMapping() { + SoapActionAnnotationMethodEndpointMapping endpointMapping = + new SoapActionAnnotationMethodEndpointMapping(); + endpointMapping.setOrder(1); + endpointMapping.setInterceptors(getInterceptors()); + return endpointMapping; + } + + /** + * Returns a {@link AnnotationActionEndpointMapping} ordered at 2 for + * mapping requests to annotated endpoints. + */ + @Bean + public AnnotationActionEndpointMapping annotationActionEndpointMapping() { + AnnotationActionEndpointMapping endpointMapping = + new AnnotationActionEndpointMapping(); + endpointMapping.setOrder(2); + endpointMapping.setPostInterceptors(getInterceptors()); + return endpointMapping; + } + + /** + * Provide access to the shared handler interceptors used to configure + * {@link EndpointMapping} instances with. This method cannot be overridden, + * use {@link #addInterceptors(List)} instead. + */ + protected final EndpointInterceptor[] getInterceptors() { + if (interceptors == null) { + interceptors = new ArrayList(); + addInterceptors(interceptors); + } + return interceptors.toArray(new EndpointInterceptor[interceptors.size()]); + } + + /** + * Template method to add endpoint interceptors. Override this method to add Spring-WS + * interceptors for pre- and post-processing of endpoint invocation. + */ + protected void addInterceptors(List interceptors) { + } + + /** + * Returns a {@link DefaultMethodEndpointAdapter} for processing requests + * through annotated endpoint methods. Consider overriding one of these + * other more fine-grained methods: + *
    + *
  • {@link #addArgumentResolvers(List)} for adding custom argument resolvers. + *
  • {@link #addReturnValueHandlers(List)} for adding custom return value handlers. + *
+ */ + @Bean + public DefaultMethodEndpointAdapter defaultMethodEndpointAdapter() { + List argumentResolvers = + new ArrayList(); + addArgumentResolvers(argumentResolvers); + + List returnValueHandlers = + new ArrayList(); + addReturnValueHandlers(returnValueHandlers); + + DefaultMethodEndpointAdapter adapter = new DefaultMethodEndpointAdapter(); + adapter.setCustomMethodArgumentResolvers(argumentResolvers); + adapter.setCustomMethodReturnValueHandlers(returnValueHandlers); + + return adapter; + } + + /** + * Add custom {@link MethodArgumentResolver}s to use in addition to + * the ones registered by default. + * @param argumentResolvers the list of custom converters; + * initially an empty list. + */ + protected void addArgumentResolvers(List argumentResolvers) { + } + + /** + * Add custom {@link MethodReturnValueHandler}s in addition to the + * ones registered by default. + * @param returnValueHandlers the list of custom handlers; + * initially an empty list. + */ + protected void addReturnValueHandlers( + List returnValueHandlers) { + } + + /** + * Returns a {@link SoapFaultAnnotationExceptionResolver} ordered at 0 for handling + * endpoint exceptions. + */ + @Bean + public SoapFaultAnnotationExceptionResolver soapFaultAnnotationExceptionResolver() { + SoapFaultAnnotationExceptionResolver exceptionResolver = new SoapFaultAnnotationExceptionResolver(); + exceptionResolver.setOrder(0); + + return exceptionResolver; + } + + /** + * Returns a {@link SimpleSoapExceptionResolver} ordered at + * {@linkplain Ordered#LOWEST_PRECEDENCE lowest precedence} for handling endpoint + * exceptions. + */ + @Bean + public SimpleSoapExceptionResolver simpleSoapExceptionResolver() { + SimpleSoapExceptionResolver exceptionResolver = new SimpleSoapExceptionResolver(); + exceptionResolver.setOrder(Ordered.LOWEST_PRECEDENCE); + + return exceptionResolver; + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurer.java b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurer.java new file mode 100644 index 00000000..70372841 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurer.java @@ -0,0 +1,43 @@ +package org.springframework.ws.config.annotation; + +import java.util.List; + +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; + +/** + * Defines callback methods to customize the Java-based configuration for + * Spring Web Services enabled via {@link EnableWs @EnableWs}. + * + *

{@code @EnableWs}-annotated configuration classes may implement + * this interface to be called back and given a chance to customize the + * default configuration. Consider extending {@link WsConfigurerAdapter}, + * which provides a stub implementation of all interface methods. + * + * @author Arjen Poutsma + * @since 2.2 + */ +public interface WsConfigurer { + + /** + * Add {@link EndpointInterceptor}s for pre- and post-processing of + * endpoint method invocations. + */ + void addInterceptors(List interceptors); + + /** + * Add resolvers to support custom endpoint method argument types. + * @param argumentResolvers initially an empty list + */ + void addArgumentResolvers(List argumentResolvers); + + /** + * Add handlers to support custom controller method return value types. + *

Using this option does not override the built-in support for handling + * return values. To customize the built-in support for handling return + * values, configure RequestMappingHandlerAdapter directly. + * @param returnValueHandlers initially an empty list + */ + void addReturnValueHandlers(List returnValueHandlers); +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurerAdapter.java b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurerAdapter.java new file mode 100644 index 00000000..7a717cb5 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurerAdapter.java @@ -0,0 +1,43 @@ +package org.springframework.ws.config.annotation; + +import java.util.List; + +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; + +/** + * An default implementation of {@link WsConfigurer} with empty methods allowing + * sub-classes to override only the methods they're interested in. + * + * @author Arjen Poutsma + * @since 2.2 + */ +public class WsConfigurerAdapter implements WsConfigurer { + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + @Override + public void addInterceptors(List interceptors) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + @Override + public void addArgumentResolvers(List argumentResolvers) { + } + + /** + * {@inheritDoc} + *

This implementation is empty. + */ + @Override + public void addReturnValueHandlers( + List returnValueHandlers) { + + } +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurerComposite.java b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurerComposite.java new file mode 100644 index 00000000..0a573f0e --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/config/annotation/WsConfigurerComposite.java @@ -0,0 +1,48 @@ +package org.springframework.ws.config.annotation; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; + +/** + * An {@link WsConfigurer} implementation that delegates to other {@link WsConfigurer} instances. + * + * @author Arjen Poutsma + * @since 2.2 + */ +public class WsConfigurerComposite implements WsConfigurer { + + private List delegates = new ArrayList(); + + public void addWsConfigurers(List configurers) { + if (configurers != null) { + this.delegates.addAll(configurers); + } + } + + @Override + public void addInterceptors(List interceptors) { + for (WsConfigurer delegate : delegates) { + delegate.addInterceptors(interceptors); + } + } + + @Override + public void addArgumentResolvers(List argumentResolvers) { + for (WsConfigurer delegate : delegates) { + delegate.addArgumentResolvers(argumentResolvers); + } + } + + @Override + public void addReturnValueHandlers( + List returnValueHandlers) { + for (WsConfigurer delegate : delegates) { + delegate.addReturnValueHandlers(returnValueHandlers); + } + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java b/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java index 73544b2c..ede1b2fb 100644 --- a/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java +++ b/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.java @@ -69,31 +69,77 @@ public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter private List methodArgumentResolvers; + private List customMethodArgumentResolvers; + private List methodReturnValueHandlers; + private List customMethodReturnValueHandlers; + private ClassLoader classLoader; - /** Returns the list of {@code MethodArgumentResolver}s to use. */ - public List getMethodArgumentResolvers() { + /** + * Returns the list of {@code MethodArgumentResolver}s to use. + */ + public List getMethodArgumentResolvers() { return methodArgumentResolvers; } - /** Sets the list of {@code MethodArgumentResolver}s to use. */ - public void setMethodArgumentResolvers(List methodArgumentResolvers) { + /** + * Sets the list of {@code MethodArgumentResolver}s to use. + */ + public void setMethodArgumentResolvers(List methodArgumentResolvers) { this.methodArgumentResolvers = methodArgumentResolvers; } - /** Returns the list of {@code MethodReturnValueHandler}s to use. */ - public List getMethodReturnValueHandlers() { + /** + * Returns the custom argument resolvers. + */ + public List getCustomMethodArgumentResolvers() { + return customMethodArgumentResolvers; + } + + /** + * Sets the custom handlers for method arguments. Custom handlers are + * ordered after built-in ones. To override the built-in support for + * return value handling use {@link #setMethodArgumentResolvers(List)}. + */ + public void setCustomMethodArgumentResolvers( + List customMethodArgumentResolvers) { + this.customMethodArgumentResolvers = customMethodArgumentResolvers; + } + + /** + * Returns the list of {@code MethodReturnValueHandler}s to use. + */ + public List getMethodReturnValueHandlers() { return methodReturnValueHandlers; } - /** Sets the list of {@code MethodReturnValueHandler}s to use. */ - public void setMethodReturnValueHandlers(List methodReturnValueHandlers) { + /** + * Sets the list of {@code MethodReturnValueHandler}s to use. + */ + public void setMethodReturnValueHandlers(List methodReturnValueHandlers) { this.methodReturnValueHandlers = methodReturnValueHandlers; } - private ClassLoader getClassLoader() { + /** + * Returns the custom return value handlers. + */ + public List getCustomMethodReturnValueHandlers() { + return customMethodReturnValueHandlers; + } + + /** + * Sets the handlers for custom return value types. Custom handlers are + * ordered after built-in ones. To override the built-in support for + * return value handling use {@link #setMethodReturnValueHandlers(List)}. + */ + public void setCustomMethodReturnValueHandlers( + List customMethodReturnValueHandlers) { + this.customMethodReturnValueHandlers = customMethodReturnValueHandlers; + } + + private ClassLoader getClassLoader() { return this.classLoader != null ? this.classLoader : DefaultMethodEndpointAdapter.class.getClassLoader(); } @@ -139,6 +185,9 @@ public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter if (logger.isDebugEnabled()) { logger.debug("No MethodArgumentResolvers set, using defaults: " + methodArgumentResolvers); } + if (getCustomMethodArgumentResolvers() != null) { + methodArgumentResolvers.addAll(getCustomMethodArgumentResolvers()); + } setMethodArgumentResolvers(methodArgumentResolvers); } } @@ -180,6 +229,9 @@ public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter if (logger.isDebugEnabled()) { logger.debug("No MethodReturnValueHandlers set, using defaults: " + methodReturnValueHandlers); } + if (getCustomMethodReturnValueHandlers() != null) { + methodReturnValueHandlers.addAll(getCustomMethodReturnValueHandlers()); + } setMethodReturnValueHandlers(methodReturnValueHandlers); } } diff --git a/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/interceptor/AbstractValidatingInterceptor.java b/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/interceptor/AbstractValidatingInterceptor.java index 816c3a43..3ed18184 100644 --- a/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/interceptor/AbstractValidatingInterceptor.java +++ b/spring-ws-core/src/main/java/org/springframework/ws/server/endpoint/interceptor/AbstractValidatingInterceptor.java @@ -20,6 +20,9 @@ import java.io.IOException; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -37,9 +40,6 @@ import org.springframework.xml.validation.XmlValidatorFactory; import org.springframework.xml.xsd.XsdSchema; import org.springframework.xml.xsd.XsdSchemaCollection; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - /** * Abstract base class for EndpointInterceptor implementations that validate part of the message using a * schema. The exact message part is determined by the getValidationRequestSource and @@ -118,7 +118,7 @@ public abstract class AbstractValidatingInterceptor extends TransformerObjectSup * @param schema the xsd schema to use * @throws IOException in case of I/O errors */ - public void setXsdSchema(XsdSchema schema) throws IOException { + public void setXsdSchema(XsdSchema schema) { this.validator = schema.createValidator(); } @@ -130,7 +130,7 @@ public abstract class AbstractValidatingInterceptor extends TransformerObjectSup * @param schemaCollection the xsd schema collection to use * @throws IOException in case of I/O errors */ - public void setXsdSchemaCollection(XsdSchemaCollection schemaCollection) throws IOException { + public void setXsdSchemaCollection(XsdSchemaCollection schemaCollection) { this.validator = schemaCollection.createValidator(); } diff --git a/spring-ws-core/src/main/java/org/springframework/ws/transport/http/support/AbstractAnnotationConfigMessageDispatcherServletInitializer.java b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/support/AbstractAnnotationConfigMessageDispatcherServletInitializer.java new file mode 100644 index 00000000..a691b9d9 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/support/AbstractAnnotationConfigMessageDispatcherServletInitializer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.transport.http.support; + +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ObjectUtils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer; +import org.springframework.ws.transport.http.MessageDispatcherServlet; + +/** + * Base class for {@link WebApplicationInitializer} implementations that register a + * {@link MessageDispatcherServlet} configured with annotated classes, e.g. Spring's + * {@link Configuration @Configuration} classes. + * + *

Concrete implementations are required to implement {@link #getRootConfigClasses()} + * and {@link #getServletConfigClasses()} as well as {@link #getServletMappings()}. + * Further template and customization methods are provided by + * {@link AbstractDispatcherServletInitializer}. + * + * @author Arjen Poutsma + * @since 2.2 + */ +public abstract class AbstractAnnotationConfigMessageDispatcherServletInitializer + extends AbstractMessageDispatcherServletInitializer { + + /** + * {@inheritDoc} + *

This implementation creates an {@link AnnotationConfigWebApplicationContext}, + * providing it the annotated classes returned by {@link #getRootConfigClasses()}. + * Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}. + */ + @Override + protected WebApplicationContext createRootApplicationContext() { + Class[] configClasses = getRootConfigClasses(); + if (!ObjectUtils.isEmpty(configClasses)) { + AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); + rootAppContext.register(configClasses); + return rootAppContext; + } + else { + return null; + } + } + + /** + * {@inheritDoc} + *

This implementation creates an {@link AnnotationConfigWebApplicationContext}, + * providing it the annotated classes returned by {@link #getServletConfigClasses()}. + */ + @Override + protected WebApplicationContext createServletApplicationContext() { + AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext(); + Class[] configClasses = getServletConfigClasses(); + if (!ObjectUtils.isEmpty(configClasses)) { + servletAppContext.register(configClasses); + } + return servletAppContext; + } + + /** + * Specify {@link org.springframework.context.annotation.Configuration @Configuration} + * and/or {@link org.springframework.stereotype.Component @Component} classes to be + * provided to the {@linkplain #createRootApplicationContext() root application context}. + * @return the configuration classes for the root application context, or {@code null} + * if creation and registration of a root context is not desired + */ + protected abstract Class[] getRootConfigClasses(); + + /** + * Specify {@link org.springframework.context.annotation.Configuration @Configuration} + * and/or {@link org.springframework.stereotype.Component @Component} classes to be + * provided to the {@linkplain #createServletApplicationContext() dispatcher servlet + * application context}. + * @return the configuration classes for the dispatcher servlet application context + * (may not be empty or {@code null}) + */ + protected abstract Class[] getServletConfigClasses(); + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/transport/http/support/AbstractMessageDispatcherServletInitializer.java b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/support/AbstractMessageDispatcherServletInitializer.java new file mode 100644 index 00000000..52d51486 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/transport/http/support/AbstractMessageDispatcherServletInitializer.java @@ -0,0 +1,159 @@ +/* + * Copyright 2005-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.transport.http.support; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.springframework.util.Assert; +import org.springframework.web.context.AbstractContextLoaderInitializer; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.ws.transport.http.MessageDispatcherServlet; + +/** + * Base class for {@link org.springframework.web.WebApplicationInitializer + * WebApplicationInitializer} implementations that register a + * {@link MessageDispatcherServlet} in the servlet context. + * + *

Concrete implementations are required to implement {@link + * #createServletApplicationContext()}, which gets invoked from + * {@link #registerMessageDispatcherServlet(ServletContext)}. Further customization can be + * achieved by overriding {@link #customizeRegistration(ServletRegistration.Dynamic)}. + * + *

Because this class extends from {@link AbstractContextLoaderInitializer}, concrete + * implementations are also required to implement {@link #createRootApplicationContext()} + * to set up a parent "root" application context. If a root context is + * not desired, implementations can simply return {@code null} in the + * {@code createRootApplicationContext()} implementation. + * + * @author Arjen Poutsma + * @since 2.2 + */ +public abstract class AbstractMessageDispatcherServletInitializer extends + AbstractContextLoaderInitializer { + + /** + * The default servlet name. Can be customized by overriding {@link #getServletName}. + */ + public static final String DEFAULT_SERVLET_NAME = "messageDispatcher"; + + /** + * The default servlet mappings. Can be customized by overriding {@link #getServletMappings()}. + */ + public static final String[] DEFAULT_SERVLET_MAPPINGS = new String[] { "/services", "*.wsdl"}; + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + + this.registerMessageDispatcherServlet(servletContext); + } + + /** + * Register a {@link MessageDispatcherServlet} against the given servlet context. + *

This method will create a {@code MessageDispatcherServlet} with the name + * returned by {@link #getServletName()}, initializing it with the application context + * returned from {@link #createServletApplicationContext()}, and mapping it to the + * patterns returned from {@link #getServletMappings()}. + *

Further customization can be achieved by overriding {@link + * #customizeRegistration(ServletRegistration.Dynamic)}. + * @param servletContext the context to register the servlet against + */ + protected void registerMessageDispatcherServlet(ServletContext servletContext) { + String servletName = this.getServletName(); + Assert.hasLength(servletName, "getServletName() may not return empty or null"); + + WebApplicationContext servletAppContext = this.createServletApplicationContext(); + Assert.notNull(servletAppContext, + "createServletApplicationContext() did not return an application " + + "context for servlet [" + servletName + "]"); + + MessageDispatcherServlet dispatcherServlet = + new MessageDispatcherServlet(servletAppContext); + dispatcherServlet.setTransformWsdlLocations(isTransformWsdlLocations()); + dispatcherServlet.setTransformSchemaLocations(isTransformSchemaLocations()); + + ServletRegistration.Dynamic registration = + servletContext.addServlet(servletName, dispatcherServlet); + + Assert.notNull(registration, + "Failed to register servlet with name '" + servletName + "'." + + "Check if there is another servlet registered under the same name."); + + registration.setLoadOnStartup(1); + registration.addMapping(getServletMappings()); + + this.customizeRegistration(registration); + } + + /** + * Return the name under which the {@link MessageDispatcherServlet} will be registered. + * Defaults to {@link #DEFAULT_SERVLET_NAME}. + * @see #registerMessageDispatcherServlet(ServletContext) + */ + protected String getServletName() { + return DEFAULT_SERVLET_NAME; + } + + /** + * Create a servlet application context to be provided to the {@code MessageDispatcherServlet}. + *

The returned context is delegated to Spring's + * {@link MessageDispatcherServlet#MessageDispatcherServlet(WebApplicationContext)}. + * As such, it typically contains endpoints, interceptors and other + * web service-related beans. + * @see #registerMessageDispatcherServlet(ServletContext) + */ + protected abstract WebApplicationContext createServletApplicationContext(); + + /** + * Specify the servlet mapping(s) for the {@code MessageDispatcherServlet}. + * Defaults to {@link #DEFAULT_SERVLET_MAPPING}. + * @see #registerMessageDispatcherServlet(ServletContext) + */ + protected String[] getServletMappings() { + return DEFAULT_SERVLET_MAPPINGS; + } + + /** + * Indicates whether relative address locations in the WSDL are to be transformed + * using the request URI of the incoming HTTP request. Defaults to {@code false}. + */ + public boolean isTransformWsdlLocations() { + return false; + } + + /** + * Indicates whether relative address locations in the XSD are to be transformed using + * the request URI of the incoming HTTP request. Defaults to {@code false}. + */ + protected boolean isTransformSchemaLocations() { + return false; + } + + + /** + * Optionally perform further registration customization once + * {@link #registerMessageDispatcherServlet(ServletContext)} has completed. + * @param registration the {@code MessageDispatcherServlet} registration to be customized + * @see #registerMessageDispatcherServlet(ServletContext) + */ + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + } + + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/wsdl/wsdl11/provider/InliningXsdSchemaTypesProvider.java b/spring-ws-core/src/main/java/org/springframework/ws/wsdl/wsdl11/provider/InliningXsdSchemaTypesProvider.java index 490cfe34..76354fa3 100644 --- a/spring-ws-core/src/main/java/org/springframework/ws/wsdl/wsdl11/provider/InliningXsdSchemaTypesProvider.java +++ b/spring-ws-core/src/main/java/org/springframework/ws/wsdl/wsdl11/provider/InliningXsdSchemaTypesProvider.java @@ -16,7 +16,6 @@ package org.springframework.ws.wsdl.wsdl11.provider; -import java.io.IOException; import javax.wsdl.Definition; import javax.wsdl.Types; import javax.wsdl.WSDLException; @@ -64,7 +63,7 @@ public class InliningXsdSchemaTypesProvider extends TransformerObjectSupport imp return new XsdSchema[]{schema}; } - public XmlValidator createValidator() throws IOException { + public XmlValidator createValidator() { throw new UnsupportedOperationException(); } }; diff --git a/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/DefaultWsConfigurationTest.java b/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/DefaultWsConfigurationTest.java new file mode 100644 index 00000000..62826204 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/DefaultWsConfigurationTest.java @@ -0,0 +1,86 @@ +package org.springframework.ws.config.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter; +import org.springframework.ws.server.endpoint.annotation.Endpoint; +import org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping; +import org.springframework.ws.soap.addressing.server.AnnotationActionEndpointMapping; +import org.springframework.ws.soap.server.endpoint.annotation.SoapAction; +import org.springframework.ws.soap.server.endpoint.mapping.SoapActionAnnotationMethodEndpointMapping; + +/** + * @author Arjen Poutsma + */ +public class DefaultWsConfigurationTest { + + private ApplicationContext applicationContext; + + @Before + public void setUp() throws Exception { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(TestConfig.class); + applicationContext.refresh(); + + this.applicationContext = applicationContext; + } + + @Test + public void payloadRootAnnotationMethodEndpointMapping() throws Exception { + PayloadRootAnnotationMethodEndpointMapping endpointMapping = this.applicationContext.getBean( + PayloadRootAnnotationMethodEndpointMapping.class); + assertEquals(0, endpointMapping.getOrder()); + } + + @Test + public void soapActionAnnotationMethodEndpointMapping() throws Exception { + SoapActionAnnotationMethodEndpointMapping endpointMapping = this.applicationContext.getBean( + SoapActionAnnotationMethodEndpointMapping.class); + assertEquals(1, endpointMapping.getOrder()); + } + + @Test + public void annotationActionEndpointMapping() throws Exception { + AnnotationActionEndpointMapping endpointMapping = this.applicationContext.getBean( + AnnotationActionEndpointMapping.class); + assertEquals(2, endpointMapping.getOrder()); + } + + @Test + public void defaultMethodEndpointAdapter() throws Exception { + + DefaultMethodEndpointAdapter adapter = + this.applicationContext.getBean(DefaultMethodEndpointAdapter.class); + + assertFalse(adapter.getMethodArgumentResolvers().isEmpty()); + assertFalse(adapter.getMethodReturnValueHandlers().isEmpty()); + } + + + @EnableWs + @Configuration + public static class TestConfig { + + @Bean(name="testEndpoint") + public TestEndpoint testEndpoint() { + return new TestEndpoint(); + } + } + + @Endpoint + private static class TestEndpoint { + + @SoapAction("handle") + public void handle() { + } + + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/WsConfigurationSupportTest.java b/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/WsConfigurationSupportTest.java new file mode 100644 index 00000000..f004c2f0 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/WsConfigurationSupportTest.java @@ -0,0 +1,80 @@ +package org.springframework.ws.config.annotation; + +import java.util.List; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter; +import org.springframework.ws.server.endpoint.interceptor.EndpointInterceptorAdapter; +import org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping; + +/** + * @author Arjen Poutsma + */ +public class WsConfigurationSupportTest { + + private ApplicationContext applicationContext; + + @Before + public void setUp() throws Exception { + AnnotationConfigApplicationContext applicationContext = + new AnnotationConfigApplicationContext(); + applicationContext.register(TestConfig.class); + applicationContext.refresh(); + + this.applicationContext = applicationContext; + } + + @Test + public void interceptors() { + PayloadRootAnnotationMethodEndpointMapping endpointMapping = this.applicationContext.getBean( + PayloadRootAnnotationMethodEndpointMapping.class); + assertEquals(0, endpointMapping.getOrder()); + + EndpointInterceptor[] interceptors = endpointMapping.getInterceptors(); + assertEquals(1, interceptors.length); + assertTrue(interceptors[0] instanceof MyInterceptor); + } + + @Test + public void defaultMethodEndpointAdapter() { + DefaultMethodEndpointAdapter endpointAdapter = + this.applicationContext.getBean(DefaultMethodEndpointAdapter.class); + assertNotNull(endpointAdapter); + + assertTrue(endpointAdapter instanceof MyDefaultMethodEndpointAdapter); + } + + + @Configuration + public static class TestConfig extends WsConfigurationSupport { + + @Override + protected void addInterceptors(List interceptors) { + interceptors.add(new MyInterceptor()); + } + + @Bean + @Override + public DefaultMethodEndpointAdapter defaultMethodEndpointAdapter() { + return new MyDefaultMethodEndpointAdapter(); + } + } + + public static class MyInterceptor extends EndpointInterceptorAdapter { + + } + + public static class MyDefaultMethodEndpointAdapter + extends DefaultMethodEndpointAdapter { + + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/WsConfigurerAdapterTest.java b/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/WsConfigurerAdapterTest.java new file mode 100644 index 00000000..0240144e --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/config/annotation/WsConfigurerAdapterTest.java @@ -0,0 +1,128 @@ +package org.springframework.ws.config.annotation; + +import java.util.List; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.MethodParameter; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler; +import org.springframework.ws.server.endpoint.interceptor.EndpointInterceptorAdapter; +import org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping; + +/** + * @author Arjen Poutsma + */ +public class WsConfigurerAdapterTest { + + private ApplicationContext applicationContext; + + @Before + public void setUp() throws Exception { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(TestConfig.class); + applicationContext.refresh(); + + this.applicationContext = applicationContext; + } + + @Test + public void interceptors() { + PayloadRootAnnotationMethodEndpointMapping endpointMapping = this.applicationContext.getBean( + PayloadRootAnnotationMethodEndpointMapping.class); + assertEquals(0, endpointMapping.getOrder()); + + EndpointInterceptor[] interceptors = endpointMapping.getInterceptors(); + assertEquals(1, interceptors.length); + assertTrue(interceptors[0] instanceof MyInterceptor); + } + + @Test + public void argumentResolvers() { + DefaultMethodEndpointAdapter endpointAdapter = this.applicationContext.getBean(DefaultMethodEndpointAdapter.class); + + List argumentResolvers = + endpointAdapter.getCustomMethodArgumentResolvers(); + assertEquals(1, argumentResolvers.size()); + assertTrue(argumentResolvers.get(0) instanceof MyMethodArgumentResolver); + + argumentResolvers = endpointAdapter.getMethodArgumentResolvers(); + assertFalse(argumentResolvers.isEmpty()); + } + + @Test + public void returnValueHandlers() { + DefaultMethodEndpointAdapter endpointAdapter = this.applicationContext.getBean(DefaultMethodEndpointAdapter.class); + + List returnValueHandlers = + endpointAdapter.getCustomMethodReturnValueHandlers(); + assertEquals(1, returnValueHandlers.size()); + assertTrue(returnValueHandlers.get(0) instanceof MyReturnValueHandler); + + returnValueHandlers = endpointAdapter.getMethodReturnValueHandlers(); + assertFalse(returnValueHandlers.isEmpty()); + } + + + @Configuration + @EnableWs + public static class TestConfig extends WsConfigurerAdapter { + + @Override + public void addInterceptors(List interceptors) { + interceptors.add(new MyInterceptor()); + } + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(new MyMethodArgumentResolver()); + } + + @Override + public void addReturnValueHandlers( + List returnValueHandlers) { + returnValueHandlers.add(new MyReturnValueHandler()); + } + } + + public static class MyInterceptor extends EndpointInterceptorAdapter { + } + + public static class MyMethodArgumentResolver implements MethodArgumentResolver { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return false; + } + + @Override + public Object resolveArgument(MessageContext messageContext, + MethodParameter parameter) throws Exception { + return null; + } + } + + public static class MyReturnValueHandler implements MethodReturnValueHandler { + + @Override + public boolean supportsReturnType(MethodParameter returnType) { + return false; + } + + @Override + public void handleReturnValue(MessageContext messageContext, + MethodParameter returnType, Object returnValue) throws Exception { + } + } + + + +} diff --git a/spring-xml/src/main/java/org/springframework/xml/xsd/SimpleXsdSchema.java b/spring-xml/src/main/java/org/springframework/xml/xsd/SimpleXsdSchema.java index f7c5b06a..0d9c7ca3 100644 --- a/spring-xml/src/main/java/org/springframework/xml/xsd/SimpleXsdSchema.java +++ b/spring-xml/src/main/java/org/springframework/xml/xsd/SimpleXsdSchema.java @@ -24,6 +24,10 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -32,10 +36,6 @@ import org.springframework.xml.sax.SaxUtils; import org.springframework.xml.validation.XmlValidator; import org.springframework.xml.validation.XmlValidatorFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - /** * The default {@link XsdSchema} implementation. *

@@ -98,8 +98,13 @@ public class SimpleXsdSchema implements XsdSchema, InitializingBean { return new DOMSource(schemaElement); } - public XmlValidator createValidator() throws IOException { - return XmlValidatorFactory.createValidator(xsdResource, XmlValidatorFactory.SCHEMA_W3C_XML); + public XmlValidator createValidator() { + try { + return XmlValidatorFactory.createValidator(xsdResource, XmlValidatorFactory.SCHEMA_W3C_XML); + } + catch (IOException ex) { + throw new XsdSchemaException(ex.getMessage(), ex); + } } public void afterPropertiesSet() throws ParserConfigurationException, IOException, SAXException { diff --git a/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchema.java b/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchema.java index bf01275b..fbbf262d 100644 --- a/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchema.java +++ b/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchema.java @@ -16,7 +16,6 @@ package org.springframework.xml.xsd; -import java.io.IOException; import javax.xml.transform.Source; import org.springframework.xml.validation.XmlValidator; @@ -48,7 +47,6 @@ public interface XsdSchema { * Creates a {@link XmlValidator} based on the schema. * * @return a validator for this schema - * @throws IOException in case of I/O errors */ - XmlValidator createValidator() throws IOException; + XmlValidator createValidator(); } diff --git a/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchemaCollection.java b/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchemaCollection.java index e3cbfa1d..da0e49b5 100644 --- a/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchemaCollection.java +++ b/spring-xml/src/main/java/org/springframework/xml/xsd/XsdSchemaCollection.java @@ -16,8 +16,6 @@ package org.springframework.xml.xsd; -import java.io.IOException; - import org.springframework.xml.validation.XmlValidator; /** @@ -39,8 +37,7 @@ public interface XsdSchemaCollection { * Creates a {@link XmlValidator} based on the schemas contained in this collection. * * @return a validator for this collection - * @throws IOException in case of I/O errors */ - XmlValidator createValidator() throws IOException; + XmlValidator createValidator(); } diff --git a/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchema.java b/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchema.java index ad466a28..82d7496d 100644 --- a/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchema.java +++ b/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchema.java @@ -27,6 +27,11 @@ import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; +import org.apache.ws.commons.schema.XmlSchema; +import org.apache.ws.commons.schema.XmlSchemaCollection; +import org.apache.ws.commons.schema.XmlSchemaSerializer; +import org.w3c.dom.Document; + import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.core.io.Resource; @@ -36,11 +41,6 @@ import org.springframework.xml.validation.XmlValidator; import org.springframework.xml.validation.XmlValidatorFactory; import org.springframework.xml.xsd.XsdSchema; -import org.apache.ws.commons.schema.XmlSchema; -import org.apache.ws.commons.schema.XmlSchemaCollection; -import org.apache.ws.commons.schema.XmlSchemaSerializer; -import org.w3c.dom.Document; - /** * Implementation of the {@link XsdSchema} interface that uses Apache WS-Commons XML Schema. * @@ -114,12 +114,18 @@ public class CommonsXsdSchema implements XsdSchema { return new StreamSource(bis); } - public XmlValidator createValidator() throws IOException { - Resource resource = new UrlResource(schema.getSourceURI()); - return XmlValidatorFactory.createValidator(resource, XmlValidatorFactory.SCHEMA_W3C_XML); + public XmlValidator createValidator() { + try { + Resource resource = new UrlResource(schema.getSourceURI()); + return XmlValidatorFactory + .createValidator(resource, XmlValidatorFactory.SCHEMA_W3C_XML); + } + catch (IOException ex) { + throw new CommonsXsdSchemaException(ex.getMessage(), ex); + } } - /** Returns the wrapped Commons XmlSchema object. */ + /** Returns the wrapped Commons XmlSchema object. */ public XmlSchema getSchema() { return schema; } diff --git a/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchemaCollection.java b/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchemaCollection.java index f6a3d609..aefc8390 100644 --- a/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchemaCollection.java +++ b/spring-xml/src/main/java/org/springframework/xml/xsd/commons/CommonsXsdSchemaCollection.java @@ -22,6 +22,18 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ws.commons.schema.XmlSchema; +import org.apache.ws.commons.schema.XmlSchemaCollection; +import org.apache.ws.commons.schema.XmlSchemaExternal; +import org.apache.ws.commons.schema.XmlSchemaImport; +import org.apache.ws.commons.schema.XmlSchemaInclude; +import org.apache.ws.commons.schema.XmlSchemaObject; +import org.apache.ws.commons.schema.resolver.DefaultURIResolver; +import org.apache.ws.commons.schema.resolver.URIResolver; +import org.xml.sax.InputSource; + import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; @@ -35,18 +47,6 @@ import org.springframework.xml.validation.XmlValidatorFactory; import org.springframework.xml.xsd.XsdSchema; import org.springframework.xml.xsd.XsdSchemaCollection; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.ws.commons.schema.XmlSchema; -import org.apache.ws.commons.schema.XmlSchemaCollection; -import org.apache.ws.commons.schema.XmlSchemaExternal; -import org.apache.ws.commons.schema.XmlSchemaImport; -import org.apache.ws.commons.schema.XmlSchemaInclude; -import org.apache.ws.commons.schema.XmlSchemaObject; -import org.apache.ws.commons.schema.resolver.DefaultURIResolver; -import org.apache.ws.commons.schema.resolver.URIResolver; -import org.xml.sax.InputSource; - /** * Implementation of the {@link XsdSchemaCollection} that uses Apache WS-Commons XML Schema. *

@@ -162,16 +162,21 @@ public class CommonsXsdSchemaCollection implements XsdSchemaCollection, Initiali return result; } - public XmlValidator createValidator() throws IOException { - Resource[] resources = new Resource[xmlSchemas.size()]; - for (int i = xmlSchemas.size() - 1; i >= 0; i--) { - XmlSchema xmlSchema = xmlSchemas.get(i); - String sourceUri = xmlSchema.getSourceURI(); - if (StringUtils.hasLength(sourceUri)) { - resources[i] = new UrlResource(sourceUri); - } - } - return XmlValidatorFactory.createValidator(resources, XmlValidatorFactory.SCHEMA_W3C_XML); + public XmlValidator createValidator() { + try { + Resource[] resources = new Resource[xmlSchemas.size()]; + for (int i = xmlSchemas.size() - 1; i >= 0; i--) { + XmlSchema xmlSchema = xmlSchemas.get(i); + String sourceUri = xmlSchema.getSourceURI(); + if (StringUtils.hasLength(sourceUri)) { + resources[i] = new UrlResource(sourceUri); + } + } + return XmlValidatorFactory + .createValidator(resources, XmlValidatorFactory.SCHEMA_W3C_XML); + } catch (IOException ex) { + throw new CommonsXsdSchemaException(ex.getMessage(), ex); + } } private void inlineIncludes(XmlSchema schema, Set processedIncludes, Set processedImports) { diff --git a/src/reference/docbook/server.xml b/src/reference/docbook/server.xml index afbf4b79..6e9944f1 100644 --- a/src/reference/docbook/server.xml +++ b/src/reference/docbook/server.xml @@ -148,6 +148,40 @@ that means that it looks for '/WEB-INF/spring-ws-servlet.xml'. This file will contain all of the Spring Web Services beans such as endpoints, marshallers and suchlike. + + As an alternative for web.xml, if you are running on a Servlet 3+ environment, you + can configure Spring-WS programmatically. + For this purpose, Spring-WS provides a number of abstract base classes that extend the + WebApplicationInitializer interface found in the Spring Framework. + If you are also using @Configuration classes for your bean definitions, you are + best of extending the AbstractAnnotationConfigMessageDispatcherServletInitializer, like so: + + [] getRootConfigClasses() { + return new Class[]{MyRootConfig.class}; + } + + @Override + protected Class[] getServletConfigClasses() { + return new Class[]{MyEndpointConfig.class}; + } + +}]]> + + In the example above, we tell Spring that endpoint bean definitions can be found in the MyEndpointConfig + class (which is a @Configuration class). + Other bean definitions (typically services, repositories, etc.) can be found in the MyRootConfig + class. + By default, the AbstractAnnotationConfigMessageDispatcherServletInitializer maps the servlet to + two patterns: /services and *.wsdl, though this can be changed by overriding the + getServletMappings() method. + For more details on the programmatic configuration of the MessageDispatcherServlet, refer to the + Javadoc of AbstractMessageDispatcherServletInitializer and + AbstractAnnotationConfigMessageDispatcherServletInitializer. +

Automatic WSDL exposure @@ -164,9 +198,16 @@ Take notice of the value of the 'id' attribute, because this will be used when exposing the WSDL. - ]]> + ]]> - The WSDL defined in the 'Orders.wsdl' file can then be accessed via + Or as @Bean method in a @Configuration class: + + + + The WSDL defined in the 'orders.wsdl' file on the classpath can then be accessed via GET requests to a URL of the following form (substitute the host, port and servlet context path as appropriate). @@ -174,9 +215,9 @@ All WsdlDefinition bean definitions are exposed by the - MessageDispatcherServlet under their bean id (or bean name) with the + MessageDispatcherServlet under their bean name with the suffix .wsdl. - So if the bean id is echo, the host name is "server", and the Servlet + So if the bean name is echo, the host name is "server", and the Servlet context (war name) is "spring-ws", the WSDL can be obtained via http://server/spring-ws/echo.wsdl @@ -209,6 +250,11 @@ ]]> + + If you use the AbstractAnnotationConfigMessageDispatcherServletInitializer, + enabling transformation is as simple as overriding the isTransformWsdlLocations() + method to return true. + Consult the class-level Javadoc on the WsdlDefinitionHandlerAdapter class to learn more about the whole transformation process. @@ -223,10 +269,33 @@ - + ]]> - The <dynamic-wsdl> builds a WSDL from a XSD schema by using conventions. + Or, as @Bean method: + + + + The <dynamic-wsdl> element depends on the + DefaultWsdl11Definition class. + This definition class uses WSDL providers in the + org.springframework.ws.wsdl.wsdl11.provider package and the + ProviderBasedWsdl4jDefinition + to generate a WSDL the first time it is requested. + Refer to the class-level Javadoc of these classes to see how you can extend this mechanism, + if necessary. + + + The DefaultWsdl11Definition (and therefore, the <dynamic-wsdl> tag) + builds a WSDL from a XSD schema by using conventions. It iterates over all element elements found in the schema, and creates a message for all elements. Next, it creates WSDL operation for all messages that end with the @@ -254,16 +323,6 @@ This greatly simplifies the deployment of the schemas, which still making it possible to edit them separately. - - The <dynamic-wsdl> element depends on the - DefaultWsdl11Definition class. - This definition class uses WSDL providers in the - org.springframework.ws.wsdl.wsdl11.provider package and the - ProviderBasedWsdl4jDefinition - to generate a WSDL the first time it is requested. - Refer to the class-level Javadoc of these classes to see how you can extend this mechanism, - if necessary. - Even though it can be quite handy to create the WSDL at runtime from your XSDs, there @@ -305,13 +364,13 @@ ... - - + + ]]> Note that by explicitly adding the WebServiceMessageReceiverHandlerAdapter, the dispatcher servlet does not load the default adapters, and is unable to handle standard Spring-MVC - Controllers. Therefore, we add the - SimpleControllerHandlerAdapter at the end. + @Controllers. Therefore, we add the + RequestMappingHandlerAdapter at the end. In a similar fashion, you can wire up a WsdlDefinitionHandlerAdapter to make sure @@ -382,12 +441,6 @@ ]]> - - As an alternative to the WebServiceMessageListener, Spring Web Services provides - a WebServiceMessageDrivenBean, an EJB - MessageDrivenBean. For more information on EJB, refer to the class level - Javadoc of the WebServiceMessageDrivenBean. -
Email transport @@ -700,13 +753,46 @@ public class AnnotationOrderEndpoint { xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sws="http://www.springframework.org/schema/web-services" xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/web-services - http://www.springframework.org/schema/web-services/web-services-2.0.xsd"> + http://www.springframework.org/schema/web-services/web-services.xsd"> <sws:annotation-driven /> </beans> + + + Or, if you are using @Configuration classes instead of Spring XML, you can + annotate your configuration class with @EnableWs, like so: + + @EnableWs +@Configuration +public class EchoConfig { + + // @Bean definitions go here + +} + + To customize the @EnableWs configuration, you can implement + WsConfigurer, or better yet extend the + WsConfigurerAdapter. + For instance:@Configuration +@EnableWs +@ComponentScan(basePackageClasses = { MyConfiguration.class }) +public class MyConfiguration extends WsConfigurerAdapter { + + @Override + public void addInterceptors(List<EndpointInterceptor> interceptors) { + interceptors.add(new MyInterceptor()); + } + + @Override + public void addArgumentResolvers(List<MethodArgumentResolver> argumentResolvers) { + argumentResolvers.add(new MyArgumentResolver()); + } + + // More overridden methods ... +} In the next couple of sections, a more elaborate description of the @Endpoint @@ -1258,7 +1344,7 @@ public class AnnotationOrderEndpoint { security-related SOAP headers, or the logging of request and response message. - Endpoint interceptors are typically defined by using a <sws;interceptors > + Endpoint interceptors are typically defined by using a <sws:interceptors> element in your application context. In this element, you can simply define endpoint interceptor beans that apply to all endpoints defined in that application context. @@ -1289,6 +1375,20 @@ public class AnnotationOrderEndpoint { is actually a reference to a bean definition outside of the <interceptors> element. You can use bean references anywhere inside the <interceptors> element. + + When using @Configuration classes, you can extend from + WsConfigurerAdapter to add interceptors. + Like so:@Configuration +@EnableWs +public class MyWsConfiguration extends WsConfigurerAdapter { + + @Override + public void addInterceptors(List<EndpointInterceptor> interceptors) { + interceptors.add(new MyPayloadRootInterceptor()); + } + +} + Interceptors must implement the EndpointInterceptor interface from the @@ -1328,14 +1428,16 @@ public class AnnotationOrderEndpoint { - - -]]> + ]]> Both of these interceptors have two properties: 'logRequest' and 'logResponse', which can be set to false to disable logging for either request or response messages. + + Of course, you could use the WsConfigurerAdapter approach, as described above, + for the PayloadLoggingInterceptor as well. +
<classname>PayloadValidatingInterceptor</classname> @@ -1367,6 +1469,10 @@ public class AnnotationOrderEndpoint { ]]> + + Of course, you could use the WsConfigurerAdapter approach, as described above, + for the PayloadValidatingInterceptor as well. +
<classname>PayloadTransformingInterceptor</classname> @@ -1389,6 +1495,10 @@ public class AnnotationOrderEndpoint { endpoint mapping that applies to the "old style" messages, and add the interceptor to that mapping. Hence, the transformation will apply only to these "old style" message. + + Of course, you could use the WsConfigurerAdapter approach, as described above, + for the PayloadTransformingInterceptor as well. +