Package org.atmosphere.jersey

Source Code of org.atmosphere.jersey.AtmosphereFilter

/*
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2007-2008 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License").  You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code.  If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license."  If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above.  However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.atmosphere.jersey;

import com.sun.jersey.api.JResponseAsResponse;
import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;
import org.atmosphere.annotation.Broadcast;
import org.atmosphere.annotation.Cluster;
import org.atmosphere.annotation.Resume;
import org.atmosphere.annotation.Schedule;
import org.atmosphere.annotation.Suspend;
import org.atmosphere.cpr.AtmosphereEventLifecycle;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.atmosphere.cpr.AtmosphereResourceEventListener;
import org.atmosphere.cpr.AtmosphereServlet;
import org.atmosphere.cpr.BroadcastFilter;
import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.cpr.BroadcasterConfig;
import org.atmosphere.cpr.BroadcasterFactory;
import org.atmosphere.cpr.ClusterBroadcastFilter;
import org.atmosphere.di.InjectorProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* {@link ResourceFilterFactory} which intercept the response and appropriately
* set the {@link AtmosphereResourceEvent} filed based on the annotation the application
* has defined.
*
* @author Jeanfrancois Arcand
*/
public class AtmosphereFilter implements ResourceFilterFactory {

    private static final Logger logger = LoggerFactory.getLogger(AtmosphereFilter.class);
    public final static String SUSPENDED_RESOURCE = AtmosphereFilter.class.getName() + ".suspendedResource";
    public final static String RESUME_UUID = AtmosphereFilter.class.getName() + ".uuid";
    public final static String RESUME_CANDIDATES = AtmosphereFilter.class.getName() + ".resumeCandidates";
    public final static String INJECTED_BROADCASTER = AtmosphereFilter.class.getName() + "injectedBroadcaster";

    // For backward compatibility
    public final static String CONTAINER_RESPONSE = AtmosphereServlet.CONTAINER_RESPONSE;
   

    enum Action {
        SUSPEND, RESUME, BROADCAST, SUSPEND_RESUME,
        SCHEDULE_RESUME, RESUME_ON_BROADCAST, NONE, SCHEDULE, SUSPEND_RESPONSE
    }

    private @Context HttpServletRequest servletReq;

    private @Context UriInfo uriInfo;

    private final ConcurrentHashMap<String, AtmosphereResource<HttpServletRequest,HttpServletResponse>> resumeCandidates =
            new ConcurrentHashMap<String, AtmosphereResource<HttpServletRequest,HttpServletResponse>>();

    private class Filter implements ResourceFilter, ContainerResponseFilter {

        private final Action action;
        private final long suspendTimeout;
        private final int waitFor;
        private final Suspend.SCOPE scope;
        private final Class<BroadcastFilter>[] filters;
        private Class<? extends AtmosphereResourceEventListener>[] listeners = null;
        private final boolean outputComments;
        private final ArrayList<ClusterBroadcastFilter> clusters
                = new ArrayList<ClusterBroadcastFilter>();

        protected Filter(Action action) {
            this(action, -1);
        }

        protected Filter(Action action, long suspendTimeout) {
            this(action, suspendTimeout, 0);
        }

        protected Filter(Action action, long suspendTimeout, int waitFor) {
            this(action, suspendTimeout, waitFor, Suspend.SCOPE.APPLICATION);
        }

        protected Filter(Action action, long suspendTimeout, int waitFor, Suspend.SCOPE scope) {
            this(action, suspendTimeout, waitFor, Suspend.SCOPE.APPLICATION, true);
        }

        protected Filter(Action action, long suspendTimeout, int waitFor, Suspend.SCOPE scope, boolean outputComments) {
            this(action, suspendTimeout, waitFor, scope, outputComments, null);
        }

        protected Filter(Action action, long suspendTimeout, int waitFor, Suspend.SCOPE scope, boolean outputComments, Class<BroadcastFilter>[] filters) {
            this.action = action;
            this.suspendTimeout = suspendTimeout;
            this.scope = scope;
            this.outputComments = outputComments;
            this.waitFor = waitFor;
            this.filters = filters;
        }

        public ContainerRequestFilter getRequestFilter() {
            return null;
        }

        public ContainerResponseFilter getResponseFilter() {
            return this;
        }

        boolean resumeOnBroadcast(ContainerRequest request, boolean resumeOnBroadcast){
            String transport = request.getHeaderValue("X-Atmosphere-Transport");
            if (transport != null && transport.equals("long-polling")) {
                return true;
            }
            return resumeOnBroadcast;
        }

