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