From 16dfd55af083de04b95a023fe22a6284001d99c4 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 17 Dec 2010 13:41:37 +0000 Subject: [PATCH] SWS-670 - sws:interceptors --- .../InterceptorsBeanDefinitionParser.java | 125 ++++++++++++++++ .../config/WebServicesNamespaceHandler.java | 5 +- .../ws/server/SmartEndpointInterceptor.java | 37 +++++ .../DelegatingSmartEndpointInterceptor.java | 80 +++++++++++ .../PayloadRootSmartEndpointInterceptor.java | 67 +++++++++ ...stractAnnotationMethodEndpointMapping.java | 3 +- .../mapping/AbstractEndpointMapping.java | 57 +++++++- .../AbstractMapBasedEndpointMapping.java | 3 +- .../SoapActionSmartEndpointInterceptor.java | 57 ++++++++ .../ws/config/web-services-2.0.xsd | 58 ++++++++ .../InterceptorsBeanDefinitionParserTest.java | 53 +++++++ ...yloadRootSmartEndpointInterceptorTest.java | 93 ++++++++++++ .../endpoint/mapping/EndpointMappingTest.java | 133 ++++++++++-------- ...oapActionSmartEndpointInterceptorTest.java | 76 ++++++++++ .../interceptorsBeanDefinitionParserTest.xml | 22 +++ 15 files changed, 801 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/org/springframework/ws/config/InterceptorsBeanDefinitionParser.java create mode 100644 core/src/main/java/org/springframework/ws/server/SmartEndpointInterceptor.java create mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/interceptor/DelegatingSmartEndpointInterceptor.java create mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptor.java create mode 100644 core/src/main/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptor.java create mode 100644 core/src/test/java/org/springframework/ws/config/InterceptorsBeanDefinitionParserTest.java create mode 100644 core/src/test/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptorTest.java create mode 100644 core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptorTest.java create mode 100644 core/src/test/resources/org/springframework/ws/config/interceptorsBeanDefinitionParserTest.xml diff --git a/core/src/main/java/org/springframework/ws/config/InterceptorsBeanDefinitionParser.java b/core/src/main/java/org/springframework/ws/config/InterceptorsBeanDefinitionParser.java new file mode 100644 index 00000000..65a83245 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/config/InterceptorsBeanDefinitionParser.java @@ -0,0 +1,125 @@ +/* + * Copyright 2005-2010 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; + +import java.util.List; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.xml.DomUtils; +import org.springframework.ws.server.SmartEndpointInterceptor; +import org.springframework.ws.server.endpoint.interceptor.DelegatingSmartEndpointInterceptor; +import org.springframework.ws.server.endpoint.interceptor.PayloadRootSmartEndpointInterceptor; +import org.springframework.ws.soap.server.endpoint.interceptor.SoapActionSmartEndpointInterceptor; + +import org.w3c.dom.Element; + +/** + * Parser for the {@code <sws:interceptors/>} element. + * + * @author Arjen Poutsma + * @since 2.0 + */ +class InterceptorsBeanDefinitionParser implements BeanDefinitionParser { + + public BeanDefinition parse(Element element, ParserContext parserContext) { + CompositeComponentDefinition compDefinition = + new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); + parserContext.pushContainingComponent(compDefinition); + + List childElements = DomUtils.getChildElements(element); + for (Element childElement : childElements) { + if ("bean".equals(childElement.getLocalName())) { + RootBeanDefinition smartInterceptorDef = + createSmartInterceptorDefinition(DelegatingSmartEndpointInterceptor.class, childElement, + parserContext); + BeanDefinitionHolder interceptorDef = createInterceptorDefinition(parserContext, childElement); + + smartInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, interceptorDef); + + registerSmartInterceptor(parserContext, smartInterceptorDef); + } + else if ("payloadRoot".equals(childElement.getLocalName())) { + List beanElements = DomUtils.getChildElementsByTagName(childElement, "bean"); + for (Element beanElement : beanElements) { + RootBeanDefinition smartInterceptorDef = + createSmartInterceptorDefinition(PayloadRootSmartEndpointInterceptor.class, childElement, + parserContext); + BeanDefinitionHolder interceptorDef = createInterceptorDefinition(parserContext, beanElement); + + String namespaceUri = childElement.getAttribute("namespaceUri"); + String localPart = childElement.getAttribute("localPart"); + + smartInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, interceptorDef); + smartInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, namespaceUri); + smartInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, localPart); + + registerSmartInterceptor(parserContext, smartInterceptorDef); + } + } + else if ("soapAction".equals(childElement.getLocalName())) { + List beanElements = DomUtils.getChildElementsByTagName(childElement, "bean"); + for (Element beanElement : beanElements) { + RootBeanDefinition smartInterceptorDef = + createSmartInterceptorDefinition(SoapActionSmartEndpointInterceptor.class, childElement, + parserContext); + BeanDefinitionHolder interceptorDef = createInterceptorDefinition(parserContext, beanElement); + + String soapAction = childElement.getAttribute("value"); + + smartInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, interceptorDef); + smartInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, soapAction); + + registerSmartInterceptor(parserContext, smartInterceptorDef); + } + } + } + + parserContext.popAndRegisterContainingComponent(); + return null; + } + + private void registerSmartInterceptor(ParserContext parserContext, RootBeanDefinition smartInterceptorDef) { + String mappedInterceptorName = + parserContext.getReaderContext().registerWithGeneratedName(smartInterceptorDef); + parserContext + .registerComponent(new BeanComponentDefinition(smartInterceptorDef, mappedInterceptorName)); + } + + private BeanDefinitionHolder createInterceptorDefinition(ParserContext parserContext, Element element) { + BeanDefinitionHolder interceptorDef = + parserContext.getDelegate().parseBeanDefinitionElement(element); + interceptorDef = + parserContext.getDelegate().decorateBeanDefinitionIfRequired(element, interceptorDef); + return interceptorDef; + } + + private RootBeanDefinition createSmartInterceptorDefinition(Class interceptorClass, + Element element, + ParserContext parserContext) { + RootBeanDefinition smartInterceptorDef = new RootBeanDefinition(interceptorClass); + smartInterceptorDef.setSource(parserContext.extractSource(element)); + smartInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + return smartInterceptorDef; + } + +} diff --git a/core/src/main/java/org/springframework/ws/config/WebServicesNamespaceHandler.java b/core/src/main/java/org/springframework/ws/config/WebServicesNamespaceHandler.java index 71071cc2..eca24e15 100644 --- a/core/src/main/java/org/springframework/ws/config/WebServicesNamespaceHandler.java +++ b/core/src/main/java/org/springframework/ws/config/WebServicesNamespaceHandler.java @@ -29,9 +29,10 @@ public class WebServicesNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); - registerBeanDefinitionParser("marshalling-endpoints", new MarshallingEndpointsBeanDefinitionParser()); - registerBeanDefinitionParser("xpath-endpoints", new XPathEndpointsBeanDefinitionParser()); + registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("static-wsdl", new StaticWsdlBeanDefinitionParser()); registerBeanDefinitionParser("dynamic-wsdl", new DynamicWsdlBeanDefinitionParser()); + registerBeanDefinitionParser("marshalling-endpoints", new MarshallingEndpointsBeanDefinitionParser()); + registerBeanDefinitionParser("xpath-endpoints", new XPathEndpointsBeanDefinitionParser()); } } diff --git a/core/src/main/java/org/springframework/ws/server/SmartEndpointInterceptor.java b/core/src/main/java/org/springframework/ws/server/SmartEndpointInterceptor.java new file mode 100644 index 00000000..0a7d9e7f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/SmartEndpointInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2010 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.server; + +import org.springframework.ws.context.MessageContext; + +/** + * Extension of the {@link EndpointInterceptor} interface that adds a way to + * @author Arjen Poutsma + * @since 2.0 + */ +public interface SmartEndpointInterceptor extends EndpointInterceptor { + + /** + * Indicates whether this interceptor should intercept the given message context. + * + * @param messageContext contains the incoming request message + * @param endpoint chosen endpoint to invoke + * @return {@code true} to indicate that this interceptor applies; {@code false} otherwise + */ + boolean shouldIntercept(MessageContext messageContext, Object endpoint); + +} diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/interceptor/DelegatingSmartEndpointInterceptor.java b/core/src/main/java/org/springframework/ws/server/endpoint/interceptor/DelegatingSmartEndpointInterceptor.java new file mode 100644 index 00000000..e05b7e85 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/interceptor/DelegatingSmartEndpointInterceptor.java @@ -0,0 +1,80 @@ +/* + * Copyright 2005-2010 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.server.endpoint.interceptor; + +import org.springframework.util.Assert; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.SmartEndpointInterceptor; + +/** + * Implementation of the {@link SmartEndpointInterceptor} interface that delegates to a delegate {@link + * EndpointInterceptor}. + * + * @author Arjen Poutsma + * @since 2.0 + */ +public class DelegatingSmartEndpointInterceptor implements SmartEndpointInterceptor { + + private final EndpointInterceptor delegate; + + /** + * Creates a new instance of the {@code DelegatingSmartEndpointInterceptor} with the given delegate. + * + * @param delegate the endpoint interceptor to delegate to. + */ + public DelegatingSmartEndpointInterceptor(EndpointInterceptor delegate) { + Assert.notNull(delegate, "'delegate' must not be null"); + this.delegate = delegate; + } + + /** + * {@inheritDoc} + *

+ * This implementation delegates to {@link #shouldIntercept(WebServiceMessage, Object)}. + */ + public boolean shouldIntercept(MessageContext messageContext, Object endpoint) { + WebServiceMessage request = messageContext.getRequest(); + return request != null && shouldIntercept(request, endpoint); + } + + /** + * Indicates whether this interceptor should intercept the given request message. + *

+ * This implementation always returns {@code true}. + * + * @param request the request message + * @param endpoint chosen endpoint to invoke + * @return {@code true} to indicate that this interceptor applies; {@code false} otherwise + */ + protected boolean shouldIntercept(WebServiceMessage request, Object endpoint) { + return true; + } + + public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { + return delegate.handleRequest(messageContext, endpoint); + } + + public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { + return delegate.handleResponse(messageContext, endpoint); + } + + public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { + return delegate.handleFault(messageContext, endpoint); + } +} diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptor.java b/core/src/main/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptor.java new file mode 100644 index 00000000..f8a40e37 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2010 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.server.endpoint.interceptor; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.support.PayloadRootUtils; +import org.springframework.xml.transform.TransformerHelper; + +/** + * Implementation of the {@link org.springframework.ws.server.SmartEndpointInterceptor} interface that only intercepts + * requests that have a specified namespace URI or local part (or both) as payload root. + * + * @author Arjen Poutsma + * @since 2.0 + */ +public class PayloadRootSmartEndpointInterceptor extends DelegatingSmartEndpointInterceptor { + + private TransformerHelper transformerHelper = new TransformerHelper(); + + private final String namespaceUri; + + private final String localPart; + + public PayloadRootSmartEndpointInterceptor(EndpointInterceptor delegate, String namespaceUri, String localPart) { + super(delegate); + Assert.hasLength(namespaceUri, "namespaceUri can not be empty"); + this.namespaceUri = namespaceUri; + this.localPart = localPart; + } + + public void setTransformerHelper(TransformerHelper transformerHelper) { + this.transformerHelper = transformerHelper; + } + + @Override + protected boolean shouldIntercept(WebServiceMessage request, Object endpoint) { + try { + QName payloadRootName = PayloadRootUtils.getPayloadRootQName(request.getPayloadSource(), transformerHelper); + return !(StringUtils.hasLength(namespaceUri) && !namespaceUri.equals(payloadRootName.getNamespaceURI()) || + StringUtils.hasLength(localPart) && !localPart.equals(payloadRootName.getLocalPart())); + + } + catch (TransformerException e) { + return false; + } + } +} diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java index f4963251..2acacef9 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractAnnotationMethodEndpointMapping.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -57,6 +57,7 @@ public abstract class AbstractAnnotationMethodEndpointMapping extends Abstrac @Override protected final void initApplicationContext() throws BeansException { + super.initApplicationContext(); if (logger.isDebugEnabled()) { logger.debug("Looking for endpoints in application context: " + getApplicationContext()); } diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractEndpointMapping.java index e42148d4..dd8c7146 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractEndpointMapping.java @@ -1,11 +1,11 @@ /* - * Copyright 2005 the original author or authors. + * Copyright 2005-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,12 +16,20 @@ package org.springframework.ws.server.endpoint.mapping; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.support.ApplicationObjectSupport; import org.springframework.core.Ordered; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.EndpointInterceptor; import org.springframework.ws.server.EndpointInvocationChain; import org.springframework.ws.server.EndpointMapping; +import org.springframework.ws.server.SmartEndpointInterceptor; /** * Abstract base class for EndpointMapping implementations. Supports a default endpoint, and endpoint interceptors. @@ -39,6 +47,8 @@ public abstract class AbstractEndpointMapping extends ApplicationObjectSupport i private EndpointInterceptor[] interceptors; + private SmartEndpointInterceptor[] smartInterceptors; + /** * Returns the the endpoint interceptors to apply to all endpoints mapped by this endpoint mapping. * @@ -72,6 +82,30 @@ public abstract class AbstractEndpointMapping extends ApplicationObjectSupport i this.order = order; } + /** + * Initializes the interceptors. + * + * @see #initInterceptors() + */ + @Override + protected void initApplicationContext() throws BeansException { + initInterceptors(); + } + + /** + * Initialize the specified interceptors, adapting them where necessary. + * + * @see #setInterceptors + */ + protected void initInterceptors() { + Map smartInterceptors = BeanFactoryUtils + .beansOfTypeIncludingAncestors(getApplicationContext(), SmartEndpointInterceptor.class, true, false); + if (!smartInterceptors.isEmpty()) { + this.smartInterceptors = + smartInterceptors.values().toArray(new SmartEndpointInterceptor[smartInterceptors.size()]); + } + } + /** * Look up an endpoint for the given message context, falling back to the default endpoint if no specific one is * found. @@ -94,7 +128,22 @@ public abstract class AbstractEndpointMapping extends ApplicationObjectSupport i return null; } } - return createEndpointInvocationChain(messageContext, endpoint, interceptors); + + List interceptors = new ArrayList(); + if (this.interceptors != null) { + interceptors.addAll(Arrays.asList(this.interceptors)); + } + + if (this.smartInterceptors != null) { + for (SmartEndpointInterceptor smartInterceptor : smartInterceptors) { + if (smartInterceptor.shouldIntercept(messageContext, endpoint)) { + interceptors.add(smartInterceptor); + } + } + } + + return createEndpointInvocationChain(messageContext, endpoint, + interceptors.toArray(new EndpointInterceptor[interceptors.size()])); } /** @@ -139,7 +188,7 @@ public abstract class AbstractEndpointMapping extends ApplicationObjectSupport i * context. * * @param endpointName the endpoint name - * @return the resolved enpoint, or null if the name could not be resolved + * @return the resolved endpoint, or null if the name could not be resolved */ protected Object resolveStringEndpoint(String endpointName) { if (getApplicationContext().containsBean(endpointName)) { diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMapBasedEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMapBasedEndpointMapping.java index 173a5801..0f50ae18 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMapBasedEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMapBasedEndpointMapping.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -167,6 +167,7 @@ public abstract class AbstractMapBasedEndpointMapping extends AbstractEndpointMa */ @Override protected final void initApplicationContext() throws BeansException { + super.initApplicationContext(); for (String key : temporaryEndpointMap.keySet()) { Object endpoint = temporaryEndpointMap.get(key); if (!validateLookupKey(key)) { diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptor.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptor.java new file mode 100644 index 00000000..16e4f596 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2010 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.soap.server.endpoint.interceptor; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.interceptor.DelegatingSmartEndpointInterceptor; +import org.springframework.ws.soap.SoapMessage; + +/** + * Implementation of the {@link org.springframework.ws.server.SmartEndpointInterceptor} interface that only intercepts + * requests that have a specified soap action. + * + * @author Arjen Poutsma + * @since 2.0 + */ +public class SoapActionSmartEndpointInterceptor extends DelegatingSmartEndpointInterceptor { + + private final String soapAction; + + public SoapActionSmartEndpointInterceptor(EndpointInterceptor delegate, String soapAction) { + super(delegate); + Assert.hasLength(soapAction, "soapAction can not be empty"); + this.soapAction = soapAction; + } + + @Override + protected boolean shouldIntercept(WebServiceMessage request, Object endpoint) { + if (request instanceof SoapMessage) { + String soapAction = ((SoapMessage) request).getSoapAction(); + if (StringUtils.hasLength(soapAction) && soapAction.charAt(0) == '"' && + soapAction.charAt(soapAction.length() - 1) == '"') { + soapAction = soapAction.substring(1, soapAction.length() - 1); + } + return this.soapAction.equals(soapAction); + } + else { + return false; + } + } +} diff --git a/core/src/main/resources/org/springframework/ws/config/web-services-2.0.xsd b/core/src/main/resources/org/springframework/ws/config/web-services-2.0.xsd index 2999a1f6..e88c7bef 100644 --- a/core/src/main/resources/org/springframework/ws/config/web-services-2.0.xsd +++ b/core/src/main/resources/org/springframework/ws/config/web-services-2.0.xsd @@ -53,6 +53,64 @@ + + + + The ordered set of interceptors that intercept web service messages handled by endpoints. + Interceptors allow requests to be pre/post processed before/after handling. + Each interceptor must implement the org.springframework.ws.server.EndpointInterceptor interface. + The interceptors in this set are automatically configured on each registered EndpointMapping. + + + + + + + + Registers an interceptor that intercepts every request. + + + + + + + + + + + + The interceptor's bean definition. + + + + + + + + + + + + + + + + The interceptor's bean definition. + + + + + + + + + + result = applicationContext.getBeansOfType(DelegatingSmartEndpointInterceptor.class); + assertEquals("no smart interceptors found", 5, result.size()); + + result = applicationContext.getBeansOfType(PayloadRootSmartEndpointInterceptor.class); + assertEquals("no interceptors found", 2, result.size()); + + result = applicationContext.getBeansOfType(SoapActionSmartEndpointInterceptor.class); + assertEquals("no interceptors found", 2, result.size()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptorTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptorTest.java new file mode 100644 index 00000000..22b827d8 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/server/endpoint/interceptor/PayloadRootSmartEndpointInterceptorTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2005-2010 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.server.endpoint.interceptor; + +import org.springframework.ws.MockWebServiceMessage; +import org.springframework.ws.MockWebServiceMessageFactory; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PayloadRootSmartEndpointInterceptorTest { + + private EndpointInterceptor delegate; + + private String namespaceUri; + + private String localPart; + + private MessageContext messageContext; + + @Before + public void setUp() { + delegate = new EndpointInterceptorAdapter(); + + namespaceUri = "http://springframework.org/spring-ws"; + localPart = "element"; + + MockWebServiceMessage request = new MockWebServiceMessage("<" + localPart + " xmlns=\"" + namespaceUri + "\" />"); + messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory()); + } + + @Test(expected = IllegalArgumentException.class) + public void neitherNamespaceNorLocalPart() { + new PayloadRootSmartEndpointInterceptor(delegate, null, null); + } + + @Test + public void shouldInterceptFullMatch() throws Exception { + PayloadRootSmartEndpointInterceptor interceptor = + new PayloadRootSmartEndpointInterceptor(delegate, namespaceUri, localPart); + + boolean result = interceptor.shouldIntercept(messageContext, null); + assertTrue("Interceptor should apply", result); + } + + @Test + public void shouldInterceptFullNonMatch() throws Exception { + PayloadRootSmartEndpointInterceptor interceptor = + new PayloadRootSmartEndpointInterceptor(delegate, "http://springframework.org/other", localPart); + + boolean result = interceptor.shouldIntercept(messageContext, null); + assertFalse("Interceptor should apply", result); + } + + @Test + public void shouldInterceptNamespaceUriMatch() throws Exception { + PayloadRootSmartEndpointInterceptor interceptor = + new PayloadRootSmartEndpointInterceptor(delegate, namespaceUri, null); + + boolean result = interceptor.shouldIntercept(messageContext, null); + assertTrue("Interceptor should apply", result); + } + + @Test + public void shouldInterceptLocalPartMatch() throws Exception { + PayloadRootSmartEndpointInterceptor interceptor = + new PayloadRootSmartEndpointInterceptor(delegate, null, localPart); + + boolean result = interceptor.shouldIntercept(messageContext, null); + assertTrue("Interceptor should apply", result); + } + +} diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/mapping/EndpointMappingTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/mapping/EndpointMappingTest.java index 9a591eaa..07df9f2b 100644 --- a/core/src/test/java/org/springframework/ws/server/endpoint/mapping/EndpointMappingTest.java +++ b/core/src/test/java/org/springframework/ws/server/endpoint/mapping/EndpointMappingTest.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,90 +17,108 @@ package org.springframework.ws.server.endpoint.mapping; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.ws.MockWebServiceMessageFactory; +import org.springframework.ws.context.DefaultMessageContext; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.EndpointInterceptor; import org.springframework.ws.server.EndpointInvocationChain; +import org.springframework.ws.server.endpoint.interceptor.DelegatingSmartEndpointInterceptor; import org.springframework.ws.server.endpoint.interceptor.EndpointInterceptorAdapter; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; +/** + * Test case for {@link AbstractEndpointMapping}. + */ public class EndpointMappingTest { - private MessageContext mockContext; + private MessageContext messageContext; @Before public void setUp() throws Exception { - mockContext = createMock(MessageContext.class); + messageContext = new DefaultMessageContext(new MockWebServiceMessageFactory()); } @Test - public void testDefaultEndpoint() throws Exception { + public void defaultEndpoint() throws Exception { Object defaultEndpoint = new Object(); AbstractEndpointMapping mapping = new AbstractEndpointMapping() { @Override protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { - Assert.assertEquals("Invalid request passed", mockContext, givenRequest); + assertEquals("Invalid request passed", messageContext, givenRequest); return null; } }; mapping.setDefaultEndpoint(defaultEndpoint); - replay(mockContext); - - EndpointInvocationChain result = mapping.getEndpoint(mockContext); - Assert.assertNotNull("No EndpointInvocatioChain returned", result); - Assert.assertEquals("Default Endpoint not returned", defaultEndpoint, result.getEndpoint()); - - verify(mockContext); + EndpointInvocationChain result = mapping.getEndpoint(messageContext); + assertNotNull("No EndpointInvocatioChain returned", result); + assertEquals("Default Endpoint not returned", defaultEndpoint, result.getEndpoint()); } @Test - public void testEndpoint() throws Exception { + public void endpoint() throws Exception { final Object endpoint = new Object(); AbstractEndpointMapping mapping = new AbstractEndpointMapping() { @Override protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { - Assert.assertEquals("Invalid request passed", mockContext, givenRequest); + assertEquals("Invalid request passed", messageContext, givenRequest); return endpoint; } }; - replay(mockContext); - EndpointInvocationChain result = mapping.getEndpoint(mockContext); - Assert.assertNotNull("No EndpointInvocatioChain returned", result); - Assert.assertEquals("Unexpected Endpoint returned", endpoint, result.getEndpoint()); - - verify(mockContext); + EndpointInvocationChain result = mapping.getEndpoint(messageContext); + assertNotNull("No EndpointInvocationChain returned", result); + assertEquals("Unexpected Endpoint returned", endpoint, result.getEndpoint()); } @Test - public void testEndpointInterceptors() throws Exception { + public void endpointInterceptors() throws Exception { final Object endpoint = new Object(); EndpointInterceptor interceptor = new EndpointInterceptorAdapter(); AbstractEndpointMapping mapping = new AbstractEndpointMapping() { @Override protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { - Assert.assertEquals("Invalid request passed", mockContext, givenRequest); + assertEquals("Invalid request passed", messageContext, givenRequest); return endpoint; } }; - replay(mockContext); - mapping.setInterceptors(new EndpointInterceptor[]{interceptor}); - EndpointInvocationChain result = mapping.getEndpoint(mockContext); - Assert.assertEquals("Unexpected amount of EndpointInterceptors returned", 1, result.getInterceptors().length); - Assert.assertEquals("Unexpected EndpointInterceptor returned", interceptor, result.getInterceptors()[0]); - - verify(mockContext); + EndpointInvocationChain result = mapping.getEndpoint(messageContext); + assertEquals("Unexpected amount of EndpointInterceptors returned", 1, result.getInterceptors().length); + assertEquals("Unexpected EndpointInterceptor returned", interceptor, result.getInterceptors()[0]); } @Test - public void testEndpointBeanName() throws Exception { + public void smartEndpointInterceptors() throws Exception { + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.registerSingleton("smartInterceptor", MySmartEndpointInterceptor.class); + + final Object endpoint = new Object(); + EndpointInterceptor interceptor = new EndpointInterceptorAdapter(); + AbstractEndpointMapping mapping = new AbstractEndpointMapping() { + @Override + protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { + assertEquals("Invalid request passed", messageContext, givenRequest); + return endpoint; + } + }; + mapping.setApplicationContext(applicationContext); + mapping.setInterceptors(new EndpointInterceptor[]{interceptor}); + + EndpointInvocationChain result = mapping.getEndpoint(messageContext); + assertEquals("Unexpected amount of EndpointInterceptors returned", 2, result.getInterceptors().length); + assertEquals("Unexpected EndpointInterceptor returned", interceptor, result.getInterceptors()[0]); + assertTrue("Unexpected EndpointInterceptor returned", + result.getInterceptors()[1] instanceof MySmartEndpointInterceptor); + } + + @Test + public void endpointBeanName() throws Exception { StaticApplicationContext applicationContext = new StaticApplicationContext(); applicationContext.registerSingleton("endpoint", Object.class); @@ -108,22 +126,18 @@ public class EndpointMappingTest { @Override protected Object getEndpointInternal(MessageContext message) throws Exception { - Assert.assertEquals("Invalid request", mockContext, message); + assertEquals("Invalid request", messageContext, message); return "endpoint"; } }; mapping.setApplicationContext(applicationContext); - replay(mockContext); - - EndpointInvocationChain result = mapping.getEndpoint(mockContext); - Assert.assertNotNull("No endpoint returned", result); - - verify(mockContext); + EndpointInvocationChain result = mapping.getEndpoint(messageContext); + assertNotNull("No endpoint returned", result); } @Test - public void testEndpointInvalidBeanName() throws Exception { + public void endpointInvalidBeanName() throws Exception { StaticApplicationContext applicationContext = new StaticApplicationContext(); applicationContext.registerSingleton("endpoint", Object.class); @@ -131,23 +145,19 @@ public class EndpointMappingTest { @Override protected Object getEndpointInternal(MessageContext message) throws Exception { - Assert.assertEquals("Invalid request", mockContext, message); + assertEquals("Invalid request", messageContext, message); return "noSuchBean"; } }; mapping.setApplicationContext(applicationContext); - replay(mockContext); + EndpointInvocationChain result = mapping.getEndpoint(messageContext); - EndpointInvocationChain result = mapping.getEndpoint(mockContext); - - Assert.assertNull("No endpoint returned", result); - - verify(mockContext); + assertNull("No endpoint returned", result); } @Test - public void testEndpointPrototype() throws Exception { + public void endpointPrototype() throws Exception { StaticApplicationContext applicationContext = new StaticApplicationContext(); applicationContext.registerPrototype("endpoint", MyEndpoint.class); @@ -155,29 +165,32 @@ public class EndpointMappingTest { @Override protected Object getEndpointInternal(MessageContext message) throws Exception { - Assert.assertEquals("Invalid request", mockContext, message); + assertEquals("Invalid request", messageContext, message); return "endpoint"; } }; mapping.setApplicationContext(applicationContext); - replay(mockContext); - - EndpointInvocationChain result = mapping.getEndpoint(mockContext); - Assert.assertNotNull("No endpoint returned", result); - result = mapping.getEndpoint(mockContext); - Assert.assertNotNull("No endpoint returned", result); - Assert.assertEquals("Prototype endpoint was not constructed twice", 2, MyEndpoint.constrCount); - - verify(mockContext); + EndpointInvocationChain result = mapping.getEndpoint(messageContext); + assertNotNull("No endpoint returned", result); + result = mapping.getEndpoint(messageContext); + assertNotNull("No endpoint returned", result); + assertEquals("Prototype endpoint was not constructed twice", 2, MyEndpoint.constructorCount); } private static class MyEndpoint { - private static int constrCount; + private static int constructorCount; private MyEndpoint() { - constrCount++; + constructorCount++; + } + } + + private static class MySmartEndpointInterceptor extends DelegatingSmartEndpointInterceptor { + + private MySmartEndpointInterceptor() { + super(new EndpointInterceptorAdapter()); } } diff --git a/core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptorTest.java b/core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptorTest.java new file mode 100644 index 00000000..cb9f0150 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/SoapActionSmartEndpointInterceptorTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2010 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.soap.server.endpoint.interceptor; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.interceptor.EndpointInterceptorAdapter; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SoapActionSmartEndpointInterceptorTest { + + private EndpointInterceptor delegate; + + private String soapAction; + + private MessageContext messageContext; + + @Before + public void setUp() { + delegate = new EndpointInterceptorAdapter(); + + soapAction = "http://springframework.org/spring-ws"; + + SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory(); + messageFactory.afterPropertiesSet(); + SaajSoapMessage request = messageFactory.createWebServiceMessage(); + request.setSoapAction(soapAction); + messageContext = new DefaultMessageContext(request, messageFactory); + } + + @Test(expected = IllegalArgumentException.class) + public void neitherNamespaceNorLocalPart() { + new SoapActionSmartEndpointInterceptor(delegate, null); + } + + @Test + public void shouldInterceptMatch() throws Exception { + SoapActionSmartEndpointInterceptor interceptor = new SoapActionSmartEndpointInterceptor(delegate, soapAction); + + boolean result = interceptor.shouldIntercept(messageContext, null); + assertTrue("Interceptor should apply", result); + } + + @Test + public void shouldInterceptNonMatch() throws Exception { + SoapActionSmartEndpointInterceptor interceptor = + new SoapActionSmartEndpointInterceptor(delegate, "http://springframework.org/other"); + + boolean result = interceptor.shouldIntercept(messageContext, null); + assertFalse("Interceptor should apply", result); + } + + +} diff --git a/core/src/test/resources/org/springframework/ws/config/interceptorsBeanDefinitionParserTest.xml b/core/src/test/resources/org/springframework/ws/config/interceptorsBeanDefinitionParserTest.xml new file mode 100644 index 00000000..040f2091 --- /dev/null +++ b/core/src/test/resources/org/springframework/ws/config/interceptorsBeanDefinitionParserTest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + +