View Javadoc

1   /*
2    * Copyright 2005-2011 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.mapping;
18  
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Proxy;
21  import java.util.Arrays;
22  import java.util.HashMap;
23  import java.util.LinkedHashSet;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.springframework.aop.support.AopUtils;
28  import org.springframework.beans.BeansException;
29  import org.springframework.context.ApplicationContextException;
30  import org.springframework.core.BridgeMethodResolver;
31  import org.springframework.util.Assert;
32  import org.springframework.util.ClassUtils;
33  import org.springframework.util.ReflectionUtils;
34  import org.springframework.ws.context.MessageContext;
35  import org.springframework.ws.server.endpoint.MethodEndpoint;
36  
37  /**
38   * Abstract base class for {@link MethodEndpoint} mappings.
39   * <p/>
40   * Subclasses typically implement {@link org.springframework.beans.factory.config.BeanPostProcessor} to look for beans
41   * that qualify as endpoint. The methods of this bean are then registered under a specific key with {@link
42   * #registerEndpoint(Object, MethodEndpoint)}.
43   *
44   * @author Arjen Poutsma
45   * @since 1.0.0
46   */
47  public abstract class AbstractMethodEndpointMapping<T> extends AbstractEndpointMapping {
48  
49      private final Map<T, MethodEndpoint> endpointMap = new HashMap<T, MethodEndpoint>();
50  
51      /**
52       * Lookup an endpoint for the given message. The extraction of the endpoint key is delegated to the concrete
53       * subclass.
54       *
55       * @return the looked up endpoint, or <code>null</code>
56       * @see #getLookupKeyForMessage(MessageContext)
57       */
58      @Override
59      protected Object getEndpointInternal(MessageContext messageContext) throws Exception {
60          T key = getLookupKeyForMessage(messageContext);
61          if (key == null) {
62              return null;
63          }
64          if (logger.isDebugEnabled()) {
65              logger.debug("Looking up endpoint for [" + key + "]");
66          }
67          return lookupEndpoint(key);
68      }
69  
70      /**
71       * Returns the the endpoint keys for the given message context.
72       *
73       * @return the registration keys
74       */
75      protected abstract T getLookupKeyForMessage(MessageContext messageContext) throws Exception;
76  
77      /**
78       * Looks up an endpoint instance for the given keys. All keys are tried in order.
79       *
80       * @param key key the beans are mapped to
81       * @return the associated endpoint instance, or <code>null</code> if not found
82       */
83      protected MethodEndpoint lookupEndpoint(T key) {
84          return endpointMap.get(key);
85      }
86  
87      /**
88       * Register the given endpoint instance under the key.
89       *
90       * @param key      the lookup key
91       * @param endpoint the method endpoint instance
92       * @throws BeansException if the endpoint could not be registered
93       */
94      protected void registerEndpoint(T key, MethodEndpoint endpoint) throws BeansException {
95          Object mappedEndpoint = endpointMap.get(key);
96          if (mappedEndpoint != null) {
97              throw new ApplicationContextException("Cannot map endpoint [" + endpoint + "] on registration key [" + key +
98                      "]: there's already endpoint [" + mappedEndpoint + "] mapped");
99          }
100         if (endpoint == null) {
101             throw new ApplicationContextException("Could not find endpoint for key [" + key + "]");
102         }
103         endpointMap.put(key, endpoint);
104         if (logger.isDebugEnabled()) {
105             logger.debug("Mapped [" + key + "] onto endpoint [" + endpoint + "]");
106         }
107     }
108 
109     /**
110      * Helper method that registers the methods of the given bean. This method iterates over the methods of the bean,
111      * and calls {@link #getLookupKeyForMethod(Method)} for each. If this returns a string, the method is registered
112      * using {@link #registerEndpoint(Object, MethodEndpoint)}.
113      *
114      * @see #getLookupKeyForMethod(Method)
115      */
116     protected void registerMethods(final Object endpoint) {
117         Assert.notNull(endpoint, "'endpoint' must not be null");
118         Class<?> endpointClass = getEndpointClass(endpoint);
119         ReflectionUtils.doWithMethods(endpointClass, new ReflectionUtils.MethodCallback() {
120 
121             public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
122                 T key = getLookupKeyForMethod(method);
123                 if (key != null) {
124                     registerEndpoint(key, new MethodEndpoint(endpoint, method));
125                 }
126             }
127         });
128     }
129 
130     /**
131      * Helper method that registers the methods of the given class. This method iterates over the methods of the class,
132      * and calls {@link #getLookupKeyForMethod(Method)} for each. If this returns a string, the method is registered
133      * using {@link #registerEndpoint(Object, MethodEndpoint)}.
134      *
135      * @see #getLookupKeyForMethod(Method)
136      */
137     protected void registerMethods(String beanName) {
138         Assert.hasText(beanName, "'beanName' must not be empty");
139         Class<?> endpointType = getApplicationContext().getType(beanName);
140         endpointType = ClassUtils.getUserClass(endpointType);
141         
142         Set<Method> methods = findEndpointMethods(endpointType, new ReflectionUtils.MethodFilter() {
143             public boolean matches(Method method) {
144                 return getLookupKeyForMethod(method) != null;
145             }
146         });
147 
148         for (Method method : methods) {
149             T key = getLookupKeyForMethod(method);
150             registerEndpoint(key, new MethodEndpoint(beanName, getApplicationContext(), method));
151         }
152 
153     }
154 
155     private Set<Method> findEndpointMethods(Class<?> endpointType,
156                                             final ReflectionUtils.MethodFilter endpointMethodFilter) {
157         final Set<Method> endpointMethods = new LinkedHashSet<Method>();
158         Set<Class<?>> endpointTypes = new LinkedHashSet<Class<?>>();
159         Class<?> specificEndpointType = null;
160         if (!Proxy.isProxyClass(endpointType)) {
161             endpointTypes.add(endpointType);
162             specificEndpointType = endpointType;
163         }
164         endpointTypes.addAll(Arrays.asList(endpointType.getInterfaces()));
165         for (Class<?> currentEndpointType : endpointTypes) {
166             final Class<?> targetClass = (specificEndpointType != null ? specificEndpointType : currentEndpointType);
167             ReflectionUtils.doWithMethods(currentEndpointType, new ReflectionUtils.MethodCallback() {
168                 public void doWith(Method method) {
169                     Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
170                     Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
171                     if (endpointMethodFilter.matches(specificMethod) &&
172                             (bridgedMethod == specificMethod || !endpointMethodFilter.matches(bridgedMethod))) {
173                         endpointMethods.add(specificMethod);
174                     }
175                 }
176             }, ReflectionUtils.USER_DECLARED_METHODS);
177         }
178         return endpointMethods;
179     }
180 
181     /**
182      * Returns the the endpoint keys for the given method. Returns <code>null</code> if the method is not to be
183      * registered, which is the default.
184      *
185      * @param method the method
186      * @return a registration key, or <code>null</code> if the method is not to be registered
187      */
188     protected T getLookupKeyForMethod(Method method) {
189         return null;
190     }
191 
192     /**
193      * Return the class or interface to use for method reflection.
194      * <p/>
195      * Default implementation delegates to {@link AopUtils#getTargetClass(Object)}.
196      *
197      * @param endpoint the bean instance (might be an AOP proxy)
198      * @return the bean class to expose
199      */
200     protected Class<?> getEndpointClass(Object endpoint) {
201         if (AopUtils.isJdkDynamicProxy(endpoint)) {
202             throw new IllegalArgumentException(ClassUtils.getShortName(getClass()) +
203                     " does not work with JDK Dynamic Proxies. " +
204                     "Please use CGLIB proxies, by setting proxy-target-class=\"true\" on the aop:aspectj-autoproxy " +
205                     "or aop:config element.");
206         }
207         return AopUtils.getTargetClass(endpoint);
208     }
209 }