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