/**
   * Process this request, publishing an event regardless of the outcome.
   *
   * <p>The actual event handling is performed by the abstract {@link #doService} template method.
   */
  protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes =
        buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(
        FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
      doService(request, response);
    } catch (ServletException ex) {
      failureCause = ex;
      throw ex;
    } catch (IOException ex) {
      failureCause = ex;
      throw ex;
    } catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
    } finally {
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
        requestAttributes.requestCompleted();
      }

      if (logger.isDebugEnabled()) {
        if (failureCause != null) {
          this.logger.debug("Could not complete request", failureCause);
        } else {
          if (asyncManager.isConcurrentHandlingStarted()) {
            logger.debug("Leaving response open for concurrent processing");
          } else {
            this.logger.debug("Successfully completed request");
          }
        }
      }

      publishRequestHandledEvent(request, response, startTime, failureCause);
    }
  }
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    TestCallableInterceptor callableInterceptor = new TestCallableInterceptor();
    asyncManager.registerCallableInterceptor("mock-mvc", callableInterceptor);

    TestDeferredResultInterceptor deferredResultInterceptor = new TestDeferredResultInterceptor();
    asyncManager.registerDeferredResultInterceptor("mock-mvc", deferredResultInterceptor);

    super.service(request, response);

    // TODO: add CountDownLatch to DeferredResultInterceptor and wait in request().asyncResult(..)

    Object handler = getMvcResult(request).getHandler();
    if (asyncManager.isConcurrentHandlingStarted() && !deferredResultInterceptor.wasInvoked) {
      if (!callableInterceptor.await()) {
        throw new ServletException(
            "Gave up waiting on Callable from [" + handler.getClass().getName() + "] to complete");
      }
    }
  }
  @Override
  public boolean preHandle(
      HttpServletRequest request, final HttpServletResponse response, final Object handler)
      throws Exception {

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTimeoutHandler(
        new Runnable() {
          @Override
          public void run() {
            try {
              logger.debug("Custom time out handler called");
              response.sendError(
                  503, "Async execution of [" + handler + "] did not complete on time");
            } catch (IOException e) {
              logger.error("Timeout not handled", e);
            }
          }
        });

    logger.debug("Registered custom timeout handler");

    return true;
  }