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