public boolean process(Exchange exchange, AsyncCallback callback) {
   // remember the callback to be used by the interceptor
   this.callback.set(callback);
   try {
     // invoke the target
     boolean done = target.process(exchange, callback);
     if (interceptorDone.get() != null) {
       // return the result from the interceptor if it was invoked
       return interceptorDone.get();
     } else {
       // otherwise from the target
       return done;
     }
   } finally {
     // cleanup
     this.callback.remove();
     this.interceptorDone.remove();
   }
 }
  private boolean doProcessSequential(
      final Exchange original,
      final AtomicExchange result,
      final Iterable<ProcessorExchangePair> pairs,
      final Iterator<ProcessorExchangePair> it,
      final ProcessorExchangePair pair,
      final AsyncCallback callback,
      final AtomicInteger total) {
    boolean sync = true;

    final Exchange exchange = pair.getExchange();
    Processor processor = pair.getProcessor();
    final Producer producer = pair.getProducer();

    TracedRouteNodes traced =
        exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getTracedRouteNodes() : null;

    // compute time taken if sending to another endpoint
    final StopWatch watch = producer != null ? new StopWatch() : null;

    try {
      // prepare tracing starting from a new block
      if (traced != null) {
        traced.pushBlock();
      }

      if (producer != null) {
        EventHelper.notifyExchangeSending(exchange.getContext(), exchange, producer.getEndpoint());
      }
      // let the prepared process it, remember to begin the exchange pair
      AsyncProcessor async = AsyncProcessorConverterHelper.convert(processor);
      pair.begin();
      sync =
          async.process(
              exchange,
              new AsyncCallback() {
                public void done(boolean doneSync) {
                  // we are done with the exchange pair
                  pair.done();

                  // okay we are done, so notify the exchange was sent
                  if (producer != null) {
                    long timeTaken = watch.stop();
                    Endpoint endpoint = producer.getEndpoint();
                    // emit event that the exchange was sent to the endpoint
                    EventHelper.notifyExchangeSent(
                        exchange.getContext(), exchange, endpoint, timeTaken);
                  }

                  // we only have to handle async completion of the routing slip
                  if (doneSync) {
                    return;
                  }

                  // continue processing the multicast asynchronously
                  Exchange subExchange = exchange;

                  // Decide whether to continue with the multicast or not; similar logic to the
                  // Pipeline
                  // remember to test for stop on exception and aggregate before copying back
                  // results
                  boolean continueProcessing =
                      PipelineHelper.continueProcessing(
                          subExchange,
                          "Sequential processing failed for number " + total.get(),
                          LOG);
                  if (stopOnException && !continueProcessing) {
                    if (subExchange.getException() != null) {
                      // wrap in exception to explain where it failed
                      subExchange.setException(
                          new CamelExchangeException(
                              "Sequential processing failed for number " + total,
                              subExchange,
                              subExchange.getException()));
                    } else {
                      // we want to stop on exception, and the exception was handled by the error
                      // handler
                      // this is similar to what the pipeline does, so we should do the same to not
                      // surprise end users
                      // so we should set the failed exchange as the result and be done
                      result.set(subExchange);
                    }
                    // and do the done work
                    doDone(original, subExchange, pairs, callback, false, true);
                    return;
                  }

                  try {
                    doAggregate(getAggregationStrategy(subExchange), result, subExchange);
                  } catch (Throwable e) {
                    // wrap in exception to explain where it failed
                    subExchange.setException(
                        new CamelExchangeException(
                            "Sequential processing failed for number " + total, subExchange, e));
                    // and do the done work
                    doDone(original, subExchange, pairs, callback, false, true);
                    return;
                  }

                  total.incrementAndGet();

                  // maybe there are more processors to multicast
                  while (it.hasNext()) {

                    // prepare and run the next
                    ProcessorExchangePair pair = it.next();
                    subExchange = pair.getExchange();
                    updateNewExchange(subExchange, total.get(), pairs, it);
                    boolean sync =
                        doProcessSequential(original, result, pairs, it, pair, callback, total);

                    if (!sync) {
                      LOG.trace(
                          "Processing exchangeId: {} is continued being processed asynchronously",
                          original.getExchangeId());
                      return;
                    }

                    // Decide whether to continue with the multicast or not; similar logic to the
                    // Pipeline
                    // remember to test for stop on exception and aggregate before copying back
                    // results
                    continueProcessing =
                        PipelineHelper.continueProcessing(
                            subExchange,
                            "Sequential processing failed for number " + total.get(),
                            LOG);
                    if (stopOnException && !continueProcessing) {
                      if (subExchange.getException() != null) {
                        // wrap in exception to explain where it failed
                        subExchange.setException(
                            new CamelExchangeException(
                                "Sequential processing failed for number " + total,
                                subExchange,
                                subExchange.getException()));
                      } else {
                        // we want to stop on exception, and the exception was handled by the error
                        // handler
                        // this is similar to what the pipeline does, so we should do the same to
                        // not surprise end users
                        // so we should set the failed exchange as the result and be done
                        result.set(subExchange);
                      }
                      // and do the done work
                      doDone(original, subExchange, pairs, callback, false, true);
                      return;
                    }

                    // must catch any exceptions from aggregation
                    try {
                      doAggregate(getAggregationStrategy(subExchange), result, subExchange);
                    } catch (Throwable e) {
                      // wrap in exception to explain where it failed
                      subExchange.setException(
                          new CamelExchangeException(
                              "Sequential processing failed for number " + total, subExchange, e));
                      // and do the done work
                      doDone(original, subExchange, pairs, callback, false, true);
                      return;
                    }

                    total.incrementAndGet();
                  }

                  // do the done work
                  subExchange = result.get() != null ? result.get() : null;
                  doDone(original, subExchange, pairs, callback, false, true);
                }
              });
    } finally {
      // pop the block so by next round we have the same staring point and thus the tracing looks
      // accurate
      if (traced != null) {
        traced.popBlock();
      }
    }

    return sync;
  }
    @Override
    public void onAfterRoute(Route route, Exchange exchange) {
      // we use the onAfterRoute callback, to ensure the data has been marshalled before
      // the consumer writes the response back

      // only trigger when it was the 1st route that was done
      if (!routeId.equals(route.getId())) {
        return;
      }

      // only marshal if there was no exception
      if (exchange.getException() != null) {
        return;
      }

      if (skipBindingOnErrorCode) {
        Integer code =
            exchange.hasOut()
                ? exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class)
                : exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
        // if there is a custom http error code then skip binding
        if (code != null && code >= 300) {
          return;
        }
      }

      boolean isXml = false;
      boolean isJson = false;

      // accept takes precedence
      if (accept != null) {
        isXml = accept.toLowerCase(Locale.ENGLISH).contains("xml");
        isJson = accept.toLowerCase(Locale.ENGLISH).contains("json");
      }
      // fallback to content type if still undecided
      if (!isXml && !isJson) {
        String contentType = ExchangeHelper.getContentType(exchange);
        if (contentType != null) {
          isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml");
          isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json");
        }
      }
      // if content type could not tell us if it was json or xml, then fallback to if the binding
      // was configured with
      // that information in the consumes
      if (!isXml && !isJson) {
        isXml = produces != null && produces.toLowerCase(Locale.ENGLISH).contains("xml");
        isJson = produces != null && produces.toLowerCase(Locale.ENGLISH).contains("json");
      }

      // only allow xml/json if the binding mode allows that (when off we still want to know if its
      // xml or json)
      if (bindingMode != null) {
        isXml &=
            bindingMode.equals("off") || bindingMode.equals("auto") || bindingMode.contains("xml");
        isJson &=
            bindingMode.equals("off") || bindingMode.equals("auto") || bindingMode.contains("json");

        // if we do not yet know if its xml or json, then use the binding mode to know the mode
        if (!isJson && !isXml) {
          isXml = bindingMode.equals("auto") || bindingMode.contains("xml");
          isJson = bindingMode.equals("auto") || bindingMode.contains("json");
        }
      }

      // in case we have not yet been able to determine if xml or json, then use the same as in the
      // unmarshaller
      if (isXml && isJson) {
        isXml = wasXml;
        isJson = !wasXml;
      }

      // need to prepare exchange first
      ExchangeHelper.prepareOutToIn(exchange);

      // ensure there is a content type header (even if binding is off)
      ensureHeaderContentType(produces, isXml, isJson, exchange);

      if (bindingMode == null || "off".equals(bindingMode)) {
        // binding is off, so no message body binding
        return;
      }

      // is there any marshaller at all
      if (jsonMarshal == null && xmlMarshal == null) {
        return;
      }

      // is the body empty
      if ((exchange.hasOut() && exchange.getOut().getBody() == null)
          || (!exchange.hasOut() && exchange.getIn().getBody() == null)) {
        return;
      }

      try {
        // favor json over xml
        if (isJson && jsonMarshal != null) {
          jsonMarshal.process(exchange);
        } else if (isXml && xmlMarshal != null) {
          xmlMarshal.process(exchange);
        } else {
          // we could not bind
          if (bindingMode.equals("auto")) {
            // okay for auto we do not mind if we could not bind
          } else {
            if (bindingMode.contains("xml")) {
              exchange.setException(
                  new BindingException(
                      "Cannot bind to xml as message body is not xml compatible", exchange));
            } else {
              exchange.setException(
                  new BindingException(
                      "Cannot bind to json as message body is not json compatible", exchange));
            }
          }
        }
      } catch (Throwable e) {
        exchange.setException(e);
      }
    }
  @Override
  public boolean process(Exchange exchange, final AsyncCallback callback) {
    if (enableCORS) {
      exchange.addOnCompletion(new RestBindingCORSOnCompletion(corsHeaders));
    }

    boolean isXml = false;
    boolean isJson = false;

    String contentType = ExchangeHelper.getContentType(exchange);
    if (contentType != null) {
      isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml");
      isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json");
    }
    // if content type could not tell us if it was json or xml, then fallback to if the binding was
    // configured with
    // that information in the consumes
    if (!isXml && !isJson) {
      isXml = consumes != null && consumes.toLowerCase(Locale.ENGLISH).contains("xml");
      isJson = consumes != null && consumes.toLowerCase(Locale.ENGLISH).contains("json");
    }

    // only allow xml/json if the binding mode allows that
    isXml &= bindingMode.equals("auto") || bindingMode.contains("xml");
    isJson &= bindingMode.equals("auto") || bindingMode.contains("json");

    // if we do not yet know if its xml or json, then use the binding mode to know the mode
    if (!isJson && !isXml) {
      isXml = bindingMode.equals("auto") || bindingMode.contains("xml");
      isJson = bindingMode.equals("auto") || bindingMode.contains("json");
    }

    String accept = exchange.getIn().getHeader("Accept", String.class);

    String body = null;
    if (exchange.getIn().getBody() != null) {

      // okay we have a binding mode, so need to check for empty body as that can cause the
      // marshaller to fail
      // as they assume a non-empty body
      if (isXml || isJson) {
        // we have binding enabled, so we need to know if there body is empty or not\
        // so force reading the body as a String which we can work with
        body = MessageHelper.extractBodyAsString(exchange.getIn());
        if (body != null) {
          exchange.getIn().setBody(body);

          if (isXml && isJson) {
            // we have still not determined between xml or json, so check the body if its xml based
            // or not
            isXml = body.startsWith("<");
            isJson = !isXml;
          }
        }
      }
    }

    // favor json over xml
    if (isJson && jsonUnmarshal != null) {
      // add reverse operation
      exchange.addOnCompletion(
          new RestBindingMarshalOnCompletion(
              exchange.getFromRouteId(), jsonMarshal, xmlMarshal, false, accept));
      if (ObjectHelper.isNotEmpty(body)) {
        return jsonUnmarshal.process(exchange, callback);
      } else {
        callback.done(true);
        return true;
      }
    } else if (isXml && xmlUnmarshal != null) {
      // add reverse operation
      exchange.addOnCompletion(
          new RestBindingMarshalOnCompletion(
              exchange.getFromRouteId(), jsonMarshal, xmlMarshal, true, accept));
      if (ObjectHelper.isNotEmpty(body)) {
        return xmlUnmarshal.process(exchange, callback);
      } else {
        callback.done(true);
        return true;
      }
    }

    // we could not bind
    if (bindingMode == null || "off".equals(bindingMode) || bindingMode.equals("auto")) {
      // okay for auto we do not mind if we could not bind
      exchange.addOnCompletion(
          new RestBindingMarshalOnCompletion(
              exchange.getFromRouteId(), jsonMarshal, xmlMarshal, false, accept));
      callback.done(true);
      return true;
    } else {
      if (bindingMode.contains("xml")) {
        exchange.setException(
            new BindingException(
                "Cannot bind to xml as message body is not xml compatible", exchange));
      } else {
        exchange.setException(
            new BindingException(
                "Cannot bind to json as message body is not json compatible", exchange));
      }
      callback.done(true);
      return true;
    }
  }
 @Override
 public String toString() {
   return "AsyncBridge[" + interceptor.toString() + "]";
 }
 /**
  * Process invoked by the interceptor
  *
  * @param exchange the message exchange
  * @throws Exception
  */
 public void process(Exchange exchange) throws Exception {
   // invoke when interceptor wants to invoke
   boolean done = interceptor.process(exchange, callback.get());
   interceptorDone.set(done);
 }