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