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 */
017package org.apache.camel.component.bean;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.AccessibleObject;
021import java.lang.reflect.AnnotatedElement;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031import java.util.concurrent.ExecutorService;
032
033import org.apache.camel.AsyncCallback;
034import org.apache.camel.CamelContext;
035import org.apache.camel.Exchange;
036import org.apache.camel.ExchangePattern;
037import org.apache.camel.Expression;
038import org.apache.camel.ExpressionEvaluationException;
039import org.apache.camel.NoTypeConversionAvailableException;
040import org.apache.camel.Pattern;
041import org.apache.camel.Processor;
042import org.apache.camel.RuntimeExchangeException;
043import org.apache.camel.processor.DynamicRouter;
044import org.apache.camel.processor.RecipientList;
045import org.apache.camel.processor.RoutingSlip;
046import org.apache.camel.processor.aggregate.AggregationStrategy;
047import org.apache.camel.support.ExpressionAdapter;
048import org.apache.camel.util.CamelContextHelper;
049import org.apache.camel.util.ObjectHelper;
050import org.apache.camel.util.ServiceHelper;
051import org.apache.camel.util.StringHelper;
052import org.apache.camel.util.StringQuoteHelper;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056import static org.apache.camel.util.ObjectHelper.asString;
057
058/**
059 * Information about a method to be used for invocation.
060 *
061 * @version 
062 */
063public class MethodInfo {
064    private static final Logger LOG = LoggerFactory.getLogger(MethodInfo.class);
065
066    private CamelContext camelContext;
067    private Class<?> type;
068    private Method method;
069    private final List<ParameterInfo> parameters;
070    private final List<ParameterInfo> bodyParameters;
071    private final boolean hasCustomAnnotation;
072    private final boolean hasHandlerAnnotation;
073    private Expression parametersExpression;
074    private ExchangePattern pattern = ExchangePattern.InOut;
075    private RecipientList recipientList;
076    private RoutingSlip routingSlip;
077    private DynamicRouter dynamicRouter;
078
079    /**
080     * Adapter to invoke the method which has been annotated with the @DynamicRouter
081     */
082    private final class DynamicRouterExpression extends ExpressionAdapter {
083        private final Object pojo;
084
085        private DynamicRouterExpression(Object pojo) {
086            this.pojo = pojo;
087        }
088
089        @Override
090        public Object evaluate(Exchange exchange) {
091            // evaluate arguments on each invocation as the parameters can have changed/updated since last invocation
092            final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class);
093            try {
094                return invoke(method, pojo, arguments, exchange);
095            } catch (Exception e) {
096                throw ObjectHelper.wrapRuntimeCamelException(e);
097            }
098        }
099
100        @Override
101        public String toString() {
102            return "DynamicRouter[invoking: " + method + " on bean: " + pojo + "]";
103        }
104    }
105
106    @SuppressWarnings("deprecation")
107    public MethodInfo(CamelContext camelContext, Class<?> type, Method method, List<ParameterInfo> parameters, List<ParameterInfo> bodyParameters,
108                      boolean hasCustomAnnotation, boolean hasHandlerAnnotation) {
109        this.camelContext = camelContext;
110        this.type = type;
111        this.method = method;
112        this.parameters = parameters;
113        this.bodyParameters = bodyParameters;
114        this.hasCustomAnnotation = hasCustomAnnotation;
115        this.hasHandlerAnnotation = hasHandlerAnnotation;
116        this.parametersExpression = createParametersExpression();
117        
118        Map<Class<?>, Annotation> collectedMethodAnnotation = collectMethodAnnotations(type, method);
119
120        Pattern oneway = findOneWayAnnotation(method);
121        if (oneway != null) {
122            pattern = oneway.value();
123        }
124        
125        org.apache.camel.RoutingSlip routingSlipAnnotation = 
126            (org.apache.camel.RoutingSlip)collectedMethodAnnotation.get(org.apache.camel.RoutingSlip.class);
127        if (routingSlipAnnotation != null && matchContext(routingSlipAnnotation.context())) {
128            routingSlip = new RoutingSlip(camelContext);
129            routingSlip.setDelimiter(routingSlipAnnotation.delimiter());
130            routingSlip.setIgnoreInvalidEndpoints(routingSlipAnnotation.ignoreInvalidEndpoints());
131            // add created routingSlip as a service so we have its lifecycle managed
132            try {
133                camelContext.addService(routingSlip);
134            } catch (Exception e) {
135                throw ObjectHelper.wrapRuntimeCamelException(e);
136            }
137        }
138
139        org.apache.camel.DynamicRouter dynamicRouterAnnotation = 
140            (org.apache.camel.DynamicRouter)collectedMethodAnnotation.get(org.apache.camel.DynamicRouter.class);
141        if (dynamicRouterAnnotation != null
142                && matchContext(dynamicRouterAnnotation.context())) {
143            dynamicRouter = new DynamicRouter(camelContext);
144            dynamicRouter.setDelimiter(dynamicRouterAnnotation.delimiter());
145            dynamicRouter.setIgnoreInvalidEndpoints(dynamicRouterAnnotation.ignoreInvalidEndpoints());
146            // add created dynamicRouter as a service so we have its lifecycle managed
147            try {
148                camelContext.addService(dynamicRouter);
149            } catch (Exception e) {
150                throw ObjectHelper.wrapRuntimeCamelException(e);
151            }
152        }
153
154        org.apache.camel.RecipientList recipientListAnnotation = 
155            (org.apache.camel.RecipientList)collectedMethodAnnotation.get(org.apache.camel.RecipientList.class);
156        if (recipientListAnnotation != null
157                && matchContext(recipientListAnnotation.context())) {
158            recipientList = new RecipientList(camelContext, recipientListAnnotation.delimiter());
159            recipientList.setStopOnException(recipientListAnnotation.stopOnException());
160            recipientList.setIgnoreInvalidEndpoints(recipientListAnnotation.ignoreInvalidEndpoints());
161            recipientList.setParallelProcessing(recipientListAnnotation.parallelProcessing());
162            recipientList.setParallelAggregate(recipientListAnnotation.parallelAggregate());
163            recipientList.setStreaming(recipientListAnnotation.streaming());
164            recipientList.setTimeout(recipientListAnnotation.timeout());
165            recipientList.setShareUnitOfWork(recipientListAnnotation.shareUnitOfWork());
166
167            if (ObjectHelper.isNotEmpty(recipientListAnnotation.executorServiceRef())) {
168                ExecutorService executor = camelContext.getExecutorServiceManager().newDefaultThreadPool(this, recipientListAnnotation.executorServiceRef());
169                recipientList.setExecutorService(executor);
170            }
171
172            if (recipientListAnnotation.parallelProcessing() && recipientList.getExecutorService() == null) {
173                // we are running in parallel so we need a thread pool
174                ExecutorService executor = camelContext.getExecutorServiceManager().newDefaultThreadPool(this, "@RecipientList");
175                recipientList.setExecutorService(executor);
176            }
177
178            if (ObjectHelper.isNotEmpty(recipientListAnnotation.strategyRef())) {
179                AggregationStrategy strategy = CamelContextHelper.mandatoryLookup(camelContext, recipientListAnnotation.strategyRef(), AggregationStrategy.class);
180                recipientList.setAggregationStrategy(strategy);
181            }
182
183            if (ObjectHelper.isNotEmpty(recipientListAnnotation.onPrepareRef())) {
184                Processor onPrepare = CamelContextHelper.mandatoryLookup(camelContext, recipientListAnnotation.onPrepareRef(), Processor.class);
185                recipientList.setOnPrepare(onPrepare);
186            }
187
188            // add created recipientList as a service so we have its lifecycle managed
189            try {
190                camelContext.addService(recipientList);
191            } catch (Exception e) {
192                throw ObjectHelper.wrapRuntimeCamelException(e);
193            }
194        }
195    }
196
197    private Map<Class<?>, Annotation> collectMethodAnnotations(Class<?> c, Method method) {
198        Map<Class<?>, Annotation> annotations = new HashMap<Class<?>, Annotation>();
199        collectMethodAnnotations(c, method, annotations);
200        return annotations;
201    }
202    
203    private void collectMethodAnnotations(Class<?> c, Method method, Map<Class<?>, Annotation> annotations) {
204        for (Class<?> i : c.getInterfaces()) {
205            collectMethodAnnotations(i, method, annotations);
206        }
207        if (!c.isInterface() && c.getSuperclass() != null) {
208            collectMethodAnnotations(c.getSuperclass(), method, annotations);
209        }
210        // make sure the sub class can override the definition
211        try {
212            Annotation[] ma = c.getDeclaredMethod(method.getName(), method.getParameterTypes()).getAnnotations();
213            for (Annotation a : ma) {
214                annotations.put(a.annotationType(), a);
215            }
216        } catch (SecurityException e) {
217            // do nothing here
218        } catch (NoSuchMethodException e) {
219            // do nothing here
220        }
221    }
222
223    /**
224     * Does the given context match this camel context
225     */
226    private boolean matchContext(String context) {
227        if (ObjectHelper.isNotEmpty(context)) {
228            if (!camelContext.getName().equals(context)) {
229                return false;
230            }
231        }
232        return true;
233    }
234
235    public String toString() {
236        return method.toString();
237    }
238
239    public MethodInvocation createMethodInvocation(final Object pojo, final Exchange exchange) {
240        final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class);
241        return new MethodInvocation() {
242            public Method getMethod() {
243                return method;
244            }
245
246            public Object[] getArguments() {
247                return arguments;
248            }
249
250            public boolean proceed(AsyncCallback callback) {
251                try {
252                    return doProceed(callback);
253                } catch (InvocationTargetException e) {
254                    exchange.setException(e.getTargetException());
255                    callback.done(true);
256                    return true;
257                } catch (Throwable e) {
258                    exchange.setException(e);
259                    callback.done(true);
260                    return true;
261                }
262            }
263
264            private boolean doProceed(AsyncCallback callback) throws Exception {
265                // dynamic router should be invoked beforehand
266                if (dynamicRouter != null) {
267                    if (!dynamicRouter.isStarted()) {
268                        ServiceHelper.startService(dynamicRouter);
269                    }
270                    // use a expression which invokes the method to be used by dynamic router
271                    Expression expression = new DynamicRouterExpression(pojo);
272                    return dynamicRouter.doRoutingSlip(exchange, expression, callback);
273                }
274
275                // invoke pojo
276                if (LOG.isTraceEnabled()) {
277                    LOG.trace(">>>> invoking: {} on bean: {} with arguments: {} for exchange: {}", new Object[]{method, pojo, asString(arguments), exchange});
278                }
279                Object result = invoke(method, pojo, arguments, exchange);
280
281                if (recipientList != null) {
282                    // ensure its started
283                    if (!recipientList.isStarted()) {
284                        ServiceHelper.startService(recipientList);
285                    }
286                    return recipientList.sendToRecipientList(exchange, result, callback);
287                }
288                if (routingSlip != null) {
289                    if (!routingSlip.isStarted()) {
290                        ServiceHelper.startService(routingSlip);
291                    }
292                    return routingSlip.doRoutingSlip(exchange, result, callback);
293                }
294
295                // if the method returns something then set the value returned on the Exchange
296                if (!getMethod().getReturnType().equals(Void.TYPE) && result != Void.TYPE) {
297                    if (exchange.getPattern().isOutCapable()) {
298                        // force out creating if not already created (as its lazy)
299                        LOG.debug("Setting bean invocation result on the OUT message: {}", result);
300                        exchange.getOut().setBody(result);
301                        // propagate headers
302                        exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
303                    } else {
304                        // if not out then set it on the in
305                        LOG.debug("Setting bean invocation result on the IN message: {}", result);
306                        exchange.getIn().setBody(result);
307                    }
308                }
309
310                // we did not use any of the eips, but just invoked the bean
311                // so notify the callback we are done synchronously
312                callback.done(true);
313                return true;
314            }
315
316            public Object getThis() {
317                return pojo;
318            }
319
320            public AccessibleObject getStaticPart() {
321                return method;
322            }
323        };
324    }
325
326    public Class<?> getType() {
327        return type;
328    }
329
330    public Method getMethod() {
331        return method;
332    }
333
334    /**
335     * Returns the {@link org.apache.camel.ExchangePattern} that should be used when invoking this method. This value
336     * defaults to {@link org.apache.camel.ExchangePattern#InOut} unless some {@link org.apache.camel.Pattern} annotation is used
337     * to override the message exchange pattern.
338     *
339     * @return the exchange pattern to use for invoking this method.
340     */
341    public ExchangePattern getPattern() {
342        return pattern;
343    }
344
345    public Expression getParametersExpression() {
346        return parametersExpression;
347    }
348
349    public List<ParameterInfo> getBodyParameters() {
350        return bodyParameters;
351    }
352
353    public Class<?> getBodyParameterType() {
354        if (bodyParameters.isEmpty()) {
355            return null;
356        }
357        ParameterInfo parameterInfo = bodyParameters.get(0);
358        return parameterInfo.getType();
359    }
360
361    public boolean bodyParameterMatches(Class<?> bodyType) {
362        Class<?> actualType = getBodyParameterType();
363        return actualType != null && ObjectHelper.isAssignableFrom(bodyType, actualType);
364    }
365
366    public List<ParameterInfo> getParameters() {
367        return parameters;
368    }
369
370    public boolean hasBodyParameter() {
371        return !bodyParameters.isEmpty();
372    }
373
374    public boolean hasCustomAnnotation() {
375        return hasCustomAnnotation;
376    }
377
378    public boolean hasHandlerAnnotation() {
379        return hasHandlerAnnotation;
380    }
381
382    public boolean hasParameters() {
383        return !parameters.isEmpty();
384    }
385
386    public boolean isReturnTypeVoid() {
387        return method.getReturnType().getName().equals("void");
388    }
389
390    public boolean isStaticMethod() {
391        return Modifier.isStatic(method.getModifiers());
392    }
393    
394    /**
395     * Returns true if this method is covariant with the specified method
396     * (this method may above or below the specified method in the class hierarchy)
397     */
398    public boolean isCovariantWith(MethodInfo method) {
399        return 
400            method.getMethod().getName().equals(this.getMethod().getName())
401            && (method.getMethod().getReturnType().isAssignableFrom(this.getMethod().getReturnType())
402            || this.getMethod().getReturnType().isAssignableFrom(method.getMethod().getReturnType())) 
403            && Arrays.deepEquals(method.getMethod().getParameterTypes(), this.getMethod().getParameterTypes());
404    }
405
406    protected Object invoke(Method mth, Object pojo, Object[] arguments, Exchange exchange) throws InvocationTargetException {
407        try {
408            return mth.invoke(pojo, arguments);
409        } catch (IllegalAccessException e) {
410            throw new RuntimeExchangeException("IllegalAccessException occurred invoking method: " + mth + " using arguments: " + Arrays.asList(arguments), exchange, e);
411        } catch (IllegalArgumentException e) {
412            throw new RuntimeExchangeException("IllegalArgumentException occurred invoking method: " + mth + " using arguments: " + Arrays.asList(arguments), exchange, e);
413        }
414    }
415
416    protected Expression[] createParameterExpressions() {
417        final int size = parameters.size();
418        LOG.trace("Creating parameters expression for {} parameters", size);
419
420        final Expression[] expressions = new Expression[size];
421        for (int i = 0; i < size; i++) {
422            Expression parameterExpression = parameters.get(i).getExpression();
423            expressions[i] = parameterExpression;
424            LOG.trace("Parameter #{} has expression: {}", i, parameterExpression);
425        }
426
427        return expressions;
428    }
429
430    protected Expression createParametersExpression() {
431        final Expression[] expressions = createParameterExpressions();
432        return new Expression() {
433            @SuppressWarnings("unchecked")
434            public <T> T evaluate(Exchange exchange, Class<T> type) {
435                Object[] answer = new Object[expressions.length];
436                Object body = exchange.getIn().getBody();
437                boolean multiParameterArray = false;
438                if (exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) != null) {
439                    multiParameterArray = exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, Boolean.class);
440                    if (multiParameterArray) {
441                        // Just change the message body to an Object array
442                        if (!(body instanceof Object[])) {
443                            body = exchange.getIn().getBody(Object[].class);
444                        }
445                    }
446                }
447
448                // if there was an explicit method name to invoke, then we should support using
449                // any provided parameter values in the method name
450                String methodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, "", String.class);
451                // the parameter values is between the parenthesis
452                String methodParameters = ObjectHelper.betweenOuterPair(methodName, '(', ')');
453                // use an iterator to walk the parameter values
454                Iterator<?> it = null;
455                if (methodParameters != null) {
456                    // split the parameters safely separated by comma, but beware that we can have
457                    // quoted parameters which contains comma as well, so do a safe quote split
458                    String[] parameters = StringQuoteHelper.splitSafeQuote(methodParameters, ',', true);
459                    it = ObjectHelper.createIterator(parameters, ",", true);
460                }
461
462                // remove headers as they should not be propagated
463                // we need to do this before the expressions gets evaluated as it may contain
464                // a @Bean expression which would by mistake read these headers. So the headers
465                // must be removed at this point of time
466                exchange.getIn().removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY);
467                exchange.getIn().removeHeader(Exchange.BEAN_METHOD_NAME);
468
469                for (int i = 0; i < expressions.length; i++) {
470                    // grab the parameter value for the given index
471                    Object parameterValue = it != null && it.hasNext() ? it.next() : null;
472                    // and the expected parameter type
473                    Class<?> parameterType = parameters.get(i).getType();
474                    // the value for the parameter to use
475                    Object value = null;
476
477                    if (multiParameterArray) {
478                        // get the value from the array
479                        value = ((Object[])body)[i];
480                    } else {
481                        // prefer to use parameter value if given, as they override any bean parameter binding
482                        // we should skip * as its a type placeholder to indicate any type
483                        if (parameterValue != null && !parameterValue.equals("*")) {
484                            // evaluate the parameter value binding
485                            value = evaluateParameterValue(exchange, i, parameterValue, parameterType);
486                        }
487                        // use bean parameter binding, if still no value
488                        Expression expression = expressions[i];
489                        if (value == null && expression != null) {
490                            value = evaluateParameterBinding(exchange, expression, i, parameterType);
491                        }
492                    }
493                    // remember the value to use
494                    if (value != Void.TYPE) {
495                        answer[i] = value;
496                    }
497                }
498                return (T) answer;
499            }
500
501            /**
502             * Evaluate using parameter values where the values can be provided in the method name syntax.
503             * <p/>
504             * This methods returns accordingly:
505             * <ul>
506             *     <li><tt>null</tt> - if not a parameter value</li>
507             *     <li><tt>Void.TYPE</tt> - if an explicit null, forcing Camel to pass in <tt>null</tt> for that given parameter</li>
508             *     <li>a non <tt>null</tt> value - if the parameter was a parameter value, and to be used</li>
509             * </ul>
510             *
511             * @since 2.9
512             */
513            private Object evaluateParameterValue(Exchange exchange, int index, Object parameterValue, Class<?> parameterType) {
514                Object answer = null;
515
516                // convert the parameter value to a String
517                String exp = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, parameterValue);
518                if (exp != null) {
519                    // check if its a valid parameter value
520                    boolean valid = BeanHelper.isValidParameterValue(exp);
521
522                    if (!valid) {
523                        // it may be a parameter type instead, and if so, then we should return null,
524                        // as this method is only for evaluating parameter values
525                        Boolean isClass = BeanHelper.isAssignableToExpectedType(exchange.getContext().getClassResolver(), exp, parameterType);
526                        // the method will return a non null value if exp is a class
527                        if (isClass != null) {
528                            return null;
529                        }
530                    }
531
532                    // use simple language to evaluate the expression, as it may use the simple language to refer to message body, headers etc.
533                    Expression expression = null;
534                    try {
535                        expression = exchange.getContext().resolveLanguage("simple").createExpression(exp);
536                        parameterValue = expression.evaluate(exchange, Object.class);
537                        // use "null" to indicate the expression returned a null value which is a valid response we need to honor
538                        if (parameterValue == null) {
539                            parameterValue = "null";
540                        }
541                    } catch (Exception e) {
542                        throw new ExpressionEvaluationException(expression, "Cannot create/evaluate simple expression: " + exp
543                                + " to be bound to parameter at index: " + index + " on method: " + getMethod(), exchange, e);
544                    }
545
546                    // special for explicit null parameter values (as end users can explicit indicate they want null as parameter)
547                    // see method javadoc for details
548                    if ("null".equals(parameterValue)) {
549                        return Void.TYPE;
550                    }
551
552                    // the parameter value may match the expected type, then we use it as-is
553                    if (parameterType.isAssignableFrom(parameterValue.getClass())) {
554                        valid = true;
555                    } else {
556                        // the parameter value was not already valid, but since the simple language have evaluated the expression
557                        // which may change the parameterValue, so we have to check it again to see if its now valid
558                        exp = exchange.getContext().getTypeConverter().tryConvertTo(String.class, parameterValue);
559                        // String values from the simple language is always valid
560                        if (!valid) {
561                            // re validate if the parameter was not valid the first time (String values should be accepted)
562                            valid = parameterValue instanceof String || BeanHelper.isValidParameterValue(exp);
563                        }
564                    }
565
566                    if (valid) {
567                        // we need to unquote String parameters, as the enclosing quotes is there to denote a parameter value
568                        if (parameterValue instanceof String) {
569                            parameterValue = StringHelper.removeLeadingAndEndingQuotes((String) parameterValue);
570                        }
571                        if (parameterValue != null) {
572                            try {
573                                // its a valid parameter value, so convert it to the expected type of the parameter
574                                answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, exchange, parameterValue);
575                                if (LOG.isTraceEnabled()) {
576                                    LOG.trace("Parameter #{} evaluated as: {} type: ", new Object[]{index, answer, ObjectHelper.type(answer)});
577                                }
578                            } catch (Exception e) {
579                                if (LOG.isDebugEnabled()) {
580                                    LOG.debug("Cannot convert from type: {} to type: {} for parameter #{}", new Object[]{ObjectHelper.type(parameterValue), parameterType, index});
581                                }
582                                throw new ParameterBindingException(e, method, index, parameterType, parameterValue);
583                            }
584                        }
585                    }
586                }
587
588                return answer;
589            }
590
591            /**
592             * Evaluate using classic parameter binding using the pre compute expression
593             */
594            private Object evaluateParameterBinding(Exchange exchange, Expression expression, int index, Class<?> parameterType) {
595                Object answer = null;
596
597                // use object first to avoid type conversion so we know if there is a value or not
598                Object result = expression.evaluate(exchange, Object.class);
599                if (result != null) {
600                    try {
601                        if (parameterType.isInstance(result)) {
602                            // optimize if the value is already the same type
603                            answer = result;
604                        } else {
605                            // we got a value now try to convert it to the expected type
606                            answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, result);
607                        }
608                        if (LOG.isTraceEnabled()) {
609                            LOG.trace("Parameter #{} evaluated as: {} type: ", new Object[]{index, answer, ObjectHelper.type(answer)});
610                        }
611                    } catch (NoTypeConversionAvailableException e) {
612                        if (LOG.isDebugEnabled()) {
613                            LOG.debug("Cannot convert from type: {} to type: {} for parameter #{}", new Object[]{ObjectHelper.type(result), parameterType, index});
614                        }
615                        throw new ParameterBindingException(e, method, index, parameterType, result);
616                    }
617                } else {
618                    LOG.trace("Parameter #{} evaluated as null", index);
619                }
620
621                return answer;
622            }
623
624            @Override
625            public String toString() {
626                return "ParametersExpression: " + Arrays.asList(expressions);
627            }
628
629        };
630    }
631
632    /**
633     * Finds the oneway annotation in priority order; look for method level annotations first, then the class level annotations,
634     * then super class annotations then interface annotations
635     *
636     * @param method the method on which to search
637     * @return the first matching annotation or none if it is not available
638     */
639    protected Pattern findOneWayAnnotation(Method method) {
640        Pattern answer = getPatternAnnotation(method);
641        if (answer == null) {
642            Class<?> type = method.getDeclaringClass();
643
644            // create the search order of types to scan
645            List<Class<?>> typesToSearch = new ArrayList<Class<?>>();
646            addTypeAndSuperTypes(type, typesToSearch);
647            Class<?>[] interfaces = type.getInterfaces();
648            for (Class<?> anInterface : interfaces) {
649                addTypeAndSuperTypes(anInterface, typesToSearch);
650            }
651
652            // now let's scan for a type which the current declared class overloads
653            answer = findOneWayAnnotationOnMethod(typesToSearch, method);
654            if (answer == null) {
655                answer = findOneWayAnnotation(typesToSearch);
656            }
657        }
658        return answer;
659    }
660
661    /**
662     * Returns the pattern annotation on the given annotated element; either as a direct annotation or
663     * on an annotation which is also annotated
664     *
665     * @param annotatedElement the element to look for the annotation
666     * @return the first matching annotation or null if none could be found
667     */
668    protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement) {
669        return getPatternAnnotation(annotatedElement, 2);
670    }
671
672    /**
673     * Returns the pattern annotation on the given annotated element; either as a direct annotation or
674     * on an annotation which is also annotated
675     *
676     * @param annotatedElement the element to look for the annotation
677     * @param depth the current depth
678     * @return the first matching annotation or null if none could be found
679     */
680    protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement, int depth) {
681        Pattern answer = annotatedElement.getAnnotation(Pattern.class);
682        int nextDepth = depth - 1;
683
684        if (nextDepth > 0) {
685            // look at all the annotations to see if any of those are annotated
686            Annotation[] annotations = annotatedElement.getAnnotations();
687            for (Annotation annotation : annotations) {
688                Class<? extends Annotation> annotationType = annotation.annotationType();
689                if (annotation instanceof Pattern || annotationType.equals(annotatedElement)) {
690                    continue;
691                } else {
692                    Pattern another = getPatternAnnotation(annotationType, nextDepth);
693                    if (pattern != null) {
694                        if (answer == null) {
695                            answer = another;
696                        } else {
697                            LOG.warn("Duplicate pattern annotation: " + another + " found on annotation: " + annotation + " which will be ignored");
698                        }
699                    }
700                }
701            }
702        }
703        return answer;
704    }
705
706    /**
707     * Adds the current class and all of its base classes (apart from {@link Object} to the given list
708     */
709    protected void addTypeAndSuperTypes(Class<?> type, List<Class<?>> result) {
710        for (Class<?> t = type; t != null && t != Object.class; t = t.getSuperclass()) {
711            result.add(t);
712        }
713    }
714
715    /**
716     * Finds the first annotation on the base methods defined in the list of classes
717     */
718    protected Pattern findOneWayAnnotationOnMethod(List<Class<?>> classes, Method method) {
719        for (Class<?> type : classes) {
720            try {
721                Method definedMethod = type.getMethod(method.getName(), method.getParameterTypes());
722                Pattern answer = getPatternAnnotation(definedMethod);
723                if (answer != null) {
724                    return answer;
725                }
726            } catch (NoSuchMethodException e) {
727                // ignore
728            }
729        }
730        return null;
731    }
732
733
734    /**
735     * Finds the first annotation on the given list of classes
736     */
737    protected Pattern findOneWayAnnotation(List<Class<?>> classes) {
738        for (Class<?> type : classes) {
739            Pattern answer = getPatternAnnotation(type);
740            if (answer != null) {
741                return answer;
742            }
743        }
744        return null;
745    }
746
747    protected boolean hasExceptionParameter() {
748        for (ParameterInfo parameter : parameters) {
749            if (Exception.class.isAssignableFrom(parameter.getType())) {
750                return true;
751            }
752        }
753        return false;
754    }
755
756}