| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.camel.component.bean; |
| |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.AnnotatedElement; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import org.apache.camel.AsyncCallback; |
| import org.apache.camel.CamelContext; |
| import org.apache.camel.Exchange; |
| import org.apache.camel.ExchangePattern; |
| import org.apache.camel.Expression; |
| import org.apache.camel.ExpressionEvaluationException; |
| import org.apache.camel.NoTypeConversionAvailableException; |
| import org.apache.camel.Pattern; |
| import org.apache.camel.Processor; |
| import org.apache.camel.RuntimeExchangeException; |
| import org.apache.camel.processor.DynamicRouter; |
| import org.apache.camel.processor.RecipientList; |
| import org.apache.camel.processor.RoutingSlip; |
| import org.apache.camel.processor.aggregate.AggregationStrategy; |
| import org.apache.camel.support.ExpressionAdapter; |
| import org.apache.camel.util.CamelContextHelper; |
| import org.apache.camel.util.ObjectHelper; |
| import org.apache.camel.util.ServiceHelper; |
| import org.apache.camel.util.StringHelper; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.camel.util.ObjectHelper.asString; |
| |
| /** |
| * Information about a method to be used for invocation. |
| * |
| * @version |
| */ |
| public class MethodInfo { |
| private static final transient Logger LOG = LoggerFactory.getLogger(MethodInfo.class); |
| |
| private CamelContext camelContext; |
| private Class<?> type; |
| private Method method; |
| private final List<ParameterInfo> parameters; |
| private final List<ParameterInfo> bodyParameters; |
| private final boolean hasCustomAnnotation; |
| private final boolean hasHandlerAnnotation; |
| private Expression parametersExpression; |
| private ExchangePattern pattern = ExchangePattern.InOut; |
| private RecipientList recipientList; |
| private RoutingSlip routingSlip; |
| private DynamicRouter dynamicRouter; |
| |
| /** |
| * Adapter to invoke the method which has been annotated with the @DynamicRouter |
| */ |
| private final class DynamicRouterExpression extends ExpressionAdapter { |
| private final Object pojo; |
| |
| private DynamicRouterExpression(Object pojo) { |
| this.pojo = pojo; |
| } |
| |
| @Override |
| public Object evaluate(Exchange exchange) { |
| // evaluate arguments on each invocation as the parameters can have changed/updated since last invocation |
| final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class); |
| try { |
| return invoke(method, pojo, arguments, exchange); |
| } catch (Exception e) { |
| throw ObjectHelper.wrapRuntimeCamelException(e); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "DynamicRouter[invoking: " + method + " on bean: " + pojo + "]"; |
| } |
| } |
| |
| public MethodInfo(CamelContext camelContext, Class<?> type, Method method, List<ParameterInfo> parameters, List<ParameterInfo> bodyParameters, |
| boolean hasCustomAnnotation, boolean hasHandlerAnnotation) { |
| this.camelContext = camelContext; |
| this.type = type; |
| this.method = method; |
| this.parameters = parameters; |
| this.bodyParameters = bodyParameters; |
| this.hasCustomAnnotation = hasCustomAnnotation; |
| this.hasHandlerAnnotation = hasHandlerAnnotation; |
| this.parametersExpression = createParametersExpression(); |
| |
| Pattern oneway = findOneWayAnnotation(method); |
| if (oneway != null) { |
| pattern = oneway.value(); |
| } |
| |
| if (method.getAnnotation(org.apache.camel.RoutingSlip.class) != null |
| && matchContext(method.getAnnotation(org.apache.camel.RoutingSlip.class).context())) { |
| org.apache.camel.RoutingSlip annotation = method.getAnnotation(org.apache.camel.RoutingSlip.class); |
| routingSlip = new RoutingSlip(camelContext); |
| routingSlip.setDelimiter(annotation.delimiter()); |
| routingSlip.setIgnoreInvalidEndpoints(annotation.ignoreInvalidEndpoints()); |
| // add created routingSlip as a service so we have its lifecycle managed |
| try { |
| camelContext.addService(routingSlip); |
| } catch (Exception e) { |
| throw ObjectHelper.wrapRuntimeCamelException(e); |
| } |
| } |
| |
| if (method.getAnnotation(org.apache.camel.DynamicRouter.class) != null |
| && matchContext(method.getAnnotation(org.apache.camel.DynamicRouter.class).context())) { |
| org.apache.camel.DynamicRouter annotation = method.getAnnotation(org.apache.camel.DynamicRouter.class); |
| dynamicRouter = new DynamicRouter(camelContext); |
| dynamicRouter.setDelimiter(annotation.delimiter()); |
| dynamicRouter.setIgnoreInvalidEndpoints(annotation.ignoreInvalidEndpoints()); |
| // add created dynamicRouter as a service so we have its lifecycle managed |
| try { |
| camelContext.addService(dynamicRouter); |
| } catch (Exception e) { |
| throw ObjectHelper.wrapRuntimeCamelException(e); |
| } |
| } |
| |
| if (method.getAnnotation(org.apache.camel.RecipientList.class) != null |
| && matchContext(method.getAnnotation(org.apache.camel.RecipientList.class).context())) { |
| |
| org.apache.camel.RecipientList annotation = method.getAnnotation(org.apache.camel.RecipientList.class); |
| |
| recipientList = new RecipientList(camelContext, annotation.delimiter()); |
| recipientList.setStopOnException(annotation.stopOnException()); |
| recipientList.setIgnoreInvalidEndpoints(annotation.ignoreInvalidEndpoints()); |
| recipientList.setParallelProcessing(annotation.parallelProcessing()); |
| recipientList.setStreaming(annotation.streaming()); |
| recipientList.setTimeout(annotation.timeout()); |
| recipientList.setShareUnitOfWork(annotation.shareUnitOfWork()); |
| |
| if (ObjectHelper.isNotEmpty(annotation.executorServiceRef())) { |
| ExecutorService executor = camelContext.getExecutorServiceManager().newDefaultThreadPool(this, annotation.executorServiceRef()); |
| recipientList.setExecutorService(executor); |
| } |
| |
| if (annotation.parallelProcessing() && recipientList.getExecutorService() == null) { |
| // we are running in parallel so we need a thread pool |
| ExecutorService executor = camelContext.getExecutorServiceManager().newDefaultThreadPool(this, "@RecipientList"); |
| recipientList.setExecutorService(executor); |
| } |
| |
| if (ObjectHelper.isNotEmpty(annotation.strategyRef())) { |
| AggregationStrategy strategy = CamelContextHelper.mandatoryLookup(camelContext, annotation.strategyRef(), AggregationStrategy.class); |
| recipientList.setAggregationStrategy(strategy); |
| } |
| |
| if (ObjectHelper.isNotEmpty(annotation.onPrepareRef())) { |
| Processor onPrepare = CamelContextHelper.mandatoryLookup(camelContext, annotation.onPrepareRef(), Processor.class); |
| recipientList.setOnPrepare(onPrepare); |
| } |
| |
| // add created recipientList as a service so we have its lifecycle managed |
| try { |
| camelContext.addService(recipientList); |
| } catch (Exception e) { |
| throw ObjectHelper.wrapRuntimeCamelException(e); |
| } |
| } |
| } |
| |
| /** |
| * Does the given context match this camel context |
| */ |
| private boolean matchContext(String context) { |
| if (ObjectHelper.isNotEmpty(context)) { |
| if (!camelContext.getName().equals(context)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public String toString() { |
| return method.toString(); |
| } |
| |
| public MethodInvocation createMethodInvocation(final Object pojo, final Exchange exchange) { |
| final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class); |
| return new MethodInvocation() { |
| public Method getMethod() { |
| return method; |
| } |
| |
| public Object[] getArguments() { |
| return arguments; |
| } |
| |
| public Object proceed(AsyncCallback callback, AtomicBoolean doneSync) throws Exception { |
| // dynamic router should be invoked beforehand |
| if (dynamicRouter != null) { |
| if (!dynamicRouter.isStarted()) { |
| ServiceHelper.startService(dynamicRouter); |
| } |
| // use a expression which invokes the method to be used by dynamic router |
| Expression expression = new DynamicRouterExpression(pojo); |
| boolean sync = dynamicRouter.doRoutingSlip(exchange, expression, callback); |
| // must remember the done sync returned from the dynamic router |
| doneSync.set(sync); |
| return Void.TYPE; |
| } |
| |
| // invoke pojo |
| if (LOG.isTraceEnabled()) { |
| LOG.trace(">>>> invoking: {} on bean: {} with arguments: {} for exchange: {}", new Object[]{method, pojo, asString(arguments), exchange}); |
| } |
| Object result = invoke(method, pojo, arguments, exchange); |
| |
| if (recipientList != null) { |
| // ensure its started |
| if (!recipientList.isStarted()) { |
| ServiceHelper.startService(recipientList); |
| } |
| boolean sync = recipientList.sendToRecipientList(exchange, result, callback); |
| // must remember the done sync returned from the recipient list |
| doneSync.set(sync); |
| // we don't want to return the list of endpoints |
| // return Void to indicate to BeanProcessor that there is no reply |
| return Void.TYPE; |
| } |
| if (routingSlip != null) { |
| if (!routingSlip.isStarted()) { |
| ServiceHelper.startService(routingSlip); |
| } |
| boolean sync = routingSlip.doRoutingSlip(exchange, result, callback); |
| // must remember the done sync returned from the routing slip |
| doneSync.set(sync); |
| return Void.TYPE; |
| } |
| |
| return result; |
| } |
| |
| public Object getThis() { |
| return pojo; |
| } |
| |
| public AccessibleObject getStaticPart() { |
| return method; |
| } |
| }; |
| } |
| |
| public Class<?> getType() { |
| return type; |
| } |
| |
| public Method getMethod() { |
| return method; |
| } |
| |
| /** |
| * Returns the {@link org.apache.camel.ExchangePattern} that should be used when invoking this method. This value |
| * defaults to {@link org.apache.camel.ExchangePattern#InOut} unless some {@link org.apache.camel.Pattern} annotation is used |
| * to override the message exchange pattern. |
| * |
| * @return the exchange pattern to use for invoking this method. |
| */ |
| public ExchangePattern getPattern() { |
| return pattern; |
| } |
| |
| public Expression getParametersExpression() { |
| return parametersExpression; |
| } |
| |
| public List<ParameterInfo> getBodyParameters() { |
| return bodyParameters; |
| } |
| |
| public Class<?> getBodyParameterType() { |
| if (bodyParameters.isEmpty()) { |
| return null; |
| } |
| ParameterInfo parameterInfo = bodyParameters.get(0); |
| return parameterInfo.getType(); |
| } |
| |
| public boolean bodyParameterMatches(Class<?> bodyType) { |
| Class<?> actualType = getBodyParameterType(); |
| return actualType != null && ObjectHelper.isAssignableFrom(bodyType, actualType); |
| } |
| |
| public List<ParameterInfo> getParameters() { |
| return parameters; |
| } |
| |
| public boolean hasBodyParameter() { |
| return !bodyParameters.isEmpty(); |
| } |
| |
| public boolean hasCustomAnnotation() { |
| return hasCustomAnnotation; |
| } |
| |
| public boolean hasHandlerAnnotation() { |
| return hasHandlerAnnotation; |
| } |
| |
| public boolean isReturnTypeVoid() { |
| return method.getReturnType().getName().equals("void"); |
| } |
| |
| public boolean isStaticMethod() { |
| return Modifier.isStatic(method.getModifiers()); |
| } |
| |
| protected Object invoke(Method mth, Object pojo, Object[] arguments, Exchange exchange) throws IllegalAccessException, InvocationTargetException { |
| try { |
| return mth.invoke(pojo, arguments); |
| } catch (IllegalArgumentException e) { |
| throw new RuntimeExchangeException("IllegalArgumentException occurred invoking method: " + mth + " using arguments: " + Arrays.asList(arguments), exchange, e); |
| } |
| } |
| |
| protected Expression createParametersExpression() { |
| final int size = parameters.size(); |
| LOG.trace("Creating parameters expression for {} parameters", size); |
| |
| final Expression[] expressions = new Expression[size]; |
| for (int i = 0; i < size; i++) { |
| Expression parameterExpression = parameters.get(i).getExpression(); |
| expressions[i] = parameterExpression; |
| LOG.trace("Parameter #{} has expression: {}", i, parameterExpression); |
| } |
| return new Expression() { |
| @SuppressWarnings("unchecked") |
| public <T> T evaluate(Exchange exchange, Class<T> type) { |
| Object[] answer = new Object[size]; |
| Object body = exchange.getIn().getBody(); |
| boolean multiParameterArray = false; |
| if (exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) != null) { |
| multiParameterArray = exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, Boolean.class); |
| } |
| |
| // if there was an explicit method name to invoke, then we should support using |
| // any provided parameter values in the method name |
| String methodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, "", String.class); |
| // the parameter values is between the parenthesis |
| String methodParameters = ObjectHelper.between(methodName, "(", ")"); |
| // use an iterator to walk the parameter values |
| Iterator it = null; |
| if (methodParameters != null) { |
| it = ObjectHelper.createIterator(methodParameters); |
| } |
| |
| // remove headers as they should not be propagated |
| // we need to do this before the expressions gets evaluated as it may contain |
| // a @Bean expression which would by mistake read these headers. So the headers |
| // must be removed at this point of time |
| exchange.getIn().removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY); |
| exchange.getIn().removeHeader(Exchange.BEAN_METHOD_NAME); |
| |
| for (int i = 0; i < size; i++) { |
| // grab the parameter value for the given index |
| Object parameterValue = it != null && it.hasNext() ? it.next() : null; |
| // and the expected parameter type |
| Class<?> parameterType = parameters.get(i).getType(); |
| // the value for the parameter to use |
| Object value = null; |
| |
| if (multiParameterArray) { |
| // get the value from the array |
| value = ((Object[])body)[i]; |
| } else { |
| // prefer to use parameter value if given, as they override any bean parameter binding |
| // we should skip * as its a type placeholder to indicate any type |
| if (parameterValue != null && !parameterValue.equals("*")) { |
| // evaluate the parameter value binding |
| value = evaluateParameterValue(exchange, i, parameterValue, parameterType); |
| } |
| |
| // use bean parameter binding, if still no value |
| Expression expression = expressions[i]; |
| if (value == null && expression != null) { |
| value = evaluateParameterBinding(exchange, expression, i, parameterType); |
| } |
| } |
| |
| // remember the value to use |
| if (value != Void.TYPE) { |
| answer[i] = value; |
| } |
| } |
| |
| return (T) answer; |
| } |
| |
| /** |
| * Evaluate using parameter values where the values can be provided in the method name syntax. |
| * <p/> |
| * This methods returns accordingly: |
| * <ul> |
| * <li><tt>null</tt> - if not a parameter value</li> |
| * <li><tt>Void.TYPE</tt> - if an explicit null, forcing Camel to pass in <tt>null</tt> for that given parameter</li> |
| * <li>a non <tt>null</tt> value - if the parameter was a parameter value, and to be used</li> |
| * </ul> |
| * |
| * @since 2.9 |
| */ |
| private Object evaluateParameterValue(Exchange exchange, int index, Object parameterValue, Class<?> parameterType) { |
| Object answer = null; |
| |
| // convert the parameter value to a String |
| String exp = exchange.getContext().getTypeConverter().convertTo(String.class, parameterValue); |
| if (exp != null) { |
| // must trim first as there may be spaces between parameters |
| exp = exp.trim(); |
| // check if its a valid parameter value |
| boolean valid = BeanHelper.isValidParameterValue(exp); |
| |
| if (!valid) { |
| // it may be a parameter type instead, and if so, then we should return null, |
| // as this method is only for evaluating parameter values |
| Boolean isClass = BeanHelper.isAssignableToExpectedType(exchange.getContext().getClassResolver(), exp, parameterType); |
| // the method will return a non null value if exp is a class |
| if (isClass != null) { |
| return null; |
| } |
| } |
| |
| // use simple language to evaluate the expression, as it may use the simple language to refer to message body, headers etc. |
| Expression expression = null; |
| try { |
| expression = exchange.getContext().resolveLanguage("simple").createExpression(exp); |
| parameterValue = expression.evaluate(exchange, Object.class); |
| } catch (Exception e) { |
| throw new ExpressionEvaluationException(expression, "Cannot create/evaluate simple expression: " + exp |
| + " to be bound to parameter at index: " + index + " on method: " + getMethod(), exchange, e); |
| } |
| |
| if (parameterValue != null) { |
| |
| // special for explicit null parameter values (as end users can explicit indicate they want null as parameter) |
| // see method javadoc for details |
| if ("null".equals(parameterValue)) { |
| return Void.TYPE; |
| } |
| |
| // the parameter value was not already valid, but since the simple language have evaluated the expression |
| // which may change the parameterValue, so we have to check it again to see if its now valid |
| exp = exchange.getContext().getTypeConverter().convertTo(String.class, parameterValue); |
| // String values from the simple language is always valid |
| if (!valid) { |
| // re validate if the parameter was not valid the first time (String values should be accepted) |
| valid = parameterValue instanceof String || BeanHelper.isValidParameterValue(exp); |
| } |
| |
| if (valid) { |
| // we need to unquote String parameters, as the enclosing quotes is there to denote a parameter value |
| if (parameterValue instanceof String) { |
| parameterValue = StringHelper.removeLeadingAndEndingQuotes((String) parameterValue); |
| } |
| try { |
| // its a valid parameter value, so convert it to the expected type of the parameter |
| answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, parameterValue); |
| if (LOG.isTraceEnabled()) { |
| LOG.trace("Parameter #{} evaluated as: {} type: ", new Object[]{index, answer, ObjectHelper.type(answer)}); |
| } |
| } catch (NoTypeConversionAvailableException e) { |
| throw ObjectHelper.wrapCamelExecutionException(exchange, e); |
| } |
| } |
| } |
| } |
| |
| return answer; |
| } |
| |
| /** |
| * Evaluate using classic parameter binding using the pre compute expression |
| */ |
| private Object evaluateParameterBinding(Exchange exchange, Expression expression, int index, Class<?> parameterType) { |
| Object answer = null; |
| |
| // use object first to avoid type conversion so we know if there is a value or not |
| Object result = expression.evaluate(exchange, Object.class); |
| if (result != null) { |
| // we got a value now try to convert it to the expected type |
| try { |
| answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, result); |
| if (LOG.isTraceEnabled()) { |
| LOG.trace("Parameter #{} evaluated as: {} type: ", new Object[]{index, answer, ObjectHelper.type(answer)}); |
| } |
| } catch (NoTypeConversionAvailableException e) { |
| throw ObjectHelper.wrapCamelExecutionException(exchange, e); |
| } |
| } else { |
| LOG.trace("Parameter #{} evaluated as null", index); |
| } |
| |
| return answer; |
| } |
| |
| @Override |
| public String toString() { |
| return "ParametersExpression: " + Arrays.asList(expressions); |
| } |
| |
| }; |
| } |
| |
| /** |
| * Finds the oneway annotation in priority order; look for method level annotations first, then the class level annotations, |
| * then super class annotations then interface annotations |
| * |
| * @param method the method on which to search |
| * @return the first matching annotation or none if it is not available |
| */ |
| protected Pattern findOneWayAnnotation(Method method) { |
| Pattern answer = getPatternAnnotation(method); |
| if (answer == null) { |
| Class<?> type = method.getDeclaringClass(); |
| |
| // lets create the search order of types to scan |
| List<Class<?>> typesToSearch = new ArrayList<Class<?>>(); |
| addTypeAndSuperTypes(type, typesToSearch); |
| Class<?>[] interfaces = type.getInterfaces(); |
| for (Class<?> anInterface : interfaces) { |
| addTypeAndSuperTypes(anInterface, typesToSearch); |
| } |
| |
| // now lets scan for a type which the current declared class overloads |
| answer = findOneWayAnnotationOnMethod(typesToSearch, method); |
| if (answer == null) { |
| answer = findOneWayAnnotation(typesToSearch); |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| * Returns the pattern annotation on the given annotated element; either as a direct annotation or |
| * on an annotation which is also annotated |
| * |
| * @param annotatedElement the element to look for the annotation |
| * @return the first matching annotation or null if none could be found |
| */ |
| protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement) { |
| return getPatternAnnotation(annotatedElement, 2); |
| } |
| |
| /** |
| * Returns the pattern annotation on the given annotated element; either as a direct annotation or |
| * on an annotation which is also annotated |
| * |
| * @param annotatedElement the element to look for the annotation |
| * @param depth the current depth |
| * @return the first matching annotation or null if none could be found |
| */ |
| protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement, int depth) { |
| Pattern answer = annotatedElement.getAnnotation(Pattern.class); |
| int nextDepth = depth - 1; |
| |
| if (nextDepth > 0) { |
| // lets look at all the annotations to see if any of those are annotated |
| Annotation[] annotations = annotatedElement.getAnnotations(); |
| for (Annotation annotation : annotations) { |
| Class<? extends Annotation> annotationType = annotation.annotationType(); |
| if (annotation instanceof Pattern || annotationType.equals(annotatedElement)) { |
| continue; |
| } else { |
| Pattern another = getPatternAnnotation(annotationType, nextDepth); |
| if (pattern != null) { |
| if (answer == null) { |
| answer = another; |
| } else { |
| LOG.warn("Duplicate pattern annotation: " + another + " found on annotation: " + annotation + " which will be ignored"); |
| } |
| } |
| } |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| * Adds the current class and all of its base classes (apart from {@link Object} to the given list |
| */ |
| protected void addTypeAndSuperTypes(Class<?> type, List<Class<?>> result) { |
| for (Class<?> t = type; t != null && t != Object.class; t = t.getSuperclass()) { |
| result.add(t); |
| } |
| } |
| |
| /** |
| * Finds the first annotation on the base methods defined in the list of classes |
| */ |
| protected Pattern findOneWayAnnotationOnMethod(List<Class<?>> classes, Method method) { |
| for (Class<?> type : classes) { |
| try { |
| Method definedMethod = type.getMethod(method.getName(), method.getParameterTypes()); |
| Pattern answer = getPatternAnnotation(definedMethod); |
| if (answer != null) { |
| return answer; |
| } |
| } catch (NoSuchMethodException e) { |
| // ignore |
| } |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Finds the first annotation on the given list of classes |
| */ |
| protected Pattern findOneWayAnnotation(List<Class<?>> classes) { |
| for (Class<?> type : classes) { |
| Pattern answer = getPatternAnnotation(type); |
| if (answer != null) { |
| return answer; |
| } |
| } |
| return null; |
| } |
| |
| protected boolean hasExceptionParameter() { |
| for (ParameterInfo parameter : parameters) { |
| if (Exception.class.isAssignableFrom(parameter.getType())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } |