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