protected Object perRequestFilter(AtmosphereResource<?, ?> r, Entry msg) {
    Object finalMsg = msg.message;

    if (AtmosphereResourceImpl.class.isAssignableFrom(r.getClass())) {
      if (AtmosphereResourceImpl.class.cast(r).isInScope()) {
        if (r.getRequest() instanceof HttpServletRequest && bc.hasPerRequestFilters()) {
          Object message = msg.originalMessage;
          BroadcastAction a =
              bc.filter(
                  (HttpServletRequest) r.getRequest(),
                  (HttpServletResponse) r.getResponse(),
                  message);
          if (a.action() == BroadcastAction.ACTION.ABORT || a.message() != msg.originalMessage) {
            finalMsg = a.message();
          }
        }
        trackBroadcastMessage(r, finalMsg);
      } else {
        // The resource is no longer valid.
        removeAtmosphereResource(r);
        BroadcasterFactory.getDefault().removeAllAtmosphereResource(r);
      }
    }
    return finalMsg;
  }
  /** {@inheritDoc} */
  public synchronized void destroy() {

    String s = config.getInitParameter(ApplicationConfig.SHARED);
    if (s != null && s.equalsIgnoreCase("true")) {
      logger.warn(
          "Factory shared, will not be destroyed. That can possibly cause memory leaks if"
              + "Broadcaster where created. Make sure you destroy them manually.");
      return;
    }

    Enumeration<Broadcaster> e = store.elements();
    Broadcaster b;
    // We just need one when shared.
    BroadcasterConfig bc = null;
    while (e.hasMoreElements()) {
      try {
        b = e.nextElement();
        b.resumeAll();
        b.destroy();
        bc = b.getBroadcasterConfig();
      } catch (Throwable t) {
        // Shield us from any bad behaviour
        logger.trace("Destroy", t);
      }
    }

    try {
      if (bc != null) bc.forceDestroy();
    } catch (Throwable t) {
      logger.trace("Destroy", t);
    }

    store.clear();
    factory = null;
  }
  protected void start() {
    if (!started.getAndSet(true)) {

      broadcasterCache = bc.getBroadcasterCache();
      broadcasterCache.start();

      notifierFuture = bc.getExecutorService().submit(getBroadcastHandler());
      asyncWriteFuture = bc.getAsyncWriteService().submit(getAsyncWriteHandler());
    }
  }
  /** {@inheritDoc} */
  @Override
  public void setBroadcasterLifeCyclePolicy(final BroadcasterLifeCyclePolicy lifeCyclePolicy) {
    this.lifeCyclePolicy = lifeCyclePolicy;
    if (currentLifecycleTask != null) {
      currentLifecycleTask.cancel(false);
    }

    if (lifeCyclePolicy.getLifeCyclePolicy()
            == BroadcasterLifeCyclePolicy.ATMOSPHERE_RESOURCE_POLICY.IDLE
        || lifeCyclePolicy.getLifeCyclePolicy()
            == BroadcasterLifeCyclePolicy.ATMOSPHERE_RESOURCE_POLICY.IDLE_DESTROY) {

      int time = lifeCyclePolicy.getTimeout();
      if (time == -1) {
        throw new IllegalStateException("BroadcasterLifeCyclePolicy time is not set");
      }

      final AtomicReference<Future<?>> ref = new AtomicReference<Future<?>>();
      currentLifecycleTask =
          bc.getScheduledExecutorService()
              .scheduleAtFixedRate(
                  new Runnable() {

                    @Override
                    public void run() {
                      try {
                        if (resources.isEmpty()) {
                          notifyEmptyListener();
                          notifyIdleListener();

                          if (lifeCyclePolicy.getLifeCyclePolicy()
                              == BroadcasterLifeCyclePolicy.ATMOSPHERE_RESOURCE_POLICY.IDLE) {
                            releaseExternalResources();
                            logger.debug("Applying BroadcasterLifeCyclePolicy IDLE policy");
                          } else {
                            notifyDestroyListener();

                            destroy();
                            /**
                             * The value may be null if the timeout is too low. Hopefully next
                             * execution will cancel the task properly.
                             */
                            if (ref.get() != null) {
                              currentLifecycleTask.cancel(true);
                            }

                            logger.debug("Applying BroadcasterLifeCyclePolicy IDLE_DESTROY policy");
                          }
                        }
                      } catch (Throwable t) {
                        logger.warn("Scheduled BroadcasterLifeCyclePolicy exception", t);
                      }
                    }
                  },
                  time,
                  time,
                  lifeCyclePolicy.getTimeUnit());
      ref.set(currentLifecycleTask);
    }
  }
  /** {@inheritDoc} */
  public void setScope(SCOPE scope) {
    this.scope = scope;
    if (scope != SCOPE.REQUEST) {
      return;
    }

    try {
      for (AtmosphereResource<?, ?> resource : resources) {
        Broadcaster b =
            BroadcasterFactory.getDefault()
                .get(getClass(), getClass().getSimpleName() + "/" + UUID.randomUUID());

        if (DefaultBroadcaster.class.isAssignableFrom(this.getClass())) {
          BroadcasterCache cache = bc.getBroadcasterCache().getClass().newInstance();
          InjectorProvider.getInjector().inject(cache);
          DefaultBroadcaster.class.cast(b).broadcasterCache = cache;
        }
        resource.setBroadcaster(b);
        if (resource.getAtmosphereResourceEvent().isSuspended()) {
          b.addAtmosphereResource(resource);
        }
      }

      if (!resources.isEmpty()) {
        destroy();
      }
    } catch (Exception e) {
      logger.error("failed to set request scope for current resources", e);
    }
  }
  /** {@inheritDoc} */
  public void destroy() {
    started.set(false);
    destroyed.set(true);
    releaseExternalResources();
    if (notifierFuture != null) {
      notifierFuture.cancel(true);
    }

    if (asyncWriteFuture != null) {
      asyncWriteFuture.cancel(true);
    }

    if (bc != null) {
      bc.destroy();
    }

    if (broadcasterCache != null) {
      broadcasterCache.stop();
    }
    resources.clear();
    broadcastOnResume.clear();
    messages.clear();
    asyncWriteQueue.clear();
    delayedBroadcast.clear();
    broadcasterCache = null;

    if (BroadcasterFactory.getDefault() != null) {
      BroadcasterFactory.getDefault().remove(this, name);
    }

    if (currentLifecycleTask != null) {
      currentLifecycleTask.cancel(true);
    }
  }
  /** {@inheritDoc} */
  public <T> Future<T> delayBroadcast(final T o, long delay, TimeUnit t) {

    if (destroyed.get())
      throw new IllegalStateException("This Broadcaster has been destroyed and cannot be used");

    start();
    final Object msg = filter(o);
    if (msg == null) return null;

    final BroadcasterFuture<Object> future = new BroadcasterFuture<Object>(msg);
    final Entry e = new Entry(msg, null, future, o);
    Future<T> f;
    if (delay > 0) {
      f =
          bc.getScheduledExecutorService()
              .schedule(
                  new Callable<T>() {

                    public T call() throws Exception {
                      delayedBroadcast.remove(e);
                      if (Callable.class.isAssignableFrom(o.getClass())) {
                        try {
                          Object r = Callable.class.cast(o).call();
                          final Object msg = filter(r);
                          if (msg != null) {
                            Entry entry = new Entry(msg, null, null, r);
                            push(entry);
                          }
                          return (T) msg;
                        } catch (Exception e1) {
                          logger.error("", e);
                        }
                      }

                      final Object msg = filter(o);
                      final Entry e = new Entry(msg, null, null, o);
                      push(e);
                      return (T) msg;
                    }
                  },
                  delay,
                  t);

      e.future = new BroadcasterFuture<Object>(f, msg);
    }
    delayedBroadcast.offer(e);
    return future;
  }
  protected void onException(Throwable t, final AtmosphereResource<?, ?> r) {
    logger.debug("onException()", t);

    if (r instanceof AtmosphereEventLifecycle) {
      ((AtmosphereEventLifecycle) r)
          .notifyListeners(
              new AtmosphereResourceEventImpl((AtmosphereResourceImpl) r, true, false, t));
      ((AtmosphereEventLifecycle) r).removeEventListeners();
    }

    /** Make sure we resume the connection on every IOException. */
    bc.getAsyncWriteService()
        .execute(
            new Runnable() {
              @Override
              public void run() {
                r.resume();
              }
            });
  }
  /** {@inheritDoc} */
  public Future<?> scheduleFixedBroadcast(final Object o, long waitFor, long period, TimeUnit t) {

    if (destroyed.get())
      throw new IllegalStateException("This Broadcaster has been destroyed and cannot be used");

    start();
    if (period == 0 || t == null) {
      return null;
    }

    final Object msg = filter(o);
    if (msg == null) return null;

    return bc.getScheduledExecutorService()
        .scheduleWithFixedDelay(
            new Runnable() {
              public void run() {
                if (Callable.class.isAssignableFrom(o.getClass())) {
                  try {
                    Object r = Callable.class.cast(o).call();
                    final Object msg = filter(r);
                    if (msg != null) {
                      Entry entry = new Entry(msg, null, null, r);
                      push(entry);
                    }
                    return;
                  } catch (Exception e) {
                    logger.error("", e);
                  }
                }
                final Object msg = filter(o);
                final Entry e = new Entry(msg, null, null, o);
                push(e);
              }
            },
            waitFor,
            period,
            t);
  }
 /**
  * Invoke the {@link BroadcastFilter}
  *
  * @param msg
  * @return
  */
 protected Object filter(Object msg) {
   BroadcastAction a = bc.filter(msg);
   if (a.action() == BroadcastAction.ACTION.ABORT || msg == null) return null;
   else return a.message();
 }