001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.component.bean;
018
019 import java.lang.annotation.Annotation;
020 import java.lang.reflect.Method;
021 import java.lang.reflect.Modifier;
022 import java.util.ArrayList;
023 import java.util.Arrays;
024 import java.util.Collection;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.concurrent.ConcurrentHashMap;
028
029 import org.apache.camel.Body;
030 import org.apache.camel.CamelContext;
031 import org.apache.camel.Exchange;
032 import org.apache.camel.Expression;
033 import org.apache.camel.Header;
034 import org.apache.camel.Headers;
035 import org.apache.camel.Message;
036 import org.apache.camel.OutHeaders;
037 import org.apache.camel.Properties;
038 import org.apache.camel.Property;
039 import org.apache.camel.RuntimeCamelException;
040 import org.apache.camel.builder.ExpressionBuilder;
041 import org.apache.camel.language.LanguageAnnotation;
042 import org.apache.camel.spi.Registry;
043 import org.apache.camel.util.ObjectHelper;
044 import org.apache.commons.logging.Log;
045 import org.apache.commons.logging.LogFactory;
046
047 import static org.apache.camel.util.ExchangeHelper.convertToType;
048
049 /**
050 * Represents the metadata about a bean type created via a combination of
051 * introspection and annotations together with some useful sensible defaults
052 *
053 * @version $Revision: 675938 $
054 */
055 public class BeanInfo {
056 private static final transient Log LOG = LogFactory.getLog(BeanInfo.class);
057 private final CamelContext camelContext;
058 private Class type;
059 private ParameterMappingStrategy strategy;
060 private Map<String, MethodInfo> operations = new ConcurrentHashMap<String, MethodInfo>();
061 private MethodInfo defaultMethod;
062 private List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>();
063 private List<MethodInfo> operationsWithCustomAnnotation = new ArrayList<MethodInfo>();
064
065 public BeanInfo(CamelContext camelContext, Class type) {
066 this(camelContext, type, createParameterMappingStrategy(camelContext));
067 }
068
069 public BeanInfo(CamelContext camelContext, Class type, ParameterMappingStrategy strategy) {
070 this.camelContext = camelContext;
071 this.type = type;
072 this.strategy = strategy;
073 introspect(getType());
074 if (operations.size() == 1) {
075 Collection<MethodInfo> methodInfos = operations.values();
076 for (MethodInfo methodInfo : methodInfos) {
077 defaultMethod = methodInfo;
078 }
079 }
080 }
081
082 public Class getType() {
083 return type;
084 }
085
086 public CamelContext getCamelContext() {
087 return camelContext;
088 }
089
090 public MethodInvocation createInvocation(Method method, Object pojo, Exchange exchange)
091 throws RuntimeCamelException {
092 MethodInfo methodInfo = introspect(type, method);
093 if (methodInfo != null) {
094 return methodInfo.createMethodInvocation(pojo, exchange);
095 }
096 return null;
097 }
098
099 public MethodInvocation createInvocation(Object pojo, Exchange exchange) throws RuntimeCamelException,
100 AmbiguousMethodCallException {
101 MethodInfo methodInfo = null;
102
103 // TODO use some other mechanism?
104 String name = exchange.getIn().getHeader(BeanProcessor.METHOD_NAME, String.class);
105 if (name != null) {
106 methodInfo = operations.get(name);
107 }
108 if (methodInfo == null) {
109 methodInfo = chooseMethod(pojo, exchange);
110 }
111 if (methodInfo == null) {
112 methodInfo = defaultMethod;
113 }
114 if (methodInfo != null) {
115 return methodInfo.createMethodInvocation(pojo, exchange);
116 }
117 return null;
118 }
119
120 protected void introspect(Class clazz) {
121 Method[] methods = clazz.getDeclaredMethods();
122 for (Method method : methods) {
123 if (isValidMethod(clazz, method)) {
124 introspect(clazz, method);
125 }
126 }
127 Class superclass = clazz.getSuperclass();
128 if (superclass != null && !superclass.equals(Object.class)) {
129 introspect(superclass);
130 }
131 }
132
133 protected MethodInfo introspect(Class clazz, Method method) {
134 Class[] parameterTypes = method.getParameterTypes();
135 Annotation[][] parametersAnnotations = method.getParameterAnnotations();
136
137 List<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
138 List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>();
139
140 boolean hasCustomAnnotation = false;
141 for (int i = 0; i < parameterTypes.length; i++) {
142 Class parameterType = parameterTypes[i];
143 Annotation[] parameterAnnotations = parametersAnnotations[i];
144 Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType,
145 parameterAnnotations);
146 hasCustomAnnotation |= expression != null;
147
148 ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations,
149 expression);
150 parameters.add(parameterInfo);
151
152 if (expression == null) {
153 hasCustomAnnotation |= ObjectHelper.hasAnnotation(parameterAnnotations, Body.class);
154 if (bodyParameters.isEmpty()) {
155 // lets assume its the body
156 if (Exchange.class.isAssignableFrom(parameterType)) {
157 expression = ExpressionBuilder.exchangeExpression();
158 } else {
159 expression = ExpressionBuilder.bodyExpression(parameterType);
160 }
161 parameterInfo.setExpression(expression);
162 bodyParameters.add(parameterInfo);
163 } else {
164 if (LOG.isDebugEnabled()) {
165 LOG.debug("No expression available for method: " + method.toString()
166 + " which already has a body so ignoring parameter: " + i
167 + " so ignoring method");
168 }
169 return null;
170 }
171 }
172
173 }
174
175 // now lets add the method to the repository
176 String opName = method.getName();
177
178 // TODO allow an annotation to expose the operation name to use
179 /* if (method.getAnnotation(Operation.class) != null) { String name =
180 * method.getAnnotation(Operation.class).name(); if (name != null &&
181 * name.length() > 0) { opName = name; } }
182 */
183 MethodInfo methodInfo = new MethodInfo(clazz, method, parameters, bodyParameters, hasCustomAnnotation);
184 operations.put(opName, methodInfo);
185 if (methodInfo.hasBodyParameter()) {
186 operationsWithBody.add(methodInfo);
187 }
188 if (methodInfo.isHasCustomAnnotation() && !methodInfo.hasBodyParameter()) {
189 operationsWithCustomAnnotation.add(methodInfo);
190 }
191 return methodInfo;
192 }
193
194 /**
195 * Lets try choose one of the available methods to invoke if we can match
196 * the message body to the body parameter
197 *
198 * @param pojo the bean to invoke a method on
199 * @param exchange the message exchange
200 * @return the method to invoke or null if no definitive method could be
201 * matched
202 */
203 protected MethodInfo chooseMethod(Object pojo, Exchange exchange) throws AmbiguousMethodCallException {
204 if (operationsWithBody.size() == 1) {
205 return operationsWithBody.get(0);
206 } else if (!operationsWithBody.isEmpty()) {
207 return chooseMethodWithMatchingBody(exchange, operationsWithBody);
208 } else if (operationsWithCustomAnnotation.size() == 1) {
209 return operationsWithCustomAnnotation.get(0);
210 }
211 return null;
212 }
213
214 protected MethodInfo chooseMethodWithMatchingBody(Exchange exchange, Collection<MethodInfo> operationList) throws AmbiguousMethodCallException {
215 // lets see if we can find a method who's body param type matches the message body
216 Message in = exchange.getIn();
217 Object body = in.getBody();
218 if (body != null) {
219 Class bodyType = body.getClass();
220
221 List<MethodInfo> possibles = new ArrayList<MethodInfo>();
222 for (MethodInfo methodInfo : operationList) {
223 // TODO: AOP proxies have additioan methods - consider having a static
224 // method exclude list to skip all known AOP proxy methods
225 // TODO: This class could use some TRACE logging
226
227 // test for MEP pattern matching
228 boolean out = exchange.getPattern().isOutCapable();
229 if (out && methodInfo.isReturnTypeVoid()) {
230 // skip this method as the MEP is Out so the method must return someting
231 continue;
232 }
233
234 // try to match the arguments
235 if (methodInfo.bodyParameterMatches(bodyType)) {
236 possibles.add(methodInfo);
237 }
238 }
239 if (possibles.size() == 1) {
240 return possibles.get(0);
241 } else if (possibles.isEmpty()) {
242 // lets try converting
243 Object newBody = null;
244 MethodInfo matched = null;
245 for (MethodInfo methodInfo : operationList) {
246 Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body);
247 if (value != null) {
248 if (newBody != null) {
249 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched,
250 methodInfo));
251 } else {
252 newBody = value;
253 matched = methodInfo;
254 }
255 }
256 }
257 if (matched != null) {
258 in.setBody(newBody);
259 return matched;
260 }
261 } else {
262 // if we only have a single method with custom annotations, lets use that one
263 if (operationsWithCustomAnnotation.size() == 1) {
264 return operationsWithCustomAnnotation.get(0);
265 }
266 return chooseMethodWithCustomAnnotations(exchange, possibles);
267 }
268 }
269 // no match so return null
270 return null;
271 }
272
273 protected MethodInfo chooseMethodWithCustomAnnotations(Exchange exchange, Collection<MethodInfo> possibles) throws AmbiguousMethodCallException {
274 // if we have only one method with custom annotations lets choose that
275 MethodInfo chosen = null;
276 for (MethodInfo possible : possibles) {
277 if (possible.isHasCustomAnnotation()) {
278 if (chosen != null) {
279 chosen = null;
280 break;
281 } else {
282 chosen = possible;
283 }
284 }
285 }
286 if (chosen != null) {
287 return chosen;
288 }
289 throw new AmbiguousMethodCallException(exchange, possibles);
290 }
291
292 /**
293 * Creates an expression for the given parameter type if the parameter can
294 * be mapped automatically or null if the parameter cannot be mapped due to
295 * unsufficient annotations or not fitting with the default type
296 * conventions.
297 */
298 protected Expression createParameterUnmarshalExpression(Class clazz, Method method, Class parameterType,
299 Annotation[] parameterAnnotation) {
300
301 // TODO look for a parameter annotation that converts into an expression
302 for (Annotation annotation : parameterAnnotation) {
303 Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType,
304 annotation);
305 if (answer != null) {
306 return answer;
307 }
308 }
309 return strategy.getDefaultParameterTypeExpression(parameterType);
310 }
311
312 protected boolean isPossibleBodyParameter(Annotation[] annotations) {
313 if (annotations != null) {
314 for (Annotation annotation : annotations) {
315 if ((annotation instanceof Property)
316 || (annotation instanceof Header)
317 || (annotation instanceof Headers)
318 || (annotation instanceof OutHeaders)
319 || (annotation instanceof Properties)) {
320 return false;
321 }
322 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class);
323 if (languageAnnotation != null) {
324 return false;
325 }
326 }
327 }
328 return true;
329 }
330
331 protected Expression createParameterUnmarshalExpressionForAnnotation(Class clazz, Method method,
332 Class parameterType,
333 Annotation annotation) {
334 if (annotation instanceof Property) {
335 Property propertyAnnotation = (Property)annotation;
336 return ExpressionBuilder.propertyExpression(propertyAnnotation.name());
337 } else if (annotation instanceof Properties) {
338 return ExpressionBuilder.propertiesExpression();
339 } else if (annotation instanceof Header) {
340 Header headerAnnotation = (Header)annotation;
341 return ExpressionBuilder.headerExpression(headerAnnotation.name());
342 } else if (annotation instanceof Headers) {
343 return ExpressionBuilder.headersExpression();
344 } else if (annotation instanceof OutHeaders) {
345 return ExpressionBuilder.outHeadersExpression();
346 } else {
347 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class);
348 if (languageAnnotation != null) {
349 Class<?> type = languageAnnotation.factory();
350 Object object = camelContext.getInjector().newInstance(type);
351 if (object instanceof AnnotationExpressionFactory) {
352 AnnotationExpressionFactory expressionFactory = (AnnotationExpressionFactory) object;
353 return expressionFactory.createExpression(camelContext, annotation, languageAnnotation, parameterType);
354 } else {
355 LOG.error("Ignoring bad annotation: " + languageAnnotation + "on method: " + method
356 + " which declares a factory: " + type.getName()
357 + " which does not implement " + AnnotationExpressionFactory.class.getName());
358 }
359 }
360 }
361
362 return null;
363 }
364
365 protected boolean isValidMethod(Class clazz, Method method) {
366 // must be a public method
367 if (!Modifier.isPublic(method.getModifiers())) {
368 return false;
369 }
370
371 // return type must not be an Exchange
372 if (method.getReturnType() != null && Exchange.class.isAssignableFrom(method.getReturnType())) {
373 return false;
374 }
375
376 return true;
377 }
378
379 public static ParameterMappingStrategy createParameterMappingStrategy(CamelContext camelContext) {
380 Registry registry = camelContext.getRegistry();
381 ParameterMappingStrategy answer = registry.lookup(ParameterMappingStrategy.class.getName(),
382 ParameterMappingStrategy.class);
383 if (answer == null) {
384 answer = new DefaultParameterMappingStrategy();
385 }
386 return answer;
387 }
388 }