        boolean outputJunk(ContainerRequest request, boolean outputJunk){
            String upgrade = servletReq.getHeader("Connection");
            String transport = request.getHeaderValue("X-Atmosphere-Transport");
            if (upgrade != null && upgrade.equalsIgnoreCase("Upgrade")) {
               return false;
            } else if (transport != null && transport.equals("long-polling")) {
                return false;
            }

            return outputJunk;
        }

        /**
         * Configure the {@link AtmosphereResourceEvent} state (suspend, resume, broadcast)
         * based on the annotation the web application has used.
         *
         * @param request  the {@link ContainerRequest}
         * @param response the {@link ContainerResponse}
         * @return the {@link ContainerResponse}
         */
        public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
            if (response.getMappedThrowable() != null) {
                return response;
            }

            AtmosphereResource<HttpServletRequest, HttpServletResponse> r =
                    (AtmosphereResource<HttpServletRequest, HttpServletResponse>) servletReq
                            .getAttribute(AtmosphereServlet.ATMOSPHERE_RESOURCE);

            boolean sessionSupported = (Boolean) servletReq.getAttribute
                    (AtmosphereServlet.SUPPORT_SESSION);

            switch (action) {
                case SUSPEND_RESPONSE:
                    SuspendResponse<?> s = SuspendResponse.class.cast(JResponseAsResponse.class.cast(response.getResponse()).getJResponse());

                    boolean outputJunk = outputJunk(request,s.outputComments());
                    boolean resumeOnBroadcast =  resumeOnBroadcast(request,s.resumeOnBroadcast());

                    for (AtmosphereResourceEventListener el: s.listeners()) {
                        if (r instanceof AtmosphereEventLifecycle) {
                            ((AtmosphereEventLifecycle) r).addEventListener(el);
                        }
                    }

                    Broadcaster bc = s.broadcaster();
                    if (bc == null){
                        bc = (Broadcaster) servletReq.getAttribute(INJECTED_BROADCASTER);
                    }

                    suspend(sessionSupported, resumeOnBroadcast, outputJunk,
                            translateTimeUnit(s.period().value(),s.period().timeUnit()), request, response,bc, r);

                    break;
                case SUSPEND:
                case SUSPEND_RESUME:
                    outputJunk = outputJunk(request,outputComments);
                    resumeOnBroadcast = resumeOnBroadcast(request,(action == Action.SUSPEND_RESUME));

                    for (Class<? extends AtmosphereResourceEventListener> listener : listeners) {
                        try {
                            AtmosphereResourceEventListener el = listener.newInstance();
                            InjectorProvider.getInjector().inject(el);
                            if (r instanceof AtmosphereEventLifecycle) {
                                ((AtmosphereEventLifecycle) r).addEventListener(el);
                            }
                        } catch (Throwable t) {
                            throw new WebApplicationException(
                                    new IllegalStateException("Invalid AtmosphereResourceEventListener " + listener, t));
                        }
                    }
                    suspend(sessionSupported, resumeOnBroadcast, outputJunk, suspendTimeout, request, response, (
                            Broadcaster) servletReq.getAttribute(INJECTED_BROADCASTER), r);
                    break;
                case RESUME:
                    if (response.getEntity() != null) {
                        try {
                            response.write();
                        } catch (IOException ex) {
                            throw new WebApplicationException(ex);
                        }
                    }

                    if (sessionSupported) {
                        r = (AtmosphereResource) servletReq.getSession().getAttribute(SUSPENDED_RESOURCE);
                    } else {
                        String path = response.getContainerRequest().getPath();
                        r = resumeCandidates.remove(path.substring(path.lastIndexOf("/") + 1));
                    }

                    if (r != null) {
                        resume(r);
                    } else {
                        throw new WebApplicationException(
                                new IllegalStateException("Unable to retrieve suspended Response. " +
                                        "Either session-support is not enabled in atmosphere.xml or the" +
                                        "path used to resume is invalid."));

                    }
                    break;
                case BROADCAST:
                case RESUME_ON_BROADCAST:
                    AtmosphereResource ar = (AtmosphereResource) servletReq.getAttribute(SUSPENDED_RESOURCE);
                    if (ar != null) {
                        r = ar;
                    }
                    broadcast(response, r, suspendTimeout);
                    break;
                case SCHEDULE:
                case SCHEDULE_RESUME:
                    Object o = response.getEntity();
                    Broadcaster b = r.getBroadcaster();
                    if (response.getEntity() instanceof Broadcastable) {
                        b = ((Broadcastable) response.getEntity()).getBroadcaster();
                        o = ((Broadcastable) response.getEntity()).getMessage();
                        response.setEntity(((Broadcastable) response.getEntity()).getResponseMessage());
                    }

                    if (response.getEntity() != null) {
                        try {
                            response.write();
                        } catch (IOException ex) {
                            throw new WebApplicationException(ex);
                        }
                    }

                    if (action == Action.SCHEDULE_RESUME) {
                        configureResumeOnBroadcast(b);
                    }

                    b.scheduleFixedBroadcast(o, waitFor, suspendTimeout, TimeUnit.SECONDS);
                    break;
            }

