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 }