/** Process the exchange using redelivery error handling. */
  protected boolean processErrorHandler(
      final Exchange exchange, final AsyncCallback callback, final RedeliveryData data) {

    // do a defensive copy of the original Exchange, which is needed for redelivery so we can ensure
    // the
    // original Exchange is being redelivered, and not a mutated Exchange
    data.original = defensiveCopyExchangeIfNeeded(exchange);

    // use looping to have redelivery attempts
    while (true) {

      // can we still run
      if (!isRunAllowed()) {
        if (exchange.getException() == null) {
          exchange.setException(new RejectedExecutionException());
        }
        // we cannot process so invoke callback
        callback.done(data.sync);
        return data.sync;
      }

      // did previous processing cause an exception?
      boolean handle = shouldHandleException(exchange);
      if (handle) {
        handleException(exchange, data);
      }

      // compute if we are exhausted or not
      boolean exhausted = isExhausted(exchange, data);
      if (exhausted) {
        Processor target = null;
        boolean deliver = true;

        // the unit of work may have an optional callback associated we need to leverage
        SubUnitOfWorkCallback uowCallback = exchange.getUnitOfWork().getSubUnitOfWorkCallback();
        if (uowCallback != null) {
          // signal to the callback we are exhausted
          uowCallback.onExhausted(exchange);
          // do not deliver to the failure processor as its been handled by the callback instead
          deliver = false;
        }

        if (deliver) {
          // should deliver to failure processor (either from onException or the dead letter
          // channel)
          target = data.failureProcessor != null ? data.failureProcessor : data.deadLetterProcessor;
        }
        // we should always invoke the deliverToFailureProcessor as it prepares, logs and does a
        // fair
        // bit of work for exhausted exchanges (its only the target processor which may be null if
        // handled by a savepoint)
        boolean sync = deliverToFailureProcessor(target, exchange, data, callback);
        // we are breaking out
        return sync;
      }

      if (data.redeliveryCounter > 0) {
        // calculate delay
        data.redeliveryDelay =
            data.currentRedeliveryPolicy.calculateRedeliveryDelay(
                data.redeliveryDelay, data.redeliveryCounter);

        if (data.redeliveryDelay > 0) {
          // okay there is a delay so create a scheduled task to have it executed in the future

          if (data.currentRedeliveryPolicy.isAsyncDelayedRedelivery() && !exchange.isTransacted()) {
            // let the RedeliverTask be the logic which tries to redeliver the Exchange which we can
            // used a scheduler to
            // have it being executed in the future, or immediately
            // we are continuing asynchronously

            // mark we are routing async from now and that this redelivery task came from a
            // synchronous routing
            data.sync = false;
            data.redeliverFromSync = true;
            AsyncRedeliveryTask task = new AsyncRedeliveryTask(exchange, callback, data);

            // schedule the redelivery task
            if (log.isTraceEnabled()) {
              log.trace(
                  "Scheduling redelivery task to run in {} millis for exchangeId: {}",
                  data.redeliveryDelay,
                  exchange.getExchangeId());
            }
            executorService.schedule(task, data.redeliveryDelay, TimeUnit.MILLISECONDS);

            return false;
          } else {
            // async delayed redelivery was disabled or we are transacted so we must be synchronous
            // as the transaction manager requires to execute in the same thread context
            try {
              data.currentRedeliveryPolicy.sleep(data.redeliveryDelay);
            } catch (InterruptedException e) {
              // we was interrupted so break out
              exchange.setException(e);
              // mark the exchange to stop continue routing when interrupted
              // as we do not want to continue routing (for example a task has been cancelled)
              exchange.setProperty(Exchange.ROUTE_STOP, Boolean.TRUE);
              callback.done(data.sync);
              return data.sync;
            }
          }
        }

        // prepare for redelivery
        prepareExchangeForRedelivery(exchange, data);

        // letting onRedeliver be executed
        deliverToOnRedeliveryProcessor(exchange, data);

        // emmit event we are doing redelivery
        EventHelper.notifyExchangeRedelivery(
            exchange.getContext(), exchange, data.redeliveryCounter);
      }

      // process the exchange (also redelivery)
      boolean sync =
          AsyncProcessorHelper.process(
              outputAsync,
              exchange,
              new AsyncCallback() {
                public void done(boolean sync) {
                  // this callback should only handle the async case
                  if (sync) {
                    return;
                  }

                  // mark we are in async mode now
                  data.sync = false;

                  // if we are done then notify callback and exit
                  if (isDone(exchange)) {
                    callback.done(sync);
                    return;
                  }

                  // error occurred so loop back around which we do by invoking the
                  // processAsyncErrorHandler
                  // method which takes care of this in a asynchronous manner
                  processAsyncErrorHandler(exchange, callback, data);
                }
              });

      if (!sync) {
        // the remainder of the Exchange is being processed asynchronously so we should return
        return false;
      }
      // we continue to route synchronously

      // if we are done then notify callback and exit
      boolean done = isDone(exchange);
      if (done) {
        callback.done(true);
        return true;
      }

      // error occurred so loop back around.....
    }
  }
    public Boolean call() throws Exception {
      // prepare for redelivery
      prepareExchangeForRedelivery(exchange, data);

      // letting onRedeliver be executed at first
      deliverToOnRedeliveryProcessor(exchange, data);

      if (log.isTraceEnabled()) {
        log.trace(
            "Redelivering exchangeId: {} -> {} for Exchange: {}",
            new Object[] {exchange.getExchangeId(), outputAsync, exchange});
      }

      // emmit event we are doing redelivery
      EventHelper.notifyExchangeRedelivery(exchange.getContext(), exchange, data.redeliveryCounter);

      // process the exchange (also redelivery)
      boolean sync;
      if (data.redeliverFromSync) {
        // this redelivery task was scheduled from synchronous, which we forced to be asynchronous
        // from
        // this error handler, which means we have to invoke the callback with false, to have the
        // callback
        // be notified when we are done
        sync =
            AsyncProcessorHelper.process(
                outputAsync,
                exchange,
                new AsyncCallback() {
                  public void done(boolean doneSync) {
                    log.trace(
                        "Redelivering exchangeId: {} done sync: {}",
                        exchange.getExchangeId(),
                        doneSync);

                    // mark we are in sync mode now
                    data.sync = false;

                    // only process if the exchange hasn't failed
                    // and it has not been handled by the error processor
                    if (isDone(exchange)) {
                      callback.done(false);
                      return;
                    }

                    // error occurred so loop back around which we do by invoking the
                    // processAsyncErrorHandler
                    processAsyncErrorHandler(exchange, callback, data);
                  }
                });
      } else {
        // this redelivery task was scheduled from asynchronous, which means we should only
        // handle when the asynchronous task was done
        sync =
            AsyncProcessorHelper.process(
                outputAsync,
                exchange,
                new AsyncCallback() {
                  public void done(boolean doneSync) {
                    log.trace(
                        "Redelivering exchangeId: {} done sync: {}",
                        exchange.getExchangeId(),
                        doneSync);

                    // this callback should only handle the async case
                    if (doneSync) {
                      return;
                    }

                    // mark we are in async mode now
                    data.sync = false;

                    // only process if the exchange hasn't failed
                    // and it has not been handled by the error processor
                    if (isDone(exchange)) {
                      callback.done(doneSync);
                      return;
                    }
                    // error occurred so loop back around which we do by invoking the
                    // processAsyncErrorHandler
                    processAsyncErrorHandler(exchange, callback, data);
                  }
                });
      }

      return sync;
    }