From 7ff820a91ca0d92cc9f3f22c6fc4c4d103f4dbeb Mon Sep 17 00:00:00 2001 From: Tareq Abedrabbo Date: Sat, 18 Sep 2010 18:29:29 +0000 Subject: [PATCH] SWS-642 - Support resolving SOAP header elements in @Endpoint method parameters --- ...apHeaderElementMethodArgumentResolver.java | 132 ++++++++++++++++ .../endpoint/annotation/SoapHeader.java | 40 +++++ ...aderElementMethodArgumentResolverTest.java | 142 ++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 core/src/main/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolver.java create mode 100644 core/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapHeader.java create mode 100644 core/src/test/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolverTest.java diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolver.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolver.java new file mode 100644 index 00000000..a270b3c6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolver.java @@ -0,0 +1,132 @@ +/* + * 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.adapter.method; + +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.server.endpoint.annotation.SoapHeader; +import org.springframework.xml.namespace.QNameUtils; + +import javax.xml.namespace.QName; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Implementation of {@link MethodArgumentResolver} that supports resolving {@link SoapHeaderElement} parameters. + * Target method parameters must be annotated with {@link SoapHeader} to indicate the SOAP header to resolve. + * This resolver supports simple {@link SoapHeaderElement} parameters and {@link List} parameters for elements that + * appear multiple times in the same SOAP header. + *

+ * The following snippet shows an example of supported declarations. + *
+ * {@code
+ * public void soapHeaderElement(@SoapHeader("{http://springframework.org/ws}header") SoapHeaderElement element)
+ * 

+ * public void soapHeaderElementList(@SoapHeader("{http://springframework.org/ws}header") List elements) + *

+ * + * @author Tareq Abedrabbo + * @see SoapHeader + * @since 2.0 + */ +public class SoapHeaderElementMethodArgumentResolver implements MethodArgumentResolver { + + public boolean supportsParameter(MethodParameter parameter) { + SoapHeader soapHeader = parameter.getParameterAnnotation(SoapHeader.class); + if (soapHeader == null) { + return false; + } + + Class type = parameter.getParameterType(); + + // Simple SoapHeaderElement parameter + if (SoapHeaderElement.class.equals(type)) { + return true; + } + + // List parameter + if (List.class.equals(type)) { + Type genericType = parameter.getGenericParameterType(); + if (genericType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + Type[] types = parameterizedType.getActualTypeArguments(); + if (types.length != 1) { + return false; + } + if (SoapHeaderElement.class.equals(types[0])) { + return true; + } + } + } + return false; + } + + + public Object resolveArgument(MessageContext messageContext, MethodParameter parameter) throws Exception { + Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); + SoapMessage request = (SoapMessage) messageContext.getRequest(); + org.springframework.ws.soap.SoapHeader soapHeader = request.getSoapHeader(); + + String qnameString = parameter.getParameterAnnotation(SoapHeader.class).value(); + + if (!QNameUtils.validateQName(qnameString)) { + throw new IllegalArgumentException("Invalid header qualified name [" + qnameString + "]. QName must be of the form '{namespace}localPart'."); + } + + QName qname = QName.valueOf(qnameString); + + Class parameterType = parameter.getParameterType(); + + if (SoapHeaderElement.class.equals(parameterType)) { + return extractSoapHeader(qname, soapHeader); + } else if (List.class.equals(parameterType)) { + return extractSoapHeaderList(qname, soapHeader); + } + // should not happen + throw new UnsupportedOperationException(); + } + + private SoapHeaderElement extractSoapHeader(QName qname, org.springframework.ws.soap.SoapHeader soapHeader) { + Iterator elements = soapHeader.examineAllHeaderElements(); + while (elements.hasNext()) { + SoapHeaderElement e = elements.next(); + if (e.getName().equals(qname)) { + return e; + } + } + return null; + } + + private List extractSoapHeaderList(QName qname, org.springframework.ws.soap.SoapHeader soapHeader) { + List result = new ArrayList(); + Iterator elements = soapHeader.examineAllHeaderElements(); + while (elements.hasNext()) { + SoapHeaderElement e = elements.next(); + if (e.getName().equals(qname)) { + result.add(e); + } + } + return result; + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapHeader.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapHeader.java new file mode 100644 index 00000000..14e108c8 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapHeader.java @@ -0,0 +1,40 @@ +/* + * 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.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; + +/** + * Marks an endpoint method parameter to be resolved as a single or a list of {@link org.springframework.ws.soap.SoapHeaderElement}. + * + * @author Tareq Abedrabbo + * @since 2.0 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SoapHeader { + /** + * The qualified name of the soap header. The format used is that of {@link javax.xml.namespace.QName#toString()}, i.e. + * "{" + Namespace URI + "}" + local part, where the namespace is optional. + */ + String value(); +} diff --git a/core/src/test/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolverTest.java b/core/src/test/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolverTest.java new file mode 100644 index 00000000..c83a0f23 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/server/endpoint/adapter/method/SoapHeaderElementMethodArgumentResolverTest.java @@ -0,0 +1,142 @@ +/* + * 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.adapter.method; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.adapter.method.AbstractMethodArgumentResolverTestCase; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.server.endpoint.annotation.SoapHeader; + +import javax.xml.namespace.QName; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * @author Tareq Abedrabbo + */ +public class SoapHeaderElementMethodArgumentResolverTest extends AbstractMethodArgumentResolverTestCase { + + private static final QName HEADER_QNAME = new QName(NAMESPACE_URI, "header"); + + private static final String HEADER_CONTENT = "content"; + + private SoapHeaderElementMethodArgumentResolver resolver; + + private MessageContext messageContext; + + private MethodParameter soapHeaderWithEmptyValue; + + private MethodParameter soapHeaderElementParameter; + + private MethodParameter soapHeaderElementListParameter; + + private MethodParameter soapHeaderMismatch; + + private MethodParameter soapHeaderMismatchList; + + @Before + public void setUp() throws Exception { + resolver = new SoapHeaderElementMethodArgumentResolver(); + messageContext = createSaajMessageContext(); + SoapMessage message = (SoapMessage) messageContext.getRequest(); + for (int i = 0; i < 3; i++) { + SoapHeaderElement element = message.getSoapHeader().addHeaderElement(HEADER_QNAME); + element.setText(HEADER_CONTENT + i); + } + soapHeaderWithEmptyValue = new MethodParameter(getClass().getMethod("soapHeaderWithEmptyValue", SoapHeaderElement.class), 0); + soapHeaderElementParameter = new MethodParameter(getClass().getMethod("soapHeaderElement", SoapHeaderElement.class), 0); + soapHeaderElementListParameter = new MethodParameter(getClass().getMethod("soapHeaderElementList", List.class), 0); + soapHeaderMismatch = new MethodParameter(getClass().getMethod("soapHeaderMismatch", SoapHeaderElement.class), 0); + soapHeaderMismatchList = new MethodParameter(getClass().getMethod("soapHeaderMismatchList", List.class), 0); + } + + @Test + public void supportsParameter() throws Exception { + assertTrue("resolver does not support soapHeaderElement", resolver.supportsParameter(soapHeaderElementParameter)); + assertTrue("resolver does not support List", resolver.supportsParameter(soapHeaderElementListParameter)); + } + + @Test(expected = IllegalArgumentException.class) + public void failOnEmptyValue() throws Exception { + resolver.resolveArgument(messageContext, soapHeaderWithEmptyValue); + } + + @Test + public void resolveSoapHeaderElement() throws Exception { + Object result = resolver.resolveArgument(messageContext, soapHeaderElementParameter); + + assertTrue("result must be a SoapHeaderElement", SoapHeaderElement.class.isAssignableFrom(result.getClass())); + + SoapHeaderElement element = (SoapHeaderElement) result; + + assertTrue("headers must be equal", element.getName().equals(HEADER_QNAME)); + assertEquals("header text must be equal to [" + HEADER_CONTENT + "0]", HEADER_CONTENT + "0", element.getText()); + } + + + @Test + public void resolveSoapHeaderElementList() throws Exception { + Object result = resolver.resolveArgument(messageContext, soapHeaderElementListParameter); + + assertTrue("result must be a List", List.class.isAssignableFrom(result.getClass())); + + List elements = (List) result; + + assertTrue("size", elements.size() > 1); + for (int i = 0; i < elements.size(); i++) { + SoapHeaderElement element = elements.get(i); + assertTrue("headers must be equal", element.getName().equals(HEADER_QNAME)); + assertEquals("header must be equal to [" + HEADER_CONTENT + i + "]", HEADER_CONTENT + i, elements.get(i).getText()); + } + } + + @Test + public void resolveSoapHeaderMismatch() throws Exception { + Object result = resolver.resolveArgument(messageContext, soapHeaderMismatch); + assertNull(result); + } + + @Test + public void resolveSoapHeaderMismatchList() throws Exception { + Object result = resolver.resolveArgument(messageContext, soapHeaderMismatchList); + assertTrue("result must be a List", List.class.isAssignableFrom(result.getClass())); + assertTrue("result List must be empty", ((List) result).isEmpty()); + } + + + public void soapHeaderWithEmptyValue(@SoapHeader("") SoapHeaderElement element) { + } + + public void soapHeaderElement(@SoapHeader("{http://springframework.org/ws}header") SoapHeaderElement element) { + } + + public void soapHeaderElementList(@SoapHeader("{http://springframework.org/ws}header") List elements) { + } + + public void soapHeaderMismatch(@SoapHeader("{http://springframework.org/ws}xxx") SoapHeaderElement element) { + } + + public void soapHeaderMismatchList(@SoapHeader("{http://springframework.org/ws}xxx") List elements) { + } + + +}