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.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.concurrent.ConcurrentHashMap;
030    
031    import org.apache.camel.Body;
032    import org.apache.camel.CamelContext;
033    import org.apache.camel.Exchange;
034    import org.apache.camel.ExchangeException;
035    import org.apache.camel.Expression;
036    import org.apache.camel.Handler;
037    import org.apache.camel.Header;
038    import org.apache.camel.Headers;
039    import org.apache.camel.Message;
040    import org.apache.camel.OutHeaders;
041    import org.apache.camel.Properties;
042    import org.apache.camel.Property;
043    import org.apache.camel.builder.ExpressionBuilder;
044    import org.apache.camel.language.LanguageAnnotation;
045    import org.apache.camel.spi.Registry;
046    import org.apache.camel.util.CastUtils;
047    import org.apache.camel.util.IntrospectionSupport;
048    import org.apache.camel.util.ObjectHelper;
049    import org.apache.commons.logging.Log;
050    import org.apache.commons.logging.LogFactory;
051    
052    import static org.apache.camel.util.ExchangeHelper.convertToType;
053    
054    /**
055     * Represents the metadata about a bean type created via a combination of
056     * introspection and annotations together with some useful sensible defaults
057     *
058     * @version $Revision: 904498 $
059     */
060    public class BeanInfo {
061        private static final transient Log LOG = LogFactory.getLog(BeanInfo.class);
062        private static final String CGLIB_CLASS_SEPARATOR = "$$";
063        private static final List<Method> EXCLUDED_METHODS = new ArrayList<Method>();
064        private final CamelContext camelContext;
065        private final Class<?> type;
066        private final ParameterMappingStrategy strategy;
067        private final Map<String, List<MethodInfo>> operations = new ConcurrentHashMap<String, List<MethodInfo>>();
068        private final List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>();
069        private final List<MethodInfo> operationsWithCustomAnnotation = new ArrayList<MethodInfo>();
070        private final List<MethodInfo> operationsWithHandlerAnnotation = new ArrayList<MethodInfo>();
071        private final Map<Method, MethodInfo> methodMap = new ConcurrentHashMap<Method, MethodInfo>();
072        private MethodInfo defaultMethod;
073        private BeanInfo superBeanInfo;
074    
075        public BeanInfo(CamelContext camelContext, Class<?> type) {
076            this(camelContext, type, createParameterMappingStrategy(camelContext));
077        }
078    
079        public BeanInfo(CamelContext camelContext, Class<?> type, ParameterMappingStrategy strategy) {
080            this.camelContext = camelContext;
081            this.type = type;
082            this.strategy = strategy;
083    
084            // configure the default excludes methods
085            synchronized (EXCLUDED_METHODS) {
086                if (EXCLUDED_METHODS.size() == 0) {
087                    // exclude all java.lang.Object methods as we dont want to invoke them
088                    EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods()));
089                    // exclude all java.lang.reflect.Proxy methods as we dont want to invoke them
090                    EXCLUDED_METHODS.addAll(Arrays.asList(Proxy.class.getMethods()));
091                }
092            }
093    
094            introspect(getType());
095            // if there are only 1 method with 1 operation then select it as a default/fallback method
096            if (operations.size() == 1) {
097                List<MethodInfo> methods = operations.values().iterator().next();
098                if (methods.size() == 1) {
099                    defaultMethod = methods.get(0);
100                }
101            }
102        }
103    
104        public Class<?> getType() {
105            return type;
106        }
107    
108        public CamelContext getCamelContext() {
109            return camelContext;
110        }
111    
112        public static ParameterMappingStrategy createParameterMappingStrategy(CamelContext camelContext) {
113            // lookup in registry first if there is a user define strategy
114            Registry registry = camelContext.getRegistry();
115            ParameterMappingStrategy answer = registry.lookup(BeanConstants.BEAN_PARAMETER_MAPPING_STRATEGY, ParameterMappingStrategy.class);
116            if (answer == null) {
117                // no then use the default one
118                answer = new DefaultParameterMappingStrategy();
119            }
120    
121            return answer;
122        }
123    
124        public MethodInvocation createInvocation(Method method, Object pojo, Exchange exchange) {
125            MethodInfo methodInfo = introspect(type, method);
126            if (methodInfo != null) {
127                return methodInfo.createMethodInvocation(pojo, exchange);
128            }
129            return null;
130        }
131    
132        public MethodInvocation createInvocation(Object pojo, Exchange exchange) throws AmbiguousMethodCallException, MethodNotFoundException {
133            MethodInfo methodInfo = null;
134    
135            String name = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, String.class);
136            if (name != null) {
137                if (operations.containsKey(name)) {
138                    List<MethodInfo> methods = operations.get(name);
139                    if (methods != null && methods.size() == 1) {
140                        // only one method then choose it
141                        methodInfo = methods.get(0);
142                    } else {
143                        // there are more methods with that name so we cannot decide which to use
144    
145                        // but first lets try to choose a method and see if that comply with the name
146                        methodInfo = chooseMethod(pojo, exchange, name);
147                        if (methodInfo == null || !name.equals(methodInfo.getMethod().getName())) {
148                            throw new AmbiguousMethodCallException(exchange, methods);
149                        }
150                    }
151                } else {
152                    // a specific method was given to invoke but not found
153                    throw new MethodNotFoundException(exchange, pojo, name);
154                }
155            }
156            if (methodInfo == null) {
157                methodInfo = chooseMethod(pojo, exchange, name);
158            }
159            if (methodInfo == null) {
160                methodInfo = defaultMethod;
161            }
162            if (methodInfo != null) {
163                if (LOG.isTraceEnabled()) {
164                    LOG.trace("Chosen method to invoke: " + methodInfo + " on bean: " + pojo);
165                }
166                return methodInfo.createMethodInvocation(pojo, exchange);
167            }
168    
169            if (LOG.isDebugEnabled()) {
170                LOG.debug("Cannot find suitable method to invoke on bean: " + pojo);
171            }
172            return null;
173        }
174    
175        /**
176         * Introspects the given class
177         *
178         * @param clazz the class
179         */
180        protected void introspect(Class<?> clazz) {
181            // get the target clazz as it could potentially have been enhanced by CGLIB etc.
182            clazz = getTargetClass(clazz);
183    
184            if (LOG.isTraceEnabled()) {
185                LOG.trace("Introspecting class: " + clazz);
186            }
187    
188            Method[] methods = clazz.getDeclaredMethods();
189            for (Method method : methods) {
190                boolean valid = isValidMethod(clazz, method);
191                if (LOG.isTraceEnabled()) {
192                    LOG.trace("Method:  " + method + " is valid: " + valid);
193                }
194                if (valid) {
195                    introspect(clazz, method);
196                }
197            }
198    
199            Class<?> superclass = clazz.getSuperclass();
200            if (superclass != null && !superclass.equals(Object.class)) {
201                introspect(superclass);
202            }
203        }
204    
205        /**
206         * Introspects the given method
207         *
208         * @param clazz the class
209         * @param method the method
210         * @return the method info, is newer <tt>null</tt>
211         */
212        protected MethodInfo introspect(Class<?> clazz, Method method) {
213            if (LOG.isTraceEnabled()) {
214                LOG.trace("Introspecting class: " + clazz + ", method: " + method);
215            }
216            String opName = method.getName();
217    
218            MethodInfo methodInfo = createMethodInfo(clazz, method);
219    
220            // methods already registered should be preferred to use instead of super classes of existing methods
221            // we want to us the method from the sub class over super classes, so if we have already registered
222            // the method then use it (we are traversing upwards: sub (child) -> super (farther) )
223            MethodInfo existingMethodInfo = overridesExistingMethod(methodInfo);
224            if (existingMethodInfo != null) {
225                if (LOG.isTraceEnabled()) {
226                    LOG.trace("This method is already overridden in a subclass, so the method from the sub class is preferred: " + existingMethodInfo);
227                }
228    
229                return existingMethodInfo;
230            }
231    
232            if (LOG.isTraceEnabled()) {
233                LOG.trace("Adding operation: " + opName + " for method: " + methodInfo);
234            }
235    
236            if (operations.containsKey(opName)) {
237                // we have an overloaded method so add the method info to the same key
238                List<MethodInfo> existing = operations.get(opName);
239                existing.add(methodInfo);
240            } else {
241                // its a new method we have not seen before so wrap it in a list and add it
242                List<MethodInfo> methods = new ArrayList<MethodInfo>();
243                methods.add(methodInfo);
244                operations.put(opName, methods);
245            }
246    
247            if (methodInfo.hasCustomAnnotation()) {
248                operationsWithCustomAnnotation.add(methodInfo);
249            } else if (methodInfo.hasBodyParameter()) {
250                operationsWithBody.add(methodInfo);
251            }
252    
253            if (methodInfo.hasHandlerAnnotation()) {
254                operationsWithHandlerAnnotation.add(methodInfo);
255            }
256    
257            // must add to method map last otherwise we break stuff
258            methodMap.put(method, methodInfo);
259    
260            return methodInfo;
261        }
262    
263    
264        /**
265         * Returns the {@link MethodInfo} for the given method if it exists or null
266         * if there is no metadata available for the given method
267         */
268        public MethodInfo getMethodInfo(Method method) {
269            MethodInfo answer = methodMap.get(method);
270            if (answer == null) {
271                // maybe the method is defined on a base class?
272                if (superBeanInfo == null && type != Object.class) {
273                    Class<?> superclass = type.getSuperclass();
274                    if (superclass != null && superclass != Object.class) {
275                        superBeanInfo = new BeanInfo(camelContext, superclass, strategy);
276                        return superBeanInfo.getMethodInfo(method);
277                    }
278                }
279            }
280            return answer;
281        }
282    
283        @SuppressWarnings("unchecked")
284        protected MethodInfo createMethodInfo(Class clazz, Method method) {
285            Class[] parameterTypes = method.getParameterTypes();
286            Annotation[][] parametersAnnotations = method.getParameterAnnotations();
287    
288            List<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
289            List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>();
290    
291            boolean hasCustomAnnotation = false;
292            boolean hasHandlerAnnotation = ObjectHelper.hasAnnotation(method.getAnnotations(), Handler.class);
293    
294            int size = parameterTypes.length;
295            if (LOG.isTraceEnabled()) {
296                LOG.trace("Creating MethodInfo for class: " + clazz + " method: " + method + " having " + size + " parameters");
297            }
298    
299            for (int i = 0; i < size; i++) {
300                Class parameterType = parameterTypes[i];
301                Annotation[] parameterAnnotations = parametersAnnotations[i];
302                Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType, parameterAnnotations);
303                hasCustomAnnotation |= expression != null;
304    
305                ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations, expression);
306                if (LOG.isTraceEnabled()) {
307                    LOG.trace("Parameter #" + i + ": " + parameterInfo);
308                }
309                parameters.add(parameterInfo);
310                if (expression == null) {
311                    boolean bodyAnnotation = ObjectHelper.hasAnnotation(parameterAnnotations, Body.class);
312                    if (LOG.isTraceEnabled() && bodyAnnotation) {
313                        LOG.trace("Parameter #" + i + " has @Body annotation");
314                    }
315                    hasCustomAnnotation |= bodyAnnotation;
316                    if (bodyParameters.isEmpty()) {
317                        // okay we have not yet set the body parameter and we have found
318                        // the candidate now to use as body parameter
319                        if (Exchange.class.isAssignableFrom(parameterType)) {
320                            // use exchange
321                            expression = ExpressionBuilder.exchangeExpression();
322                        } else {
323                            // lets assume its the body and it must be mandatory convertable to the parameter type
324                            // but we allow null bodies in case the message really contains a null body
325                            expression = ExpressionBuilder.mandatoryBodyExpression(parameterType, true);
326                        }
327                        if (LOG.isTraceEnabled()) {
328                            LOG.trace("Parameter #" + i + " is the body parameter using expression " + expression);
329                        }
330                        parameterInfo.setExpression(expression);
331                        bodyParameters.add(parameterInfo);
332                    } else {
333                        // will ignore the expression for parameter evaluation
334                    }
335                }
336                if (LOG.isTraceEnabled()) {
337                    LOG.trace("Parameter #" + i + " has parameter info: " + parameterInfo);
338                }
339            }
340    
341            // now lets add the method to the repository
342            return new MethodInfo(camelContext, clazz, method, parameters, bodyParameters, hasCustomAnnotation, hasHandlerAnnotation);
343        }
344    
345        /**
346         * Lets try choose one of the available methods to invoke if we can match
347         * the message body to the body parameter
348         *
349         * @param pojo the bean to invoke a method on
350         * @param exchange the message exchange
351         * @param name an optional name of the method that must match, use <tt>null</tt> to indicate all methods
352         * @return the method to invoke or null if no definitive method could be matched
353         * @throws AmbiguousMethodCallException is thrown if cannot chose method due to ambiguous
354         */
355        protected MethodInfo chooseMethod(Object pojo, Exchange exchange, String name) throws AmbiguousMethodCallException {
356            // @Handler should be select first
357            // then any single method that has a custom @annotation
358            // or any single method that has a match parameter type that matches the Exchange payload
359            // and last then try to select the best among the rest
360    
361            if (name != null) {
362                // filter all lists to only include methods with this name
363                removeNonMatchingMethods(operationsWithHandlerAnnotation, name);
364                removeNonMatchingMethods(operationsWithCustomAnnotation, name);
365                removeNonMatchingMethods(operationsWithBody, name);
366            } else {
367                // remove all getter/setter as we do not want to consider these methods
368                removeAllSetterOrGetterMethods(operationsWithHandlerAnnotation);
369                removeAllSetterOrGetterMethods(operationsWithCustomAnnotation);
370                removeAllSetterOrGetterMethods(operationsWithBody);
371            }
372    
373            if (operationsWithHandlerAnnotation.size() > 1) {
374                // if we have more than 1 @Handler then its ambiguous
375                throw new AmbiguousMethodCallException(exchange, operationsWithHandlerAnnotation);
376            }
377    
378            if (operationsWithHandlerAnnotation.size() == 1) {
379                // methods with handler should be preferred
380                return operationsWithHandlerAnnotation.get(0);
381            } else if (operationsWithCustomAnnotation.size() == 1) {
382                // if there is one method with an annotation then use that one
383                return operationsWithCustomAnnotation.get(0);
384            } else if (operationsWithBody.size() == 1) {
385                // if there is one method with body then use that one
386                return operationsWithBody.get(0);
387            }
388    
389            Collection<MethodInfo> possibleOperations = new ArrayList<MethodInfo>();
390            possibleOperations.addAll(operationsWithBody);
391            possibleOperations.addAll(operationsWithCustomAnnotation);
392    
393            if (!possibleOperations.isEmpty()) {
394                 // multiple possible operations so find the best suited if possible
395                MethodInfo answer = chooseMethodWithMatchingBody(exchange, possibleOperations);
396                if (answer == null) {
397                    throw new AmbiguousMethodCallException(exchange, possibleOperations);
398                } else {
399                    return answer;
400                }
401            }
402    
403            // not possible to determine
404            return null;
405        }
406    
407        @SuppressWarnings("unchecked")
408        private MethodInfo chooseMethodWithMatchingBody(Exchange exchange, Collection<MethodInfo> operationList)
409            throws AmbiguousMethodCallException {
410            // lets see if we can find a method who's body param type matches the message body
411            Message in = exchange.getIn();
412            Object body = in.getBody();
413            if (body != null) {
414                Class bodyType = body.getClass();
415                if (LOG.isTraceEnabled()) {
416                    LOG.trace("Matching for method with a single parameter that matches type: " + bodyType.getCanonicalName());
417                }
418    
419                List<MethodInfo> possibles = new ArrayList<MethodInfo>();
420                List<MethodInfo> possiblesWithException = new ArrayList<MethodInfo>();
421                for (MethodInfo methodInfo : operationList) {
422                    // test for MEP pattern matching
423                    boolean out = exchange.getPattern().isOutCapable();
424                    if (out && methodInfo.isReturnTypeVoid()) {
425                        // skip this method as the MEP is Out so the method must return something
426                        continue;
427                    }
428    
429                    // try to match the arguments
430                    if (methodInfo.bodyParameterMatches(bodyType)) {
431                        if (LOG.isTraceEnabled()) {
432                            LOG.trace("Found a possible method: " + methodInfo);
433                        }
434                        if (methodInfo.hasExceptionParameter()) {
435                            // methods with accepts exceptions
436                            possiblesWithException.add(methodInfo);
437                        } else {
438                            // regular methods with no exceptions
439                            possibles.add(methodInfo);
440                        }
441                    }
442                }
443    
444                // find best suited method to use
445                return chooseBestPossibleMethodInfo(exchange, operationList, body, possibles, possiblesWithException);
446            }
447    
448            // no match so return null
449            return null;
450        }
451    
452        @SuppressWarnings("unchecked")
453        private MethodInfo chooseBestPossibleMethodInfo(Exchange exchange, Collection<MethodInfo> operationList, Object body,
454                                                        List<MethodInfo> possibles, List<MethodInfo> possiblesWithException)
455            throws AmbiguousMethodCallException {
456    
457            Exception exception = ExpressionBuilder.exchangeExceptionExpression().evaluate(exchange, Exception.class);
458            if (exception != null && possiblesWithException.size() == 1) {
459                if (LOG.isTraceEnabled()) {
460                    LOG.trace("Exchange has exception set so we prefer method that also has exception as parameter");
461                }
462                // prefer the method that accepts exception in case we have an exception also
463                return possiblesWithException.get(0);
464            } else if (possibles.size() == 1) {
465                return possibles.get(0);
466            } else if (possibles.isEmpty()) {
467                if (LOG.isTraceEnabled()) {
468                    LOG.trace("No possible methods so now trying to convert body to parameter types");
469                }
470    
471                // lets try converting
472                Object newBody = null;
473                MethodInfo matched = null;
474                for (MethodInfo methodInfo : operationList) {
475                    Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body);
476                    if (value != null) {
477                        if (LOG.isTraceEnabled()) {
478                            LOG.trace("Converted body from: " + body.getClass().getCanonicalName()
479                                    + "to: " + methodInfo.getBodyParameterType().getCanonicalName());
480                        }
481                        if (newBody != null) {
482                            // we already have found one new body that could be converted so now we have 2 methods
483                            // and then its ambiguous
484                            throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, methodInfo));
485                        } else {
486                            newBody = value;
487                            matched = methodInfo;
488                        }
489                    }
490                }
491                if (matched != null) {
492                    if (LOG.isTraceEnabled()) {
493                        LOG.trace("Setting converted body: " + body);
494                    }
495                    Message in = exchange.getIn();
496                    in.setBody(newBody);
497                    return matched;
498                }
499            } else {
500                // if we only have a single method with custom annotations, lets use that one
501                if (operationsWithCustomAnnotation.size() == 1) {
502                    MethodInfo answer = operationsWithCustomAnnotation.get(0);
503                    if (LOG.isTraceEnabled()) {
504                        LOG.trace("There are only one method with annotations so we choose it: " + answer);
505                    }
506                    return answer;
507                }
508                // phew try to choose among multiple methods with annotations
509                return chooseMethodWithCustomAnnotations(exchange, possibles);
510            }
511    
512            // cannot find a good method to use
513            return null;
514        }
515    
516        /**
517         * Validates whether the given method is a valid candidate for Camel Bean Binding.
518         *
519         * @param clazz   the class
520         * @param method  the method
521         * @return true if valid, false to skip the method
522         */
523        protected boolean isValidMethod(Class<?> clazz, Method method) {
524            // must not be in the excluded list
525            for (Method excluded : EXCLUDED_METHODS) {
526                if (ObjectHelper.isOverridingMethod(excluded, method)) {
527                    // the method is overriding an excluded method so its not valid
528                    return false;
529                }
530            }
531    
532            // must be a public method
533            if (!Modifier.isPublic(method.getModifiers())) {
534                return false;
535            }
536    
537            // return type must not be an Exchange and it should not be a bridge method
538            if ((method.getReturnType() != null && Exchange.class.isAssignableFrom(method.getReturnType())) || method.isBridge()) {
539                return false;
540            }
541    
542            return true;
543        }
544    
545        /**
546         * Does the given method info override an existing method registered before (from a subclass)
547         *
548         * @param methodInfo  the method to test
549         * @return the already registered method to use, null if not overriding any
550         */
551        private MethodInfo overridesExistingMethod(MethodInfo methodInfo) {
552            for (MethodInfo info : methodMap.values()) {
553                Method source = info.getMethod();
554                Method target = methodInfo.getMethod();
555    
556                boolean override = ObjectHelper.isOverridingMethod(source, target);
557                if (override) {
558                    // same name, same parameters, then its overrides an existing class
559                    return info;
560                }
561            }
562    
563            return null;
564        }
565    
566        private MethodInfo chooseMethodWithCustomAnnotations(Exchange exchange, Collection<MethodInfo> possibles)
567            throws AmbiguousMethodCallException {
568            // if we have only one method with custom annotations lets choose that
569            MethodInfo chosen = null;
570            for (MethodInfo possible : possibles) {
571                if (possible.hasCustomAnnotation()) {
572                    if (chosen != null) {
573                        chosen = null;
574                        break;
575                    } else {
576                        chosen = possible;
577                    }
578                }
579            }
580            if (chosen != null) {
581                return chosen;
582            }
583            throw new AmbiguousMethodCallException(exchange, possibles);
584        }
585    
586        /**
587         * Creates an expression for the given parameter type if the parameter can
588         * be mapped automatically or null if the parameter cannot be mapped due to
589         * insufficient annotations or not fitting with the default type
590         * conventions.
591         */
592        private Expression createParameterUnmarshalExpression(Class<?> clazz, Method method, 
593                Class<?> parameterType, Annotation[] parameterAnnotation) {
594    
595            // look for a parameter annotation that converts into an expression
596            for (Annotation annotation : parameterAnnotation) {
597                Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType, annotation);
598                if (answer != null) {
599                    return answer;
600                }
601            }
602            // no annotations then try the default parameter mappings
603            return strategy.getDefaultParameterTypeExpression(parameterType);
604        }
605    
606        private Expression createParameterUnmarshalExpressionForAnnotation(Class<?> clazz, Method method, 
607                Class<?> parameterType, Annotation annotation) {
608    
609            if (annotation instanceof Property) {
610                Property propertyAnnotation = (Property)annotation;
611                return ExpressionBuilder.propertyExpression(propertyAnnotation.value());
612            } else if (annotation instanceof Properties) {
613                return ExpressionBuilder.propertiesExpression();
614            } else if (annotation instanceof Header) {
615                Header headerAnnotation = (Header)annotation;
616                return ExpressionBuilder.headerExpression(headerAnnotation.value());
617            } else if (annotation instanceof Headers) {
618                return ExpressionBuilder.headersExpression();
619            } else if (annotation instanceof OutHeaders) {
620                return ExpressionBuilder.outHeadersExpression();
621            } else if (annotation instanceof ExchangeException) {
622                return ExpressionBuilder.exchangeExceptionExpression(CastUtils.cast(parameterType, Exception.class));
623            } else {
624                LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class);
625                if (languageAnnotation != null) {
626                    Class<?> type = languageAnnotation.factory();
627                    Object object = camelContext.getInjector().newInstance(type);
628                    if (object instanceof AnnotationExpressionFactory) {
629                        AnnotationExpressionFactory expressionFactory = (AnnotationExpressionFactory) object;
630                        return expressionFactory.createExpression(camelContext, annotation, languageAnnotation, parameterType);
631                    } else {
632                        LOG.warn("Ignoring bad annotation: " + languageAnnotation + "on method: " + method
633                                + " which declares a factory: " + type.getName()
634                                + " which does not implement " + AnnotationExpressionFactory.class.getName());
635                    }
636                }
637            }
638    
639            return null;
640        }
641    
642        private static void removeAllSetterOrGetterMethods(List<MethodInfo> methods) {
643            Iterator<MethodInfo> it = methods.iterator();
644            while (it.hasNext()) {
645                MethodInfo info = it.next();
646                if (IntrospectionSupport.isGetter(info.getMethod())) {
647                    // skip getters
648                    it.remove();
649                } else if (IntrospectionSupport.isSetter(info.getMethod())) {
650                    // skip setters
651                    it.remove();
652                }
653            }
654        }
655    
656        private static void removeNonMatchingMethods(List<MethodInfo> methods, String name) {
657            Iterator<MethodInfo> it = methods.iterator();
658            while (it.hasNext()) {
659                MethodInfo info = it.next();
660                if (!name.equals(info.getMethod().getName())) {
661                    // name does not match so remove it
662                    it.remove();
663                }
664            }
665        }
666    
667        private static Class<?> getTargetClass(Class<?> clazz) {
668            if (clazz.getName().indexOf(CGLIB_CLASS_SEPARATOR) != -1) {
669                return clazz.getSuperclass();
670            }
671            return clazz;
672        }
673    
674    }