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