1 /*
2  * Copyright 2005-2014 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.method;
18
19 import javax.xml.namespace.QName;
20 import javax.xml.transform.Source;
21 import javax.xml.transform.TransformerException;
22 import javax.xml.transform.dom.DOMResult;
23 import javax.xml.xpath.XPath;
24 import javax.xml.xpath.XPathConstants;
25 import javax.xml.xpath.XPathExpressionException;
26 import javax.xml.xpath.XPathFactory;
27
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Element;
30 import org.w3c.dom.Node;
31 import org.w3c.dom.NodeList;
32
33 import org.springframework.core.MethodParameter;
34 import org.springframework.core.convert.ConversionService;
35 import org.springframework.core.convert.support.ConversionServiceFactory;
36 import org.springframework.core.convert.support.DefaultConversionService;
37 import org.springframework.ws.context.MessageContext;
38 import org.springframework.ws.server.endpoint.annotation.XPathParam;
39 import org.springframework.ws.server.endpoint.support.NamespaceUtils;
40 import org.springframework.xml.transform.TransformerHelper;
41
42 /**
43  * Implementation of {@link MethodArgumentResolver} that supports the {@link XPathParam @XPathParam} annotation.
44  *
45  * <p>This resolver supports parameters annotated with {@link XPathParam @XPathParam} that specifies the XPath expression
46  * that should be bound to that parameter. The parameter can either a "natively supported" XPath type ({@link Boolean
47  * boolean}, {@link Double double}, {@link String}, {@link Node}, or {@link NodeList}), or a type that is {@linkplain
48  * ConversionService#canConvert(Class, Class) supported} by the {@link ConversionService}.
49  *
50  * @author Arjen Poutsma
51  * @since 2.0
52  */

53 public class XPathParamMethodArgumentResolver implements MethodArgumentResolver {
54
55     private final XPathFactory xpathFactory = createXPathFactory();
56
57     private TransformerHelper transformerHelper = new TransformerHelper();
58
59     private ConversionService conversionService = new DefaultConversionService();
60
61     /**
62      * Sets the conversion service to use.
63      *
64      * <p>Defaults to the {@linkplain ConversionServiceFactory#createDefaultConversionService() default conversion
65      * service}.
66      */

67     public void setConversionService(ConversionService conversionService) {
68         this.conversionService = conversionService;
69     }
70
71     public void setTransformerHelper(TransformerHelper transformerHelper) {
72         this.transformerHelper = transformerHelper;
73     }
74
75     @Override
76     public boolean supportsParameter(MethodParameter parameter) {
77         if (parameter.getParameterAnnotation(XPathParam.class) == null) {
78             return false;
79         }
80         Class<?> parameterType = parameter.getParameterType();
81         if (Boolean.class.equals(parameterType) || Boolean.TYPE.equals(parameterType) ||
82                 Double.class.equals(parameterType) || Double.TYPE.equals(parameterType) ||
83                 Node.class.isAssignableFrom(parameterType) || NodeList.class.isAssignableFrom(parameterType) ||
84                 String.class.isAssignableFrom(parameterType)) {
85             return true;
86         }
87         else {
88             return conversionService.canConvert(String.class, parameterType);
89         }
90     }
91
92     @Override
93     public Object resolveArgument(MessageContext messageContext, MethodParameter parameter)
94             throws TransformerException, XPathExpressionException {
95         Class<?> parameterType = parameter.getParameterType();
96         QName evaluationReturnType = getReturnType(parameterType);
97         boolean useConversionService = false;
98         if (evaluationReturnType == null) {
99             evaluationReturnType = XPathConstants.STRING;
100             useConversionService = true;
101         }
102
103         XPath xpath = createXPath();
104         xpath.setNamespaceContext(NamespaceUtils.getNamespaceContext(parameter.getMethod()));
105
106         Element rootElement = getRootElement(messageContext.getRequest().getPayloadSource());
107         String expression = parameter.getParameterAnnotation(XPathParam.class).value();
108         Object result = xpath.evaluate(expression, rootElement, evaluationReturnType);
109         return useConversionService ? conversionService.convert(result, parameterType) : result;
110     }
111
112     private QName getReturnType(Class<?> parameterType) {
113         if (Boolean.class.equals(parameterType) || Boolean.TYPE.equals(parameterType)) {
114             return XPathConstants.BOOLEAN;
115         }
116         else if (Double.class.equals(parameterType) || Double.TYPE.equals(parameterType)) {
117             return XPathConstants.NUMBER;
118         }
119         else if (Node.class.equals(parameterType)) {
120             return XPathConstants.NODE;
121         }
122         else if (NodeList.class.equals(parameterType)) {
123             return XPathConstants.NODESET;
124         }
125         else if (String.class.equals(parameterType)) {
126             return XPathConstants.STRING;
127         }
128         else {
129             return null;
130         }
131     }
132
133     private XPath createXPath() {
134         synchronized (xpathFactory) {
135             return xpathFactory.newXPath();
136         }
137     }
138
139     private Element getRootElement(Source source) throws TransformerException {
140         DOMResult domResult = new DOMResult();
141         transformerHelper.transform(source, domResult);
142         Document document = (Document) domResult.getNode();
143         return document.getDocumentElement();
144     }
145
146     /**
147      * Create a {@code XPathFactory} that this resolver will use to create {@link XPath} objects.
148      *
149      * <p>Can be overridden in subclasses, adding further initialization of the factory. The resulting factory is cached,
150      * so this method will only be called once.
151      *
152      * @return the created factory
153      */

154     protected XPathFactory createXPathFactory() {
155         return XPathFactory.newInstance();
156     }
157
158
159 }
160