content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.camel.component.bean;
  
  import java.lang.annotation.Annotation;
  import java.lang.reflect.AccessibleObject;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.Callable;
  import java.util.concurrent.CompletionStage;
  
  import org.apache.camel.AsyncCallback;
  import org.apache.camel.AsyncProcessor;
  import org.apache.camel.CamelContext;
  import org.apache.camel.Exchange;
  import org.apache.camel.ExchangePattern;
  import org.apache.camel.ExchangePropertyKey;
  import org.apache.camel.Expression;
  import org.apache.camel.ExpressionEvaluationException;
  import org.apache.camel.InOnly;
  import org.apache.camel.InOut;
  import org.apache.camel.Message;
  import org.apache.camel.NoTypeConversionAvailableException;
  import org.apache.camel.Pattern;
  import org.apache.camel.Processor;
  import org.apache.camel.RuntimeCamelException;
  import org.apache.camel.RuntimeExchangeException;
  import org.apache.camel.StreamCache;
  import org.apache.camel.spi.ErrorHandlerAware;
  import org.apache.camel.support.DefaultMessage;
  import org.apache.camel.support.ExchangeHelper;
  import org.apache.camel.support.ExpressionAdapter;
  import org.apache.camel.support.MessageHelper;
  import org.apache.camel.support.ObjectHelper;
  import org.apache.camel.support.PluginHelper;
  import org.apache.camel.support.service.ServiceHelper;
  import org.apache.camel.util.StringHelper;
  import org.apache.camel.util.StringQuoteHelper;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import static org.apache.camel.util.ObjectHelper.asList;
  import static org.apache.camel.util.ObjectHelper.asString;
  
  /**
   * Information about a method to be used for invocation.
   */
  public class MethodInfo {
      private static final Logger LOG = LoggerFactory.getLogger(MethodInfo.class);
  
      private final CamelContext camelContext;
      private final Class<?> type;
      private final Method method;
      private final List<ParameterInfo> parameters;
      private final List<ParameterInfo> bodyParameters;
      private final boolean hasCustomAnnotation;
      private final boolean hasHandlerAnnotation;
      private final Expression parametersExpression;
      private ExchangePattern pattern = ExchangePattern.InOut;
      private AsyncProcessor recipientList;
      private AsyncProcessor routingSlip;
      private AsyncProcessor dynamicRouter;
  
      /**
       * Adapter to invoke the method which has been annotated with the @DynamicRouter
       */
      private final class DynamicRouterExpression extends ExpressionAdapter {
          private final Object pojo;
  
          private DynamicRouterExpression(Object pojo) {
              this.pojo = pojo;
          }
  
          @Override
          public Object evaluate(Exchange exchange) {
              // evaluate arguments on each invocation as the parameters can have changed/updated since last invocation
              final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class);
              try {
                  return invoke(method, pojo, arguments, exchange);
              } catch (Exception e) {
                  throw RuntimeCamelException.wrapRuntimeCamelException(e);
              }
          }
  
          @Override
          public String toString() {
              return "DynamicRouter[invoking: " + method + " on bean: " + pojo + "]";
          }
      }
  
      public MethodInfo(CamelContext camelContext, Class<?> type, Method method, List<ParameterInfo> parameters,
                        List<ParameterInfo> bodyParameters,
                        boolean hasCustomAnnotation, boolean hasHandlerAnnotation) {
          this.camelContext = camelContext;
          this.type = type;
          this.method = method;
          this.parameters = parameters;
          this.bodyParameters = bodyParameters;
          this.hasCustomAnnotation = hasCustomAnnotation;
          this.hasHandlerAnnotation = hasHandlerAnnotation;
          this.parametersExpression = createParametersExpression();
  
          Map<Class<?>, Annotation> collectedMethodAnnotation = collectMethodAnnotations(type, method);
  
          // configure MEP if there was an annotation with @Pattern/@InOnly/@InOut
          ExchangePattern aep = findExchangePatternAnnotation(collectedMethodAnnotation);
          if (aep != null) {
              pattern = aep;
          }
  
          org.apache.camel.RoutingSlip routingSlipAnnotation
                  = (org.apache.camel.RoutingSlip) collectedMethodAnnotation.get(org.apache.camel.RoutingSlip.class);
          if (routingSlipAnnotation != null) {
              routingSlip = PluginHelper.getAnnotationBasedProcessorFactory(camelContext)
                      .createRoutingSlip(camelContext, routingSlipAnnotation);
              // add created routingSlip as a service so we have its lifecycle managed
              try {
                  camelContext.addService(routingSlip);
              } catch (Exception e) {
                  throw RuntimeCamelException.wrapRuntimeCamelException(e);
              }
          }
  
          org.apache.camel.DynamicRouter dynamicRouterAnnotation
                  = (org.apache.camel.DynamicRouter) collectedMethodAnnotation.get(org.apache.camel.DynamicRouter.class);
          if (dynamicRouterAnnotation != null) {
              dynamicRouter = PluginHelper.getAnnotationBasedProcessorFactory(camelContext)
                      .createDynamicRouter(camelContext, dynamicRouterAnnotation);
              // add created dynamicRouter as a service so we have its lifecycle managed
              try {
                  camelContext.addService(dynamicRouter);
              } catch (Exception e) {
                  throw RuntimeCamelException.wrapRuntimeCamelException(e);
              }
          }
  
          org.apache.camel.RecipientList recipientListAnnotation
                  = (org.apache.camel.RecipientList) collectedMethodAnnotation.get(org.apache.camel.RecipientList.class);
          if (recipientListAnnotation != null) {
              recipientList = PluginHelper.getAnnotationBasedProcessorFactory(camelContext)
                      .createRecipientList(camelContext, recipientListAnnotation);
              // add created recipientList as a service so we have its lifecycle managed
              try {
                  camelContext.addService(recipientList);
              } catch (Exception e) {
                  throw RuntimeCamelException.wrapRuntimeCamelException(e);
              }
          }
      }
  
      /**
       * Finds the oneway annotation in priority order; look for method level annotations first, then the class level
       * annotations, then super class annotations then interface annotations
       */
      private Map<Class<?>, Annotation> collectMethodAnnotations(Class<?> c, Method method) {
          Map<Class<?>, Annotation> annotations = new HashMap<>();
  
          Set<Class<?>> search = new LinkedHashSet<>();
          // first look for the top class itself and its super classes
          addTypeAndSuperTypes(c, search);
          // and interfaces for all the classes as last
          Set<Class<?>> searchInterfaces = new LinkedHashSet<>();
          for (Class<?> aClazz : search) {
              Class<?>[] interfaces = aClazz.getInterfaces();
              for (Class<?> anInterface : interfaces) {
                  addTypeAndSuperTypes(anInterface, searchInterfaces);
              }
          }
          search.addAll(searchInterfaces);
          collectMethodAnnotations(search.iterator(), method.getName(), annotations);
          return annotations;
      }
  
      /**
       * Adds the current class and all of its base classes (apart from {@link Object} to the given list
       */
      private static void addTypeAndSuperTypes(Class<?> type, Set<Class<?>> result) {
          for (Class<?> t = type; t != null && t != Object.class; t = t.getSuperclass()) {
              result.add(t);
          }
      }
  
      private void collectMethodAnnotations(
              Iterator<?> it, String targetMethodName, Map<Class<?>, Annotation> annotations) {
          Class<?>[] paramTypes = method.getParameterTypes();
          boolean aep = false;
          while (it.hasNext()) {
              Class<?> searchType = (Class<?>) it.next();
              Method[] methods = searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods();
              for (Method method : methods) {
                  if (targetMethodName.equals(method.getName()) && Arrays.equals(paramTypes, method.getParameterTypes())) {
                      for (Annotation a : method.getAnnotations()) {
                          // favour existing annotation so only add if not exists
                          Class<?> at = a.annotationType();
  
                          annotations.putIfAbsent(at, a);
  
                          aep |= at == Pattern.class || at == InOnly.class || at == InOut.class;
                      }
                  }
              }
              // special for @Pattern/@InOut/@InOnly
              if (!aep) {
                  // look for this on class level
                  for (Annotation a : searchType.getAnnotations()) {
                      // favour existing annotation so only add if not exists
                      Class<?> at = a.annotationType();
                      boolean valid = at == Pattern.class || at == InOnly.class || at == InOut.class;
                      if (valid && !annotations.containsKey(at)) {
                          aep = true;
                          annotations.put(at, a);
                      }
                  }
              }
          }
      }
  
      @Override
      public String toString() {
          return method.toString();
      }
  
      /**
       * For fine grained error handling for outputs of this EIP. The base error handler is used as base and then cloned
       * for each output processor.
       *
       * This is used internally only by Camel - not for end users.
       */
      public void setErrorHandler(Processor errorHandler) {
          // special for @RecipientList which needs to be injected with error handler it should use
          if (recipientList instanceof ErrorHandlerAware) {
              ((ErrorHandlerAware) recipientList).setErrorHandler(errorHandler);
          }
      }
  
      private Object[] initializeArguments(boolean hasParameters, Exchange exchange) {
          if (hasParameters) {
              if (parametersExpression != null) {
                  parametersExpression.init(camelContext);
                  return parametersExpression.evaluate(exchange, Object[].class);
              }
          }
  
          return null;
      }
  
      public MethodInvocation createMethodInvocation(final Object pojo, boolean hasParameters, final Exchange exchange) {
          final Object[] arguments = initializeArguments(hasParameters, exchange);
  
          return new MethodInvocation() {
              public Method getMethod() {
                  return method;
              }
  
              public Object[] getArguments() {
                  return arguments;
              }
  
              public boolean proceed(AsyncCallback callback) {
                  try {
                      // reset cached streams so they can be read again
                      MessageHelper.resetStreamCache(exchange.getIn());
                      return doProceed(callback);
                  } catch (InvocationTargetException e) {
                      exchange.setException(e.getTargetException());
                      callback.done(true);
                      return true;
                  } catch (Exception e) {
                      exchange.setException(e);
                      callback.done(true);
                      return true;
                  }
              }
  
              private boolean doProceed(AsyncCallback callback) throws Exception {
                  // dynamic router should be invoked beforehand
                  if (dynamicRouter != null) {
                      return dynamicRouterInvocation(callback);
                  }
  
                  // invoke pojo
                  if (LOG.isTraceEnabled()) {
                      LOG.trace(">>>> invoking: {} on bean: {} with arguments: {} for exchange: {}", method, pojo,
                              asString(arguments), exchange);
                  }
                  Object result = invoke(method, pojo, arguments, exchange);
  
                  // the method may be a closure or chained method returning a callable which should be called
                  if (result instanceof Callable) {
                      LOG.trace("Method returned Callback which will be called: {}", result);
                      Object callableResult = ((Callable) result).call();
                      if (callableResult != null) {
                          result = callableResult;
                      } else {
                          // if callable returned null we should not change the body
                          result = Void.TYPE;
                      }
                  }
  
                  if (recipientList != null) {
                      return recipientListInvocation(callback, result);
                  }
                  if (routingSlip != null) {
                      return routingSlipInvocation(callback, result);
                  }
  
                  //If it's Java 8 async result
                  if (CompletionStage.class.isAssignableFrom(method.getReturnType())) {
                      CompletionStage<?> completionStage = (CompletionStage<?>) result;
  
                      completionStage
                              .whenComplete((resultObject, e) -> {
                                  if (e != null) {
                                      exchange.setException(e);
                                  } else if (resultObject != null) {
                                      fillResult(exchange, resultObject);
                                  }
                                  callback.done(false);
                              });
                      return false;
                  }
  
                  // if the method returns something then set the value returned on the Exchange
                  if (result != Void.TYPE && !method.getReturnType().equals(Void.TYPE)) {
                      fillResult(exchange, result);
                  }
  
                  // we did not use any of the eips, but just invoked the bean
                  // so notify the callback we are done synchronously
                  callback.done(true);
                  return true;
              }
  
              private boolean routingSlipInvocation(AsyncCallback callback, Object result) {
                  if (!ServiceHelper.isStarted(routingSlip)) {
                      ServiceHelper.startService(routingSlip);
                  }
                  exchange.setProperty(ExchangePropertyKey.EVALUATE_EXPRESSION_RESULT, result);
                  return routingSlip.process(exchange, callback);
              }
  
              private boolean recipientListInvocation(AsyncCallback callback, Object result) {
                  // ensure its started
                  if (!ServiceHelper.isStarted(recipientList)) {
                      ServiceHelper.startService(recipientList);
                  }
                  exchange.setProperty(ExchangePropertyKey.EVALUATE_EXPRESSION_RESULT, result);
                  return recipientList.process(exchange, callback);
              }
  
              private boolean dynamicRouterInvocation(AsyncCallback callback) {
                  if (!ServiceHelper.isStarted(dynamicRouter)) {
                      ServiceHelper.startService(dynamicRouter);
                  }
                  // use an expression which invokes the method to be used by dynamic router
                  Expression expression = new DynamicRouterExpression(pojo);
                  expression.init(camelContext);
                  exchange.setProperty(ExchangePropertyKey.EVALUATE_EXPRESSION_RESULT, expression);
                  return dynamicRouter.process(exchange, callback);
              }
  
              public Object getThis() {
                  return pojo;
              }
  
              public AccessibleObject getStaticPart() {
                  return method;
              }
          };
      }
  
      private void fillResult(Exchange exchange, Object result) {
          LOG.trace("Setting bean invocation result : {}", result);
  
          // the bean component forces OUT if the MEP is OUT capable
          boolean out = exchange.hasOut() || ExchangeHelper.isOutCapable(exchange);
          Message old;
          if (out) {
              old = exchange.getOut();
              // propagate headers
              exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
          } else {
              old = exchange.getIn();
          }
  
          // create a new message container, so we do not drag specialized message objects along
          // but that is only needed if the old message is a specialized message
          boolean copyNeeded = !(old.getClass().equals(DefaultMessage.class));
  
          if (copyNeeded) {
              Message msg = new DefaultMessage(exchange.getContext());
              msg.copyFromWithNewBody(old, result);
  
              // replace message on exchange
              ExchangeHelper.replaceMessage(exchange, msg, false);
          } else {
              // no copy needed so set replace value directly
              old.setBody(result);
          }
      }
  
      public Class<?> getType() {
          return type;
      }
  
      public Method getMethod() {
          return method;
      }
  
      /**
       * Returns the {@link org.apache.camel.ExchangePattern} that should be used when invoking this method. This value
       * defaults to {@link org.apache.camel.ExchangePattern#InOut} unless some {@link org.apache.camel.Pattern}
       * annotation is used to override the message exchange pattern.
       *
       * @return the exchange pattern to use for invoking this method.
       */
      public ExchangePattern getPattern() {
          return pattern;
      }
  
      public Expression getParametersExpression() {
          return parametersExpression;
      }
  
      public List<ParameterInfo> getBodyParameters() {
          return bodyParameters;
      }
  
      public Class<?> getBodyParameterType() {
          if (bodyParameters.isEmpty()) {
              return null;
          }
          ParameterInfo parameterInfo = bodyParameters.get(0);
          return parameterInfo.getType();
      }
  
      public boolean bodyParameterMatches(Class<?> bodyType) {
          Class<?> actualType = getBodyParameterType();
          return actualType != null && org.apache.camel.util.ObjectHelper.isAssignableFrom(bodyType, actualType);
      }
  
      public List<ParameterInfo> getParameters() {
          return parameters;
      }
  
      public boolean hasBodyParameter() {
          return !bodyParameters.isEmpty();
      }
  
      public boolean hasCustomAnnotation() {
          return hasCustomAnnotation;
      }
  
      public boolean hasHandlerAnnotation() {
          return hasHandlerAnnotation;
      }
  
      public boolean hasParameters() {
          return !parameters.isEmpty();
      }
  
      public boolean isReturnTypeVoid() {
          return method.getReturnType().getName().equals("void");
      }
  
      public boolean isStaticMethod() {
          return Modifier.isStatic(method.getModifiers());
      }
  
      /**
       * Returns true if this method is covariant with the specified method (this method may above or below the specified
       * method in the class hierarchy)
       */
      public boolean isCovariantWith(MethodInfo method) {
          return method.getMethod().getName().equals(this.getMethod().getName())
                  && (method.getMethod().getReturnType().isAssignableFrom(this.getMethod().getReturnType())
                          || this.getMethod().getReturnType().isAssignableFrom(method.getMethod().getReturnType()))
                  && Arrays.deepEquals(method.getMethod().getParameterTypes(), this.getMethod().getParameterTypes());
      }
  
      protected Object invoke(Method mth, Object pojo, Object[] arguments, Exchange exchange) throws InvocationTargetException {
          try {
              return ObjectHelper.invokeMethodSafe(mth, pojo, arguments);
          } catch (IllegalAccessException e) {
              throw new RuntimeExchangeException(
                      "IllegalAccessException occurred invoking method: " + mth + " using arguments: " + asList(arguments),
                      exchange, e);
          } catch (IllegalArgumentException e) {
              throw new RuntimeExchangeException(
                      "IllegalArgumentException occurred invoking method: " + mth + " using arguments: " + asList(arguments),
                      exchange, e);
          }
      }
  
      protected Expression[] createParameterExpressions() {
          final int size = parameters.size();
          LOG.trace("Creating parameters expression for {} parameters", size);
  
          final Expression[] expressions = new Expression[size];
          for (int i = 0; i < size; i++) {
              Expression parameterExpression = parameters.get(i).getExpression();
              expressions[i] = parameterExpression;
              LOG.trace("Parameter #{} has expression: {}", i, parameterExpression);
          }
  
          return expressions;
      }
  
      protected Expression createParametersExpression() {
          return new ParameterExpression(createParameterExpressions());
      }
  
      protected ExchangePattern findExchangePatternAnnotation(Map<Class<?>, Annotation> collectedMethodAnnotation) {
          if (collectedMethodAnnotation.get(InOnly.class) != null) {
              return ExchangePattern.InOnly;
          } else if (collectedMethodAnnotation.get(InOut.class) != null) {
              return ExchangePattern.InOut;
          } else {
              Pattern pattern = (Pattern) collectedMethodAnnotation.get(Pattern.class);
              return pattern != null ? pattern.value() : null;
          }
      }
  
      protected boolean hasExceptionParameter() {
          for (ParameterInfo parameter : parameters) {
              if (Exception.class.isAssignableFrom(parameter.getType())) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Expression to evaluate the bean parameter parameters and provide the correct values when the method is invoked.
       */
      private final class ParameterExpression implements Expression {
          private final Expression[] expressions;
  
          ParameterExpression(Expression[] expressions) {
              this.expressions = expressions;
          }
  
          @Override
          public void init(CamelContext context) {
              if (expressions != null) {
                  for (Expression exp : expressions) {
                      if (exp != null) {
                          exp.init(context);
                      }
                  }
              }
          }
  
          @Override
          @SuppressWarnings("unchecked")
          public <T> T evaluate(Exchange exchange, Class<T> type) {
              Object body = exchange.getIn().getBody();
  
              // if there was an explicit method name to invoke, then we should support using
              // any provided parameter values in the method name
              String methodName = exchange.getProperty(BeanConstants.BEAN_METHOD_NAME, String.class);
              // the parameter values is between the parenthesis
              String methodParameters = StringHelper.betweenOuterPair(methodName, '(', ')');
              // use an iterator to walk the parameter values
              Iterator<?> it = null;
              if (methodParameters != null) {
                  // split the parameters safely separated by comma, but beware that we can have
                  // quoted parameters which contains comma as well, so do a safe quote split (keep quotes)
                  String[] parameters = StringQuoteHelper.splitSafeQuote(methodParameters, ',', true, true);
                  it = ObjectHelper.createIterator(parameters, ",", true);
              }
  
              // remove headers as they should not be propagated
              // we need to do this before the expressions gets evaluated as it may contain
              // a @Bean expression which would by mistake read these headers. So the headers
              // must be removed at this point of time
              if (methodName != null) {
                  exchange.removeProperty(BeanConstants.BEAN_METHOD_NAME);
              }
  
              Object[] answer = evaluateParameterExpressions(exchange, body, it);
              return (T) answer;
          }
  
          /**
           * Evaluates all the parameter expressions
           */
          private Object[] evaluateParameterExpressions(Exchange exchange, Object body, Iterator<?> it) {
              Object[] answer = new Object[expressions != null ? expressions.length : 1];
              for (int i = 0; expressions == null || i < expressions.length; i++) {
                  if (body instanceof StreamCache) {
                      // need to reset stream cache for each expression as you may access the message body in multiple parameters
                      ((StreamCache) body).reset();
                  }
  
                  // whether its vararg
                  boolean varargs = parameters.get(i).isVarargs();
                  // and the expected parameter type
                  Class<?> parameterType = parameters.get(i).getType();
                  // the value for the parameter to use
                  Object value = null;
  
                  if (varargs) {
                      value = evaluateVarargsParameterExpressions(exchange, body, it);
                  } else {
                      // grab the parameter value for the given index
                      Object parameterValue = it != null && it.hasNext() ? it.next() : null;
                      // prefer to use parameter value if given, as they override any bean parameter binding
                      // we should skip * as its a type placeholder to indicate any type
                      if (parameterValue != null && !parameterValue.equals("*")) {
                          // evaluate the parameter value binding
                          value = evaluateParameterValue(exchange, i, parameterValue, parameterType, false);
                      }
                  }
                  // use bean parameter binding, if still no value
                  Expression expression = expressions != null ? expressions[i] : null;
                  if (value == null && expression != null) {
                      value = evaluateParameterBinding(exchange, expression, i, parameterType, false);
                  }
                  // remember the value to use
                  if (value != Void.TYPE) {
                      answer[i] = value;
                  }
              }
  
              return answer;
          }
  
          /**
           * Evaluate the remainder parameter as a single vararg
           */
          private Object evaluateVarargsParameterExpressions(Exchange exchange, Object body, Iterator<?> it) {
              // special for varargs
              if (body instanceof StreamCache) {
                  // need to reset stream cache for each expression as you may access the message body in multiple parameters
                  ((StreamCache) body).reset();
              }
              List<Object> answer = new ArrayList<>();
              int i = 0;
              while (it.hasNext()) {
                  Object parameterValue = it.next();
                  Object value = null;
                  // prefer to use parameter value if given, as they override any bean parameter binding
                  // we should skip * as its a type placeholder to indicate any type
                  if (parameterValue != null && !parameterValue.equals("*")) {
                      // evaluate the parameter value binding
                      value = evaluateParameterValue(exchange, i, parameterValue, Object.class, true);
                  }
                  // remember the value to use
                  if (value != Void.TYPE) {
                      answer.add(value);
                  }
                  i++;
              }
              return answer.toArray(new Object[0]);
          }
  
          /**
           * Evaluate using parameter values where the values can be provided in the method name syntax.
           * <p/>
           * This method returns accordingly:
           * <ul>
           * <li><tt>null</tt> - if not a parameter value</li>
           * <li><tt>Void.TYPE</tt> - if an explicit null, forcing Camel to pass in <tt>null</tt> for that given
           * parameter</li>
           * <li>a non <tt>null</tt> value - if the parameter was a parameter value, and to be used</li>
           * </ul>
           */
          private Object evaluateParameterValue(
                  Exchange exchange, int index, Object parameterValue, Class<?> parameterType,
                  boolean varargs) {
              Object answer = null;
  
              // convert the parameter value to a String
              String exp = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, parameterValue);
              boolean valid;
              if (exp != null) {
                  int pos1 = exp.indexOf(' ');
                  int pos2 = exp.indexOf(".class");
                  if (pos1 != -1 && pos2 != -1 && pos1 > pos2) {
                      exp = exp.substring(pos2 + 7); // clip <space>.class
                  }
  
                  // check if its a valid parameter value (no type declared via .class syntax)
                  valid = BeanHelper.isValidParameterValue(exp);
                  if (!valid && !varargs) {
                      // it may be a parameter type instead, and if so, then we should return null,
                      // as this method is only for evaluating parameter values
                      Boolean isClass = BeanHelper.isAssignableToExpectedType(exchange.getContext().getClassResolver(), exp,
                              parameterType);
                      // the method will return a non-null value if exp is a class
                      if (isClass != null) {
                          return null;
                      }
                  }
  
                  // use simple language to evaluate the expression, as it may use the simple language to refer to message body, headers etc.
                  Expression expression = null;
                  try {
                      expression = exchange.getContext().resolveLanguage("simple").createExpression(exp);
                      parameterValue = expression.evaluate(exchange, Object.class);
                      // use "null" to indicate the expression returned a null value which is a valid response we need to honor
                      if (parameterValue == null) {
                          parameterValue = "null";
                      }
                  } catch (Exception e) {
                      throw new ExpressionEvaluationException(
                              expression, "Cannot create/evaluate simple expression: " + exp
                                          + " to be bound to parameter at index: " + index + " on method: " + getMethod(),
                              exchange, e);
                  }
  
                  // special for explicit null parameter values (as end users can explicit indicate they want null as parameter)
                  // see method javadoc for details
                  if ("null".equals(parameterValue)) {
                      return Void.TYPE;
                  }
  
                  // the parameter value may match the expected type, then we use it as-is
                  if (varargs || parameterType.isAssignableFrom(parameterValue.getClass())) {
                      valid = true;
                  } else {
                      // String values from the simple language is always valid
                      if (!valid) {
                          valid = parameterValue instanceof String;
                          if (!valid) {
                              // the parameter value was not already valid, but since the simple language have evaluated the expression
                              // which may change the parameterValue, so we have to check it again to see if it is now valid
                              exp = exchange.getContext().getTypeConverter().tryConvertTo(String.class, parameterValue);
                              // re-validate if the parameter was not valid the first time
                              valid = BeanHelper.isValidParameterValue(exp);
                          }
                      }
                  }
  
                  if (valid) {
                      // we need to unquote String parameters, as the enclosing quotes is there to denote a parameter value
                      if (parameterValue instanceof String) {
                          parameterValue = StringHelper.removeLeadingAndEndingQuotes((String) parameterValue);
                      }
                      if (varargs) {
                          // use the value as-is
                          answer = parameterValue;
                      } else {
                          try {
                              // it is a valid parameter value, so convert it to the expected type of the parameter
                              answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, exchange,
                                      parameterValue);
                              if (LOG.isTraceEnabled()) {
                                  LOG.trace("Parameter #{} evaluated as: {} type: {}", index, answer,
                                          org.apache.camel.util.ObjectHelper.type(answer));
                              }
                          } catch (Exception e) {
                              if (LOG.isDebugEnabled()) {
                                  LOG.debug("Cannot convert from type: {} to type: {} for parameter #{}",
                                          org.apache.camel.util.ObjectHelper.type(parameterValue), parameterType, index);
                              }
                              throw new ParameterBindingException(e, method, index, parameterType, parameterValue);
                          }
                      }
                  }
              }
  
              return answer;
          }
  
          /**
           * Evaluate using classic parameter binding using the pre compute expression
           */
          private Object evaluateParameterBinding(
                  Exchange exchange, Expression expression, int index, Class<?> parameterType, boolean varargs) {
              Object answer = null;
  
              // use object first to avoid type conversion so we know if there is a value or not
              Object result = expression.evaluate(exchange, Object.class);
              if (result != null) {
                  if (varargs) {
                      answer = result;
                  } else {
                      try {
                          if (parameterType.isInstance(result)) {
                              // optimize if the value is already the same type
                              answer = result;
                          } else {
                              // we got a value now try to convert it to the expected type
                              answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(parameterType, result);
                          }
                          if (LOG.isTraceEnabled()) {
                              LOG.trace("Parameter #{} evaluated as: {} type: {}", index, answer,
                                      org.apache.camel.util.ObjectHelper.type(answer));
                          }
                      } catch (NoTypeConversionAvailableException e) {
                          if (LOG.isDebugEnabled()) {
                              LOG.debug("Cannot convert from type: {} to type: {} for parameter #{}",
                                      org.apache.camel.util.ObjectHelper.type(result), parameterType, index);
                          }
                          throw new ParameterBindingException(e, method, index, parameterType, result);
                      }
                  }
              } else {
                  LOG.trace("Parameter #{} evaluated as null", index);
              }
  
              return answer;
          }
  
          @Override
          public String toString() {
              return "ParametersExpression: " + Arrays.asList(expressions);
          }
  
      }
  }
  