/** {@inheritDoc} */
  @Override
  public Broadcaster lookup(Class<? extends Broadcaster> c, Object id, boolean createIfNull) {
    Broadcaster b = store.get(id);
    if (b != null && !c.isAssignableFrom(b.getClass())) {
      String msg =
          "Invalid lookup class " + c.getName() + ". Cached class is: " + b.getClass().getName();
      logger.debug(msg);
      throw new IllegalStateException(msg);
    }

    if ((b == null && createIfNull) || (b != null && b.isDestroyed())) {
      if (b != null) {
        logger.debug("Removing destroyed Broadcaster {}", b.getID());
        store.remove(b.getID(), b);
      }

      Broadcaster nb = store.get(id);
      if (nb == null) {
        nb = createBroadcaster(c, id);
        store.put(id, nb);
      }

      if (nb == null) {
        logger.debug("Added Broadcaster {} . Factory size: {}", id, store.size());
      }

      b = nb;
    }

    return b;
  }
  protected Broadcaster getBroadcaster(boolean autoCreate) {
    if (broadcaster == null) {
      throw new IllegalStateException("No Broadcaster associated with this AtmosphereResource.");
    }

    String s = config.getInitParameter(ApplicationConfig.RECOVER_DEAD_BROADCASTER);
    if (s != null) {
      autoCreate = Boolean.parseBoolean(s);
    }

    if (autoCreate && broadcaster.isDestroyed() && config.getBroadcasterFactory() != null) {
      logger.debug(
          "Broadcaster {} has been destroyed and cannot be re-used. Recreating a new one with the same name. You can turn off this"
              + " mechanism by adding, in web.xml, {} set to false",
          broadcaster.getID(),
          ApplicationConfig.RECOVER_DEAD_BROADCASTER);

      Broadcaster.SCOPE scope = broadcaster.getScope();
      synchronized (this) {
        String id =
            scope != Broadcaster.SCOPE.REQUEST
                ? broadcaster.getID()
                : broadcaster.getID() + ".recovered" + UUID.randomUUID();

        // Another Thread may have added the Broadcaster.
        broadcaster = config.getBroadcasterFactory().lookup(id, true);
        broadcaster.setScope(scope);
        broadcaster.addAtmosphereResource(this);
      }
    }
    return broadcaster;
  }
  /** {@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;
  }
  @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;
  }
  /**
   * Invoke the {@link AtmosphereHandler#onRequest} method.
   *
   * @param req the {@link AtmosphereRequest}
   * @param res the {@link AtmosphereResponse}
   * @return action the Action operation.
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  Action action(AtmosphereRequest req, AtmosphereResponse res)
      throws IOException, ServletException {

    boolean webSocketEnabled = false;
    if (req.getHeaders("Connection") != null && req.getHeaders("Connection").hasMoreElements()) {
      String[] e = req.getHeaders("Connection").nextElement().toString().split(",");
      for (String upgrade : e) {
        if (upgrade.equalsIgnoreCase("Upgrade")) {
          webSocketEnabled = true;
          break;
        }
      }
    }

    if (webSocketEnabled && !supportWebSocket()) {
      res.setStatus(501);
      res.addHeader(X_ATMOSPHERE_ERROR, "Websocket protocol not supported");
      res.flushBuffer();
      return new Action();
    }

    if (config.handlers().isEmpty()) {
      logger.error(
          "No AtmosphereHandler found. Make sure you define it inside META-INF/atmosphere.xml");
      throw new AtmosphereMappingException(
          "No AtmosphereHandler found. Make sure you define it insides META-INF/atmosphere.xml");
    }

    if (supportSession()) {
      // Create the session needed to support the Resume
      // operation from disparate requests.
      HttpSession session = req.getSession(true);
      // Do not allow times out.
      if (session.getMaxInactiveInterval() == DEFAULT_SESSION_TIMEOUT) {
        session.setMaxInactiveInterval(-1);
      }
    }

    req.setAttribute(FrameworkConfig.SUPPORT_SESSION, supportSession());

    AtmosphereHandlerWrapper handlerWrapper = map(req);
    // Check Broadcaster state. If destroyed, replace it.
    Broadcaster b = handlerWrapper.broadcaster;
    if (b.isDestroyed()) {
      synchronized (handlerWrapper) {
        config.getBroadcasterFactory().remove(b, b.getID());
        handlerWrapper.broadcaster = config.getBroadcasterFactory().get(b.getID());
      }
    }
    AtmosphereResourceImpl resource =
        new AtmosphereResourceImpl(
            config, handlerWrapper.broadcaster, req, res, this, handlerWrapper.atmosphereHandler);

    req.setAttribute(FrameworkConfig.ATMOSPHERE_RESOURCE, resource);
    req.setAttribute(FrameworkConfig.ATMOSPHERE_HANDLER, handlerWrapper.atmosphereHandler);

    try {
      handlerWrapper.atmosphereHandler.onRequest(resource);
    } catch (IOException t) {
      resource.onThrowable(t);
      throw t;
    }

    if (trackActiveRequest
        && resource.getAtmosphereResourceEvent().isSuspended()
        && req.getAttribute(FrameworkConfig.CANCEL_SUSPEND_OPERATION) == null) {
      req.setAttribute(MAX_INACTIVE, System.currentTimeMillis());
      aliveRequests.put(req, resource);
    }
    return resource.action();
  }