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;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22
23 import org.springframework.beans.BeanUtils;
24 import org.springframework.beans.factory.BeanClassLoaderAware;
25 import org.springframework.beans.factory.InitializingBean;
26 import org.springframework.core.MethodParameter;
27 import org.springframework.util.ClassUtils;
28 import org.springframework.util.CollectionUtils;
29 import org.springframework.ws.context.MessageContext;
30 import org.springframework.ws.server.endpoint.MethodEndpoint;
31 import org.springframework.ws.server.endpoint.adapter.method.MessageContextMethodArgumentResolver;
32 import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver;
33 import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler;
34 import org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor;
35 import org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver;
36 import org.springframework.ws.server.endpoint.adapter.method.XPathParamMethodArgumentResolver;
37 import org.springframework.ws.server.endpoint.adapter.method.dom.Dom4jPayloadMethodProcessor;
38 import org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor;
39 import org.springframework.ws.server.endpoint.adapter.method.dom.JDomPayloadMethodProcessor;
40 import org.springframework.ws.server.endpoint.adapter.method.dom.XomPayloadMethodProcessor;
41 import org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor;
42 import org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor;
43
44 /**
45  * Default extension of {@link AbstractMethodEndpointAdapter} with support for pluggable {@linkplain
46  * MethodArgumentResolver argument resolvers} and {@linkplain MethodReturnValueHandler return value handlers}.
47  *
48  * @author Arjen Poutsma
49  * @since 2.0
50  */

