summaryrefslogtreecommitdiffstats
path: root/push
diff options
context:
space:
mode:
Diffstat (limited to 'push')
-rw-r--r--push/ivy.xml6
-rw-r--r--push/src/org/atmosphere/cpr/AtmosphereFramework.java1779
2 files changed, 1784 insertions, 1 deletions
diff --git a/push/ivy.xml b/push/ivy.xml
index 6e648bfc01..d847e21ace 100644
--- a/push/ivy.xml
+++ b/push/ivy.xml
@@ -25,7 +25,11 @@
<!-- API DEPENDENCIES -->
-
+
+ <!--Servlet API version 2.4 -->
+ <dependency org="javax.servlet" name="servlet-api"
+ rev="2.4" conf="build-provided,ide,test -> default" />
+
<!-- Atmosphere -->
<dependency org="org.atmosphere" name="atmosphere-runtime" rev="1.0.12"
conf="build,ide,test -> default">
diff --git a/push/src/org/atmosphere/cpr/AtmosphereFramework.java b/push/src/org/atmosphere/cpr/AtmosphereFramework.java
new file mode 100644
index 0000000000..62f867fc94
--- /dev/null
+++ b/push/src/org/atmosphere/cpr/AtmosphereFramework.java
@@ -0,0 +1,1779 @@
+/*
+ * Copyright 2013 Jeanfrancois Arcand
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.atmosphere.cpr;
+
+import org.atmosphere.cache.UUIDBroadcasterCache;
+import org.atmosphere.config.ApplicationConfiguration;
+import org.atmosphere.config.AtmosphereHandlerConfig;
+import org.atmosphere.config.AtmosphereHandlerProperty;
+import org.atmosphere.config.FrameworkConfiguration;
+import org.atmosphere.container.BlockingIOCometSupport;
+import org.atmosphere.container.Tomcat7BIOSupportWithWebSocket;
+import org.atmosphere.di.InjectorProvider;
+import org.atmosphere.di.ServletContextHolder;
+import org.atmosphere.di.ServletContextProvider;
+import org.atmosphere.handler.AbstractReflectorAtmosphereHandler;
+import org.atmosphere.handler.ReflectorServletProcessor;
+import org.atmosphere.interceptor.AndroidAtmosphereInterceptor;
+import org.atmosphere.interceptor.JSONPAtmosphereInterceptor;
+import org.atmosphere.interceptor.JavaScriptProtocol;
+import org.atmosphere.interceptor.OnDisconnectInterceptor;
+import org.atmosphere.interceptor.SSEAtmosphereInterceptor;
+import org.atmosphere.util.AtmosphereConfigReader;
+import org.atmosphere.util.IntrospectionUtils;
+import org.atmosphere.util.Version;
+import org.atmosphere.websocket.DefaultWebSocketProcessor;
+import org.atmosphere.websocket.WebSocket;
+import org.atmosphere.websocket.WebSocketProtocol;
+import org.atmosphere.websocket.protocol.SimpleHttpProtocol;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.atmosphere.cpr.ApplicationConfig.ALLOW_QUERYSTRING_AS_REQUEST;
+import static org.atmosphere.cpr.ApplicationConfig.ATMOSPHERE_HANDLER;
+import static org.atmosphere.cpr.ApplicationConfig.ATMOSPHERE_HANDLER_MAPPING;
+import static org.atmosphere.cpr.ApplicationConfig.ATMOSPHERE_HANDLER_PATH;
+import static org.atmosphere.cpr.ApplicationConfig.BROADCASTER_CACHE;
+import static org.atmosphere.cpr.ApplicationConfig.BROADCASTER_CLASS;
+import static org.atmosphere.cpr.ApplicationConfig.BROADCASTER_FACTORY;
+import static org.atmosphere.cpr.ApplicationConfig.BROADCASTER_LIFECYCLE_POLICY;
+import static org.atmosphere.cpr.ApplicationConfig.BROADCAST_FILTER_CLASSES;
+import static org.atmosphere.cpr.ApplicationConfig.DISABLE_ONSTATE_EVENT;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_ATMOSPHERE_XML;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_BLOCKING_COMETSUPPORT;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_COMET_SUPPORT;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_NATIVE_COMETSUPPORT;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_SERVLET_MAPPING;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_SESSION_SUPPORT;
+import static org.atmosphere.cpr.ApplicationConfig.PROPERTY_USE_STREAM;
+import static org.atmosphere.cpr.ApplicationConfig.RESUME_AND_KEEPALIVE;
+import static org.atmosphere.cpr.ApplicationConfig.SUSPENDED_ATMOSPHERE_RESOURCE_UUID;
+import static org.atmosphere.cpr.ApplicationConfig.WEBSOCKET_PROCESSOR;
+import static org.atmosphere.cpr.ApplicationConfig.WEBSOCKET_PROTOCOL;
+import static org.atmosphere.cpr.ApplicationConfig.WEBSOCKET_SUPPORT;
+import static org.atmosphere.cpr.FrameworkConfig.ATMOSPHERE_CONFIG;
+import static org.atmosphere.cpr.FrameworkConfig.HAZELCAST_BROADCASTER;
+import static org.atmosphere.cpr.FrameworkConfig.JERSEY_BROADCASTER;
+import static org.atmosphere.cpr.FrameworkConfig.JERSEY_CONTAINER;
+import static org.atmosphere.cpr.FrameworkConfig.JGROUPS_BROADCASTER;
+import static org.atmosphere.cpr.FrameworkConfig.JMS_BROADCASTER;
+import static org.atmosphere.cpr.FrameworkConfig.REDIS_BROADCASTER;
+import static org.atmosphere.cpr.FrameworkConfig.WRITE_HEADERS;
+import static org.atmosphere.cpr.FrameworkConfig.XMPP_BROADCASTER;
+import static org.atmosphere.cpr.HeaderConfig.ATMOSPHERE_POST_BODY;
+import static org.atmosphere.cpr.HeaderConfig.X_ATMOSPHERE_TRACKING_ID;
+import static org.atmosphere.websocket.WebSocket.WEBSOCKET_SUSPEND;
+
+/**
+ * The {@link AtmosphereFramework} is the entry point for the framework. This class can be used to from Servlet/filter
+ * to dispatch {@link AtmosphereRequest} and {@link AtmosphereResponse}. The framework can also be configured using
+ * the setXXX method. The life cycle of this class is
+ * <blockquote><pre>
+ * AtmosphereFramework f = new AtmosphereFramework();
+ * f.init();
+ * f.doCometSupport(AtmosphereRequest, AtmosphereResource);
+ * f.destroy();
+ * </pre></blockquote>
+ *
+ * @author Jeanfrancois Arcand
+ */
+public class AtmosphereFramework implements ServletContextProvider {
+ public static final String DEFAULT_ATMOSPHERE_CONFIG_PATH = "/META-INF/atmosphere.xml";
+ public static final String DEFAULT_LIB_PATH = "/WEB-INF/lib/";
+ public static final String MAPPING_REGEX = "[a-zA-Z0-9-&.*=@~;\\?]+";
+
+ protected static final Logger logger = LoggerFactory.getLogger(AtmosphereFramework.class);
+
+ protected final List<String> broadcasterFilters = new ArrayList<String>();
+ protected final List<AsyncSupportListener> asyncSupportListeners = new ArrayList<AsyncSupportListener>();
+ protected final ArrayList<String> possibleComponentsCandidate = new ArrayList<String>();
+ protected final HashMap<String, String> initParams = new HashMap<String, String>();
+ protected final AtmosphereConfig config;
+ protected final AtomicBoolean isCometSupportConfigured = new AtomicBoolean(false);
+ protected final boolean isFilter;
+ protected final Map<String, AtmosphereHandlerWrapper> atmosphereHandlers = new ConcurrentHashMap<String, AtmosphereHandlerWrapper>();
+ protected final ConcurrentLinkedQueue<String> broadcasterTypes = new ConcurrentLinkedQueue<String>();
+
+ protected boolean useNativeImplementation = false;
+ protected boolean useBlockingImplementation = false;
+ protected boolean useStreamForFlushingComments = false;
+ protected AsyncSupport asyncSupport;
+ protected String broadcasterClassName = DefaultBroadcaster.class.getName();
+ protected boolean isCometSupportSpecified = false;
+ protected boolean isBroadcasterSpecified = false;
+ protected boolean isSessionSupportSpecified = false;
+ protected BroadcasterFactory broadcasterFactory;
+ protected String broadcasterFactoryClassName;
+ protected String broadcasterCacheClassName;
+ protected boolean webSocketEnabled = true;
+ protected String broadcasterLifeCyclePolicy = "NEVER";
+ protected String webSocketProtocolClassName = SimpleHttpProtocol.class.getName();
+ protected WebSocketProtocol webSocketProtocol;
+ protected String handlersPath = "/WEB-INF/classes/";
+ protected ServletConfig servletConfig;
+ protected boolean autoDetectHandlers = true;
+ private boolean hasNewWebSocketProtocol = false;
+ protected String atmosphereDotXmlPath = DEFAULT_ATMOSPHERE_CONFIG_PATH;
+ protected final LinkedList<AtmosphereInterceptor> interceptors = new LinkedList<AtmosphereInterceptor>();
+ protected boolean scanDone = false;
+ protected String annotationProcessorClassName = "org.atmosphere.cpr.DefaultAnnotationProcessor";
+ protected final List<BroadcasterListener> broadcasterListeners = new ArrayList<BroadcasterListener>();
+ protected String webSocketProcessorClassName = DefaultWebSocketProcessor.class.getName();
+ protected String libPath = DEFAULT_LIB_PATH;
+ protected boolean isInit;
+ protected boolean sharedThreadPools = true;
+
+ public static final class AtmosphereHandlerWrapper {
+
+ public final AtmosphereHandler atmosphereHandler;
+ public Broadcaster broadcaster;
+ public String mapping;
+ public List<AtmosphereInterceptor> interceptors = Collections.<AtmosphereInterceptor>emptyList();
+
+ public AtmosphereHandlerWrapper(BroadcasterFactory broadcasterFactory, AtmosphereHandler atmosphereHandler, String mapping) {
+ this.atmosphereHandler = atmosphereHandler;
+ try {
+ if (broadcasterFactory != null) {
+ this.broadcaster = broadcasterFactory.lookup(mapping, true);
+ } else {
+ this.mapping = mapping;
+ }
+ } catch (Exception t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ public AtmosphereHandlerWrapper(AtmosphereHandler atmosphereHandler, Broadcaster broadcaster) {
+ this.atmosphereHandler = atmosphereHandler;
+ this.broadcaster = broadcaster;
+ }
+
+ @Override
+ public String toString() {
+ return "AtmosphereHandlerWrapper{ atmosphereHandler=" + atmosphereHandler + ", broadcaster=" +
+ broadcaster + " }";
+ }
+ }
+
+ /**
+ * Create an AtmosphereFramework.
+ */
+ public AtmosphereFramework() {
+ this(false, true);
+ }
+
+ /**
+ * Create an AtmosphereFramework and initialize it via {@link AtmosphereFramework#init(javax.servlet.ServletConfig)}
+ */
+ public AtmosphereFramework(ServletConfig sc) throws ServletException {
+ this(false, true);
+ init(sc);
+ }
+
+ /**
+ * Create an AtmosphereFramework.
+ *
+ * @param isFilter true if this instance is used as an {@link AtmosphereFilter}
+ */
+ public AtmosphereFramework(boolean isFilter, boolean autoDetectHandlers) {
+ this.isFilter = isFilter;
+ this.autoDetectHandlers = autoDetectHandlers;
+ readSystemProperties();
+ populateBroadcasterType();
+ config = new AtmosphereConfig(this);
+ }
+
+ /**
+ * The order of addition is quite important here.
+ */
+ private void populateBroadcasterType() {
+ broadcasterTypes.add(HAZELCAST_BROADCASTER);
+ broadcasterTypes.add(XMPP_BROADCASTER);
+ broadcasterTypes.add(REDIS_BROADCASTER);
+ broadcasterTypes.add(JGROUPS_BROADCASTER);
+ broadcasterTypes.add(JMS_BROADCASTER);
+ }
+
+ /**
+ * Add an {@link AtmosphereHandler} serviced by the {@link Servlet}
+ * This API is exposed to allow embedding an Atmosphere application.
+ *
+ * @param mapping The servlet mapping (servlet path)
+ * @param h implementation of an {@link AtmosphereHandler}
+ * @param l An attay of {@link AtmosphereInterceptor}
+ */
+ public AtmosphereFramework addAtmosphereHandler(String mapping, AtmosphereHandler h, List<AtmosphereInterceptor> l) {
+ if (!mapping.startsWith("/")) {
+ mapping = "/" + mapping;
+ }
+
+ AtmosphereHandlerWrapper w = new AtmosphereHandlerWrapper(broadcasterFactory, h, mapping);
+ w.interceptors = l;
+ addMapping(mapping, w);
+
+ logger.info("Installed AtmosphereHandler {} mapped to context-path: {}", h.getClass().getName(), mapping);
+ if (l.size() > 0) {
+ logger.info("Installed AtmosphereInterceptor {} mapped to AtmosphereHandler {}", l, h.getClass().getName());
+ }
+ return this;
+ }
+
+ /**
+ * Add an {@link AtmosphereHandler} serviced by the {@link Servlet}
+ * This API is exposed to allow embedding an Atmosphere application.
+ *
+ * @param mapping The servlet mapping (servlet path)
+ * @param h implementation of an {@link AtmosphereHandler}
+ */
+ public AtmosphereFramework addAtmosphereHandler(String mapping, AtmosphereHandler h) {
+ addAtmosphereHandler(mapping, h, Collections.<AtmosphereInterceptor>emptyList());
+ return this;
+ }
+
+ private AtmosphereFramework addMapping(String path, AtmosphereHandlerWrapper w) {
+ // We are using JAXRS mapping algorithm.
+ if (path.contains("*")) {
+ path = path.replace("*", MAPPING_REGEX);
+ }
+
+ if (path.endsWith("/")) {
+ path = path + MAPPING_REGEX;
+ }
+
+ InjectorProvider.getInjector().inject(w.atmosphereHandler);
+ atmosphereHandlers.put(path, w);
+ return this;
+ }
+
+ /**
+ * Add an {@link AtmosphereHandler} serviced by the {@link Servlet}
+ * This API is exposed to allow embedding an Atmosphere application.
+ *
+ * @param mapping The servlet mapping (servlet path)
+ * @param h implementation of an {@link AtmosphereHandler}
+ * @param broadcasterId The {@link Broadcaster#getID} value.
+ * @param l An attay of {@link AtmosphereInterceptor}
+ */
+ public AtmosphereFramework addAtmosphereHandler(String mapping, AtmosphereHandler h, String broadcasterId, List<AtmosphereInterceptor> l) {
+ if (!mapping.startsWith("/")) {
+ mapping = "/" + mapping;
+ }
+
+ AtmosphereHandlerWrapper w = new AtmosphereHandlerWrapper(broadcasterFactory, h, mapping);
+ w.broadcaster.setID(broadcasterId);
+ w.interceptors = l;
+ addMapping(mapping, w);
+ logger.info("Installed AtmosphereHandler {} mapped to context-path: {}", h.getClass().getName(), mapping);
+ if (l.size() > 0) {
+ logger.info("Installed AtmosphereInterceptor {} mapped to AtmosphereHandler {}", l, h.getClass().getName());
+ }
+ return this;
+ }
+
+ /**
+ * Add an {@link AtmosphereHandler} serviced by the {@link Servlet}
+ * This API is exposed to allow embedding an Atmosphere application.
+ *
+ * @param mapping The servlet mapping (servlet path)
+ * @param h implementation of an {@link AtmosphereHandler}
+ * @param broadcasterId The {@link Broadcaster#getID} value.
+ */
+ public AtmosphereFramework addAtmosphereHandler(String mapping, AtmosphereHandler h, String broadcasterId) {
+ addAtmosphereHandler(mapping, h, broadcasterId, Collections.<AtmosphereInterceptor>emptyList());
+ return this;
+ }
+
+ /**
+ * Add an {@link AtmosphereHandler} serviced by the {@link Servlet}
+ * This API is exposed to allow embedding an Atmosphere application.
+ *
+ * @param mapping The servlet mapping (servlet path)
+ * @param h implementation of an {@link AtmosphereHandler}
+ * @param broadcaster The {@link Broadcaster} associated with AtmosphereHandler.
+ * @param l An attay of {@link AtmosphereInterceptor}
+ */
+ public AtmosphereFramework addAtmosphereHandler(String mapping, AtmosphereHandler h, Broadcaster broadcaster, List<AtmosphereInterceptor> l) {
+ if (!mapping.startsWith("/")) {
+ mapping = "/" + mapping;
+ }
+
+ AtmosphereHandlerWrapper w = new AtmosphereHandlerWrapper(h, broadcaster);
+ w.interceptors = l;
+
+ addMapping(mapping, w);
+ logger.info("Installed AtmosphereHandler {} mapped to context-path: {}", h.getClass().getName(), mapping);
+ if (l.size() > 0) {
+ logger.info("Installed AtmosphereInterceptor {} mapped to AtmosphereHandler {}", l, h.getClass().getName());
+ }
+ return this;
+ }
+
+ /**
+ * Add an {@link AtmosphereHandler} serviced by the {@link Servlet}
+ * This API is exposed to allow embedding an Atmosphere application.
+ *
+ * @param mapping The servlet mapping (servlet path)
+ * @param h implementation of an {@link AtmosphereHandler}
+ * @param broadcaster The {@link Broadcaster} associated with AtmosphereHandler.
+ */
+ public AtmosphereFramework addAtmosphereHandler(String mapping, AtmosphereHandler h, Broadcaster broadcaster) {
+ addAtmosphereHandler(mapping, h, broadcaster, Collections.<AtmosphereInterceptor>emptyList());
+ return this;
+ }
+
+ /**
+ * Remove an {@link AtmosphereHandler}
+ *
+ * @param mapping the mapping used when invoking {@link #addAtmosphereHandler(String, AtmosphereHandler)};
+ * @return true if removed
+ */
+ public AtmosphereFramework removeAtmosphereHandler(String mapping) {
+
+ if (mapping.endsWith("/")) {
+ mapping += MAPPING_REGEX;
+ }
+
+ atmosphereHandlers.remove(mapping);
+ return this;
+ }
+
+ /**
+ * Remove all {@link AtmosphereHandler}
+ */
+ public AtmosphereFramework removeAllAtmosphereHandler() {
+ atmosphereHandlers.clear();
+ return this;
+ }
+
+ /**
+ * Remove all init parameters.
+ */
+ public AtmosphereFramework removeAllInitParams() {
+ initParams.clear();
+ return this;
+ }
+
+ /**
+ * Add init-param like if they were defined in web.xml
+ *
+ * @param name The name
+ * @param value The value
+ */
+ public AtmosphereFramework addInitParameter(String name, String value) {
+ initParams.put(name, value);
+ return this;
+ }
+
+ protected void readSystemProperties() {
+ if (System.getProperty(PROPERTY_NATIVE_COMETSUPPORT) != null) {
+ useNativeImplementation = Boolean
+ .parseBoolean(System.getProperty(PROPERTY_NATIVE_COMETSUPPORT));
+ isCometSupportSpecified = true;
+ }
+
+ if (System.getProperty(PROPERTY_BLOCKING_COMETSUPPORT) != null) {
+ useBlockingImplementation = Boolean
+ .parseBoolean(System.getProperty(PROPERTY_BLOCKING_COMETSUPPORT));
+ isCometSupportSpecified = true;
+ }
+ atmosphereDotXmlPath = System.getProperty(PROPERTY_ATMOSPHERE_XML, atmosphereDotXmlPath);
+
+ if (System.getProperty(DISABLE_ONSTATE_EVENT) != null) {
+ initParams.put(DISABLE_ONSTATE_EVENT, System.getProperty(DISABLE_ONSTATE_EVENT));
+ }
+ }
+
+ /**
+ * Path specific container using their own property.
+ */
+ public void patchContainer() {
+ System.setProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE", "false");
+ }
+
+ /**
+ * Initialize the AtmosphereFramework. Invoke that method after having properly configured this class using the setter.
+ */
+ public AtmosphereFramework init() {
+ try {
+ init(new ServletConfig() {
+
+ @Override
+ public String getServletName() {
+ return "AtmosphereFramework";
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return (ServletContext) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ServletContext.class},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ logger.trace("Method {} not supported", method.getName());
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ return initParams.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(initParams.values());
+ }
+ });
+ } catch (ServletException e) {
+ logger.error("", e);
+ }
+ return this;
+ }
+
+ /**
+ * Initialize the AtmosphereFramework using the {@link ServletContext}
+ *
+ * @param sc the {@link ServletContext}
+ */
+ public AtmosphereFramework init(final ServletConfig sc) throws ServletException {
+
+ if (isInit) return this;
+
+ try {
+ ServletContextHolder.register(this);
+
+ ServletConfig scFacade = new ServletConfig() {
+
+ public String getServletName() {
+ return sc.getServletName();
+ }
+
+ public ServletContext getServletContext() {
+ return sc.getServletContext();
+ }
+
+ public String getInitParameter(String name) {
+ String param = initParams.get(name);
+ if (param == null) {
+ return sc.getInitParameter(name);
+ }
+ return param;
+ }
+
+ public Enumeration<String> getInitParameterNames() {
+ Enumeration en = sc.getInitParameterNames();
+ while (en.hasMoreElements()) {
+ String name = (String) en.nextElement();
+ if (!initParams.containsKey(name)) {
+ initParams.put(name, sc.getInitParameter(name));
+ }
+ }
+ return Collections.enumeration(initParams.keySet());
+ }
+ };
+ this.servletConfig = scFacade;
+ asyncSupportListener(new AsyncSupportListenerAdapter());
+
+ autoConfigureService(scFacade.getServletContext());
+ patchContainer();
+ doInitParams(scFacade);
+ doInitParamsForWebSocket(scFacade);
+ configureBroadcaster();
+ loadConfiguration(scFacade);
+ initWebSocket();
+
+ autoDetectContainer();
+ configureWebDotXmlAtmosphereHandler(sc);
+ asyncSupport.init(scFacade);
+ initAtmosphereHandler(scFacade);
+ configureAtmosphereInterceptor(sc);
+
+ if (broadcasterCacheClassName == null) {
+ logger.warn("No BroadcasterCache configured. Broadcasted message between client reconnection will be LOST. " +
+ "It is recommended to configure the {}", UUIDBroadcasterCache.class.getName());
+ } else {
+ logger.info("Using BroadcasterCache: {}", broadcasterCacheClassName);
+ }
+
+ // http://java.net/jira/browse/ATMOSPHERE-157
+ if (sc.getServletContext() != null) {
+ sc.getServletContext().setAttribute(BroadcasterFactory.class.getName(), broadcasterFactory);
+ }
+
+ for (String i : broadcasterFilters) {
+ logger.info("Using BroadcastFilter: {}", i);
+ }
+
+ String s = config.getInitParameter(ApplicationConfig.BROADCASTER_SHARABLE_THREAD_POOLS);
+ if (s != null) {
+ sharedThreadPools = Boolean.parseBoolean(s);
+ }
+
+ logger.info("Shared ExecutorService supported: {}", sharedThreadPools);
+ logger.info("HttpSession supported: {}", config.isSupportSession());
+ logger.info("Using BroadcasterFactory: {}", broadcasterFactory.getClass().getName());
+ logger.info("Using WebSocketProcessor: {}", webSocketProcessorClassName);
+ logger.info("Using Broadcaster: {}", broadcasterClassName);
+ logger.info("Atmosphere Framework {} started.", Version.getRawVersion());
+
+ String showSupportMessage = config.getInitParameter("org.atmosphere.cpr.showSupportMessage");
+ if (showSupportMessage == null || Boolean.parseBoolean(showSupportMessage)) {
+ logger.info("\n\n\tFor Commercial Support, visit \n\t{} " +
+ "or send an email to {}\n", "http://www.async-io.org/", "support@async-io.org");
+ }
+ } catch (Throwable t) {
+ logger.error("Failed to initialize Atmosphere Framework", t);
+
+ if (t instanceof ServletException) {
+ throw (ServletException) t;
+ }
+
+ throw new ServletException(t);
+ }
+ isInit = true;
+ return this;
+ }
+
+ /**
+ * Configure the list of {@link AtmosphereInterceptor}.
+ *
+ * @param sc a ServletConfig
+ */
+ protected void configureAtmosphereInterceptor(ServletConfig sc) {
+ String s = sc.getInitParameter(ApplicationConfig.ATMOSPHERE_INTERCEPTORS);
+ if (s != null) {
+ String[] list = s.split(",");
+ for (String a : list) {
+ try {
+ AtmosphereInterceptor ai = (AtmosphereInterceptor) Thread.currentThread().getContextClassLoader()
+ .loadClass(a.trim()).newInstance();
+ ai.configure(config);
+ interceptor(ai);
+ } catch (InstantiationException e) {
+ logger.warn("", e);
+ } catch (IllegalAccessException e) {
+ logger.warn("", e);
+ } catch (ClassNotFoundException e) {
+ logger.warn("", e);
+ }
+ }
+ }
+
+ s = sc.getInitParameter(ApplicationConfig.DISABLE_ATMOSPHEREINTERCEPTOR);
+ if (s == null) {
+ // OnDisconnect
+ interceptors.addFirst(newAInterceptor(OnDisconnectInterceptor.class));
+ // ADD Tracking ID Handshake
+ interceptors.addFirst(newAInterceptor(JavaScriptProtocol.class));
+ // ADD JSONP support
+ interceptors.addFirst(newAInterceptor(JSONPAtmosphereInterceptor.class));
+ // Add SSE support
+ interceptors.addFirst(newAInterceptor(SSEAtmosphereInterceptor.class));
+ // Android 2.3.x streaming support
+ interceptors.addFirst(newAInterceptor(AndroidAtmosphereInterceptor.class));
+ logger.info("Installed Default AtmosphereInterceptor {}. " +
+ "Set org.atmosphere.cpr.AtmosphereInterceptor.disableDefaults in your xml to disable them.", interceptors);
+ }
+ }
+
+ protected AtmosphereInterceptor newAInterceptor(Class<? extends AtmosphereInterceptor> a) {
+ AtmosphereInterceptor ai = null;
+ try {
+ ai = (AtmosphereInterceptor) getClass().getClassLoader().loadClass(a.getName()).newInstance();
+ ai.configure(config);
+ } catch (Exception ex) {
+ logger.warn("", ex);
+ }
+ return ai;
+ }
+
+ protected void configureWebDotXmlAtmosphereHandler(ServletConfig sc) {
+ String s = sc.getInitParameter(ATMOSPHERE_HANDLER);
+ if (s != null) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ try {
+
+ String mapping = sc.getInitParameter(ATMOSPHERE_HANDLER_MAPPING);
+ if (mapping == null) {
+ mapping = "/*";
+ }
+ addAtmosphereHandler(mapping, (AtmosphereHandler) cl.loadClass(s).newInstance());
+ } catch (Exception ex) {
+ logger.warn("Unable to load WebSocketHandle instance", ex);
+ }
+ }
+ }
+
+ protected void configureBroadcaster() {
+
+ try {
+ // Check auto supported one
+ if (isBroadcasterSpecified == false) {
+ broadcasterClassName = lookupDefaultBroadcasterType(broadcasterClassName);
+ }
+
+ Class<? extends Broadcaster> bc =
+ (Class<? extends Broadcaster>) Thread.currentThread().getContextClassLoader()
+ .loadClass(broadcasterClassName);
+ if (broadcasterFactoryClassName != null) {
+ broadcasterFactory = (BroadcasterFactory) Thread.currentThread().getContextClassLoader()
+ .loadClass(broadcasterFactoryClassName).newInstance();
+ }
+
+ if (broadcasterFactory == null) {
+ broadcasterFactory = new DefaultBroadcasterFactory(bc, broadcasterLifeCyclePolicy, config);
+ }
+
+ for (BroadcasterListener b : broadcasterListeners) {
+ broadcasterFactory.addBroadcasterListener(b);
+ }
+
+ BroadcasterFactory.setBroadcasterFactory(broadcasterFactory, config);
+ InjectorProvider.getInjector().inject(broadcasterFactory);
+
+ Iterator<Entry<String, AtmosphereHandlerWrapper>> i = atmosphereHandlers.entrySet().iterator();
+ AtmosphereHandlerWrapper w;
+ Entry<String, AtmosphereHandlerWrapper> e;
+ while (i.hasNext()) {
+ e = i.next();
+ w = e.getValue();
+
+ if (w.broadcaster == null) {
+ w.broadcaster = broadcasterFactory.get(w.mapping);
+ } else {
+ if (broadcasterCacheClassName != null) {
+ BroadcasterCache cache = (BroadcasterCache) Thread.currentThread().getContextClassLoader()
+ .loadClass(broadcasterCacheClassName).newInstance();
+ InjectorProvider.getInjector().inject(cache);
+ w.broadcaster.getBroadcasterConfig().setBroadcasterCache(cache);
+ }
+ }
+ }
+ } catch (Exception ex) {
+ logger.error("Unable to configure Broadcaster/Factory/Cache", ex);
+ }
+ }
+
+ protected void doInitParamsForWebSocket(ServletConfig sc) {
+ String s = sc.getInitParameter(WEBSOCKET_SUPPORT);
+ if (s != null) {
+ webSocketEnabled = Boolean.parseBoolean(s);
+ sessionSupport(false);
+ }
+ s = sc.getInitParameter(WEBSOCKET_PROTOCOL);
+ if (s != null) {
+ webSocketProtocolClassName = s;
+ }
+
+ s = sc.getInitParameter(WEBSOCKET_PROCESSOR);
+ if (s != null) {
+ webSocketProcessorClassName = s;
+ }
+ }
+
+ /**
+ * Read init param from web.xml and apply them.
+ *
+ * @param sc {@link ServletConfig}
+ */
+ protected void doInitParams(ServletConfig sc) {
+ String s = sc.getInitParameter(PROPERTY_NATIVE_COMETSUPPORT);
+ if (s != null) {
+ useNativeImplementation = Boolean.parseBoolean(s);
+ if (useNativeImplementation) isCometSupportSpecified = true;
+ }
+ s = sc.getInitParameter(PROPERTY_BLOCKING_COMETSUPPORT);
+ if (s != null) {
+ useBlockingImplementation = Boolean.parseBoolean(s);
+ if (useBlockingImplementation) isCometSupportSpecified = true;
+ }
+ s = sc.getInitParameter(PROPERTY_USE_STREAM);
+ if (s != null) {
+ useStreamForFlushingComments = Boolean.parseBoolean(s);
+ }
+ s = sc.getInitParameter(PROPERTY_COMET_SUPPORT);
+ if (s != null) {
+ asyncSupport = new DefaultAsyncSupportResolver(config).newCometSupport(s);
+ isCometSupportSpecified = true;
+ }
+ s = sc.getInitParameter(BROADCASTER_CLASS);
+ if (s != null) {
+ broadcasterClassName = s;
+ isBroadcasterSpecified = true;
+ }
+ s = sc.getInitParameter(BROADCASTER_CACHE);
+ if (s != null) {
+ broadcasterCacheClassName = s;
+ }
+ s = sc.getInitParameter(PROPERTY_SESSION_SUPPORT);
+ if (s != null) {
+ config.setSupportSession(Boolean.valueOf(s));
+ isSessionSupportSpecified = true;
+ }
+ s = sc.getInitParameter(DISABLE_ONSTATE_EVENT);
+ if (s != null) {
+ initParams.put(DISABLE_ONSTATE_EVENT, s);
+ } else {
+ initParams.put(DISABLE_ONSTATE_EVENT, "false");
+ }
+ s = sc.getInitParameter(RESUME_AND_KEEPALIVE);
+ if (s != null) {
+ initParams.put(RESUME_AND_KEEPALIVE, s);
+ }
+ s = sc.getInitParameter(BROADCAST_FILTER_CLASSES);
+ if (s != null) {
+ broadcasterFilters.addAll(Arrays.asList(s.split(",")));
+ logger.info("Installing BroadcastFilter class(es) {}", s);
+ }
+ s = sc.getInitParameter(BROADCASTER_LIFECYCLE_POLICY);
+ if (s != null) {
+ broadcasterLifeCyclePolicy = s;
+ }
+ s = sc.getInitParameter(BROADCASTER_FACTORY);
+ if (s != null) {
+ broadcasterFactoryClassName = s;
+ }
+ s = sc.getInitParameter(ATMOSPHERE_HANDLER_PATH);
+ if (s != null) {
+ handlersPath = s;
+ }
+ s = sc.getInitParameter(PROPERTY_ATMOSPHERE_XML);
+ if (s != null) {
+ atmosphereDotXmlPath = s;
+ }
+ }
+
+ public void loadConfiguration(ServletConfig sc) throws ServletException {
+
+ if (!autoDetectHandlers) return;
+
+ try {
+ URL url = sc.getServletContext().getResource(handlersPath);
+ URLClassLoader urlC = new URLClassLoader(new URL[]{url},
+ Thread.currentThread().getContextClassLoader());
+ loadAtmosphereDotXml(sc.getServletContext().
+ getResourceAsStream(atmosphereDotXmlPath), urlC);
+
+ if (atmosphereHandlers.size() == 0) {
+ autoDetectAtmosphereHandlers(sc.getServletContext(), urlC);
+
+ if (atmosphereHandlers.size() == 0) {
+ detectSupportedFramework(sc);
+ }
+ }
+
+ autoDetectWebSocketHandler(sc.getServletContext(), urlC);
+ } catch (Throwable t) {
+ throw new ServletException(t);
+ }
+ }
+
+ /**
+ * Auto-detect Jersey when no atmosphere.xml file are specified.
+ *
+ * @param sc {@link ServletConfig}
+ * @return true if Jersey classes are detected
+ * @throws ClassNotFoundException
+ */
+ protected boolean detectSupportedFramework(ServletConfig sc) throws ClassNotFoundException, IllegalAccessException,
+ InstantiationException, NoSuchMethodException, InvocationTargetException {
+
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ String broadcasterClassNameTmp = null;
+
+ try {
+ cl.loadClass(JERSEY_CONTAINER);
+
+ if (!isBroadcasterSpecified) {
+ broadcasterClassNameTmp = lookupDefaultBroadcasterType(JERSEY_BROADCASTER);
+
+ cl.loadClass(broadcasterClassNameTmp);
+ }
+ useStreamForFlushingComments = true;
+ } catch (Throwable t) {
+ logger.trace("", t);
+ return false;
+ }
+
+ logger.warn("Missing META-INF/atmosphere.xml but found the Jersey runtime. Starting Jersey");
+
+ // Jersey will handle itself the headers.
+ initParams.put(WRITE_HEADERS, "false");
+
+ ReflectorServletProcessor rsp = new ReflectorServletProcessor();
+ if (broadcasterClassNameTmp != null) broadcasterClassName = broadcasterClassNameTmp;
+ rsp.setServletClassName(JERSEY_CONTAINER);
+ sessionSupport(false);
+ initParams.put(DISABLE_ONSTATE_EVENT, "true");
+
+ String mapping = sc.getInitParameter(PROPERTY_SERVLET_MAPPING);
+ if (mapping == null) {
+ mapping = "/*";
+ }
+ Class<? extends Broadcaster> bc = (Class<? extends Broadcaster>) cl.loadClass(broadcasterClassName);
+
+
+ if (broadcasterFactory != null) {
+ broadcasterFactory.destroy();
+ }
+ broadcasterFactory = new DefaultBroadcasterFactory(bc, broadcasterLifeCyclePolicy, config);
+ BroadcasterFactory.setBroadcasterFactory(broadcasterFactory, config);
+
+ for (BroadcasterListener b : broadcasterListeners) {
+ broadcasterFactory.addBroadcasterListener(b);
+ }
+
+ Broadcaster b;
+
+ try {
+ b = broadcasterFactory.get(bc, mapping);
+ } catch (IllegalStateException ex) {
+ logger.warn("Two Broadcaster's named {}. Renaming the second one to {}", mapping, sc.getServletName() + mapping);
+ b = broadcasterFactory.get(bc, sc.getServletName() + mapping);
+ }
+
+ addAtmosphereHandler(mapping, rsp, b);
+ return true;
+ }
+
+ protected String lookupDefaultBroadcasterType(String defaultB) {
+ for (String b : broadcasterTypes) {
+ try {
+ Class.forName(b);
+ return b;
+ } catch (ClassNotFoundException e) {
+ }
+ }
+ return defaultB;
+ }
+
+ protected void sessionSupport(boolean sessionSupport) {
+ if (!isSessionSupportSpecified) {
+ config.setSupportSession(sessionSupport);
+ } else if (!config.isSupportSession()) {
+ // Don't turn off session support. Once it's on, leave it on.
+ config.setSupportSession(sessionSupport);
+ }
+ }
+
+ /**
+ * Initialize {@link AtmosphereServletProcessor}
+ *
+ * @param sc the {@link ServletConfig}
+ * @throws javax.servlet.ServletException
+ */
+ public void initAtmosphereHandler(ServletConfig sc) throws ServletException {
+ AtmosphereHandler a;
+ AtmosphereHandlerWrapper w;
+ for (Entry<String, AtmosphereHandlerWrapper> h : atmosphereHandlers.entrySet()) {
+ w = h.getValue();
+ a = w.atmosphereHandler;
+ if (a instanceof AtmosphereServletProcessor) {
+ ((AtmosphereServletProcessor) a).init(sc);
+ }
+ }
+
+ if (atmosphereHandlers.size() == 0 && !SimpleHttpProtocol.class.isAssignableFrom(webSocketProtocol.getClass())) {
+ logger.debug("Adding a void AtmosphereHandler mapped to /* to allow WebSocket application only");
+ addAtmosphereHandler("/*", new AbstractReflectorAtmosphereHandler() {
+ @Override
+ public void onRequest(AtmosphereResource r) throws IOException {
+ logger.debug("No AtmosphereHandler defined.");
+ }
+
+ @Override
+ public void destroy() {
+ }
+ });
+ }
+ }
+
+ protected void initWebSocket() {
+ if (webSocketProtocol == null) {
+ try {
+ webSocketProtocol = (WebSocketProtocol) Thread.currentThread().getContextClassLoader()
+ .loadClass(webSocketProtocolClassName).newInstance();
+ logger.info("Installed WebSocketProtocol {} ", webSocketProtocolClassName);
+ } catch (Exception ex) {
+ try {
+ webSocketProtocol = (WebSocketProtocol) AtmosphereFramework.class.getClassLoader()
+ .loadClass(webSocketProtocolClassName).newInstance();
+ logger.info("Installed WebSocketProtocol {} ", webSocketProtocolClassName);
+ } catch (Exception ex2) {
+ logger.error("Cannot load the WebSocketProtocol {}", getWebSocketProtocolClassName(), ex);
+ webSocketProtocol = new SimpleHttpProtocol();
+ }
+ }
+ }
+ webSocketProtocol.configure(config);
+ }
+
+ public AtmosphereFramework destroy() {
+ if (asyncSupport != null && AsynchronousProcessor.class.isAssignableFrom(asyncSupport.getClass())) {
+ ((AsynchronousProcessor) asyncSupport).shutdown();
+ }
+
+ // We just need one bc to shutdown the shared thread pool
+ for (Entry<String, AtmosphereHandlerWrapper> entry : atmosphereHandlers.entrySet()) {
+ AtmosphereHandlerWrapper handlerWrapper = entry.getValue();
+ handlerWrapper.atmosphereHandler.destroy();
+ }
+
+ BroadcasterFactory factory = broadcasterFactory;
+ if (factory != null) {
+ factory.destroy();
+ BroadcasterFactory.factory = null;
+ }
+ WebSocketProcessorFactory.getDefault().destroy();
+ return this;
+ }
+
+ /**
+ * Load AtmosphereHandler defined under META-INF/atmosphere.xml
+ *
+ * @param stream The input stream we read from.
+ * @param c The classloader
+ */
+ protected void loadAtmosphereDotXml(InputStream stream, URLClassLoader c)
+ throws IOException, ServletException {
+
+ if (stream == null) {
+ return;
+ }
+
+ AtmosphereConfigReader.getInstance().parse(config, stream);
+ for (AtmosphereHandlerConfig atmoHandler : config.getAtmosphereHandlerConfig()) {
+ try {
+ AtmosphereHandler handler;
+
+ if (!ReflectorServletProcessor.class.getName().equals(atmoHandler.getClassName())) {
+ handler = (AtmosphereHandler) c.loadClass(atmoHandler.getClassName()).newInstance();
+ } else {
+ handler = new ReflectorServletProcessor();
+ }
+
+ logger.info("Installed AtmosphereHandler {} mapped to context-path: {}", handler, atmoHandler.getContextRoot());
+
+ for (ApplicationConfiguration a : atmoHandler.getApplicationConfig()) {
+ initParams.put(a.getParamName(), a.getParamValue());
+ }
+
+ for (FrameworkConfiguration a : atmoHandler.getFrameworkConfig()) {
+ initParams.put(a.getParamName(), a.getParamValue());
+ }
+
+ for (AtmosphereHandlerProperty handlerProperty : atmoHandler.getProperties()) {
+
+ if (handlerProperty.getValue() != null && handlerProperty.getValue().indexOf("jersey") != -1) {
+ initParams.put(DISABLE_ONSTATE_EVENT, "true");
+ useStreamForFlushingComments = true;
+ broadcasterClassName = lookupDefaultBroadcasterType(JERSEY_BROADCASTER);
+ broadcasterFactory = null;
+ configureBroadcaster();
+ }
+
+ IntrospectionUtils.setProperty(handler, handlerProperty.getName(), handlerProperty.getValue());
+ IntrospectionUtils.addProperty(handler, handlerProperty.getName(), handlerProperty.getValue());
+ }
+
+ sessionSupport(Boolean.valueOf(atmoHandler.getSupportSession()));
+
+ String broadcasterClass = atmoHandler.getBroadcaster();
+ Broadcaster b;
+ /**
+ * If there is more than one AtmosphereHandler defined, their Broadcaster
+ * may clash each other with the BroadcasterFactory. In that case we will use the
+ * last one defined.
+ */
+ if (broadcasterClass != null) {
+ broadcasterClassName = broadcasterClass;
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ Class<? extends Broadcaster> bc = (Class<? extends Broadcaster>) cl.loadClass(broadcasterClassName);
+ broadcasterFactory = new DefaultBroadcasterFactory(bc, broadcasterLifeCyclePolicy, config);
+ BroadcasterFactory.setBroadcasterFactory(broadcasterFactory, config);
+ }
+
+ b = broadcasterFactory.lookup(atmoHandler.getContextRoot(), true);
+
+ AtmosphereHandlerWrapper wrapper = new AtmosphereHandlerWrapper(handler, b);
+ addMapping(atmoHandler.getContextRoot(), wrapper);
+
+ String bc = atmoHandler.getBroadcasterCache();
+ if (bc != null) {
+ broadcasterCacheClassName = bc;
+ }
+
+ if (atmoHandler.getCometSupport() != null) {
+ asyncSupport = (AsyncSupport) c.loadClass(atmoHandler.getCometSupport())
+ .getDeclaredConstructor(new Class[]{AtmosphereConfig.class})
+ .newInstance(new Object[]{config});
+ }
+
+ if (atmoHandler.getBroadcastFilterClasses() != null) {
+ broadcasterFilters.addAll(atmoHandler.getBroadcastFilterClasses());
+ }
+
+ List<AtmosphereInterceptor> l = new ArrayList<AtmosphereInterceptor>();
+ if (atmoHandler.getAtmosphereInterceptorClasses() != null) {
+ for (String a : atmoHandler.getAtmosphereInterceptorClasses()) {
+ try {
+ AtmosphereInterceptor ai = (AtmosphereInterceptor) c.loadClass(a).newInstance();
+ ai.configure(config);
+ l.add(ai);
+ } catch (Throwable e) {
+ logger.warn("", e);
+ }
+ }
+ }
+ wrapper.interceptors = l;
+ if (l.size() > 0) {
+ logger.info("Installed AtmosphereInterceptor {} mapped to AtmosphereHandler {}", l, atmoHandler.getClassName());
+ }
+ } catch (Throwable t) {
+ logger.warn("Unable to load AtmosphereHandler class: " + atmoHandler.getClassName(), t);
+ throw new ServletException(t);
+ }
+
+ }
+ }
+
+ /**
+ * Set the {@link AsyncSupport} implementation. Make sure you don't set
+ * an implementation that only works on some Container. See {@link BlockingIOCometSupport}
+ * for an example.
+ *
+ * @param asyncSupport
+ */
+ public AtmosphereFramework setAsyncSupport(AsyncSupport asyncSupport) {
+ this.asyncSupport = asyncSupport;
+ return this;
+ }
+
+ /**
+ * @param asyncSupport
+ * @return
+ * @Deprecated - Use {@link #setAsyncSupport(AsyncSupport)}
+ */
+ public AtmosphereFramework setCometSupport(AsyncSupport asyncSupport) {
+ return setAsyncSupport(asyncSupport);
+ }
+
+ /**
+ * Return the current {@link AsyncSupport}
+ *
+ * @return the current {@link AsyncSupport}
+ */
+ public AsyncSupport getAsyncSupport() {
+ return asyncSupport;
+ }
+
+ /**
+ * Return the current {@link AsyncSupport}
+ *
+ * @return the current {@link AsyncSupport}
+ * @deprecated Use getAsyncSupport
+ */
+ public AsyncSupport getCometSupport() {
+ return asyncSupport;
+ }
+
+ /**
+ * Returns an instance of AsyncSupportResolver {@link AsyncSupportResolver}
+ *
+ * @return CometSupportResolver
+ */
+ protected AsyncSupportResolver createAsyncSupportResolver() {
+ return new DefaultAsyncSupportResolver(config);
+ }
+
+
+ /**
+ * Auto detect the underlying Servlet Container we are running on.
+ */
+ protected void autoDetectContainer() {
+ // Was defined in atmosphere.xml
+ if (getAsyncSupport() == null) {
+ setAsyncSupport(createAsyncSupportResolver()
+ .resolve(useNativeImplementation, useBlockingImplementation, webSocketEnabled));
+ }
+
+ logger.info("Atmosphere is using async support: {} running under container: {}",
+ getAsyncSupport().getClass().getName(), asyncSupport.getContainerName());
+ }
+
+ /**
+ * Auto detect instance of {@link AtmosphereHandler} in case META-INF/atmosphere.xml
+ * is missing.
+ *
+ * @param servletContext {@link ServletContext}
+ * @param classloader {@link URLClassLoader} to load the class.
+ * @throws java.net.MalformedURLException
+ * @throws java.net.URISyntaxException
+ */
+ public void autoDetectAtmosphereHandlers(ServletContext servletContext, URLClassLoader classloader)
+ throws MalformedURLException, URISyntaxException {
+
+ // If Handler has been added
+ if (atmosphereHandlers.size() > 0) return;
+
+ logger.info("Auto detecting atmosphere handlers {}", handlersPath);
+
+ String realPath = servletContext.getRealPath(handlersPath);
+
+ // Weblogic bug
+ if (realPath == null) {
+ URL u = servletContext.getResource(handlersPath);
+ if (u == null) return;
+ realPath = u.getPath();
+ }
+
+ loadAtmosphereHandlersFromPath(classloader, realPath);
+ }
+
+ public void loadAtmosphereHandlersFromPath(URLClassLoader classloader, String realPath) {
+ File file = new File(realPath);
+
+ if (file.isDirectory()) {
+ getFiles(file);
+ scanDone = true;
+
+ for (String className : possibleComponentsCandidate) {
+ try {
+ className = className.replace('\\', '/');
+ className = className.replaceFirst("^.*/(WEB-INF|target)(?:/scala-[^/]+)?/(test-)?classes/(.*)\\.class", "$3").replace("/", ".");
+ Class<?> clazz = classloader.loadClass(className);
+
+ if (AtmosphereHandler.class.isAssignableFrom(clazz)) {
+ AtmosphereHandler handler = (AtmosphereHandler) clazz.newInstance();
+ InjectorProvider.getInjector().inject(handler);
+ addMapping("/" + handler.getClass().getSimpleName(),
+ new AtmosphereHandlerWrapper(broadcasterFactory, handler, "/" + handler.getClass().getSimpleName()));
+ logger.info("Installed AtmosphereHandler {} mapped to context-path: {}", handler, handler.getClass().getName());
+ }
+ } catch (Throwable t) {
+ logger.trace("failed to load class as an AtmosphereHandler: " + className, t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Auto detect instance of {@link org.atmosphere.websocket.WebSocketHandler} in case META-INF/atmosphere.xml
+ * is missing.
+ *
+ * @param servletContext {@link ServletContext}
+ * @param classloader {@link URLClassLoader} to load the class.
+ * @throws java.net.MalformedURLException
+ * @throws java.net.URISyntaxException
+ */
+ protected void autoDetectWebSocketHandler(ServletContext servletContext, URLClassLoader classloader)
+ throws MalformedURLException, URISyntaxException {
+
+ if (hasNewWebSocketProtocol) return;
+
+ logger.info("Auto detecting WebSocketHandler in {}", handlersPath);
+
+ String realPath = servletContext.getRealPath(handlersPath);
+
+ // Weblogic bug
+ if (realPath == null) {
+ URL u = servletContext.getResource(handlersPath);
+ if (u == null) return;
+ realPath = u.getPath();
+ }
+
+ loadWebSocketFromPath(classloader, realPath);
+ }
+
+ protected void loadWebSocketFromPath(URLClassLoader classloader, String realPath) {
+ File file = new File(realPath);
+
+ if (file.isDirectory()) {
+ getFiles(file);
+ scanDone = true;
+
+ for (String className : possibleComponentsCandidate) {
+ try {
+ className = className.replace('\\', '/');
+ className = className.replaceFirst("^.*/(WEB-INF|target)(?:/scala-[^/]+)?/(test-)?classes/(.*)\\.class", "$3").replace("/", ".");
+ Class<?> clazz = classloader.loadClass(className);
+
+ if (WebSocketProtocol.class.isAssignableFrom(clazz)) {
+ webSocketProtocol = (WebSocketProtocol) clazz.newInstance();
+ InjectorProvider.getInjector().inject(webSocketProtocol);
+ logger.info("Installed WebSocketProtocol {}", webSocketProtocol);
+ }
+ } catch (Throwable t) {
+ logger.trace("failed to load class as an WebSocketProtocol: " + className, t);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Get the list of possible candidate to load as {@link AtmosphereHandler}
+ *
+ * @param f the real path {@link File}
+ */
+ private void getFiles(File f) {
+ if (scanDone) return;
+
+ File[] files = f.listFiles();
+ for (File test : files) {
+ if (test.isDirectory()) {
+ getFiles(test);
+ } else {
+ String clazz = test.getAbsolutePath();
+ if (clazz.endsWith(".class")) {
+ possibleComponentsCandidate.add(clazz);
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoke the proprietary {@link AsyncSupport}
+ *
+ * @param req
+ * @param res
+ * @return an {@link Action}
+ * @throws IOException
+ * @throws ServletException
+ */
+ public Action doCometSupport(AtmosphereRequest req, AtmosphereResponse res) throws IOException, ServletException {
+ req.setAttribute(BROADCASTER_FACTORY, broadcasterFactory);
+ req.setAttribute(PROPERTY_USE_STREAM, useStreamForFlushingComments);
+ req.setAttribute(BROADCASTER_CLASS, broadcasterClassName);
+ req.setAttribute(ATMOSPHERE_CONFIG, config);
+
+ Action a = null;
+ try {
+ boolean skip = true;
+ String s = config.getInitParameter(ALLOW_QUERYSTRING_AS_REQUEST);
+ if (s != null) {
+ skip = Boolean.valueOf(s);
+ }
+ if (!skip || req.getAttribute(WEBSOCKET_SUSPEND) == null) {
+ Map<String, String> headers = configureQueryStringAsRequest(req);
+ String body = headers.remove(ATMOSPHERE_POST_BODY);
+ if (body != null && body.isEmpty()) {
+ body = null;
+ }
+
+ req.headers(headers)
+ .method(body != null && req.getMethod().equalsIgnoreCase("GET") ? "POST" : req.getMethod());
+
+ if (body != null) {
+ req.body(body);
+ }
+ }
+
+ s = req.getHeader(X_ATMOSPHERE_TRACKING_ID);
+
+ // Lookup for websocket
+ if (s == null || s.equals("0")) {
+ String unique = config.getInitParameter(ApplicationConfig.UNIQUE_UUID_WEBSOCKET);
+ if (unique != null && Boolean.valueOf(unique)) {
+ s = (String) req.getAttribute(SUSPENDED_ATMOSPHERE_RESOURCE_UUID);
+ }
+ }
+
+ if (s == null || s.equals("0")) {
+ s = UUID.randomUUID().toString();
+ res.setHeader(X_ATMOSPHERE_TRACKING_ID, s);
+ } else {
+ // This may breaks 1.0.0 application because the WebSocket's associated AtmosphereResource will
+ // all have the same UUID, and retrieving the original one for WebSocket, so we don't set it at all.
+ // Null means it is not an HTTP request.
+ if (req.resource() == null) {
+ res.setHeader(X_ATMOSPHERE_TRACKING_ID, s);
+ } else if (req.getAttribute(WebSocket.WEBSOCKET_INITIATED) == null) {
+ // WebSocket reconnect, in case an application manually set the header
+ // (impossible to retrieve the headers normally with WebSocket or SSE)
+ res.setHeader(X_ATMOSPHERE_TRACKING_ID, s);
+ }
+ }
+
+ if (req.getAttribute(SUSPENDED_ATMOSPHERE_RESOURCE_UUID) == null) {
+ req.setAttribute(SUSPENDED_ATMOSPHERE_RESOURCE_UUID, s);
+ }
+
+ a = asyncSupport.service(req, res);
+ } catch (IllegalStateException ex) {
+ if (ex.getMessage() != null && (ex.getMessage().startsWith("Tomcat failed") || ex.getMessage().startsWith("JBoss failed"))) {
+ if (!isFilter) {
+ logger.warn("Failed using comet support: {}, error: {} Is the Nio or Apr Connector enabled?", asyncSupport.getClass().getName(),
+ ex.getMessage());
+ }
+ logger.trace(ex.getMessage(), ex);
+
+ AsyncSupport current = asyncSupport;
+ asyncSupport = asyncSupport.supportWebSocket() ? new Tomcat7BIOSupportWithWebSocket(config) : new BlockingIOCometSupport(config);
+ if (current instanceof AsynchronousProcessor) {
+ ((AsynchronousProcessor) current).shutdown();
+ }
+
+ asyncSupport.init(config.getServletConfig());
+ logger.warn("Using " + asyncSupport.getClass().getName());
+
+ a = asyncSupport.service(req, res);
+ } else {
+ logger.error("AtmosphereFramework exception", ex);
+ throw ex;
+ }
+ } finally {
+ if (a != null) {
+ notify(a.type(), req, res);
+ }
+
+ if (req != null && a != null && a.type() != Action.TYPE.SUSPEND) {
+ req.destroy();
+ res.destroy();
+ notify(Action.TYPE.DESTROYED, req, res);
+ }
+ }
+ return a;
+ }
+
+ /**
+ * Return the default {@link Broadcaster} class name.
+ *
+ * @return the broadcasterClassName
+ */
+ public String getDefaultBroadcasterClassName() {
+ return broadcasterClassName;
+ }
+
+ /**
+ * Set the default {@link Broadcaster} class name
+ *
+ * @param bccn the broadcasterClassName to set
+ */
+ public AtmosphereFramework setDefaultBroadcasterClassName(String bccn) {
+ broadcasterClassName = bccn;
+ return this;
+ }
+
+ /**
+ * <tt>true</tt> if Atmosphere uses {@link AtmosphereResponse#getOutputStream()}
+ * by default for write operation.
+ *
+ * @return the useStreamForFlushingComments
+ */
+ public boolean isUseStreamForFlushingComments() {
+ return useStreamForFlushingComments;
+ }
+
+ /**
+ * Set to <tt>true</tt> so Atmosphere uses {@link AtmosphereResponse#getOutputStream()}
+ * by default for write operation. Default is false.
+ *
+ * @param useStreamForFlushingComments the useStreamForFlushingComments to set
+ */
+ public AtmosphereFramework setUseStreamForFlushingComments(boolean useStreamForFlushingComments) {
+ this.useStreamForFlushingComments = useStreamForFlushingComments;
+ return this;
+ }
+
+ /**
+ * Get the {@link BroadcasterFactory} which is used by Atmosphere to construct
+ * {@link Broadcaster}
+ *
+ * @return {@link BroadcasterFactory}
+ */
+ public BroadcasterFactory getBroadcasterFactory() {
+ return broadcasterFactory;
+ }
+
+ /**
+ * Set the {@link BroadcasterFactory} which is used by Atmosphere to construct
+ * {@link Broadcaster}
+ *
+ * @return {@link BroadcasterFactory}
+ */
+ public AtmosphereFramework setBroadcasterFactory(final BroadcasterFactory broadcasterFactory) {
+ this.broadcasterFactory = broadcasterFactory;
+ configureBroadcaster();
+ return this;
+ }
+
+ /**
+ * Return the {@link org.atmosphere.cpr.BroadcasterCache} class name.
+ *
+ * @return the {@link org.atmosphere.cpr.BroadcasterCache} class name.
+ */
+ public String getBroadcasterCacheClassName() {
+ return broadcasterCacheClassName;
+ }
+
+ /**
+ * Set the {@link org.atmosphere.cpr.BroadcasterCache} class name.
+ *
+ * @param broadcasterCacheClassName
+ */
+ public void setBroadcasterCacheClassName(String broadcasterCacheClassName) {
+ this.broadcasterCacheClassName = broadcasterCacheClassName;
+ configureBroadcaster();
+ }
+
+ /**
+ * Add a new Broadcaster class name AtmosphereServlet can use when initializing requests, and when
+ * atmosphere.xml broadcaster element is unspecified.
+ *
+ * @param broadcasterTypeString
+ */
+ public AtmosphereFramework addBroadcasterType(String broadcasterTypeString) {
+ broadcasterTypes.add(broadcasterTypeString);
+ return this;
+ }
+
+ public String getWebSocketProtocolClassName() {
+ return webSocketProtocolClassName;
+ }
+
+ public AtmosphereFramework setWebSocketProtocolClassName(String webSocketProtocolClassName) {
+ hasNewWebSocketProtocol = true;
+ this.webSocketProtocolClassName = webSocketProtocolClassName;
+ return this;
+ }
+
+ public Map<String, AtmosphereHandlerWrapper> getAtmosphereHandlers() {
+ return atmosphereHandlers;
+ }
+
+ protected Map<String, String> configureQueryStringAsRequest(AtmosphereRequest request) {
+ Map<String, String> headers = new HashMap<String, String>();
+
+ Enumeration<String> e = request.getParameterNames();
+ String s;
+ while (e.hasMoreElements()) {
+ s = e.nextElement();
+ if (s.equalsIgnoreCase("Content-Type")) {
+ // Use the one set by the user first.
+ if (request.getContentType() == null ||
+ !request.getContentType().equalsIgnoreCase(request.getParameter(s))) {
+ request.contentType(request.getParameter(s));
+ }
+ }
+ headers.put(s, request.getParameter(s));
+ }
+ logger.trace("Query String translated to headers {}", headers);
+ return headers;
+ }
+
+ protected boolean isIECandidate(AtmosphereRequest request) {
+ String userAgent = request.getHeader("User-Agent");
+ if (userAgent == null) return false;
+
+ if (userAgent.contains("MSIE") || userAgent.contains(".NET")) {
+ // Now check the header
+ String transport = request.getHeader(HeaderConfig.X_ATMOSPHERE_TRANSPORT);
+ if (transport != null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public WebSocketProtocol getWebSocketProtocol() {
+ // TODO: Spagetthi code, needs to rework.
+ // Make sure we initialized the WebSocketProtocol
+ initWebSocket();
+ return webSocketProtocol;
+ }
+
+ public boolean isUseNativeImplementation() {
+ return useNativeImplementation;
+ }
+
+ public AtmosphereFramework setUseNativeImplementation(boolean useNativeImplementation) {
+ this.useNativeImplementation = useNativeImplementation;
+ return this;
+ }
+
+ public boolean isUseBlockingImplementation() {
+ return useBlockingImplementation;
+ }
+
+ public AtmosphereFramework setUseBlockingImplementation(boolean useBlockingImplementation) {
+ this.useBlockingImplementation = useBlockingImplementation;
+ return this;
+ }
+
+ public String getAtmosphereDotXmlPath() {
+ return atmosphereDotXmlPath;
+ }
+
+ public AtmosphereFramework setAtmosphereDotXmlPath(String atmosphereDotXmlPath) {
+ this.atmosphereDotXmlPath = atmosphereDotXmlPath;
+ return this;
+ }
+
+ public String getHandlersPath() {
+ return handlersPath;
+ }
+
+ public AtmosphereFramework setHandlersPath(String handlersPath) {
+ this.handlersPath = handlersPath;
+ return this;
+ }
+
+ /**
+ * Return the location of the jars containing the application classes. Default is WEB-INF/lib
+ *
+ * @return the location of the jars containing the application classes. Default is WEB-INF/lib
+ */
+ public String getLibPath() {
+ return libPath;
+ }
+
+ /**
+ * Set the location of the jars containing the application.
+ *
+ * @param libPath the location of the jars containing the application.
+ * @return this
+ */
+ public AtmosphereFramework setLibPath(String libPath) {
+ this.libPath = libPath;
+ return this;
+ }
+
+ public String getWebSocketProcessorClassName() {
+ return webSocketProcessorClassName;
+ }
+
+ public AtmosphereFramework setWebsocketProcessorClassName(String webSocketProcessorClassName) {
+ this.webSocketProcessorClassName = webSocketProcessorClassName;
+ return this;
+ }
+
+ /**
+ * Add an {@link AtmosphereInterceptor} implementation. The adding order or {@link AtmosphereInterceptor} will be used, e.g
+ * the first added {@link AtmosphereInterceptor} will always be called first.
+ *
+ * @param c {@link AtmosphereInterceptor}
+ * @return this
+ */
+ public AtmosphereFramework interceptor(AtmosphereInterceptor c) {
+ boolean found = false;
+ for (AtmosphereInterceptor interceptor : interceptors) {
+ if (interceptor.getClass().equals(c.getClass())) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ interceptors.addLast(c);
+ logger.info("Installed AtmosphereInterceptor {}. ", c);
+ }
+ return this;
+ }
+
+ /**
+ * Return the list of {@link AtmosphereInterceptor}
+ *
+ * @return the list of {@link AtmosphereInterceptor}
+ */
+ public LinkedList<AtmosphereInterceptor> interceptors() {
+ return interceptors;
+ }
+
+ /**
+ * Set the {@link AnnotationProcessor} class name.
+ *
+ * @param annotationProcessorClassName the {@link AnnotationProcessor} class name.
+ * @return this
+ */
+ public AtmosphereFramework annotationProcessorClassName(String annotationProcessorClassName) {
+ this.annotationProcessorClassName = annotationProcessorClassName;
+ return this;
+ }
+
+ /**
+ * Add an {@link AsyncSupportListener}
+ *
+ * @param asyncSupportListener an {@link AsyncSupportListener}
+ * @return this;
+ */
+ public AtmosphereFramework asyncSupportListener(AsyncSupportListener asyncSupportListener) {
+ asyncSupportListeners.add(asyncSupportListener);
+ return this;
+ }
+
+ /**
+ * Return the list of an {@link AsyncSupportListener}
+ *
+ * @return
+ */
+ public List<AsyncSupportListener> asyncSupportListeners() {
+ return asyncSupportListeners;
+ }
+
+ /**
+ * Add {@link BroadcasterListener} to all created {@link Broadcaster}
+ */
+ public AtmosphereFramework addBroadcasterListener(BroadcasterListener b) {
+ if (isInit) {
+ broadcasterFactory.addBroadcasterListener(b);
+ } else {
+ broadcasterListeners.add(b);
+ }
+ return this;
+ }
+
+ /**
+ * Return a configured instance of {@link AtmosphereConfig}
+ *
+ * @return a configured instance of {@link AtmosphereConfig}
+ */
+ public AtmosphereConfig getAtmosphereConfig() {
+ return config;
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return servletConfig.getServletContext();
+ }
+
+ public ServletConfig getServletConfig() {
+ return servletConfig;
+ }
+
+ /**
+ * Return the list of {@link BroadcastFilter}
+ *
+ * @return the list of {@link BroadcastFilter
+ */
+ public List<String> broadcasterFilters() {
+ return broadcasterFilters;
+ }
+
+ /**
+ * Returns true if {@link java.util.concurrent.ExecutorService} shared amongst all components.
+ *
+ * @return true if {@link java.util.concurrent.ExecutorService} shared amongst all components.
+ */
+ public boolean isShareExecutorServices() {
+ return sharedThreadPools;
+ }
+
+ /**
+ * Set to true to have a {@link java.util.concurrent.ExecutorService} shared amongst all components.
+ *
+ * @param sharedThreadPools
+ * @return this
+ */
+ public AtmosphereFramework shareExecutorServices(boolean sharedThreadPools) {
+ this.sharedThreadPools = sharedThreadPools;
+ return this;
+ }
+
+ protected void autoConfigureService(ServletContext sc) throws IOException {
+ final ClassLoader cl = Thread.currentThread().getContextClassLoader();
+
+ String path = libPath != DEFAULT_LIB_PATH ? libPath : sc.getRealPath(handlersPath);
+ try {
+ AnnotationProcessor p = (AnnotationProcessor) cl.loadClass(annotationProcessorClassName).newInstance();
+ logger.info("Atmosphere is using {} for processing annotation", annotationProcessorClassName);
+
+ p.configure(this);
+ if (path != null) {
+ p.scan(new File(path));
+ }
+
+ String pathLibs = sc.getRealPath(DEFAULT_LIB_PATH);
+ if (pathLibs != null) {
+ File libFolder = new File(pathLibs);
+ File jars[] = libFolder.listFiles(new FilenameFilter() {
+
+ @Override
+ public boolean accept(File arg0, String arg1) {
+ return arg1.endsWith(".jar");
+ }
+ });
+
+ for (File file : jars) {
+ p.scan(file);
+ }
+ }
+ } catch (Throwable e) {
+ logger.debug("Atmosphere's Service Annotation Not Supported. Please add https://github.com/rmuller/infomas-asl as dependencies or your own AnnotationProcessor to support @Service");
+ logger.trace("", e);
+ return;
+ }
+ }
+
+ protected void notify(Action.TYPE type, AtmosphereRequest request, AtmosphereResponse response) {
+ for (AsyncSupportListener l : asyncSupportListeners()) {
+ try {
+ switch (type) {
+ case TIMEOUT:
+ l.onTimeout(request, response);
+ break;
+ case CANCELLED:
+ l.onClose(request, response);
+ break;
+ case SUSPEND:
+ l.onSuspend(request, response);
+ break;
+ case RESUME:
+ l.onSuspend(request, response);
+ break;
+ case DESTROYED:
+ l.onDestroyed(request, response);
+ break;
+ }
+ } catch (Throwable t) {
+ logger.warn("", t);
+ }
+ }
+ }
+} \ No newline at end of file