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