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