/**
   * Suspend the connection by blocking the current {@link Thread}
   *
   * @param action The {@link Action}
   * @param req the {@link AtmosphereRequest}
   * @param res the {@link AtmosphereResponse}
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  protected void suspend(Action action, AtmosphereRequest req, AtmosphereResponse res)
      throws IOException, ServletException {

    CountDownLatch latch = new CountDownLatch(1);
    req.setAttribute(LATCH, latch);

    boolean ok = true;
    try {
      if (action.timeout() != -1) {
        ok = latch.await(action.timeout(), TimeUnit.MILLISECONDS);
      } else {
        latch.await();
      }
    } catch (InterruptedException ex) {
      logger.trace("", ex);
    } finally {
      if (!ok) {
        timedout(req, res);
      } else {
        if (req.resource() != null) {
          AtmosphereResourceImpl.class.cast(req.resource()).cancel();
        }
      }
    }
  }
  /**
   * Invoked by the Tomcat AIO when a Comet request gets detected.
   *
   * @param req the {@link AtmosphereRequest}
   * @param res the {@link AtmosphereResponse}
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  public Action service(AtmosphereRequest req, AtmosphereResponse res)
      throws IOException, ServletException {

    HttpEvent event = (HttpEvent) req.getAttribute(HTTP_EVENT);

    // Comet is not enabled.
    if (event == null) {
      logger.error("HttpEvent is null, JBoss APR Not Properly installed");
      throw unableToDetectComet;
    }

    if (logger.isTraceEnabled()) {
      logger.trace("Event Type {} for {}", event.getType(), req.getRequestURL().toString());
    }

    Action action = null;
    // For now, we are just interested in HttpEvent.REA
    AtmosphereResource r = req.resource();
    if (event.getType() == HttpEvent.EventType.BEGIN) {
      action = suspended(req, res);
      if (action.type() == Action.TYPE.SUSPEND) {
        // Do nothing except setting the times out
        try {
          if (action.timeout() != -1) {
            event.setTimeout((int) action.timeout());
          } else {
            event.setTimeout(Integer.MAX_VALUE);
          }
          req.setAttribute(SUSPENDED, true);
        } catch (UnsupportedOperationException ex) {
          // Swallow s Tomcat APR isn't supporting time out
          // TODO: Must implement the same functionality using a Scheduler
        }
      } else if (action.type() == Action.TYPE.RESUME) {
        close(event);
      } else {
        close(event);
      }
    } else if (event.getType() == HttpEvent.EventType.READ) {
      // Not implemented
      logger.debug("Receiving bytes, unable to process them.");
    } else if (event.getType() == HttpEvent.EventType.EOF
        || event.getType() == HttpEvent.EventType.ERROR
        || event.getType() == HttpEvent.EventType.END) {

      if (r != null && r.isResumed()) {
        AtmosphereResourceImpl.class.cast(req.resource()).cancel();
      } else if (req.getAttribute(SUSPENDED) != null && closeConnectionOnInputStream) {
        req.setAttribute(SUSPENDED, null);
        action = cancelled(req, res);
      } else {
        close(event);
      }
    } else if (event.getType() == HttpEvent.EventType.TIMEOUT) {
      action = timedout(req, res);
      close(event);
    }
    return action;
  }
  /**
   * Invoked by the Tomcat AIO when a Comet request gets detected.
   *
   * @param req the {@link AtmosphereRequest}
   * @param res the {@link AtmosphereResponse}
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  @Override
  public Action service(AtmosphereRequest req, AtmosphereResponse res)
      throws IOException, ServletException {

    CometEvent event = (CometEvent) req.getAttribute(COMET_EVENT);

    // Comet is not enabled.
    if (event == null) {
      throw new IllegalStateException(unableToDetectComet());
    }

    Action action = null;
    // For now, we are just interested in CometEvent.READ
    if (event.getEventType() == EventType.BEGIN) {
      action = suspended(req, res);
      if (action.type() == Action.TYPE.SUSPEND) {
        // Do nothing except setting the times out
        try {
          if (action.timeout() != -1) {
            event.setTimeout((int) action.timeout());
          } else {
            event.setTimeout(Integer.MAX_VALUE);
          }
        } catch (UnsupportedOperationException ex) {
          // TODO: Must implement the same functionality using a Scheduler
          logger.trace(
              "Warning: CometEvent.setTimeout not supported on this Tomcat instance. "
                  + " [The Tomcat native connector does not support timeouts on asynchronous I/O.]");
        }
        req.setAttribute(SUSPENDED, true);
      } else if (action.type() == Action.TYPE.RESUME) {
        bz51881(event);
      } else {
        bz51881(event);
      }
    } else if (event.getEventType() == EventType.READ) {
      // Not implemented
    } else if (event.getEventSubType() == CometEvent.EventSubType.CLIENT_DISCONNECT) {

      if (req.getAttribute(SUSPENDED) != null) {
        req.setAttribute(SUSPENDED, null);
        action = cancelled(req, res);
      }

      bz51881(event);
    } else if (event.getEventSubType() == CometEvent.EventSubType.TIMEOUT) {
      action = timedout(req, res);
      bz51881(event);
    } else if (event.getEventType() == EventType.ERROR) {
      bz51881(event);
    } else if (event.getEventType() == EventType.END) {

      if (req.resource().isResumed()) {
        AtmosphereResourceImpl.class.cast(req.resource()).cancel();
      } else if (req.getAttribute(SUSPENDED) != null && closeConnectionOnInputStream) {
        req.setAttribute(SUSPENDED, null);
        action = cancelled(req, res);
      } else {
        bz51881(event);
      }
    }
    return action;
  }
  /** {@inheritDoc} */
  public Action service(AtmosphereRequest req, AtmosphereResponse res)
      throws IOException, ServletException {

    Action action = null;
    try {
      action = suspended(req, res);
      if (action.type() == Action.TYPE.SUSPEND) {
        suspend(action, req, res);
      } else if (action.type() == Action.TYPE.RESUME) {
        CountDownLatch latch = (CountDownLatch) req.getAttribute(LATCH);

        if (latch == null || req.getAttribute(AtmosphereResourceImpl.PRE_SUSPEND) == null) {
          logger.debug("response wasn't suspended: {}", res);
          return action;
        }

        latch.countDown();

        Action nextAction = resumed(req, res);
        if (nextAction.type() == Action.TYPE.SUSPEND) {
          suspend(action, req, res);
        }
      }
    } finally {
      Object event = req.getAttribute(TomcatCometSupport.COMET_EVENT);
      if (event != null) {

        try {
          Class.forName(org.apache.catalina.CometEvent.class.getName());

          if (org.apache.catalina.CometEvent.class.isAssignableFrom(event.getClass())) {
            org.apache.catalina.CometEvent.class.cast(event).close();
          }
        } catch (Throwable e) {
          logger.trace("", e);
        }

        try {
          Class.forName(org.apache.catalina.comet.CometEvent.class.getName());

          if (org.apache.catalina.comet.CometEvent.class.isAssignableFrom(event.getClass())) {
            org.apache.catalina.comet.CometEvent.class.cast(event).close();
          }
        } catch (Throwable e) {
          logger.trace("", e);
        }
      }

      try {
        event = req.getAttribute(JBossWebCometSupport.HTTP_EVENT);

        Class.forName(org.jboss.servlet.http.HttpEvent.class.getName());
        if (event != null) {
          org.jboss.servlet.http.HttpEvent.class.cast(event).close();
        }
      } catch (Throwable e) {
        logger.trace("", e);
      }
    }
    return action;
  }