            return response;
        }

        void configureResumeOnBroadcast(Broadcaster b) {
            Iterator<AtmosphereResource<?,?>> i = b.getAtmosphereResources().iterator();
            while (i.hasNext()) {
                HttpServletRequest r = (HttpServletRequest) i.next().getRequest();
                r.setAttribute(AtmosphereServlet.RESUME_ON_BROADCAST, true);
            }
        }


        void configureFilter(Broadcaster bc) {
            if (bc == null) throw new WebApplicationException(new IllegalStateException("Broadcaster cannot be null"));

            /**
             * Here we can't predict if it's the same set of filter shared across all Broadcaster as
             * Broadcaster can have their own BroadcasterConfig instance.
             */
            BroadcasterConfig c = bc.getBroadcasterConfig();
            // Already configured
            if (c.hasFilters()) {
                return;
            }

            // Always the first one, before any transformation/filtering
            for (ClusterBroadcastFilter cbf : clusters) {
                cbf.setBroadcaster(bc);
                c.addFilter(cbf);
            }

            BroadcastFilter f = null;
            if (filters != null) {
                for (Class<BroadcastFilter> filter : filters) {
                    try {
                        f = filter.newInstance();
                        InjectorProvider.getInjector().inject(f);
                    }
                    catch (Throwable t) {
                        logger.warn("Invalid @BroadcastFilter: " + filter, t);
                    }
                    c.addFilter(f);
                }
            }
        }

        private void setListeners(Class<? extends AtmosphereResourceEventListener>[] listeners) {
            this.listeners = listeners;
        }

        void broadcast(ContainerResponse r, AtmosphereResource ar, long delay) {
            Object o = r.getEntity();

            Broadcaster b = ar.getBroadcaster();
            Object msg = o;
            String returnMsg = null;
            // Something went wrong if null.
            if (o instanceof Broadcastable) {
                b = ((Broadcastable) o).getBroadcaster();
                msg = ((Broadcastable) o).getMessage();
                returnMsg = ((Broadcastable) o).getResponseMessage().toString();
            }

            if (action == Action.RESUME_ON_BROADCAST) {
                configureResumeOnBroadcast(b);
            }

            if (o != null) {
                addFilter(b);
                try {
                    r.setEntity(msg);
                    if (msg == null) return;

                    if (delay == -1) {
                        Future<Object> f = b.broadcast(msg);
                        if (f == null) return;
                        Object t = f.get();
                        if (o instanceof Broadcastable) {
                            r.setEntity(returnMsg);
                        }
                    } else if (delay == 0) {
                        b.delayBroadcast(msg);
                    } else {
                        b.delayBroadcast(msg, delay, TimeUnit.SECONDS);
                    }
                } catch (InterruptedException ex) {
                    logger.error("broadcast interrupted", ex);
                } catch (ExecutionException ex) {
                    logger.error("execution exception during broadcast", ex);
                }
            }
        }

        void addFilter(Broadcaster bc) {
            configureFilter(bc);
        }

        void resume(AtmosphereResource resource) {
            resource.resume();
        }

        void addCluster(ClusterBroadcastFilter f) {
            clusters.add(f);
        }

