/**
   * All proprietary Comet based {@link Servlet} must invoke the cancelled method when the
   * underlying WebServer detect that the client closed the connection.
   *
   * @param req the {@link AtmosphereRequest}
   * @param res the {@link AtmosphereResponse}
   * @return action the Action operation.
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  public Action cancelled(AtmosphereRequest req, AtmosphereResponse res)
      throws IOException, ServletException {

    synchronized (req) {
      AtmosphereResourceImpl r = null;
      try {
        if (trackActiveRequest) {
          long l = (Long) req.getAttribute(MAX_INACTIVE);
          if (l == -1) {
            // The closedDetector closed the connection.
            return timedoutAction;
          }
          req.setAttribute(MAX_INACTIVE, (long) -1);
        }

        logger.debug("Cancelling the connection for request {}", req);

        r = (AtmosphereResourceImpl) req.getAttribute(FrameworkConfig.ATMOSPHERE_RESOURCE);
        if (r != null) {
          r.getAtmosphereResourceEvent().setCancelled(true);
          invokeAtmosphereHandler(r);

          try {
            r.getResponse().sendError(503);
            r.getResponse().getOutputStream().close();
          } catch (Throwable t) {
            try {
              r.getResponse().getWriter().close();
            } catch (Throwable t2) {
            }
          }
        }
      } catch (Throwable ex) {
        // Something wrong happenned, ignore the exception
        logger.debug("failed to cancel resource: " + r, ex);
      } finally {
        try {
          if (r != null) {
            r.notifyListeners();
            r.setIsInScope(false);
            r.cancel();
          }
        } catch (Throwable t) {
          logger.trace("cancel", t);
        } finally {
          if (r != null) {
            destroyResource(r);
          }
        }
      }
    }

    return cancelledAction;
  }
 @Override
 public void close() throws IOException {
   cancel();
   event.setCloseByApplication(true);
   notifyListeners();
 }
 @Override
 public AtmosphereResource notifyListeners() {
   notifyListeners(event);
   return this;
 }
  @Override
  public AtmosphereResource suspend(long timeout) {

    if (event.isSuspended() || disableSuspend) return this;

    if (config.isSupportSession()
        && req.getSession(false) != null
        && req.getSession().getMaxInactiveInterval() != -1
        && req.getSession().getMaxInactiveInterval() * 1000 < timeout) {
      throw new IllegalStateException(
          "Cannot suspend a "
              + "response longer than the session timeout. Increase the value of session-timeout in web.xml");
    }

    if (transport().equals(TRANSPORT.JSONP) || transport().equals(TRANSPORT.LONG_POLLING)) {
      resumeOnBroadcast.set(true);
    }

    onPreSuspend(event);

    // Recheck based on preSuspend
    if (event.isSuspended() || disableSuspend) return this;

    if (!event.isResumedOnTimeout()) {

      Enumeration<String> connection = req.getHeaders("Connection");
      if (connection == null) {
        connection = req.getHeaders("connection");
      }

      if (connection != null && connection.hasMoreElements()) {
        String[] e = connection.nextElement().toString().split(",");
        for (String upgrade : e) {
          if (upgrade.trim().equalsIgnoreCase(WEBSOCKET_UPGRADE)) {
            if (!asyncSupport.supportWebSocket()) {
              response.addHeader(X_ATMOSPHERE_ERROR, "Websocket protocol not supported");
            } else {
              req.setAttribute(FrameworkConfig.TRANSPORT_IN_USE, HeaderConfig.WEBSOCKET_TRANSPORT);
            }
          }
        }
      }

      if (req.getHeader(X_ATMOSPHERE_TRANSPORT) == null) {
        req.setAttribute(FrameworkConfig.TRANSPORT_IN_USE, HeaderConfig.LONG_POLLING_TRANSPORT);
      }

      req.setAttribute(PRE_SUSPEND, "true");
      action.type(Action.TYPE.SUSPEND);
      action.timeout(timeout);

      // TODO: We can possibly optimize that call by avoiding creating a Broadcaster if we are sure
      // the Broadcaster
      // is unique.
      boolean isJersey = req.getAttribute(FrameworkConfig.CONTAINER_RESPONSE) != null;

      boolean skipCreation = false;
      if (req.getAttribute(SKIP_BROADCASTER_CREATION) != null) {
        skipCreation = true;
      }

      // Null means SCOPE=REQUEST set by a Meteor
      if (!skipCreation
          && (broadcaster == null || broadcaster.getScope() == Broadcaster.SCOPE.REQUEST)
          && !isJersey) {
        String id = broadcaster != null ? broadcaster.getID() : getClass().getName();
        Class<? extends Broadcaster> clazz =
            broadcaster != null ? broadcaster.getClass() : DefaultBroadcaster.class;

        broadcaster = config.getBroadcasterFactory().lookup(clazz, id, false);
        if (broadcaster == null || broadcaster.getAtmosphereResources().size() > 0) {
          broadcaster =
              config.getBroadcasterFactory().lookup(clazz, id + "/" + UUID.randomUUID(), true);
        }
      }

      broadcaster.addAtmosphereResource(this);
      if (req.getAttribute(DefaultBroadcaster.CACHED) != null
          && transport() != null
          && (transport().equals(TRANSPORT.LONG_POLLING) || transport().equals(TRANSPORT.JSONP))) {
        action.type(Action.TYPE.CONTINUE);
        // Do nothing because we have found cached message which was written already, and the
        // handler resumed.
        logger.debug("Cached message found, not suspending {}", uuid());
        return this;
      }
      req.removeAttribute(PRE_SUSPEND);
      notifyListeners();
    }
    return this;
  }
  @Override
  public AtmosphereResource resume() {

    if (!isSuspended()) {
      logger.warn("AtmosphereResource {} not suspend, cannot resume it.", uuid());
      return this;
    }

    try {
      if (!isResumed.getAndSet(true) && isInScope.get()) {
        logger.trace("AtmosphereResource {} is resuming", uuid());

        action.type(Action.TYPE.RESUME);

        // We need it as Jetty doesn't support timeout
        Broadcaster b = getBroadcaster(false);
        if (!b.isDestroyed() && b instanceof DefaultBroadcaster) {
          ((DefaultBroadcaster) b).broadcastOnResume(this);
        }

        notifyListeners();

        try {
          if (!b.isDestroyed()) {
            broadcaster.removeAtmosphereResource(this);
          }
        } catch (IllegalStateException ex) {
          logger.warn("Unable to resume", this);
          logger.debug(ex.getMessage(), ex);
        }

        if (b.getScope() == Broadcaster.SCOPE.REQUEST) {
          logger.debug("Broadcaster's scope is set to request, destroying it {}", b.getID());
          b.destroy();
        }

        // Resuming here means we need to pull away from all other Broadcaster, if they exists.
        if (config.getBroadcasterFactory() != null) {
          config.getBroadcasterFactory().removeAllAtmosphereResource(this);
        }

        try {
          req.setAttribute(ApplicationConfig.RESUMED_ON_TIMEOUT, Boolean.FALSE);
        } catch (Exception ex) {
          logger.debug("Resume exception: Cannot resume an already resumed/cancelled request", ex);
        } finally {
          try {
            Meteor m = (Meteor) req.getAttribute(METEOR);
            if (m != null) {
              m.destroy();
            }
          } catch (Exception ex) {
            logger.debug(
                "Meteor resume exception: Cannot resume an already resumed/cancelled request", ex);
          }
        }

        if (req.getAttribute(PRE_SUSPEND) == null) {
          asyncSupport.action(this);
        }
      } else {
        logger.trace("Already resumed {}", this);
        return this;
      }
    } catch (Throwable t) {
      logger.trace("Wasn't able to resume a connection {}", this, t);
    }
    listeners.clear();
    return this;
  }
  /** {@inheritDoc} */
  @Override
  public synchronized AtmosphereResource resume() {
    // We need to synchronize the method because the resume may occurs at the same time a message is
    // published
    // and we will miss that message. The DefaultBroadcaster synchronize on that method before
    // writing a message.
    try {
      if (!isResumed && isInScope) {
        action.type(Action.TYPE.RESUME);
        isResumed = true;

        // We need it as Jetty doesn't support timeout
        Broadcaster b = getBroadcaster(false);
        if (!b.isDestroyed() && b instanceof DefaultBroadcaster) {
          ((DefaultBroadcaster) b).broadcastOnResume(this);
        }

        notifyListeners();

        try {
          if (!b.isDestroyed()) {
            broadcaster.removeAtmosphereResource(this);
          }
        } catch (IllegalStateException ex) {
          logger.warn("Unable to resume", this);
          logger.debug(ex.getMessage(), ex);
        }

        if (b.getScope() == Broadcaster.SCOPE.REQUEST) {
          logger.debug("Broadcaster's scope is set to request, destroying it {}", b.getID());
          b.destroy();
        }

        // Resuming here means we need to pull away from all other Broadcaster, if they exists.
        if (config.getBroadcasterFactory() != null) {
          config.getBroadcasterFactory().removeAllAtmosphereResource(this);
        }

        try {
          req.setAttribute(ApplicationConfig.RESUMED_ON_TIMEOUT, Boolean.FALSE);
        } catch (Exception ex) {
          logger.debug("Resume exception: Cannot resume an already resumed/cancelled request", ex);
        } finally {
          try {
            Meteor m = (Meteor) req.getAttribute(METEOR);
            if (m != null) {
              m.destroy();
            }
          } catch (Exception ex) {
            logger.debug(
                "Meteor resume exception: Cannot resume an already resumed/cancelled request", ex);
          }
        }

        if (req.getAttribute(PRE_SUSPEND) == null) {
          asyncSupport.action(this);
        }
      } else {
        logger.debug("Cannot resume an already resumed/cancelled request {}", this);
      }
    } catch (Throwable t) {
      logger.trace("Wasn't able to resume a connection {}", this, t);
    }
    notifyListeners(new AtmosphereResourceEventImpl(this, true, false));
    listeners.clear();
    return this;
  }
  /**
   * All proprietary Comet based {@link Servlet} must invoke the timedout method when the underlying
   * WebServer time out the {@link AtmosphereResponse}. The returned value, of type {@link Action},
   * tells the proprietary Comet {@link Servlet} to resume (again), suspended or do nothing with the
   * current {@link AtmosphereResponse}.
   *
   * @param request the {@link AtmosphereRequest}
   * @param response the {@link AtmosphereResponse}
   * @return action the Action operation.
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  public Action timedout(AtmosphereRequest request, AtmosphereResponse response)
      throws IOException, ServletException {

    AtmosphereResourceImpl r = null;
    try {
      if (trackActiveRequest) {
        long l = (Long) request.getAttribute(MAX_INACTIVE);
        if (l == -1) {
          // The closedDetector closed the connection.
          return timedoutAction;
        }
        request.setAttribute(MAX_INACTIVE, (long) -1);
      }

      logger.debug("Timing out the connection for request {}", request);

      // Something went wrong.
      if (request == null || response == null) {
        logger.warn("Invalid Request/Response: {}/{}", request, response);
        return timedoutAction;
      }

      r = (AtmosphereResourceImpl) request.getAttribute(FrameworkConfig.ATMOSPHERE_RESOURCE);

      if (r != null && r.getAtmosphereResourceEvent().isSuspended()) {
        r.getAtmosphereResourceEvent().setIsResumedOnTimeout(true);

        Broadcaster b = r.getBroadcaster();
        if (b instanceof DefaultBroadcaster) {
          ((DefaultBroadcaster) b).broadcastOnResume(r);
        }

        if (request.getAttribute(ApplicationConfig.RESUMED_ON_TIMEOUT) != null) {
          r.getAtmosphereResourceEvent()
              .setIsResumedOnTimeout(
                  (Boolean) request.getAttribute(ApplicationConfig.RESUMED_ON_TIMEOUT));
        }

        invokeAtmosphereHandler(r);
      }
    } catch (Throwable t) {
      logger.error("failed to timeout resource {}", r, t);
    } finally {
      try {
        if (r != null) {
          r.notifyListeners();
          r.setIsInScope(false);
          r.cancel();
        }
      } catch (Throwable t) {
        logger.trace("timedout", t);
      } finally {

        try {
          response.getOutputStream().close();
        } catch (Throwable t) {
          try {
            response.getWriter().close();
          } catch (Throwable t2) {
          }
        }

        if (r != null) {
          destroyResource(r);
        }
      }
    }

    return timedoutAction;
  }