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 createParametersExpression() { 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 return new Expression() { 427 @SuppressWarnings("unchecked") 428 public <T> T evaluate(Exchange exchange, Class<T> type) { 429 Object[] answer = new Object[size]; 430 Object body = exchange.getIn().getBody(); 431 boolean multiParameterArray = false; 432 if (exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) != null) { 433 multiParameterArray = exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, Boolean.class); 434 if (multiParameterArray) { 435 // Just change the message body to an Object array 436 if (!(body instanceof Object[])) { 437 body = exchange.getIn().getBody(Object[].class); 438 } 439 } 440 } 441 442 // if there was an explicit method name to invoke, then we should support using 443 // any provided parameter values in the method name 444 String methodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, "", String.class); 445 // the parameter values is between the parenthesis 446 String methodParameters = ObjectHelper.betweenOuterPair(methodName, '(', ')'); 447 // use an iterator to walk the parameter values 448 Iterator<?> it = null; 449 if (methodParameters != null) { 450 // split the parameters safely separated by comma, but beware that we can have 451 // quoted parameters which contains comma as well, so do a safe quote split 452 String[] parameters = StringQuoteHelper.splitSafeQuote(methodParameters, ',', true); 453 it = ObjectHelper.createIterator(parameters, ",", true); 454 } 455 456 // remove headers as they should not be propagated 457 // we need to do this before the expressions gets evaluated as it may contain 458 // a @Bean expression which would by mistake read these headers. So the headers 459 // must be removed at this point of time 460 exchange.getIn().removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY); 461 exchange.getIn().removeHeader(Exchange.BEAN_METHOD_NAME); 462 463 for (int i = 0; i < size; i++) { 464 // grab the parameter value for the given index 465 Object parameterValue = it != null && it.hasNext() ? it.next() : null; 466 // and the expected parameter type 467 Class<?> parameterType = parameters.get(i).getType(); 468 // the value for the parameter to use 469 Object value = null; 470 471 if (multiParameterArray) { 472 // get the value from the array 473 value = ((Object[])body)[i]; 474 } else { 475 // prefer to use parameter value if given, as they override any bean parameter binding 476 // we should skip * as its a type placeholder to indicate any type 477 if (parameterValue != null && !parameterValue.equals("*")) { 478 // evaluate the parameter value binding 479 value = evaluateParameterValue(exchange, i, parameterValue, parameterType); 480 } 481 // use bean parameter binding, if still no value 482 Expression expression = expressions[i]; 483 if (value == null && expression != null) { 484 value = evaluateParameterBinding(exchange, expression, i, parameterType); 485 } 486 } 487 // remember the value to use 488 if (value != Void.TYPE) { 489 answer[i] = value; 490 } 491 } 492 return (T) answer; 493 } 494 495 /** 496 * Evaluate using parameter values where the values can be provided in the method name syntax. 497 * <p/> 498 * This methods returns accordingly: 499 * <ul> 500 * <li><tt>null</tt> - if not a parameter value</li> 501 * <li><tt>Void.TYPE</tt> - if an explicit null, forcing Camel to pass in <tt>null</tt> for that given parameter</li> 502 * <li>a non <tt>null</tt> value - if the parameter was a parameter value, and to be used</li> 503 * </ul> 504 * 505 * @since 2.9 506 */ 507 private Object evaluateParameterValue(Exchange exchange, int index, Object parameterValue, Class<?> parameterType) { 508 Object answer = null; 509 510 // convert the parameter value to a String 511 String exp = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, parameterValue); 512 if (exp != null) { 513 // check if its a valid parameter value 514 boolean valid = BeanHelper.isValidParameterValue(exp); 515 516 if (!valid) { 517 // it may be a parameter type instead, and if so, then we should return null, 518 // as this method is only for evaluating parameter values 519 Boolean isClass = BeanHelper.isAssignableToExpectedType(exchange.getContext().getClassResolver(), exp, parameterType); 520 // the method will return a non null value if exp is a class 521 if (isClass != null) { 522 return null; 523 } 524 } 525 526 // use simple language to evaluate the expression, as it may use the simple language to refer to message body, headers etc. 527 Expression expression = null; 528 try { 529 expression = exchange.getContext().resolveLanguage("simple").createExpression(exp); 530 parameterValue = expression.evaluate(exchange, Object.class); 531 // use "null" to indicate the expression returned a null value which is a valid response we need to honor 532 if (parameterValue == null) { 533 parameterValue = "null"; 534 } 535 } catch (Exception e) { 536 throw new ExpressionEvaluationException(expression, "Cannot create/evaluate simple expression: " + exp 537 + " to be bound to parameter at index: " + index + " on method: " + getMethod(), exchange, e); 538 } 539 540 // special for explicit null parameter values (as end users can explicit indicate they want null as parameter) 541 // see method javadoc for details 542 if ("null".equals(parameterValue)) { 543 return Void.TYPE; 544 } 545 546 // the parameter value may match the expected type, then we use it as-is 547 if (parameterType.isAssignableFrom(parameterValue.getClass())) { 548 valid = true; 549 } else { 550 // the parameter value was not already valid, but since the simple language have evaluated the expression 551 // which may change the parameterValue, so we have to check it again to see if its now valid 552 exp = exchange.getContext().getTypeConverter().tryConvertTo(String.class, parameterValue); 553 // String values from the simple language is always valid 554 if (!valid) { 555 // re validate if the parameter was not valid the first time (String values should be accepted) 556 valid = parameterValue instanceof String || BeanHelper.isValidParameterValue(exp); 557 } 558 } 559 560 if (valid) { 561 // we need to unquote String parameters, as the enclosing quotes is there to denote a parameter value 562 if (parameterValue instanceof String) { 563 parameterValue = StringHelper.removeLeadingAndEndingQuotes((String) parameterValue); 564 } 565 if (parameterValue != null) { 566 try { 567 // its a valid parameter value, so convert it to the expected type of the parameter 568 answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, exchange, parameterValue); 569 if (LOG.isTraceEnabled()) { 570 LOG.trace("Parameter #{} evaluated as: {} type: ", new Object[]{index, answer, ObjectHelper.type(answer)}); 571 } 572 } catch (Exception e) { 573 if (LOG.isDebugEnabled()) { 574 LOG.debug("Cannot convert from type: {} to type: {} for parameter #{}", new Object[]{ObjectHelper.type(parameterValue), parameterType, index}); 575 } 576 throw new ParameterBindingException(e, method, index, parameterType, parameterValue); 577 } 578 } 579 } 580 } 581 582 return answer; 583 } 584 585 /** 586 * Evaluate using classic parameter binding using the pre compute expression 587 */ 588 private Object evaluateParameterBinding(Exchange exchange, Expression expression, int index, Class<?> parameterType) { 589 Object answer = null; 590 591 // use object first to avoid type conversion so we know if there is a value or not 592 Object result = expression.evaluate(exchange, Object.class); 593 if (result != null) { 594 try { 595 if (parameterType.isInstance(result)) { 596 // optimize if the value is already the same type 597 answer = result; 598 } else { 599 // we got a value now try to convert it to the expected type 600 answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, result); 601 } 602 if (LOG.isTraceEnabled()) { 603 LOG.trace("Parameter #{} evaluated as: {} type: ", new Object[]{index, answer, ObjectHelper.type(answer)}); 604 } 605 } catch (NoTypeConversionAvailableException e) { 606 if (LOG.isDebugEnabled()) { 607 LOG.debug("Cannot convert from type: {} to type: {} for parameter #{}", new Object[]{ObjectHelper.type(result), parameterType, index}); 608 } 609 throw new ParameterBindingException(e, method, index, parameterType, result); 610 } 611 } else { 612 LOG.trace("Parameter #{} evaluated as null", index); 613 } 614 615 return answer; 616 } 617 618 @Override 619 public String toString() { 620 return "ParametersExpression: " + Arrays.asList(expressions); 621 } 622 623 }; 624 } 625 626 /** 627 * Finds the oneway annotation in priority order; look for method level annotations first, then the class level annotations, 628 * then super class annotations then interface annotations 629 * 630 * @param method the method on which to search 631 * @return the first matching annotation or none if it is not available 632 */ 633 protected Pattern findOneWayAnnotation(Method method) { 634 Pattern answer = getPatternAnnotation(method); 635 if (answer == null) { 636 Class<?> type = method.getDeclaringClass(); 637 638 // create the search order of types to scan 639 List<Class<?>> typesToSearch = new ArrayList<Class<?>>(); 640 addTypeAndSuperTypes(type, typesToSearch); 641 Class<?>[] interfaces = type.getInterfaces(); 642 for (Class<?> anInterface : interfaces) { 643 addTypeAndSuperTypes(anInterface, typesToSearch); 644 } 645 646 // now let's scan for a type which the current declared class overloads 647 answer = findOneWayAnnotationOnMethod(typesToSearch, method); 648 if (answer == null) { 649 answer = findOneWayAnnotation(typesToSearch); 650 } 651 } 652 return answer; 653 } 654 655 /** 656 * Returns the pattern annotation on the given annotated element; either as a direct annotation or 657 * on an annotation which is also annotated 658 * 659 * @param annotatedElement the element to look for the annotation 660 * @return the first matching annotation or null if none could be found 661 */ 662 protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement) { 663 return getPatternAnnotation(annotatedElement, 2); 664 } 665 666 /** 667 * Returns the pattern annotation on the given annotated element; either as a direct annotation or 668 * on an annotation which is also annotated 669 * 670 * @param annotatedElement the element to look for the annotation 671 * @param depth the current depth 672 * @return the first matching annotation or null if none could be found 673 */ 674 protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement, int depth) { 675 Pattern answer = annotatedElement.getAnnotation(Pattern.class); 676 int nextDepth = depth - 1; 677 678 if (nextDepth > 0) { 679 // look at all the annotations to see if any of those are annotated 680 Annotation[] annotations = annotatedElement.getAnnotations(); 681 for (Annotation annotation : annotations) { 682 Class<? extends Annotation> annotationType = annotation.annotationType(); 683 if (annotation instanceof Pattern || annotationType.equals(annotatedElement)) { 684 continue; 685 } else { 686 Pattern another = getPatternAnnotation(annotationType, nextDepth); 687 if (pattern != null) { 688 if (answer == null) { 689 answer = another; 690 } else { 691 LOG.warn("Duplicate pattern annotation: " + another + " found on annotation: " + annotation + " which will be ignored"); 692 } 693 } 694 } 695 } 696 } 697 return answer; 698 } 699 700 /** 701 * Adds the current class and all of its base classes (apart from {@link Object} to the given list 702 */ 703 protected void addTypeAndSuperTypes(Class<?> type, List<Class<?>> result) { 704 for (Class<?> t = type; t != null && t != Object.class; t = t.getSuperclass()) { 705 result.add(t); 706 } 707 } 708 709 /** 710 * Finds the first annotation on the base methods defined in the list of classes 711 */ 712 protected Pattern findOneWayAnnotationOnMethod(List<Class<?>> classes, Method method) { 713 for (Class<?> type : classes) { 714 try { 715 Method definedMethod = type.getMethod(method.getName(), method.getParameterTypes()); 716 Pattern answer = getPatternAnnotation(definedMethod); 717 if (answer != null) { 718 return answer; 719 } 720 } catch (NoSuchMethodException e) { 721 // ignore 722 } 723 } 724 return null; 725 } 726 727 728 /** 729 * Finds the first annotation on the given list of classes 730 */ 731 protected Pattern findOneWayAnnotation(List<Class<?>> classes) { 732 for (Class<?> type : classes) { 733 Pattern answer = getPatternAnnotation(type); 734 if (answer != null) { 735 return answer; 736 } 737 } 738 return null; 739 } 740 741 protected boolean hasExceptionParameter() { 742 for (ParameterInfo parameter : parameters) { 743 if (Exception.class.isAssignableFrom(parameter.getType())) { 744 return true; 745 } 746 } 747 return false; 748 } 749 750}