        void suspend(boolean sessionSupported,
                     boolean resumeOnBroadcast,
                     boolean comments,
                     long timeout,
                     ContainerRequest request,
                     ContainerResponse response,
                     Broadcaster bc,
                     AtmosphereResource<HttpServletRequest, HttpServletResponse> r) {

            // Force the status code to 200 events independently of the value of the entity (null or not)
            if (response.getStatus() == 204) {
                response.setStatus(200);
            }
                      
            BroadcasterFactory broadcasterFactory = (BroadcasterFactory) servletReq
                    .getAttribute(AtmosphereServlet.BROADCASTER_FACTORY);

            // Do not add location header if already there.
            if (!sessionSupported && !resumeOnBroadcast && response.getHttpHeaders().getFirst("Location") == null) {
                String uuid = UUID.randomUUID().toString();

                response.getHttpHeaders().putSingle(
                        HttpHeaders.LOCATION,
                        uriInfo.getAbsolutePathBuilder().path(uuid).build(""));

                resumeCandidates.put(uuid, r);
                servletReq.setAttribute(RESUME_UUID, uuid);
                servletReq.setAttribute(RESUME_CANDIDATES, resumeCandidates);
            }

            if (bc == null) {
                bc = r.getBroadcaster();
            }

            if (sessionSupported && servletReq.getSession().getAttribute(SUSPENDED_RESOURCE) != null) {
                AtmosphereResource<HttpServletRequest, HttpServletResponse> cached =
                        (AtmosphereResource) servletReq.getSession().getAttribute(SUSPENDED_RESOURCE);
                bc = cached.getBroadcaster();
                // Just in case something went wrong.
                bc.removeAtmosphereResource(cached);
            }

            if (response.getEntity() instanceof Broadcastable) {
                Broadcastable b = (Broadcastable) response.getEntity();
                bc = b.getBroadcaster();
                response.setEntity(b.getResponseMessage());

                if ((scope == Suspend.SCOPE.REQUEST) && (bc.getScope() != Broadcaster.SCOPE.REQUEST)) {
                    bc.setScope(Broadcaster.SCOPE.REQUEST);
                }
            } else if ((scope == Suspend.SCOPE.REQUEST) && (bc.getScope() != Broadcaster.SCOPE.REQUEST)) {
                try {
                    String id = bc.getID();
                    bc.setID(bc.getClass().getSimpleName() + "-" + UUID.randomUUID());

                    // Re-generate a new one with proper scope.
                    bc = broadcasterFactory.get();
                    bc.setScope(Broadcaster.SCOPE.REQUEST);
                    bc.setID(id);
                }
                catch (Exception ex) {
                    logger.error("failed to instantiate broadcaster with factory: " + broadcasterFactory, ex);
                }
            }
            configureFilter(bc);
            r.setBroadcaster(bc);

            if (sessionSupported) {
                servletReq.getSession().setAttribute(SUSPENDED_RESOURCE, r);
                servletReq.getSession().setAttribute(AtmosphereServlet.CONTAINER_RESPONSE, response);
            }

            servletReq.setAttribute(SUSPENDED_RESOURCE, r);
            servletReq.setAttribute(AtmosphereServlet.CONTAINER_RESPONSE, response);

            logger.debug("Linking HttpServletRequest {} with ContainerResponse {}", servletReq, response);

            if (resumeOnBroadcast) {
                servletReq.setAttribute(AtmosphereServlet.RESUME_ON_BROADCAST, new Boolean(true));
            }

            // Set the content-type based on the returned entity.
            try {
                MediaType contentType = response.getMediaType();
                if (contentType == null && response.getEntity() != null) {
                    LinkedList<MediaType> l = new LinkedList<MediaType>();
                    // Will retrun the first
                    l.add(request.getAcceptableMediaType(new LinkedList<MediaType>()));
                    contentType = response.getMessageBodyWorkers().getMessageBodyWriterMediaType(
                            response.getEntity().getClass(),
                            response.getEntityType(),
                            response.getAnnotations(),
                            l);

                    if (contentType == null ||
                            contentType.isWildcardType() || contentType.isWildcardSubtype())
                        contentType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
                }

                r.getResponse().setContentType(contentType != null ?
                        contentType.toString() : "text/html;charset=ISO-8859-1");

                r.suspend(timeout, comments && !resumeOnBroadcast);
                if (response.getEntity() != null) {
                    response.write();
                }
            } catch (IOException ex) {
                throw new WebApplicationException(ex);
            }
        }
    }

    /**
     * Create a {@link ResourceFilter} which contains the information about the
     * annotation being processed.
     * <p/>
     * XXX Need to filter invalid mix of annotation.
     *
     * @param am an {@link AbstractMethod}
     * @return a List of {@link ResourceFilter} to invoke.
     */
    public List<ResourceFilter> create(AbstractMethod am) {
        LinkedList<ResourceFilter> list = new LinkedList<ResourceFilter>();
        Filter f;

        if (logger.isDebugEnabled()) {
            for (Annotation annotation : am.getAnnotations()) {
                logger.debug("AtmosphereFilter processing annotation: {}", annotation);
            }
        }

        if (am.getMethod() == null) {
            return null;
        }

        if (SuspendResponse.class.isAssignableFrom(am.getMethod().getReturnType())) {
            list.addLast(new Filter(Action.SUSPEND_RESPONSE));
            return list;
        }

        if (am.isAnnotationPresent(Broadcast.class)) {
            int delay = am.getAnnotation(Broadcast.class).delay();
            Class[] suspendTimeout = am.getAnnotation(Broadcast.class).value();

            if (am.getAnnotation(Broadcast.class).resumeOnBroadcast()) {
                f = new Filter(Action.RESUME_ON_BROADCAST, delay, 0, Suspend.SCOPE.APPLICATION, true, suspendTimeout);
            } else {
                f = new Filter(Action.BROADCAST, delay, 0, Suspend.SCOPE.APPLICATION, true, suspendTimeout);
            }

            list.addLast((ResourceFilter) f);

            if (am.isAnnotationPresent(Cluster.class)) {
                suspendTimeout = am.getAnnotation(Cluster.class).value();
                for (Class<ClusterBroadcastFilter> c : suspendTimeout) {
                    try {
                        ClusterBroadcastFilter cbf = c.newInstance();
                        InjectorProvider.getInjector().inject(cbf);
                        cbf.setUri(am.getAnnotation(Cluster.class).name());
                        f.addCluster(cbf);
                    }
                    catch (Throwable t) {
                        logger.warn("Invalid ClusterBroadcastFilter", t);
                    }
                }
            }
        }

        if (am.isAnnotationPresent(Suspend.class)) {

            long suspendTimeout = am.getAnnotation(Suspend.class).period();
            TimeUnit tu = am.getAnnotation(Suspend.class).timeUnit();
            suspendTimeout = translateTimeUnit(suspendTimeout,tu);

            Suspend.SCOPE scope = am.getAnnotation(Suspend.class).scope();
            boolean outputComments = am.getAnnotation(Suspend.class).outputComments();

            if (am.getAnnotation(Suspend.class).resumeOnBroadcast()) {
                f = new Filter(Action.SUSPEND_RESUME, suspendTimeout, 0, scope, outputComments);
            } else {
                f = new Filter(Action.SUSPEND, suspendTimeout, 0, scope, outputComments);
            }
            f.setListeners(am.getAnnotation(Suspend.class).listeners());

            list.addFirst((ResourceFilter) f);
        }

        if (am.isAnnotationPresent(Resume.class)) {
            int suspendTimeout = am.getAnnotation(Resume.class).value();
            list.addFirst(new Filter(Action.RESUME, suspendTimeout));
        }

        if (am.isAnnotationPresent(Schedule.class)) {
            int period = am.getAnnotation(Schedule.class).period();
            int waitFor = am.getAnnotation(Schedule.class).waitFor();

            if (am.getAnnotation(Schedule.class).resumeOnBroadcast()) {
                list.addFirst(new Filter(Action.SCHEDULE_RESUME, period, waitFor));
            } else {
                list.addFirst(new Filter(Action.SCHEDULE, period, waitFor));
            }
        }

        // Nothing, normal Jersey application.
        return list.size() > 0 ? list : null;
    }

    private long translateTimeUnit(long period, TimeUnit tu) {
        if (period == -1) return period;

        switch (tu) {
            case SECONDS:
                return TimeUnit.MILLISECONDS.convert(period,TimeUnit.SECONDS);
            case MINUTES:
                return TimeUnit.MILLISECONDS.convert(period,TimeUnit.MINUTES);
            case HOURS:
                return TimeUnit.MILLISECONDS.convert(period,TimeUnit.HOURS);
            case DAYS:
                return TimeUnit.MILLISECONDS.convert(period,TimeUnit.DAYS);
            case MILLISECONDS:
                return period ;
            case MICROSECONDS:
                return TimeUnit.MILLISECONDS.convert(period,TimeUnit.MICROSECONDS);
            case NANOSECONDS:
                return TimeUnit.MILLISECONDS.convert(period,TimeUnit.NANOSECONDS);
        }
        return period;
    }

}
TOP

Related Classes of org.atmosphere.jersey.AtmosphereFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.