View Javadoc

1   /*
2    * Copyright 2005-2012 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<MethodReturnValueHandler> methodReturnValueHandlers;
73  
74      private ClassLoader classLoader;
75  
76      /** Returns the list of {@code MethodArgumentResolver}s to use. */
77      public List<MethodArgumentResolver> getMethodArgumentResolvers() {
78          return methodArgumentResolvers;
79      }
80  
81      /** Sets the list of {@code MethodArgumentResolver}s to use. */
82      public void setMethodArgumentResolvers(List<MethodArgumentResolver> methodArgumentResolvers) {
83          this.methodArgumentResolvers = methodArgumentResolvers;
84      }
85  
86      /** Returns the list of {@code MethodReturnValueHandler}s to use. */
87      public List<MethodReturnValueHandler> getMethodReturnValueHandlers() {
88          return methodReturnValueHandlers;
89      }
90  
91      /** Sets the list of {@code MethodReturnValueHandler}s to use. */
92      public void setMethodReturnValueHandlers(List<MethodReturnValueHandler> methodReturnValueHandlers) {
93          this.methodReturnValueHandlers = methodReturnValueHandlers;
94      }
95  
96      private ClassLoader getClassLoader() {
97          return this.classLoader != null ? this.classLoader : DefaultMethodEndpointAdapter.class.getClassLoader();
98      }
99  
100     public void setBeanClassLoader(ClassLoader classLoader) {
101         this.classLoader = classLoader;
102     }
103 
104     public void afterPropertiesSet() throws Exception {
105         initDefaultStrategies();
106     }
107 
108     /** Initialize the default implementations for the adapter's strategies. */
109     protected void initDefaultStrategies() {
110         initMethodArgumentResolvers();
111         initMethodReturnValueHandlers();
112     }
113 
114     private void initMethodArgumentResolvers() {
115         if (CollectionUtils.isEmpty(methodArgumentResolvers)) {
116             List<MethodArgumentResolver> methodArgumentResolvers = new ArrayList<MethodArgumentResolver>();
117             methodArgumentResolvers.add(new DomPayloadMethodProcessor());
118             methodArgumentResolvers.add(new MessageContextMethodArgumentResolver());
119             methodArgumentResolvers.add(new SourcePayloadMethodProcessor());
120             methodArgumentResolvers.add(new XPathParamMethodArgumentResolver());
121             addMethodArgumentResolver(SOAP_METHOD_ARGUMENT_RESOLVER_CLASS_NAME, methodArgumentResolvers);
122             addMethodArgumentResolver(SOAP_HEADER_ELEMENT_ARGUMENT_RESOLVER_CLASS_NAME, methodArgumentResolvers);
123             if (isPresent(DOM4J_CLASS_NAME)) {
124                 methodArgumentResolvers.add(new Dom4jPayloadMethodProcessor());
125             }
126             if (isPresent(JAXB2_CLASS_NAME)) {
127                 methodArgumentResolvers.add(new XmlRootElementPayloadMethodProcessor());
128                 methodArgumentResolvers.add(new JaxbElementPayloadMethodProcessor());
129             }
130             if (isPresent(JDOM_CLASS_NAME)) {
131                 methodArgumentResolvers.add(new JDomPayloadMethodProcessor());
132             }
133             if (isPresent(STAX_CLASS_NAME)) {
134                 methodArgumentResolvers.add(new StaxPayloadMethodArgumentResolver());
135             }
136             if (isPresent(XOM_CLASS_NAME)) {
137                 methodArgumentResolvers.add(new XomPayloadMethodProcessor());
138             }
139             if (logger.isDebugEnabled()) {
140                 logger.debug("No MethodArgumentResolvers set, using defaults: " + methodArgumentResolvers);
141             }
142             setMethodArgumentResolvers(methodArgumentResolvers);
143         }
144     }
145 
146     /**
147      * Certain (SOAP-specific) {@code MethodArgumentResolver}s have to be instantiated by class name, in order to not
148      * introduce a cyclic dependency.
149      */
150     @SuppressWarnings("unchecked")
151     private void addMethodArgumentResolver(String className, List<MethodArgumentResolver> methodArgumentResolvers) {
152         try {
153             Class<MethodArgumentResolver> methodArgumentResolverClass =
154                     (Class<MethodArgumentResolver>) ClassUtils.forName(className, getClassLoader());
155             methodArgumentResolvers.add(BeanUtils.instantiate(methodArgumentResolverClass));
156         }
157         catch (ClassNotFoundException e) {
158             logger.warn("Could not find \"" + className + "\" on the classpath");
159         }
160     }
161 
162     private void initMethodReturnValueHandlers() {
163         if (CollectionUtils.isEmpty(methodReturnValueHandlers)) {
164             List<MethodReturnValueHandler> methodReturnValueHandlers = new ArrayList<MethodReturnValueHandler>();
165             methodReturnValueHandlers.add(new DomPayloadMethodProcessor());
166             methodReturnValueHandlers.add(new SourcePayloadMethodProcessor());
167             if (isPresent(DOM4J_CLASS_NAME)) {
168                 methodReturnValueHandlers.add(new Dom4jPayloadMethodProcessor());
169             }
170             if (isPresent(JAXB2_CLASS_NAME)) {
171                 methodReturnValueHandlers.add(new XmlRootElementPayloadMethodProcessor());
172                 methodReturnValueHandlers.add(new JaxbElementPayloadMethodProcessor());
173             }
174             if (isPresent(JDOM_CLASS_NAME)) {
175                 methodReturnValueHandlers.add(new JDomPayloadMethodProcessor());
176             }
177             if (isPresent(XOM_CLASS_NAME)) {
178                 methodReturnValueHandlers.add(new XomPayloadMethodProcessor());
179             }
180             if (logger.isDebugEnabled()) {
181                 logger.debug("No MethodReturnValueHandlers set, using defaults: " + methodReturnValueHandlers);
182             }
183             setMethodReturnValueHandlers(methodReturnValueHandlers);
184         }
185     }
186 
187     private boolean isPresent(String className) {
188         return ClassUtils.isPresent(className, getClassLoader());
189     }
190 
191     @Override
192     protected boolean supportsInternal(MethodEndpoint methodEndpoint) {
193         return supportsParameters(methodEndpoint.getMethodParameters()) &&
194                 supportsReturnType(methodEndpoint.getReturnType());
195     }
196 
197     private boolean supportsParameters(MethodParameter[] methodParameters) {
198         for (MethodParameter methodParameter : methodParameters) {
199             boolean supported = false;
200             for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
201                 if (logger.isTraceEnabled()) {
202                     logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
203                             methodParameter.getGenericParameterType() + "]");
204                 }
205                 if (methodArgumentResolver.supportsParameter(methodParameter)) {
206                     supported = true;
207                     break;
208                 }
209             }
210             if (!supported) {
211                 return false;
212             }
213         }
214         return true;
215     }
216 
217     private boolean supportsReturnType(MethodParameter methodReturnType) {
218         if (Void.TYPE.equals(methodReturnType.getParameterType())) {
219             return true;
220         }
221         for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
222             if (methodReturnValueHandler.supportsReturnType(methodReturnType)) {
223                 return true;
224             }
225         }
226         return false;
227     }
228 
229     @Override
230     protected final void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception {
231         Object[] args = getMethodArguments(messageContext, methodEndpoint);
232 
233         if (logger.isTraceEnabled()) {
234             StringBuilder builder = new StringBuilder("Invoking [");
235             builder.append(methodEndpoint).append("] with arguments ");
236             builder.append(Arrays.asList(args));
237             logger.trace(builder.toString());
238         }
239 
240         Object returnValue = methodEndpoint.invoke(args);
241 
242         if (logger.isTraceEnabled()) {
243             logger.trace("Method [" + methodEndpoint + "] returned [" + returnValue + "]");
244         }
245 
246         Class<?> returnType = methodEndpoint.getMethod().getReturnType();
247         if (!Void.TYPE.equals(returnType)) {
248             handleMethodReturnValue(messageContext, returnValue, methodEndpoint);
249         }
250     }
251 
252     /**
253      * Returns the argument array for the given method endpoint.
254      * <p/>
255      * This implementation iterates over the set {@linkplain #setMethodArgumentResolvers(List) argument resolvers} to
256      * resolve each argument.
257      *
258      * @param messageContext the current message context
259      * @param methodEndpoint the method endpoint to get arguments for
260      * @return the arguments
261      * @throws Exception in case of errors
262      */
263     protected Object[] getMethodArguments(MessageContext messageContext, MethodEndpoint methodEndpoint)
264             throws Exception {
265         MethodParameter[] parameters = methodEndpoint.getMethodParameters();
266         Object[] args = new Object[parameters.length];
267         for (int i = 0; i < parameters.length; i++) {
268             for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
269                 if (methodArgumentResolver.supportsParameter(parameters[i])) {
270                     args[i] = methodArgumentResolver.resolveArgument(messageContext, parameters[i]);
271                     break;
272                 }
273             }
274         }
275         return args;
276     }
277 
278     /**
279      * Handle the return value for the given method endpoint.
280      * <p/>
281      * This implementation iterates over the set {@linkplain #setMethodReturnValueHandlers(java.util.List)}  return value
282      * handlers} to resolve the return value.
283      *
284      * @param messageContext the current message context
285      * @param returnValue    the return value
286      * @param methodEndpoint the method endpoint to get arguments for
287      * @throws Exception in case of errors
288      */
289     protected void handleMethodReturnValue(MessageContext messageContext,
290                                            Object returnValue,
291                                            MethodEndpoint methodEndpoint) throws Exception {
292         MethodParameter returnType = methodEndpoint.getReturnType();
293         for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
294             if (methodReturnValueHandler.supportsReturnType(returnType)) {
295                 methodReturnValueHandler.handleReturnValue(messageContext, returnType, returnValue);
296                 return;
297             }
298         }
299         throw new IllegalStateException(
300                 "Return value [" + returnValue + "] not resolved by any MethodReturnValueHandler");
301     }
302 }