diff --git a/sandbox/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java b/sandbox/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java new file mode 100644 index 00000000..c1722bc2 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapter.java @@ -0,0 +1,173 @@ +/* + * Copyright 2007 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.adapter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Properties; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.MethodEndpoint; +import org.springframework.ws.server.endpoint.annotation.XPathParam; +import org.springframework.xml.namespace.SimpleNamespaceContext; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Adapter that supports endpoint methods that use marshalling. Supports methods with the following signature: + *
+ * void handleMyMessage(@XPathParam("/root/child/text")String param);
+ * 
+ * or + *
+ * Source handleMyMessage(@XPathParam("/root/child/text")String param1, @XPathParam("/root/child/number")double
+ * param2);
+ * 
+ * I.e. methods that return either void or a {@link Source}, and have parameters annotated with {@link + * XPathParam} that specify the XPath expression that should be bound to that parameter. The parameter can be of the + * following types: + * + * @author Arjen Poutsma + */ +public class XPathParamAnnotationEndpointAdapter extends AbstractMethodEndpointAdapter implements InitializingBean { + + private XPathFactory xpathFactory; + + private Properties namespaces; + + public void setNamespaces(Properties namespaces) { + this.namespaces = namespaces; + } + + public void afterPropertiesSet() throws Exception { + xpathFactory = XPathFactory.newInstance(); + } + + /** Supports methods with @XPathParam parameters, and return either Source or nothing. */ + protected boolean supportsInternal(MethodEndpoint methodEndpoint) { + Method method = methodEndpoint.getMethod(); + if (!(Source.class.isAssignableFrom(method.getReturnType()) || Void.TYPE.equals(method.getReturnType()))) { + return false; + } + Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + if (getXPathParamAnnotation(method, i) == null || !isSuportedType(parameterTypes[i])) { + return false; + } + } + return true; + } + + private XPathParam getXPathParamAnnotation(Method method, int paramIdx) { + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + for (int annIdx = 0; annIdx < paramAnnotations[paramIdx].length; annIdx++) { + if (paramAnnotations[paramIdx][annIdx].annotationType().equals(XPathParam.class)) { + return (XPathParam) paramAnnotations[paramIdx][annIdx]; + } + } + return null; + } + + private boolean isSuportedType(Class clazz) { + return Boolean.class.isAssignableFrom(clazz) || Boolean.TYPE.isAssignableFrom(clazz) || + Double.class.isAssignableFrom(clazz) || Double.TYPE.isAssignableFrom(clazz) || + Node.class.isAssignableFrom(clazz) || NodeList.class.isAssignableFrom(clazz) || + String.class.isAssignableFrom(clazz); + } + + protected void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception { + Element payloadElement = getMessagePayloadElement(messageContext.getRequest()); + Object[] args = getMethodArguments(payloadElement, methodEndpoint.getMethod()); + Object result = methodEndpoint.invoke(args); + if (result != null && result instanceof Source) { + Source responseSource = (Source) result; + WebServiceMessage response = messageContext.getResponse(); + Transformer transformer = createTransformer(); + transformer.transform(responseSource, response.getPayloadResult()); + } + } + + private Element getMessagePayloadElement(WebServiceMessage message) throws TransformerException { + if (message.getPayloadSource() instanceof DOMSource) { + DOMSource domSource = (DOMSource) message.getPayloadSource(); + if (domSource.getNode().getNodeType() == Node.ELEMENT_NODE) { + return (Element) domSource.getNode(); + } + } + Transformer transformer = createTransformer(); + DOMResult domResult = new DOMResult(); + transformer.transform(message.getPayloadSource(), domResult); + return (Element) domResult.getNode().getFirstChild(); + } + + private Object[] getMethodArguments(Element payloadElement, Method method) throws XPathExpressionException { + Class[] parameterTypes = method.getParameterTypes(); + XPath xpath = createXPath(); + Object[] args = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + String expression = getXPathParamAnnotation(method, i).value(); + QName conversionType; + if (Boolean.class.isAssignableFrom(parameterTypes[i]) || Boolean.TYPE.isAssignableFrom(parameterTypes[i])) { + conversionType = XPathConstants.BOOLEAN; + } + else + if (Double.class.isAssignableFrom(parameterTypes[i]) || Double.TYPE.isAssignableFrom(parameterTypes[i])) { + conversionType = XPathConstants.NUMBER; + } + else if (Node.class.isAssignableFrom(parameterTypes[i])) { + conversionType = XPathConstants.NODE; + } + else if (NodeList.class.isAssignableFrom(parameterTypes[i])) { + conversionType = XPathConstants.NODESET; + } + else if (String.class.isAssignableFrom(parameterTypes[i])) { + conversionType = XPathConstants.STRING; + } + else { + throw new IllegalArgumentException("Invalid parameter type [" + parameterTypes[i] + "]. " + + "Supported are: Boolean, Double, Node, NodeList, and String."); + } + args[i] = xpath.evaluate(expression, payloadElement, conversionType); + } + return args; + } + + private XPath createXPath() { + XPath xpath = xpathFactory.newXPath(); + if (namespaces != null) { + SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + namespaceContext.setBindings(namespaces); + xpath.setNamespaceContext(namespaceContext); + } + return xpath; + } + +} diff --git a/sandbox/src/main/java/org/springframework/ws/server/endpoint/annotation/XPathParam.java b/sandbox/src/main/java/org/springframework/ws/server/endpoint/annotation/XPathParam.java new file mode 100644 index 00000000..c5ba8717 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/server/endpoint/annotation/XPathParam.java @@ -0,0 +1,35 @@ +/* + * Copyright 2007 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** @author Arjen Poutsma */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface XPathParam { + + /** + * The XPathParam value. + * + * @return the + */ + String value(); +} diff --git a/sandbox/src/main/java/org/springframework/ws/server/endpoint/annotation/package.html b/sandbox/src/main/java/org/springframework/ws/server/endpoint/annotation/package.html new file mode 100644 index 00000000..2db8b2fc --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/server/endpoint/annotation/package.html @@ -0,0 +1,5 @@ + + +JDK 1.5+ annotations for Spring-WS endpoints. + + diff --git a/sandbox/src/test/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapterTest.java b/sandbox/src/test/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapterTest.java new file mode 100644 index 00000000..6e08cd10 --- /dev/null +++ b/sandbox/src/test/java/org/springframework/ws/server/endpoint/adapter/XPathParamAnnotationEndpointAdapterTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2007 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.adapter; + +import javax.xml.transform.Source; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; +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.endpoint.MethodEndpoint; +import org.springframework.ws.server.endpoint.annotation.XPathParam; +import org.springframework.xml.transform.StringSource; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XPathParamAnnotationEndpointAdapterTest extends TestCase { + + private XPathParamAnnotationEndpointAdapter adapter; + + private boolean supportedTypesInvoked = false; + + private boolean supportedSourceInvoked; + + protected void setUp() throws Exception { + adapter = new XPathParamAnnotationEndpointAdapter(); + adapter.afterPropertiesSet(); + } + + public void testUnsupportedInvalidParam() throws NoSuchMethodException { + MethodEndpoint endpoint = new MethodEndpoint(this, "unsupportedInvalidParamType", new Class[]{Integer.TYPE}); + assertFalse("Method supported", adapter.supports(endpoint)); + } + + public void testUnsupportedInvalidReturnType() throws NoSuchMethodException { + MethodEndpoint endpoint = new MethodEndpoint(this, "unsupportedInvalidReturnType", new Class[]{String.class}); + assertFalse("Method supported", adapter.supports(endpoint)); + } + + public void testUnsupportedInvalidParams() throws NoSuchMethodException { + MethodEndpoint endpoint = + new MethodEndpoint(this, "unsupportedInvalidParams", new Class[]{String.class, String.class}); + assertFalse("Method supported", adapter.supports(endpoint)); + } + + public void testSupportedTypes() throws NoSuchMethodException { + MethodEndpoint endpoint = new MethodEndpoint(this, "supportedTypes", + new Class[]{Boolean.TYPE, Double.TYPE, Node.class, NodeList.class, String.class}); + assertTrue("Not all types supported", adapter.supports(endpoint)); + } + + public void testSupportsStringSource() throws NoSuchMethodException { + MethodEndpoint endpoint = new MethodEndpoint(this, "supportedStringSource", new Class[]{String.class}); + assertTrue("StringSource method not supported", adapter.supports(endpoint)); + } + + public void testSupportsSource() throws NoSuchMethodException { + MethodEndpoint endpoint = new MethodEndpoint(this, "supportedSource", new Class[]{String.class}); + assertTrue("Source method not supported", adapter.supports(endpoint)); + } + + public void testSupportsVoid() throws NoSuchMethodException { + MethodEndpoint endpoint = new MethodEndpoint(this, "supportedVoid", new Class[]{String.class}); + assertTrue("void method not supported", adapter.supports(endpoint)); + } + + public void testInvokeTypes() throws Exception { + MockWebServiceMessage request = + new MockWebServiceMessage(new ClassPathResource("nonamespaces.xml", getClass())); + MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory()); + MethodEndpoint endpoint = new MethodEndpoint(this, "supportedTypes", + new Class[]{Boolean.TYPE, Double.TYPE, Node.class, NodeList.class, String.class}); + adapter.invoke(messageContext, endpoint); + assertTrue("Method not invoked", supportedTypesInvoked); + } + + public void testInvokeSource() throws Exception { + MockWebServiceMessage request = + new MockWebServiceMessage(new ClassPathResource("nonamespaces.xml", getClass())); + MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory()); + MethodEndpoint endpoint = new MethodEndpoint(this, "supportedSource", new Class[]{String.class}); + adapter.invoke(messageContext, endpoint); + assertTrue("Method not invoked", supportedSourceInvoked); + } + + public void supportedVoid(@XPathParam("/")String param1) { + } + + public Source supportedSource(@XPathParam("/")String param1) { + supportedSourceInvoked = true; + return new StringSource(""); + } + + public StringSource supportedStringSource(@XPathParam("/")String param1) { + return null; + } + + public void supportedTypes(@XPathParam("/root/child")boolean param1, + @XPathParam("/root/child/number")double param2, + @XPathParam("/root/child")Node param3, + @XPathParam("/root/*")NodeList param4, + @XPathParam("/root/child/text")String param5) { + supportedTypesInvoked = true; + assertTrue("Invalid boolean value", param1); + assertEquals("Invalid double value", 42D, param2, 0.00001D); + assertEquals("Invalid Node value", "child", param3.getLocalName()); + assertEquals("Invalid NodeList value", 1, param4.getLength()); + assertEquals("Invalid Node value", "child", param4.item(0).getLocalName()); + assertEquals("Invalid Node value", "text", param5); + } + + public void unsupportedInvalidParams(@XPathParam("/")String param1, String param2) { + + } + + public String unsupportedInvalidReturnType(@XPathParam("/")String param1) { + return null; + } + + public void unsupportedInvalidParamType(@XPathParam("/")int param1) { + } +} \ No newline at end of file diff --git a/sandbox/src/test/resources/org/springframework/ws/server/endpoint/adapter/nonamespaces.xml b/sandbox/src/test/resources/org/springframework/ws/server/endpoint/adapter/nonamespaces.xml new file mode 100644 index 00000000..2f3e5c26 --- /dev/null +++ b/sandbox/src/test/resources/org/springframework/ws/server/endpoint/adapter/nonamespaces.xml @@ -0,0 +1,7 @@ + + + + text + 42.0 + + \ No newline at end of file