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.lang.reflect.Proxy;
023 import java.util.ArrayList;
024 import java.util.Arrays;
025 import java.util.Collection;
026 import java.util.Collections;
027 import java.util.Comparator;
028 import java.util.HashMap;
029 import java.util.Iterator;
030 import java.util.List;
031 import java.util.Map;
032
033 import org.apache.camel.Attachments;
034 import org.apache.camel.Body;
035 import org.apache.camel.CamelContext;
036 import org.apache.camel.Exchange;
037 import org.apache.camel.ExchangeException;
038 import org.apache.camel.Expression;
039 import org.apache.camel.Handler;
040 import org.apache.camel.Header;
041 import org.apache.camel.Headers;
042 import org.apache.camel.Message;
043 import org.apache.camel.OutHeaders;
044 import org.apache.camel.Properties;
045 import org.apache.camel.Property;
046 import org.apache.camel.builder.ExpressionBuilder;
047 import org.apache.camel.language.LanguageAnnotation;
048 import org.apache.camel.spi.Registry;
049 import org.apache.camel.util.CastUtils;
050 import org.apache.camel.util.IntrospectionSupport;
051 import org.apache.camel.util.ObjectHelper;
052 import org.slf4j.Logger;
053 import org.slf4j.LoggerFactory;
054
055 import static org.apache.camel.util.ExchangeHelper.convertToType;
056
057 /**
058 * Represents the metadata about a bean type created via a combination of
059 * introspection and annotations together with some useful sensible defaults
060 *
061 * @version
062 */
063 public class BeanInfo {
064 private static final transient Logger LOG = LoggerFactory.getLogger(BeanInfo.class);
065 private static final String CGLIB_CLASS_SEPARATOR = "$$";
066 private static final List<Method> EXCLUDED_METHODS = new ArrayList<Method>();
067 private final CamelContext camelContext;
068 private final Class<?> type;
069 private final ParameterMappingStrategy strategy;
070 private final MethodInfo defaultMethod;
071 // shared state with details of operations introspected from the bean, created during the constructor
072 private Map<String, List<MethodInfo>> operations = new HashMap<String, List<MethodInfo>>();
073 private List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>();
074 private List<MethodInfo> operationsWithNoBody = new ArrayList<MethodInfo>();
075 private List<MethodInfo> operationsWithCustomAnnotation = new ArrayList<MethodInfo>();
076 private List<MethodInfo> operationsWithHandlerAnnotation = new ArrayList<MethodInfo>();
077 private Map<Method, MethodInfo> methodMap = new HashMap<Method, MethodInfo>();
078
079 static {
080 // exclude all java.lang.Object methods as we dont want to invoke them
081 EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods()));
082 // exclude all java.lang.reflect.Proxy methods as we dont want to invoke them
083 EXCLUDED_METHODS.addAll(Arrays.asList(Proxy.class.getMethods()));
084 try {
085 // but keep toString as this method is okay
086 EXCLUDED_METHODS.remove(Object.class.getMethod("toString"));
087 EXCLUDED_METHODS.remove(Proxy.class.getMethod("toString"));
088 } catch (Throwable e) {
089 // ignore
090 }
091 }
092
093 public BeanInfo(CamelContext camelContext, Class<?> type) {
094 this(camelContext, type, createParameterMappingStrategy(camelContext));
095 }
096
097 public BeanInfo(CamelContext camelContext, Method explicitMethod) {
098 this(camelContext, explicitMethod.getDeclaringClass(), explicitMethod, createParameterMappingStrategy(camelContext));
099 }
100
101 public BeanInfo(CamelContext camelContext, Class<?> type, ParameterMappingStrategy strategy) {
102 this(camelContext, type, null, strategy);
103 }
104
105 public BeanInfo(CamelContext camelContext, Class<?> type, Method explicitMethod, ParameterMappingStrategy strategy) {
106 this.camelContext = camelContext;
107 this.type = type;
108 this.strategy = strategy;
109
110 if (explicitMethod != null) {
111 // must be a valid method
112 if (!isValidMethod(type, explicitMethod)) {
113 throw new IllegalArgumentException("The method " + explicitMethod + " is not valid (for example the method must be public)");
114 }
115 introspect(getType(), explicitMethod);
116 } else {
117 introspect(getType());
118 }
119
120 // if there are only 1 method with 1 operation then select it as a default/fallback method
121 MethodInfo method = null;
122 if (operations.size() == 1) {
123 List<MethodInfo> methods = operations.values().iterator().next();
124 if (methods.size() == 1) {
125 method = methods.get(0);
126 }
127 }
128 defaultMethod = method;
129
130 // mark the operations lists as unmodifiable, as they should not change during runtime
131 // to keep this code thread safe
132 operations = Collections.unmodifiableMap(operations);
133 operationsWithBody = Collections.unmodifiableList(operationsWithBody);
134 operationsWithNoBody = Collections.unmodifiableList(operationsWithNoBody);
135 operationsWithCustomAnnotation = Collections.unmodifiableList(operationsWithCustomAnnotation);
136 operationsWithHandlerAnnotation = Collections.unmodifiableList(operationsWithHandlerAnnotation);
137 methodMap = Collections.unmodifiableMap(methodMap);
138 }
139
140 public Class<?> getType() {
141 return type;
142 }
143
144 public CamelContext getCamelContext() {
145 return camelContext;
146 }
147
148 public static ParameterMappingStrategy createParameterMappingStrategy(CamelContext camelContext) {
149 // lookup in registry first if there is a user define strategy
150 Registry registry = camelContext.getRegistry();
151 ParameterMappingStrategy answer = registry.lookup(BeanConstants.BEAN_PARAMETER_MAPPING_STRATEGY, ParameterMappingStrategy.class);
152 if (answer == null) {
153 // no then use the default one
154 answer = new DefaultParameterMappingStrategy();
155 }
156
157 return answer;
158 }
159
160 public MethodInvocation createInvocation(Object pojo, Exchange exchange)
161 throws AmbiguousMethodCallException, MethodNotFoundException {
162 return createInvocation(pojo, exchange, null);
163 }
164
165 private MethodInvocation createInvocation(Object pojo, Exchange exchange, Method explicitMethod)
166 throws AmbiguousMethodCallException, MethodNotFoundException {
167 MethodInfo methodInfo = null;
168
169 // find the explicit method to invoke
170 if (explicitMethod != null) {
171 Iterator<List<MethodInfo>> it = operations.values().iterator();
172 while (it.hasNext()) {
173 List<MethodInfo> infos = it.next();
174 for (MethodInfo info : infos) {
175 if (explicitMethod.equals(info.getMethod())) {
176 return info.createMethodInvocation(pojo, exchange);
177 }
178 }
179 }
180 throw new MethodNotFoundException(exchange, pojo, explicitMethod.getName());
181 }
182
183 String methodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, String.class);
184 if (methodName != null) {
185
186 // do not use qualifier for name
187 String name = methodName;
188 if (methodName.contains("(")) {
189 name = ObjectHelper.before(methodName, "(");
190 }
191
192 // special for getClass, as we want the user to be able to invoke this method
193 // for example to log the class type or the likes
194 if ("class".equals(name) || "getClass".equals(name)) {
195 try {
196 Method method = pojo.getClass().getMethod("getClass");
197 methodInfo = new MethodInfo(exchange.getContext(), pojo.getClass(), method, Collections.<ParameterInfo>emptyList(), Collections.<ParameterInfo>emptyList(), false, false);
198 } catch (NoSuchMethodException e) {
199 throw new MethodNotFoundException(exchange, pojo, "getClass");
200 }
201 } else {
202 List<MethodInfo> methods = getOperations(name);
203 if (methods != null && methods.size() == 1) {
204 // only one method then choose it
205 methodInfo = methods.get(0);
206 } else if (methods != null) {
207 // there are more methods with that name so we cannot decide which to use
208
209 // but first let's try to choose a method and see if that complies with the name
210 // must use the method name which may have qualifiers
211 methodInfo = chooseMethod(pojo, exchange, methodName);
212
213 if (methodInfo == null || !name.equals(methodInfo.getMethod().getName())) {
214 throw new AmbiguousMethodCallException(exchange, methods);
215 }
216 } else {
217 // a specific method was given to invoke but not found
218 throw new MethodNotFoundException(exchange, pojo, methodName);
219 }
220 }
221 }
222
223 if (methodInfo == null) {
224 // no name or type
225 methodInfo = chooseMethod(pojo, exchange, null);
226 }
227 if (methodInfo == null) {
228 methodInfo = defaultMethod;
229 }
230 if (methodInfo != null) {
231 LOG.trace("Chosen method to invoke: {} on bean: {}", methodInfo, pojo);
232 return methodInfo.createMethodInvocation(pojo, exchange);
233 }
234
235 LOG.debug("Cannot find suitable method to invoke on bean: {}", pojo);
236 return null;
237 }
238
239 /**
240 * Introspects the given class
241 *
242 * @param clazz the class
243 */
244 private void introspect(Class<?> clazz) {
245 // get the target clazz as it could potentially have been enhanced by CGLIB etc.
246 clazz = getTargetClass(clazz);
247 ObjectHelper.notNull(clazz, "clazz", this);
248
249 LOG.trace("Introspecting class: {}", clazz);
250
251 // if the class is not public then fallback and use interface methods if possible
252 // this allow Camel to invoke private beans which implements interfaces
253 List<Method> methods = Arrays.asList(clazz.getDeclaredMethods());
254 if (!Modifier.isPublic(clazz.getModifiers())) {
255 LOG.trace("Preferring interface methods as class: {} is not public accessible", clazz);
256 List<Method> interfaceMethods = getInterfaceMethods(clazz);
257
258 // still keep non-accessible class methods to provide more specific Exception if method is non-accessible
259 interfaceMethods.addAll(methods);
260 methods = interfaceMethods;
261 }
262
263 for (Method method : methods) {
264 boolean valid = isValidMethod(clazz, method);
265 LOG.trace("Method: {} is valid: {}", method, valid);
266 if (valid) {
267 introspect(clazz, method);
268 }
269 }
270
271 Class<?> superclass = clazz.getSuperclass();
272 if (superclass != null && !superclass.equals(Object.class)) {
273 introspect(superclass);
274 }
275 }
276
277 /**
278 * Introspects the given method
279 *
280 * @param clazz the class
281 * @param method the method
282 * @return the method info, is newer <tt>null</tt>
283 */
284 private MethodInfo introspect(Class<?> clazz, Method method) {
285 LOG.trace("Introspecting class: {}, method: {}", clazz, method);
286 String opName = method.getName();
287
288 MethodInfo methodInfo = createMethodInfo(clazz, method);
289
290 // methods already registered should be preferred to use instead of super classes of existing methods
291 // we want to us the method from the sub class over super classes, so if we have already registered
292 // the method then use it (we are traversing upwards: sub (child) -> super (farther) )
293 MethodInfo existingMethodInfo = overridesExistingMethod(methodInfo);
294 if (existingMethodInfo != null) {
295 LOG.trace("This method is already overridden in a subclass, so the method from the sub class is preferred: {}", existingMethodInfo);
296 return existingMethodInfo;
297 }
298
299 LOG.trace("Adding operation: {} for method: {}", opName, methodInfo);
300
301 if (hasMethod(opName)) {
302 // we have an overloaded method so add the method info to the same key
303 List<MethodInfo> existing = getOperations(opName);
304 existing.add(methodInfo);
305 } else {
306 // its a new method we have not seen before so wrap it in a list and add it
307 List<MethodInfo> methods = new ArrayList<MethodInfo>();
308 methods.add(methodInfo);
309 operations.put(opName, methods);
310 }
311
312 if (methodInfo.hasCustomAnnotation()) {
313 operationsWithCustomAnnotation.add(methodInfo);
314 } else if (methodInfo.hasBodyParameter()) {
315 operationsWithBody.add(methodInfo);
316 } else {
317 operationsWithNoBody.add(methodInfo);
318 }
319
320 if (methodInfo.hasHandlerAnnotation()) {
321 operationsWithHandlerAnnotation.add(methodInfo);
322 }
323
324 // must add to method map last otherwise we break stuff
325 methodMap.put(method, methodInfo);
326
327 return methodInfo;
328 }
329
330
331 /**
332 * Returns the {@link MethodInfo} for the given method if it exists or null
333 * if there is no metadata available for the given method
334 */
335 public MethodInfo getMethodInfo(Method method) {
336 MethodInfo answer = methodMap.get(method);
337 if (answer == null) {
338 // maybe the method is defined on a base class?
339 if (type != Object.class) {
340 Class<?> superclass = type.getSuperclass();
341 if (superclass != null && superclass != Object.class) {
342 BeanInfo superBeanInfo = new BeanInfo(camelContext, superclass, strategy);
343 return superBeanInfo.getMethodInfo(method);
344 }
345 }
346 }
347 return answer;
348 }
349
350 protected MethodInfo createMethodInfo(Class<?> clazz, Method method) {
351 Class<?>[] parameterTypes = method.getParameterTypes();
352 List<Annotation>[] parametersAnnotations = collectParameterAnnotations(clazz, method);
353
354 List<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
355 List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>();
356
357 boolean hasCustomAnnotation = false;
358 boolean hasHandlerAnnotation = ObjectHelper.hasAnnotation(method.getAnnotations(), Handler.class);
359
360 int size = parameterTypes.length;
361 if (LOG.isTraceEnabled()) {
362 LOG.trace("Creating MethodInfo for class: {} method: {} having {} parameters", new Object[]{clazz, method, size});
363 }
364
365 for (int i = 0; i < size; i++) {
366 Class<?> parameterType = parameterTypes[i];
367 Annotation[] parameterAnnotations = parametersAnnotations[i].toArray(new Annotation[parametersAnnotations[i].size()]);
368 Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType, parameterAnnotations);
369 hasCustomAnnotation |= expression != null;
370
371 ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations, expression);
372 LOG.trace("Parameter #{}: {}", i, parameterInfo);
373 parameters.add(parameterInfo);
374 if (expression == null) {
375 boolean bodyAnnotation = ObjectHelper.hasAnnotation(parameterAnnotations, Body.class);
376 LOG.trace("Parameter #{} has @Body annotation", i);
377 hasCustomAnnotation |= bodyAnnotation;
378 if (bodyParameters.isEmpty()) {
379 // okay we have not yet set the body parameter and we have found
380 // the candidate now to use as body parameter
381 if (Exchange.class.isAssignableFrom(parameterType)) {
382 // use exchange
383 expression = ExpressionBuilder.exchangeExpression();
384 } else {
385 // assume it's the body and it must be mandatory convertible to the parameter type
386 // but we allow null bodies in case the message really contains a null body
387 expression = ExpressionBuilder.mandatoryBodyExpression(parameterType, true);
388 }
389 LOG.trace("Parameter #{} is the body parameter using expression {}", i, expression);
390 parameterInfo.setExpression(expression);
391 bodyParameters.add(parameterInfo);
392 } else {
393 // will ignore the expression for parameter evaluation
394 }
395 }
396 LOG.trace("Parameter #{} has parameter info: ", i, parameterInfo);
397 }
398
399 // now let's add the method to the repository
400 return new MethodInfo(camelContext, clazz, method, parameters, bodyParameters, hasCustomAnnotation, hasHandlerAnnotation);
401 }
402
403 protected List<Annotation>[] collectParameterAnnotations(Class<?> c, Method m) {
404 @SuppressWarnings("unchecked")
405 List<Annotation>[] annotations = new List[m.getParameterTypes().length];
406 for (int i = 0; i < annotations.length; i++) {
407 annotations[i] = new ArrayList<Annotation>();
408 }
409 collectParameterAnnotations(c, m, annotations);
410 return annotations;
411 }
412
413 protected void collectParameterAnnotations(Class<?> c, Method m, List<Annotation>[] a) {
414 try {
415 Annotation[][] pa = c.getDeclaredMethod(m.getName(), m.getParameterTypes()).getParameterAnnotations();
416 for (int i = 0; i < pa.length; i++) {
417 a[i].addAll(Arrays.asList(pa[i]));
418 }
419 } catch (NoSuchMethodException e) {
420 // no method with signature of m declared on c
421 }
422 for (Class<?> i : c.getInterfaces()) {
423 collectParameterAnnotations(i, m, a);
424 }
425 if (!c.isInterface() && c.getSuperclass() != null) {
426 collectParameterAnnotations(c.getSuperclass(), m, a);
427 }
428 }
429
430 /**
431 * Choose one of the available methods to invoke if we can match
432 * the message body to the body parameter
433 *
434 * @param pojo the bean to invoke a method on
435 * @param exchange the message exchange
436 * @param name an optional name of the method that must match, use <tt>null</tt> to indicate all methods
437 * @return the method to invoke or null if no definitive method could be matched
438 * @throws AmbiguousMethodCallException is thrown if cannot choose method due to ambiguity
439 */
440 protected MethodInfo chooseMethod(Object pojo, Exchange exchange, String name) throws AmbiguousMethodCallException {
441 // @Handler should be select first
442 // then any single method that has a custom @annotation
443 // or any single method that has a match parameter type that matches the Exchange payload
444 // and last then try to select the best among the rest
445
446 // must use defensive copy, to avoid altering the shared lists
447 // and we want to remove unwanted operations from these local lists
448 final List<MethodInfo> localOperationsWithBody = new ArrayList<MethodInfo>(operationsWithBody);
449 final List<MethodInfo> localOperationsWithNoBody = new ArrayList<MethodInfo>(operationsWithNoBody);
450 final List<MethodInfo> localOperationsWithCustomAnnotation = new ArrayList<MethodInfo>(operationsWithCustomAnnotation);
451 final List<MethodInfo> localOperationsWithHandlerAnnotation = new ArrayList<MethodInfo>(operationsWithHandlerAnnotation);
452
453 if (name != null) {
454 // filter all lists to only include methods with this name
455 removeNonMatchingMethods(localOperationsWithHandlerAnnotation, name);
456 removeNonMatchingMethods(localOperationsWithCustomAnnotation, name);
457 removeNonMatchingMethods(localOperationsWithBody, name);
458 removeNonMatchingMethods(localOperationsWithNoBody, name);
459 } else {
460 // remove all getter/setter as we do not want to consider these methods
461 removeAllSetterOrGetterMethods(localOperationsWithHandlerAnnotation);
462 removeAllSetterOrGetterMethods(localOperationsWithCustomAnnotation);
463 removeAllSetterOrGetterMethods(localOperationsWithBody);
464 removeAllSetterOrGetterMethods(localOperationsWithNoBody);
465 }
466
467 if (localOperationsWithHandlerAnnotation.size() > 1) {
468 // if we have more than 1 @Handler then its ambiguous
469 throw new AmbiguousMethodCallException(exchange, localOperationsWithHandlerAnnotation);
470 }
471
472 if (localOperationsWithHandlerAnnotation.size() == 1) {
473 // methods with handler should be preferred
474 return localOperationsWithHandlerAnnotation.get(0);
475 } else if (localOperationsWithCustomAnnotation.size() == 1) {
476 // if there is one method with an annotation then use that one
477 return localOperationsWithCustomAnnotation.get(0);
478 }
479
480 // named method and with no parameters
481 boolean noParameters = name != null && name.endsWith("()");
482 if (noParameters && localOperationsWithNoBody.size() == 1) {
483 // if there was a method name configured and it has no parameters, then use the method with no body (eg no parameters)
484 return localOperationsWithNoBody.get(0);
485 } else if (localOperationsWithBody.size() == 1) {
486 // if there is one method with body then use that one
487 return localOperationsWithBody.get(0);
488 }
489
490 Collection<MethodInfo> possibleOperations = new ArrayList<MethodInfo>();
491 possibleOperations.addAll(localOperationsWithBody);
492 possibleOperations.addAll(localOperationsWithCustomAnnotation);
493
494 if (!possibleOperations.isEmpty()) {
495 // multiple possible operations so find the best suited if possible
496 MethodInfo answer = chooseMethodWithMatchingBody(exchange, possibleOperations, localOperationsWithCustomAnnotation);
497 if (answer == null) {
498 throw new AmbiguousMethodCallException(exchange, possibleOperations);
499 } else {
500 return answer;
501 }
502 }
503
504 // not possible to determine
505 return null;
506 }
507
508 private MethodInfo chooseMethodWithMatchingBody(Exchange exchange, Collection<MethodInfo> operationList,
509 List<MethodInfo> operationsWithCustomAnnotation)
510 throws AmbiguousMethodCallException {
511 // see if we can find a method whose body param type matches the message body
512 Message in = exchange.getIn();
513 Object body = in.getBody();
514 if (body != null) {
515 Class<?> bodyType = body.getClass();
516 if (LOG.isTraceEnabled()) {
517 LOG.trace("Matching for method with a single parameter that matches type: {}", bodyType.getCanonicalName());
518 }
519
520 List<MethodInfo> possibles = new ArrayList<MethodInfo>();
521 List<MethodInfo> possiblesWithException = new ArrayList<MethodInfo>();
522 for (MethodInfo methodInfo : operationList) {
523 // test for MEP pattern matching
524 boolean out = exchange.getPattern().isOutCapable();
525 if (out && methodInfo.isReturnTypeVoid()) {
526 // skip this method as the MEP is Out so the method must return something
527 continue;
528 }
529
530 // try to match the arguments
531 if (methodInfo.bodyParameterMatches(bodyType)) {
532 LOG.trace("Found a possible method: {}", methodInfo);
533 if (methodInfo.hasExceptionParameter()) {
534 // methods with accepts exceptions
535 possiblesWithException.add(methodInfo);
536 } else {
537 // regular methods with no exceptions
538 possibles.add(methodInfo);
539 }
540 }
541 }
542
543 // find best suited method to use
544 return chooseBestPossibleMethodInfo(exchange, operationList, body, possibles, possiblesWithException, operationsWithCustomAnnotation);
545 }
546
547 // no match so return null
548 return null;
549 }
550
551 private MethodInfo chooseBestPossibleMethodInfo(Exchange exchange, Collection<MethodInfo> operationList, Object body,
552 List<MethodInfo> possibles, List<MethodInfo> possiblesWithException,
553 List<MethodInfo> possibleWithCustomAnnotation)
554 throws AmbiguousMethodCallException {
555
556 Exception exception = ExpressionBuilder.exchangeExceptionExpression().evaluate(exchange, Exception.class);
557 if (exception != null && possiblesWithException.size() == 1) {
558 LOG.trace("Exchange has exception set so we prefer method that also has exception as parameter");
559 // prefer the method that accepts exception in case we have an exception also
560 return possiblesWithException.get(0);
561 } else if (possibles.size() == 1) {
562 return possibles.get(0);
563 } else if (possibles.isEmpty()) {
564 LOG.trace("No possible methods so now trying to convert body to parameter types");
565
566 // let's try converting
567 Object newBody = null;
568 MethodInfo matched = null;
569 int matchCounter = 0;
570 for (MethodInfo methodInfo : operationList) {
571 if (methodInfo.getBodyParameterType().isInstance(body)) {
572 return methodInfo;
573 }
574
575 Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body);
576 if (value != null) {
577 if (LOG.isTraceEnabled()) {
578 LOG.trace("Converted body from: {} to: {}",
579 body.getClass().getCanonicalName(), methodInfo.getBodyParameterType().getCanonicalName());
580 }
581 matchCounter++;
582 newBody = value;
583 matched = methodInfo;
584 }
585 }
586 if (matchCounter > 1) {
587 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, matched));
588 }
589 if (matched != null) {
590 LOG.trace("Setting converted body: {}", body);
591 Message in = exchange.getIn();
592 in.setBody(newBody);
593 return matched;
594 }
595 } else {
596 // if we only have a single method with custom annotations, let's use that one
597 if (possibleWithCustomAnnotation.size() == 1) {
598 MethodInfo answer = possibleWithCustomAnnotation.get(0);
599 LOG.trace("There are only one method with annotations so we choose it: {}", answer);
600 return answer;
601 }
602 // phew try to choose among multiple methods with annotations
603 return chooseMethodWithCustomAnnotations(exchange, possibles);
604 }
605
606 // cannot find a good method to use
607 return null;
608 }
609
610 /**
611 * Validates whether the given method is a valid candidate for Camel Bean Binding.
612 *
613 * @param clazz the class
614 * @param method the method
615 * @return true if valid, false to skip the method
616 */
617 protected boolean isValidMethod(Class<?> clazz, Method method) {
618 // must not be in the excluded list
619 for (Method excluded : EXCLUDED_METHODS) {
620 if (ObjectHelper.isOverridingMethod(excluded, method)) {
621 // the method is overriding an excluded method so its not valid
622 return false;
623 }
624 }
625
626 // must be a public method
627 if (!Modifier.isPublic(method.getModifiers())) {
628 return false;
629 }
630
631 // return type must not be an Exchange and it should not be a bridge method
632 if ((method.getReturnType() != null && Exchange.class.isAssignableFrom(method.getReturnType())) || method.isBridge()) {
633 return false;
634 }
635
636 return true;
637 }
638
639 /**
640 * Does the given method info override an existing method registered before (from a subclass)
641 *
642 * @param methodInfo the method to test
643 * @return the already registered method to use, null if not overriding any
644 */
645 private MethodInfo overridesExistingMethod(MethodInfo methodInfo) {
646 for (MethodInfo info : methodMap.values()) {
647 Method source = info.getMethod();
648 Method target = methodInfo.getMethod();
649
650 boolean override = ObjectHelper.isOverridingMethod(source, target);
651 if (override) {
652 // same name, same parameters, then its overrides an existing class
653 return info;
654 }
655 }
656
657 return null;
658 }
659
660 private MethodInfo chooseMethodWithCustomAnnotations(Exchange exchange, Collection<MethodInfo> possibles)
661 throws AmbiguousMethodCallException {
662 // if we have only one method with custom annotations let's choose that
663 MethodInfo chosen = null;
664 for (MethodInfo possible : possibles) {
665 if (possible.hasCustomAnnotation()) {
666 if (chosen != null) {
667 chosen = null;
668 break;
669 } else {
670 chosen = possible;
671 }
672 }
673 }
674 if (chosen != null) {
675 return chosen;
676 }
677 throw new AmbiguousMethodCallException(exchange, possibles);
678 }
679
680 /**
681 * Creates an expression for the given parameter type if the parameter can
682 * be mapped automatically or null if the parameter cannot be mapped due to
683 * insufficient annotations or not fitting with the default type
684 * conventions.
685 */
686 private Expression createParameterUnmarshalExpression(Class<?> clazz, Method method,
687 Class<?> parameterType, Annotation[] parameterAnnotation) {
688
689 // look for a parameter annotation that converts into an expression
690 for (Annotation annotation : parameterAnnotation) {
691 Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType, annotation);
692 if (answer != null) {
693 return answer;
694 }
695 }
696 // no annotations then try the default parameter mappings
697 return strategy.getDefaultParameterTypeExpression(parameterType);
698 }
699
700 private Expression createParameterUnmarshalExpressionForAnnotation(Class<?> clazz, Method method,
701 Class<?> parameterType, Annotation annotation) {
702 if (annotation instanceof Attachments) {
703 return ExpressionBuilder.attachmentsExpression();
704 } else if (annotation instanceof Property) {
705 Property propertyAnnotation = (Property)annotation;
706 return ExpressionBuilder.propertyExpression(propertyAnnotation.value());
707 } else if (annotation instanceof Properties) {
708 return ExpressionBuilder.propertiesExpression();
709 } else if (annotation instanceof Header) {
710 Header headerAnnotation = (Header)annotation;
711 return ExpressionBuilder.headerExpression(headerAnnotation.value());
712 } else if (annotation instanceof Headers) {
713 return ExpressionBuilder.headersExpression();
714 } else if (annotation instanceof OutHeaders) {
715 return ExpressionBuilder.outHeadersExpression();
716 } else if (annotation instanceof ExchangeException) {
717 return ExpressionBuilder.exchangeExceptionExpression(CastUtils.cast(parameterType, Exception.class));
718 } else {
719 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class);
720 if (languageAnnotation != null) {
721 Class<?> type = languageAnnotation.factory();
722 Object object = camelContext.getInjector().newInstance(type);
723 if (object instanceof AnnotationExpressionFactory) {
724 AnnotationExpressionFactory expressionFactory = (AnnotationExpressionFactory) object;
725 return expressionFactory.createExpression(camelContext, annotation, languageAnnotation, parameterType);
726 } else {
727 LOG.warn("Ignoring bad annotation: " + languageAnnotation + "on method: " + method
728 + " which declares a factory: " + type.getName()
729 + " which does not implement " + AnnotationExpressionFactory.class.getName());
730 }
731 }
732 }
733
734 return null;
735 }
736
737 private static List<Method> getInterfaceMethods(Class<?> clazz) {
738 final List<Method> answer = new ArrayList<Method>();
739 for (Class<?> interfaceClazz : clazz.getInterfaces()) {
740 for (Method interfaceMethod : interfaceClazz.getDeclaredMethods()) {
741 answer.add(interfaceMethod);
742 }
743 }
744
745 return answer;
746 }
747
748 private static void removeAllSetterOrGetterMethods(List<MethodInfo> methods) {
749 Iterator<MethodInfo> it = methods.iterator();
750 while (it.hasNext()) {
751 MethodInfo info = it.next();
752 if (IntrospectionSupport.isGetter(info.getMethod())) {
753 // skip getters
754 it.remove();
755 } else if (IntrospectionSupport.isSetter(info.getMethod())) {
756 // skip setters
757 it.remove();
758 }
759 }
760 }
761
762 private void removeNonMatchingMethods(List<MethodInfo> methods, String name) {
763 Iterator<MethodInfo> it = methods.iterator();
764 while (it.hasNext()) {
765 MethodInfo info = it.next();
766 if (!matchMethod(info.getMethod(), name)) {
767 // method does not match so remove it
768 it.remove();
769 }
770 }
771 }
772
773 private boolean matchMethod(Method method, String methodName) {
774 if (methodName == null) {
775 return true;
776 }
777
778 if (methodName.contains("(") && !methodName.endsWith(")")) {
779 throw new IllegalArgumentException("Name must have both starting and ending parenthesis, was: " + methodName);
780 }
781
782 // do not use qualifier for name matching
783 String name = methodName;
784 if (name.contains("(")) {
785 name = ObjectHelper.before(name, "(");
786 }
787
788 // must match name
789 if (!name.equals(method.getName())) {
790 return false;
791 }
792
793 // match qualifier types which is used to select among overloaded methods
794 String types = ObjectHelper.between(methodName, "(", ")");
795 if (types != null) {
796 // we must qualify based on types to match method
797 Iterator<?> it = ObjectHelper.createIterator(types);
798 for (int i = 0; i < method.getParameterTypes().length; i++) {
799 if (it.hasNext()) {
800 Class<?> parameterType = method.getParameterTypes()[i];
801
802 String qualifyType = (String) it.next();
803 if (ObjectHelper.isEmpty(qualifyType)) {
804 continue;
805 }
806 // trim the type
807 qualifyType = qualifyType.trim();
808
809 if ("*".equals(qualifyType)) {
810 // * is a wildcard so we accept and match that parameter type
811 continue;
812 }
813
814 if (BeanHelper.isValidParameterValue(qualifyType)) {
815 // its a parameter value, so continue to next parameter
816 // as we should only check for FQN/type parameters
817 continue;
818 }
819
820 // if qualify type indeed is a class, then it must be assignable with the parameter type
821 Boolean assignable = BeanHelper.isAssignableToExpectedType(getCamelContext().getClassResolver(), qualifyType, parameterType);
822 // the method will return null if the qualifyType is not a class
823 if (assignable != null && !assignable) {
824 return false;
825 }
826
827 } else {
828 // there method has more parameters than was specified in the method name qualifiers
829 return false;
830 }
831 }
832
833 // if the method has no more types then we can only regard it as matched
834 // if there are no more qualifiers
835 if (it.hasNext()) {
836 return false;
837 }
838 }
839
840 // the method matched
841 return true;
842 }
843
844 private static Class<?> getTargetClass(Class<?> clazz) {
845 if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
846 Class<?> superClass = clazz.getSuperclass();
847 if (superClass != null && !Object.class.equals(superClass)) {
848 return superClass;
849 }
850 }
851 return clazz;
852 }
853
854 /**
855 * Do we have a method with the given name.
856 * <p/>
857 * Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel
858 * will can find the real 'getName' method instead.
859 *
860 * @param methodName the method name
861 * @return <tt>true</tt> if we have such a method.
862 */
863 public boolean hasMethod(String methodName) {
864 return getOperations(methodName) != null;
865 }
866
867 /**
868 * Do we have a static method with the given name.
869 * <p/>
870 * Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel
871 * will can find the real 'getName' method instead.
872 *
873 * @param methodName the method name
874 * @return <tt>true</tt> if we have such a static method.
875 */
876 public boolean hasStaticMethod(String methodName) {
877 List<MethodInfo> methods = getOperations(methodName);
878 if (methods == null || methods.isEmpty()) {
879 return false;
880 }
881 for (MethodInfo method : methods) {
882 if (method.isStaticMethod()) {
883 return true;
884 }
885 }
886 return false;
887 }
888
889 /**
890 * Gets the list of methods sorted by A..Z method name.
891 *
892 * @return the methods.
893 */
894 public List<MethodInfo> getMethods() {
895 if (operations.isEmpty()) {
896 return Collections.emptyList();
897 }
898
899 List<MethodInfo> methods = new ArrayList<MethodInfo>();
900 for (Collection<MethodInfo> col : operations.values()) {
901 methods.addAll(col);
902 }
903
904 // sort the methods by name A..Z
905 Collections.sort(methods, new Comparator<MethodInfo>() {
906 public int compare(MethodInfo o1, MethodInfo o2) {
907 return o1.getMethod().getName().compareTo(o2.getMethod().getName());
908 }
909 });
910 return methods;
911 }
912
913 /**
914 * Get the operation(s) with the given name. We can have multiple when methods is overloaded.
915 * <p/>
916 * Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel
917 * will can find the real 'getName' method instead.
918 *
919 * @param methodName the method name
920 * @return the found method, or <tt>null</tt> if not found
921 */
922 private List<MethodInfo> getOperations(String methodName) {
923 // do not use qualifier for name
924 if (methodName.contains("(")) {
925 methodName = ObjectHelper.before(methodName, "(");
926 }
927
928 List<MethodInfo> answer = operations.get(methodName);
929 if (answer != null) {
930 return answer;
931 }
932
933 // now try all getters to see if any of those matched the methodName
934 for (Method method : methodMap.keySet()) {
935 if (IntrospectionSupport.isGetter(method)) {
936 String shorthandMethodName = IntrospectionSupport.getGetterShorthandName(method);
937 // if the two names matches then see if we can find it using that name
938 if (methodName.equals(shorthandMethodName)) {
939 return operations.get(method.getName());
940 }
941 }
942 }
943
944 return null;
945 }
946
947 }