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}