001: /*
002: * Copyright 2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.ws.server.endpoint.adapter;
018:
019: import java.lang.annotation.Annotation;
020: import java.lang.reflect.Method;
021: import java.util.Properties;
022: import javax.xml.namespace.QName;
023: import javax.xml.transform.Source;
024: import javax.xml.transform.TransformerException;
025: import javax.xml.transform.dom.DOMResult;
026: import javax.xml.xpath.XPath;
027: import javax.xml.xpath.XPathConstants;
028: import javax.xml.xpath.XPathExpressionException;
029: import javax.xml.xpath.XPathFactory;
030:
031: import org.springframework.beans.factory.InitializingBean;
032: import org.springframework.ws.WebServiceMessage;
033: import org.springframework.ws.context.MessageContext;
034: import org.springframework.ws.server.endpoint.MethodEndpoint;
035: import org.springframework.ws.server.endpoint.annotation.XPathParam;
036: import org.springframework.xml.namespace.SimpleNamespaceContext;
037: import org.w3c.dom.Document;
038: import org.w3c.dom.Element;
039: import org.w3c.dom.Node;
040: import org.w3c.dom.NodeList;
041:
042: /**
043: * Adapter that supports endpoint methods that use marshalling. Supports methods with the following signature:
044: * <pre>
045: * void handleMyMessage(@XPathParam("/root/child/text")String param);
046: * </pre>
047: * or
048: * <pre>
049: * Source handleMyMessage(@XPathParam("/root/child/text")String param1, @XPathParam("/root/child/number")double
050: * param2);
051: * </pre>
052: * I.e. methods that return either <code>void</code> or a {@link Source}, and have parameters annotated with {@link
053: * XPathParam} that specify the XPath expression that should be bound to that parameter. The parameter can be of the
054: * following types: <ul> <li><code>boolean</code>, or {@link Boolean}</li> <li><code>double</code>, or {@link
055: * Double}</li> <li>{@link String}</li> <li>{@link Node}</li> <li>{@link NodeList}</li> </ul>
056: *
057: * @author Arjen Poutsma
058: * @since 1.0.0
059: */
060: public class XPathParamAnnotationMethodEndpointAdapter extends
061: AbstractMethodEndpointAdapter implements InitializingBean {
062:
063: private XPathFactory xpathFactory;
064:
065: private Properties namespaces;
066:
067: /** Sets namespaces used in the XPath expression. Maps prefixes to namespaces. */
068: public void setNamespaces(Properties namespaces) {
069: this .namespaces = namespaces;
070: }
071:
072: public void afterPropertiesSet() throws Exception {
073: xpathFactory = XPathFactory.newInstance();
074: }
075:
076: /** Supports methods with @XPathParam parameters, and return either <code>Source</code> or nothing. */
077: protected boolean supportsInternal(MethodEndpoint methodEndpoint) {
078: Method method = methodEndpoint.getMethod();
079: if (!(Source.class.isAssignableFrom(method.getReturnType()) || Void.TYPE
080: .equals(method.getReturnType()))) {
081: return false;
082: }
083: Class<?>[] parameterTypes = method.getParameterTypes();
084: for (int i = 0; i < parameterTypes.length; i++) {
085: if (getXPathParamAnnotation(method, i) == null
086: || !isSuportedType(parameterTypes[i])) {
087: return false;
088: }
089: }
090: return true;
091: }
092:
093: private XPathParam getXPathParamAnnotation(Method method,
094: int paramIdx) {
095: Annotation[][] paramAnnotations = method
096: .getParameterAnnotations();
097: for (int annIdx = 0; annIdx < paramAnnotations[paramIdx].length; annIdx++) {
098: if (paramAnnotations[paramIdx][annIdx].annotationType()
099: .equals(XPathParam.class)) {
100: return (XPathParam) paramAnnotations[paramIdx][annIdx];
101: }
102: }
103: return null;
104: }
105:
106: private boolean isSuportedType(Class<?> clazz) {
107: return Boolean.class.isAssignableFrom(clazz)
108: || Boolean.TYPE.isAssignableFrom(clazz)
109: || Double.class.isAssignableFrom(clazz)
110: || Double.TYPE.isAssignableFrom(clazz)
111: || Node.class.isAssignableFrom(clazz)
112: || NodeList.class.isAssignableFrom(clazz)
113: || String.class.isAssignableFrom(clazz);
114: }
115:
116: protected void invokeInternal(MessageContext messageContext,
117: MethodEndpoint methodEndpoint) throws Exception {
118: Element payloadElement = getRootElement(messageContext
119: .getRequest().getPayloadSource());
120: Object[] args = getMethodArguments(payloadElement,
121: methodEndpoint.getMethod());
122: Object result = methodEndpoint.invoke(args);
123: if (result != null && result instanceof Source) {
124: Source responseSource = (Source) result;
125: WebServiceMessage response = messageContext.getResponse();
126: transform(responseSource, response.getPayloadResult());
127: }
128: }
129:
130: private Object[] getMethodArguments(Element payloadElement,
131: Method method) throws XPathExpressionException {
132: Class[] parameterTypes = method.getParameterTypes();
133: XPath xpath = createXPath();
134: Object[] args = new Object[parameterTypes.length];
135: for (int i = 0; i < parameterTypes.length; i++) {
136: String expression = getXPathParamAnnotation(method, i)
137: .value();
138: QName conversionType;
139: if (Boolean.class.isAssignableFrom(parameterTypes[i])
140: || Boolean.TYPE.isAssignableFrom(parameterTypes[i])) {
141: conversionType = XPathConstants.BOOLEAN;
142: } else if (Double.class.isAssignableFrom(parameterTypes[i])
143: || Double.TYPE.isAssignableFrom(parameterTypes[i])) {
144: conversionType = XPathConstants.NUMBER;
145: } else if (Node.class.isAssignableFrom(parameterTypes[i])) {
146: conversionType = XPathConstants.NODE;
147: } else if (NodeList.class
148: .isAssignableFrom(parameterTypes[i])) {
149: conversionType = XPathConstants.NODESET;
150: } else if (String.class.isAssignableFrom(parameterTypes[i])) {
151: conversionType = XPathConstants.STRING;
152: } else {
153: throw new IllegalArgumentException(
154: "Invalid parameter type ["
155: + parameterTypes[i]
156: + "]. "
157: + "Supported are: Boolean, Double, Node, NodeList, and String.");
158: }
159: args[i] = xpath.evaluate(expression, payloadElement,
160: conversionType);
161: }
162: return args;
163: }
164:
165: private XPath createXPath() {
166: XPath xpath = xpathFactory.newXPath();
167: if (namespaces != null) {
168: SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
169: namespaceContext.setBindings(namespaces);
170: xpath.setNamespaceContext(namespaceContext);
171: }
172: return xpath;
173: }
174:
175: /**
176: * Returns the root element of the given source.
177: *
178: * @param source the source to get the root element from
179: * @return the root element
180: */
181: private Element getRootElement(Source source)
182: throws TransformerException {
183: DOMResult domResult = new DOMResult();
184: transform(source, domResult);
185: Document document = (Document) domResult.getNode();
186: return document.getDocumentElement();
187: }
188:
189: }
|