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}