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