Added XPathParamMethodEndpointAdapter.

This commit is contained in:
Arjen Poutsma
2007-04-03 22:35:27 +00:00
parent 28de474db3
commit f579139dbc
5 changed files with 358 additions and 0 deletions

View File

@@ -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:
* <pre>
* void handleMyMessage(@XPathParam("/root/child/text")String param);
* </pre>
* or
* <pre>
* Source handleMyMessage(@XPathParam("/root/child/text")String param1, @XPathParam("/root/child/number")double
* param2);
* </pre>
* I.e. methods that return either <code>void</code> 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: <ul> <li><code>boolean</code>, or {@link Boolean}</li> <li><code>double</code>, or {@link
* Double}</li> <li>{@link String}</li> <li>{@link Node}</li> <li>{@link NodeList}</li> </ul>
*
* @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 <code>Source</code> 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;
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,5 @@
<html>
<body>
JDK 1.5+ annotations for Spring-WS endpoints.
</body>
</html>

View File

@@ -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("<response/>");
}
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) {
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>
<text>text</text>
<number>42.0</number>
</child>
</root>