51 public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter
52         implements BeanClassLoaderAware, InitializingBean {
53
54     private static final String DOM4J_CLASS_NAME = "org.dom4j.Element";
55
56     private static final String JAXB2_CLASS_NAME = "javax.xml.bind.Binder";
57
58     private static final String JDOM_CLASS_NAME = "org.jdom2.Element";
59
60     private static final String STAX_CLASS_NAME = "javax.xml.stream.XMLInputFactory";
61
62     private static final String XOM_CLASS_NAME = "nu.xom.Element";
63
64     private static final String SOAP_METHOD_ARGUMENT_RESOLVER_CLASS_NAME =
65             "org.springframework.ws.soap.server.endpoint.adapter.method.SoapMethodArgumentResolver";
66
67     private static final String SOAP_HEADER_ELEMENT_ARGUMENT_RESOLVER_CLASS_NAME =
68             "org.springframework.ws.soap.server.endpoint.adapter.method.SoapHeaderElementMethodArgumentResolver";
69
70     private List<MethodArgumentResolver> methodArgumentResolvers;
71
72     private List<MethodArgumentResolver> customMethodArgumentResolvers;
73
74     private List<MethodReturnValueHandler> methodReturnValueHandlers;
75
76     private List<MethodReturnValueHandler> customMethodReturnValueHandlers;
77
78     private ClassLoader classLoader;
79
80     /**
81      * Returns the list of {@code MethodArgumentResolver}s to use.
82      */

83     public List<MethodArgumentResolver> getMethodArgumentResolvers() {
84         return methodArgumentResolvers;
85     }
86
87     /**
88      * Sets the list of {@code MethodArgumentResolver}s to use.
89      */

90     public void setMethodArgumentResolvers(List<MethodArgumentResolver> methodArgumentResolvers) {
91         this.methodArgumentResolvers = methodArgumentResolvers;
92     }
93
94     /**
95      * Returns the custom argument resolvers.
96      */

97     public List<MethodArgumentResolver> getCustomMethodArgumentResolvers() {
98         return customMethodArgumentResolvers;
99     }
100
101     /**
102      * Sets the custom handlers for method arguments. Custom handlers are
103      * ordered after built-in ones. To override the built-in support for
104      * return value handling use {@link #setMethodArgumentResolvers(List)}.
105      */

106     public void setCustomMethodArgumentResolvers(
107             List<MethodArgumentResolver> customMethodArgumentResolvers) {
108         this.customMethodArgumentResolvers = customMethodArgumentResolvers;
109     }
110
111     /**
112      * Returns the list of {@code MethodReturnValueHandler}s to use.
113      */

114     public List<MethodReturnValueHandler> getMethodReturnValueHandlers() {
115         return methodReturnValueHandlers;
116     }
117
118     /**
119      * Sets the list of {@code MethodReturnValueHandler}s to use.
120      */

121     public void setMethodReturnValueHandlers(List<MethodReturnValueHandler> methodReturnValueHandlers) {
122         this.methodReturnValueHandlers = methodReturnValueHandlers;
123     }
124
125     /**
126      * Returns the custom return value handlers.
127      */

128     public List<MethodReturnValueHandler> getCustomMethodReturnValueHandlers() {
129         return customMethodReturnValueHandlers;
130     }
131
132     /**
133      * Sets the handlers for custom return value types. Custom handlers are
134      * ordered after built-in ones. To override the built-in support for
135      * return value handling use {@link #setMethodReturnValueHandlers(List)}.
136      */

137     public void setCustomMethodReturnValueHandlers(
138             List<MethodReturnValueHandler> customMethodReturnValueHandlers) {
139         this.customMethodReturnValueHandlers = customMethodReturnValueHandlers;
140     }
141
142     private ClassLoader getClassLoader() {
143         return this.classLoader != null ? this.classLoader : DefaultMethodEndpointAdapter.class.getClassLoader();
144     }
145
146     @Override
147     public void setBeanClassLoader(ClassLoader classLoader) {
148         this.classLoader = classLoader;
149     }
150
151     @Override
152     public void afterPropertiesSet() throws Exception {
153         initDefaultStrategies();
154     }
155
156     /** Initialize the default implementations for the adapter's strategies. */
157     protected void initDefaultStrategies() {
158         initMethodArgumentResolvers();
159         initMethodReturnValueHandlers();
160     }
161
162     private void initMethodArgumentResolvers() {
163         if (CollectionUtils.isEmpty(methodArgumentResolvers)) {
164             List<MethodArgumentResolver> methodArgumentResolvers = new ArrayList<MethodArgumentResolver>();
165             methodArgumentResolvers.add(new DomPayloadMethodProcessor());
166             methodArgumentResolvers.add(new MessageContextMethodArgumentResolver());
167             methodArgumentResolvers.add(new SourcePayloadMethodProcessor());
168             methodArgumentResolvers.add(new XPathParamMethodArgumentResolver());
169             addMethodArgumentResolver(SOAP_METHOD_ARGUMENT_RESOLVER_CLASS_NAME, methodArgumentResolvers);
170             addMethodArgumentResolver(SOAP_HEADER_ELEMENT_ARGUMENT_RESOLVER_CLASS_NAME, methodArgumentResolvers);
171             if (isPresent(DOM4J_CLASS_NAME)) {
172                 methodArgumentResolvers.add(new Dom4jPayloadMethodProcessor());
173             }
174             if (isPresent(JAXB2_CLASS_NAME)) {
175                 methodArgumentResolvers.add(new XmlRootElementPayloadMethodProcessor());
176                 methodArgumentResolvers.add(new JaxbElementPayloadMethodProcessor());
177             }
178             if (isPresent(JDOM_CLASS_NAME)) {
179                 methodArgumentResolvers.add(new JDomPayloadMethodProcessor());
180             }
181             if (isPresent(STAX_CLASS_NAME)) {
182                 methodArgumentResolvers.add(new StaxPayloadMethodArgumentResolver());
183             }
184             if (isPresent(XOM_CLASS_NAME)) {
185                 methodArgumentResolvers.add(new XomPayloadMethodProcessor());
186             }
187             if (logger.isDebugEnabled()) {
188                 logger.debug("No MethodArgumentResolvers set, using defaults: " + methodArgumentResolvers);
189             }
190             if (getCustomMethodArgumentResolvers() != null) {
191                 methodArgumentResolvers.addAll(getCustomMethodArgumentResolvers());
192             }
193             setMethodArgumentResolvers(methodArgumentResolvers);
194         }
195     }
196
197     /**
198      * Certain (SOAP-specific) {@code MethodArgumentResolver}s have to be instantiated by class name, in order to not
199      * introduce a cyclic dependency.
200      */

201     @SuppressWarnings("unchecked")
202     private void addMethodArgumentResolver(String className, List<MethodArgumentResolver> methodArgumentResolvers) {
203         try {
204             Class<MethodArgumentResolver> methodArgumentResolverClass =
205                     (Class<MethodArgumentResolver>) ClassUtils.forName(className, getClassLoader());
206             methodArgumentResolvers.add(BeanUtils.instantiateClass(methodArgumentResolverClass));
207         }
208         catch (ClassNotFoundException e) {
209             logger.warn("Could not find \"" + className + "\" on the classpath");
210         }
211     }
212
213     private void initMethodReturnValueHandlers() {
214         if (CollectionUtils.isEmpty(methodReturnValueHandlers)) {
215             List<MethodReturnValueHandler> methodReturnValueHandlers = new ArrayList<MethodReturnValueHandler>();
216             methodReturnValueHandlers.add(new DomPayloadMethodProcessor());
217             methodReturnValueHandlers.add(new SourcePayloadMethodProcessor());
218             if (isPresent(DOM4J_CLASS_NAME)) {
219                 methodReturnValueHandlers.add(new Dom4jPayloadMethodProcessor());
220             }
221             if (isPresent(JAXB2_CLASS_NAME)) {
222                 methodReturnValueHandlers.add(new XmlRootElementPayloadMethodProcessor());
223                 methodReturnValueHandlers.add(new JaxbElementPayloadMethodProcessor());
224             }
225             if (isPresent(JDOM_CLASS_NAME)) {
226                 methodReturnValueHandlers.add(new JDomPayloadMethodProcessor());
227             }
228             if (isPresent(XOM_CLASS_NAME)) {
229                 methodReturnValueHandlers.add(new XomPayloadMethodProcessor());
230             }
231             if (logger.isDebugEnabled()) {
232                 logger.debug("No MethodReturnValueHandlers set, using defaults: " + methodReturnValueHandlers);
233             }
234             if (getCustomMethodReturnValueHandlers() != null) {
235                 methodReturnValueHandlers.addAll(getCustomMethodReturnValueHandlers());
236             }
237             setMethodReturnValueHandlers(methodReturnValueHandlers);
238         }
239     }
240
241     private boolean isPresent(String className) {
242         return ClassUtils.isPresent(className, getClassLoader());
243     }
244
245     @Override
246     protected boolean supportsInternal(MethodEndpoint methodEndpoint) {
247         return supportsParameters(methodEndpoint.getMethodParameters()) &&
248                 supportsReturnType(methodEndpoint.getReturnType());
249     }
250
251     private boolean supportsParameters(MethodParameter[] methodParameters) {
252         for (MethodParameter methodParameter : methodParameters) {
253             boolean supported = false;
254             for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
255                 if (logger.isTraceEnabled()) {
256                     logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
257                             methodParameter.getGenericParameterType() + "]");
258                 }
259                 if (methodArgumentResolver.supportsParameter(methodParameter)) {
260                     supported = true;
261                     break;
262                 }
263             }
264             if (!supported) {
265                 return false;
266             }
267         }
268         return true;
269     }
270
271     private boolean supportsReturnType(MethodParameter methodReturnType) {
272         if (Void.TYPE.equals(methodReturnType.getParameterType())) {
273             return true;
274         }
275         for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
276             if (methodReturnValueHandler.supportsReturnType(methodReturnType)) {
277                 return true;
278             }
279         }
280         return false;
281     }
282
283     @Override
284     protected final void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception {
285         Object[] args = getMethodArguments(messageContext, methodEndpoint);
286
287         if (logger.isTraceEnabled()) {
288             logger.trace("Invoking [" + methodEndpoint + "] with arguments " + Arrays.asList(args));
289         }
290
291         Object returnValue = methodEndpoint.invoke(args);
292
293         if (logger.isTraceEnabled()) {
294             logger.trace("Method [" + methodEndpoint + "] returned [" + returnValue + "]");
295         }
296
297         Class<?> returnType = methodEndpoint.getMethod().getReturnType();
298         if (!Void.TYPE.equals(returnType)) {
299             handleMethodReturnValue(messageContext, returnValue, methodEndpoint);
300         }
301     }
302
303     /**
304      * Returns the argument array for the given method endpoint.
305      *
306      * <p>This implementation iterates over the set {@linkplain #setMethodArgumentResolvers(List) argument resolvers} to
307      * resolve each argument.
308      *
309      * @param messageContext the current message context
310      * @param methodEndpoint the method endpoint to get arguments for
311      * @return the arguments
312      * @throws Exception in case of errors
313      */

