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: boolean, or {@link Boolean}double, or {@link
+ * Double}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("