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