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