314     protected Object[] getMethodArguments(MessageContext messageContext, MethodEndpoint methodEndpoint)
315             throws Exception {
316         MethodParameter[] parameters = methodEndpoint.getMethodParameters();
317         Object[] args = new Object[parameters.length];
318         for (int i = 0; i < parameters.length; i++) {
319             for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
320                 if (methodArgumentResolver.supportsParameter(parameters[i])) {
321                     args[i] = methodArgumentResolver.resolveArgument(messageContext, parameters[i]);
322                     break;
323                 }
324             }
325         }
326         return args;
327     }
328
329     /**
330      * Handle the return value for the given method endpoint.
331      *
332      * <p>This implementation iterates over the set {@linkplain #setMethodReturnValueHandlers(java.util.List)}    return value
333      * handlers} to resolve the return value.
334      *
335      * @param messageContext the current message context
336      * @param returnValue     the return value
337      * @param methodEndpoint the method endpoint to get arguments for
338      * @throws Exception in case of errors
339      */

340     protected void handleMethodReturnValue(MessageContext messageContext,
341                                            Object returnValue,
342                                            MethodEndpoint methodEndpoint) throws Exception {
343         MethodParameter returnType = methodEndpoint.getReturnType();
344         for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
345             if (methodReturnValueHandler.supportsReturnType(returnType)) {
346                 methodReturnValueHandler.handleReturnValue(messageContext, returnType, returnValue);
347                 return;
348             }
349         }
350         throw new IllegalStateException(
351                 "Return value [" + returnValue + "] not resolved by any MethodReturnValueHandler");
352     }
353 }
354