/* * Copyright 2005-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.ws.server.endpoint.mapping; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContextException; import org.springframework.core.BridgeMethodResolver; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.endpoint.MethodEndpoint; /** * Abstract base class for {@link MethodEndpoint} mappings. * * <p>Subclasses typically implement {@link org.springframework.beans.factory.config.BeanPostProcessor} to look for beans * that qualify as endpoint. The methods of this bean are then registered under a specific key with {@link * #registerEndpoint(Object, MethodEndpoint)}. * * @author Arjen Poutsma * @since 1.0.0 */ public abstract class AbstractMethodEndpointMapping<T> extends AbstractEndpointMapping { private final Map<T, MethodEndpoint> endpointMap = new HashMap<T, MethodEndpoint>(); /** * Lookup an endpoint for the given message. The extraction of the endpoint key is delegated to the concrete * subclass. * * @return the looked up endpoint, or {@code null} * @see #getLookupKeyForMessage(MessageContext) */ @Override protected Object getEndpointInternal(MessageContext messageContext) throws Exception { T key = getLookupKeyForMessage(messageContext); if (key == null) { return null; } if (logger.isDebugEnabled()) { logger.debug("Looking up endpoint for [" + key + "]"); } return lookupEndpoint(key); } /** * Returns the endpoint keys for the given message context. * * @return the registration keys */ protected abstract T getLookupKeyForMessage(MessageContext messageContext) throws Exception; /** * Looks up an endpoint instance for the given keys. All keys are tried in order. * * @param key key the beans are mapped to * @return the associated endpoint instance, or {@code null} if not found */ protected MethodEndpoint lookupEndpoint(T key) { return endpointMap.get(key); } /** * Register the given endpoint instance under the key. * * @param key the lookup key * @param endpoint the method endpoint instance * @throws BeansException if the endpoint could not be registered */ protected void registerEndpoint(T key, MethodEndpoint endpoint) throws BeansException { Object mappedEndpoint = endpointMap.get(key); if (mappedEndpoint != null) { throw new ApplicationContextException("Cannot map endpoint [" + endpoint + "] on registration key [" + key + "]: there's already endpoint [" + mappedEndpoint + "] mapped"); } if (endpoint == null) { throw new ApplicationContextException("Could not find endpoint for key [" + key + "]"); } endpointMap.put(key, endpoint); if (logger.isDebugEnabled()) { logger.debug("Mapped [" + key + "] onto endpoint [" + endpoint + "]"); } } /** * Helper method that registers the methods of the given bean. This method iterates over the methods of the bean, * and calls {@link #getLookupKeyForMethod(Method)} for each. If this returns a string, the method is registered * using {@link #registerEndpoint(Object, MethodEndpoint)}. * * @see #getLookupKeyForMethod(Method) */ protected void registerMethods(final Object endpoint) { Assert.notNull(endpoint, "'endpoint' must not be null"); Class<?> endpointClass = getEndpointClass(endpoint); ReflectionUtils.doWithMethods(endpointClass, new ReflectionUtils.MethodCallback() { public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { List<T> keys = getLookupKeysForMethod(method); for (T key : keys) { registerEndpoint(key, new MethodEndpoint(endpoint, method)); } } }); } /** * Helper method that registers the methods of the given class. This method iterates over the methods of the class, * and calls {@link #getLookupKeyForMethod(Method)} for each. If this returns a string, the method is registered * using {@link #registerEndpoint(Object, MethodEndpoint)}. * * @see #getLookupKeyForMethod(Method) * @see #getLookupKeysForMethod(Method) */ protected void registerMethods(String beanName) { Assert.hasText(beanName, "'beanName' must not be empty"); Class<?> endpointType = getApplicationContext().getType(beanName); endpointType = ClassUtils.getUserClass(endpointType); Set<Method> methods = findEndpointMethods(endpointType, new ReflectionUtils.MethodFilter() { public boolean matches(Method method) { return !getLookupKeysForMethod(method).isEmpty(); } }); for (Method method : methods) { List<T> keys = getLookupKeysForMethod(method); for (T key : keys) { registerEndpoint(key, new MethodEndpoint(beanName, getApplicationContext(), method)); } } } private Set<Method> findEndpointMethods(Class<?> endpointType, final ReflectionUtils.MethodFilter endpointMethodFilter) { final Set<Method> endpointMethods = new LinkedHashSet<Method>(); Set<Class<?>> endpointTypes = new LinkedHashSet<Class<?>>(); Class<?> specificEndpointType = null; if (!Proxy.isProxyClass(endpointType)) { endpointTypes.add(endpointType); specificEndpointType = endpointType; } endpointTypes.addAll(Arrays.asList(endpointType.getInterfaces())); for (Class<?> currentEndpointType : endpointTypes) { final Class<?> targetClass = (specificEndpointType != null ? specificEndpointType : currentEndpointType); ReflectionUtils.doWithMethods(currentEndpointType, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); if (endpointMethodFilter.matches(specificMethod) && (bridgedMethod == specificMethod || !endpointMethodFilter.matches(bridgedMethod))) { endpointMethods.add(specificMethod); } } }, ReflectionUtils.USER_DECLARED_METHODS); } return endpointMethods; } /** * Returns the endpoint key for the given method. Returns {@code null} if the method is not to be * registered, which is the default. * * @param method the method * @return a registration key, or {@code null} if the method is not to be registered * @see #getLookupKeysForMethod(Method) */ protected T getLookupKeyForMethod(Method method) { return null; } /** * Returns the endpoint keys for the given method. Should return an empty array if the method is not to be * registered. The default delegates to {@link #getLookupKeysForMethod(Method)}. * * @param method the method * @return a list of registration keys * @since 2.2 */ protected List<T> getLookupKeysForMethod(Method method) { T key = getLookupKeyForMethod(method); return key != null ? Collections.singletonList(key) : Collections.<T>emptyList(); } /** * Return the class or interface to use for method reflection. * * <p>Default implementation delegates to {@link AopUtils#getTargetClass(Object)}. * * @param endpoint the bean instance (might be an AOP proxy) * @return the bean class to expose */ protected Class<?> getEndpointClass(Object endpoint) { if (AopUtils.isJdkDynamicProxy(endpoint)) { throw new IllegalArgumentException(ClassUtils.getShortName(getClass()) + " does not work with JDK Dynamic Proxies. " + "Please use CGLIB proxies, by setting proxy-target-class=\"true\" on the aop:aspectj-autoproxy " + "or aop:config element."); } return AopUtils.getTargetClass(endpoint); } }