View Javadoc

1   /*
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.server.endpoint.adapter;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.Method;
21  import java.util.Properties;
22  import javax.xml.namespace.QName;
23  import javax.xml.transform.Source;
24  import javax.xml.transform.TransformerException;
25  import javax.xml.transform.dom.DOMResult;
26  import javax.xml.xpath.XPath;
27  import javax.xml.xpath.XPathConstants;
28  import javax.xml.xpath.XPathExpressionException;
29  import javax.xml.xpath.XPathFactory;
30  
31  import org.springframework.beans.factory.InitializingBean;
32  import org.springframework.ws.WebServiceMessage;
33  import org.springframework.ws.context.MessageContext;
34  import org.springframework.ws.server.endpoint.MethodEndpoint;
35  import org.springframework.ws.server.endpoint.annotation.XPathParam;
36  import org.springframework.xml.namespace.SimpleNamespaceContext;
37  import org.w3c.dom.Document;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.Node;
40  import org.w3c.dom.NodeList;
41  
42  /**
43   * Adapter that supports endpoint methods that use marshalling. Supports methods with the following signature:
44   * <pre>
45   * void handleMyMessage(@XPathParam("/root/child/text")String param);
46   * </pre>
47   * or
48   * <pre>
49   * Source handleMyMessage(@XPathParam("/root/child/text")String param1, @XPathParam("/root/child/number")double
50   * param2);
51   * </pre>
52   * I.e. methods that return either <code>void</code> or a {@link Source}, and have parameters annotated with {@link
53   * XPathParam} that specify the XPath expression that should be bound to that parameter. The parameter can be of the
54   * following types: <ul> <li><code>boolean</code>, or {@link Boolean}</li> <li><code>double</code>, or {@link
55   * Double}</li> <li>{@link String}</li> <li>{@link Node}</li> <li>{@link NodeList}</li> </ul>
56   *
57   * @author Arjen Poutsma
58   * @since 1.0.0
59   */
60  public class XPathParamAnnotationMethodEndpointAdapter extends AbstractMethodEndpointAdapter
61          implements InitializingBean {
62  
63      private XPathFactory xpathFactory;
64  
65      private Properties namespaces;
66  
67      /** Sets namespaces used in the XPath expression. Maps prefixes to namespaces. */
68      public void setNamespaces(Properties namespaces) {
69          this.namespaces = namespaces;
70      }
71  
72      public void afterPropertiesSet() throws Exception {
73          xpathFactory = XPathFactory.newInstance();
74      }
75  
76      /** Supports methods with @XPathParam parameters, and return either <code>Source</code> or nothing. */
77      protected boolean supportsInternal(MethodEndpoint methodEndpoint) {
78          Method method = methodEndpoint.getMethod();
79          if (!(Source.class.isAssignableFrom(method.getReturnType()) || Void.TYPE.equals(method.getReturnType()))) {
80              return false;
81          }
82          Class<?>[] parameterTypes = method.getParameterTypes();
83          for (int i = 0; i < parameterTypes.length; i++) {
84              if (getXPathParamAnnotation(method, i) == null || !isSuportedType(parameterTypes[i])) {
85                  return false;
86              }
87          }
88          return true;
89      }
90  
91      private XPathParam getXPathParamAnnotation(Method method, int paramIdx) {
92          Annotation[][] paramAnnotations = method.getParameterAnnotations();
93          for (int annIdx = 0; annIdx < paramAnnotations[paramIdx].length; annIdx++) {
94              if (paramAnnotations[paramIdx][annIdx].annotationType().equals(XPathParam.class)) {
95                  return (XPathParam) paramAnnotations[paramIdx][annIdx];
96              }
97          }
98          return null;
99      }
100 
101     private boolean isSuportedType(Class<?> clazz) {
102         return Boolean.class.isAssignableFrom(clazz) || Boolean.TYPE.isAssignableFrom(clazz) ||
103                 Double.class.isAssignableFrom(clazz) || Double.TYPE.isAssignableFrom(clazz) ||
104                 Node.class.isAssignableFrom(clazz) || NodeList.class.isAssignableFrom(clazz) ||
105                 String.class.isAssignableFrom(clazz);
106     }
107 
108     protected void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception {
109         Element payloadElement = getRootElement(messageContext.getRequest().getPayloadSource());
110         Object[] args = getMethodArguments(payloadElement, methodEndpoint.getMethod());
111         Object result = methodEndpoint.invoke(args);
112         if (result != null && result instanceof Source) {
113             Source responseSource = (Source) result;
114             WebServiceMessage response = messageContext.getResponse();
115             transform(responseSource, response.getPayloadResult());
116         }
117     }
118 
119     private Object[] getMethodArguments(Element payloadElement, Method method) throws XPathExpressionException {
120         Class<?>[] parameterTypes = method.getParameterTypes();
121         XPath xpath = createXPath();
122         Object[] args = new Object[parameterTypes.length];
123         for (int i = 0; i < parameterTypes.length; i++) {
124             String expression = getXPathParamAnnotation(method, i).value();
125             QName conversionType;
126             if (Boolean.class.isAssignableFrom(parameterTypes[i]) || Boolean.TYPE.isAssignableFrom(parameterTypes[i])) {
127                 conversionType = XPathConstants.BOOLEAN;
128             }
129             else
130             if (Double.class.isAssignableFrom(parameterTypes[i]) || Double.TYPE.isAssignableFrom(parameterTypes[i])) {
131                 conversionType = XPathConstants.NUMBER;
132             }
133             else if (Node.class.isAssignableFrom(parameterTypes[i])) {
134                 conversionType = XPathConstants.NODE;
135             }
136             else if (NodeList.class.isAssignableFrom(parameterTypes[i])) {
137                 conversionType = XPathConstants.NODESET;
138             }
139             else if (String.class.isAssignableFrom(parameterTypes[i])) {
140                 conversionType = XPathConstants.STRING;
141             }
142             else {
143                 throw new IllegalArgumentException("Invalid parameter type [" + parameterTypes[i] + "]. " +
144                         "Supported are: Boolean, Double, Node, NodeList, and String.");
145             }
146             args[i] = xpath.evaluate(expression, payloadElement, conversionType);
147         }
148         return args;
149     }
150 
151     private XPath createXPath() {
152         XPath xpath = xpathFactory.newXPath();
153         if (namespaces != null) {
154             SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
155             namespaceContext.setBindings(namespaces);
156             xpath.setNamespaceContext(namespaceContext);
157         }
158         return xpath;
159     }
160 
161     /**
162      * Returns the root element of the given source.
163      *
164      * @param source the source to get the root element from
165      * @return the root element
166      */
167     private Element getRootElement(Source source) throws TransformerException {
168         DOMResult domResult = new DOMResult();
169         transform(source, domResult);
170         Document document = (Document) domResult.getNode();
171         return document.getDocumentElement();
172     }
173 
174 
175 }