All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.grizzly.comet.CometEngine Maven / Gradle / Ivy

There is a newer version: 10.0-b28
Show newest version
/*
 * 
 * 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 com.sun.grizzly.comet;

import com.sun.grizzly.arp.AsyncHandler;
import com.sun.grizzly.arp.AsyncTask;
import com.sun.grizzly.Pipeline;
import com.sun.grizzly.http.SelectorThread;
import com.sun.grizzly.arp.AsyncProcessorTask;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Main class allowing Comet support on top of Grizzly Asynchronous
 * Request Processing mechanism. This class is the entry point to any
 * component interested to execute Comet request style. Components can be
 * Servlets, JSP, JSF or pure Java class. A component interested to support
 * Comet request must do:
 * 

 * (1) First, register the topic on which Comet support will be applied:
 *     CometEngine cometEngine = CometEngine.getEngine()
 *     CometContext cometContext = cometEngine.register(topic)
 * (2) Second, add an instance of {@link CometHandler} to the
 *     {@link CometContext} returned by the register method:
 *     {@link CometContext#addCometHandler}. Executing this operation
 *     will tells Grizzly to suspend the response.
 * (3) Finally, you can {@link CometContext#notify} other {@link CometHandler} 
 *     to share information between {@ CometHandler}. When notified, 
 *     {@link CometHandler} can decides to push back the data, resume the 
 *     response, or simply ignore the content of the notification.
 * 
* You can also select the stage where the suspension of the response happens when * registering the {@link CometContext}'s topic (see {@link #register}), which can be * before, during or after invoking a {@link Servlet} * * @author Jeanfrancois Arcand */ public class CometEngine { /** * The token used to support BEFORE_REQUEST_PROCESSING polling. */ public final static int BEFORE_REQUEST_PROCESSING = 0; /** * The token used to support AFTER_SERVLET_PROCESSING polling. */ public final static int AFTER_SERVLET_PROCESSING = 1; /** * The token used to support BEFORE_RESPONSE_PROCESSING polling. */ public final static int AFTER_RESPONSE_PROCESSING = 2; /** * Main logger */ private final static Logger logger = SelectorThread.logger(); /** * The {@link Pipeline} used to execute {@link CometTask} */ protected Pipeline pipeline; /** * The single instance of this class. */ private static CometEngine cometEngine; /** * The current active {@link CometContext} keyed by context path. */ protected ConcurrentHashMap activeContexts; /** * Cache of {@link CometTask} instance */ protected ConcurrentLinkedQueue cometTasks; /** * Cache of {@link CometContext} instance. */ protected ConcurrentLinkedQueue cometContexts; /** * The {@link CometSelector} used to poll requests. */ protected CometSelector cometSelector; /** * The default class to use when deciding which NotificationHandler * to use. The default is DefaultNotificationHandler. */ protected static String notificationHandlerClassName = DefaultNotificationHandler.class.getName(); /** * Temporary repository that associate a Thread ID with a Key. * NOTE: A ThreadLocal might be more efficient. */ protected ConcurrentHashMap threadsId; /** * Store modified CometContext. */ protected ConcurrentHashMap updatedCometContexts; /** * The list of registered {@link AsyncProcessorTask}. This object * are mainly keeping the state of the Comet request. */ private ConcurrentLinkedQueue asyncTasks; // Simple lock. private ReentrantLock lock = new ReentrantLock(); // --------------------------------------------------------------------- // /** * Creat a singleton and initialize all lists required. Also create and * start the {@link CometSelector} */ protected CometEngine() { activeContexts = new ConcurrentHashMap(); cometTasks = new ConcurrentLinkedQueue(); cometContexts = new ConcurrentLinkedQueue(); cometSelector = new CometSelector(this); try{ cometSelector.start(); } catch(InterruptedException ex){ logger.log(Level.SEVERE,"Unable to start CometSelector",ex); } threadsId = new ConcurrentHashMap(); updatedCometContexts = new ConcurrentHashMap(); asyncTasks = new ConcurrentLinkedQueue(); } /** * Return a singleton of this Class. * @return CometEngine the singleton. */ public synchronized static CometEngine getEngine(){ if (cometEngine == null) { cometEngine = new CometEngine(); } return cometEngine; } /** * Unregister the {@link CometHandler} to the list of the * {@link CometContext}. */ public synchronized CometContext unregister(String topic){ CometContext cometContext = activeContexts.get(topic); try{ cometContext.notify(cometContext,CometEvent.TERMINATE); } catch (IOException ex){ logger.log(Level.WARNING,"unregister",ex); } finalizeContext(cometContext); return activeContexts.remove(topic); } /** * Register a context path with this {@link CometEngine}. The * {@link CometContext} returned will be of type * AFTER_SERVLET_PROCESSING, which means the request target (most probably * a Servlet) will be executed first and then polled. * @param topic the context path used to create the * {@link CometContext} * @return CometContext a configured {@link CometContext}. */ public CometContext register(String topic){ return register(topic,AFTER_SERVLET_PROCESSING); } /** * Register a context path with this {@link CometEngine}. The * {@link CometContext} returned will be of type * type. * @param topic the context path used to create the * {@link CometContext} * @return CometContext a configured {@link CometContext}. */ public synchronized CometContext register(String topic, int type){ CometContext cometContext = activeContexts.get(topic); if (cometContext == null){ cometContext = cometContexts.poll(); if (cometContext == null){ cometContext = new CometContext(topic,type); cometContext.setCometSelector(cometSelector); NotificationHandler notificationHandler = loadNotificationHandlerInstance (notificationHandlerClassName); cometContext.setNotificationHandler(notificationHandler); if (notificationHandler != null && (notificationHandler instanceof DefaultNotificationHandler)){ ((DefaultNotificationHandler)notificationHandler) .setPipeline(pipeline); } } activeContexts.put(topic,cometContext); } return cometContext; } /** * Handle an interrupted(or polled) request by matching the current context * path with the registered one. * If required, the bring the target component (Servlet) to the proper * execution stage and then {@link CometContext#notify} the {@link CometHandler} * @param apt the current apt representing the request. * @return boolean true if the request can be polled. */ protected boolean handle(AsyncProcessorTask apt) throws IOException{ if (pipeline == null){ pipeline = apt.getPipeline(); } String topic = apt.getAsyncExecutor().getProcessorTask().getRequestURI(); CometContext cometContext = null; if (topic != null){ cometContext = activeContexts.get(topic); try{ lock.lock(); if (cometContext != null){ NotificationHandler notificationHandler = cometContext.getNotificationHandler(); if (notificationHandler instanceof DefaultNotificationHandler){ ((DefaultNotificationHandler)notificationHandler) .setPipeline(pipeline); } } } finally { lock.unlock(); } } /* * If the cometContext is null, it means the context has never * been registered. The registration might happens during the * Servlet.service() execution so we need to keep a reference * to the current thread so we can later retrieve the associated * SelectionKey. The SelectionKey is required in order to park the * request. */ boolean activateContinuation = true; SelectionKey key = apt.getAsyncExecutor().getProcessorTask().getSelectionKey(); threadsId.put(Thread.currentThread().getId(),key); int continuationType = (cometContext == null)? AFTER_SERVLET_PROCESSING:cometContext.continuationType; /* * Execute the Servlet.service method. CometEngine.register() or * CometContext.addCometHandler() might be invoked during the * execution. */ executeServlet(continuationType,apt); /* * Will return a CometContext instance if and only if the * Servlet.service() have invoked CometContext.addCometHandler(). * If the returned CometContext is null, it means we need to * execute a synchronous request. */ cometContext = updatedCometContexts.remove(Thread.currentThread().getId()); if (cometContext == null){ activateContinuation = false; } boolean parkRequest = true; if (activateContinuation) { // Prevent the Servlet to suspend/resume the request in a single // transaction CometContext.addInProgressSelectionKey(key); // Disable keep-alive key.attach(null); boolean isBlocking = cometContext.isBlockingNotification(); // We must initialize in blocking mode in case the connection is resumed // during the invocation of that method. If non blocking, there is a possible // thread race. cometContext.setBlockingNotification(true); cometContext.initialize(key); cometContext.setBlockingNotification(isBlocking); /** * The CometHandler has been resumed during the onIntialize method * call if getCometHandler return null. */ if (cometContext.getCometHandler(key) != null){ asyncTasks.offer(apt); CometTask cometTask = getCometTask(cometContext,key, apt.getPipeline()); cometTask.setSelectorThread(apt.getSelectorThread()); cometTask.setExpirationDelay(cometContext.getExpirationDelay()); cometTask.setSelectorThread(apt.getSelectorThread()); cometContext.addActiveCometTask(cometTask); cometSelector.registerKey(key,cometTask); } else{ parkRequest = false; } // Now we can allow full control CometContext.removeInProgressSelectionKey(key); } else { parkRequest = false; } return parkRequest; } /** * Tell the CometEngine to activate Grizzly ARP on that CometContext. * This method is called when CometContext.addCometHandler() is * invoked. * @param threadId the Thread.getId(). * @param cometContext An instance of CometContext. * @return key The SelectionKey associated with the current request. */ protected SelectionKey activateContinuation(Long threadId, CometContext cometContext, boolean continueExecution){ if (!continueExecution){ updatedCometContexts.put(threadId,cometContext); } return threadsId.remove(threadId); } /** * Return a clean and configured {@link CometTask} * @param cometContext the CometContext to clean * @param key The current {@link SelectionKey} * @return a new CometContext */ protected CometTask getCometTask(CometContext cometContext,SelectionKey key, Pipeline ctxPipeline){ if (ctxPipeline == null){ ctxPipeline = pipeline; } CometTask cometTask = cometTasks.poll(); if (cometTask == null){ cometTask = new CometTask(); } cometTask.setCometContext(cometContext); cometTask.setSelectionKey(key); cometTask.setCometSelector(cometSelector); cometTask.setPipeline(ctxPipeline); return cometTask; } /** * Cleanup the {@link CometContext} * @param cometContext the CometContext to clean */ private void finalizeContext(CometContext cometContext) { Iterator iterator = activeContexts.keySet().iterator(); String topic; while(iterator.hasNext()){ topic = iterator.next(); if ( activeContexts.get(topic).equals(cometContext) ){ activeContexts.remove(topic); break; } } for (AsyncProcessorTask apt: asyncTasks){ flushResponse(apt); } cometContext.recycle(); cometContexts.offer(cometContext); } /** * Return the {@link CometContext} associated with the topic. * @param topic the topic used to creates the {@link CometContext} */ public CometContext getCometContext(String topic){ return activeContexts.get(topic); } /** * The {@link CometSelector} is expiring idle {@link SelectionKey}, * hence we need to resume the current request. * @param key the expired SelectionKey */ protected void interrupt(SelectionKey key) { CometTask cometTask = (CometTask)key.attachment(); key.attach(null); if (cometTask == null) throw new IllegalStateException("cometTask cannot be null"); SelectionKey akey = cometTask.getSelectionKey(); try{ if (akey == null || !akey.isValid()) return; Iterator iterator = asyncTasks.iterator(); AsyncHandler ah = null; while (iterator.hasNext()){ AsyncProcessorTask apt = iterator.next(); ah = apt.getAsyncExecutor().getAsyncHandler(); if (apt.getAsyncExecutor().getProcessorTask().getSelectionKey() == akey){ iterator.remove(); if (akey != null){ akey.attach(null); } /** * The connection was parked and resumed before * the CometEngine.handle() terminated. */ if (apt.getStage() != AsyncTask.POST_EXECUTE){ break; } flushResponse(apt); break; } } } finally { returnTask(cometTask); } } /** * Return a {@link Task} to the pool. */ protected void returnTask(CometTask cometTask){ cometTask.recycle(); cometTasks.offer(cometTask); } /** * Resume the long polling request by unblocking the current * {@link SelectionKey} */ protected void resume(SelectionKey key) { Iterator iterator = asyncTasks.iterator(); while (iterator.hasNext()){ AsyncProcessorTask apt = iterator.next(); if (apt.getAsyncExecutor().getProcessorTask().getSelectionKey() == key){ iterator.remove(); /** * The connection was parked and resumed before * the CometEngine.handle() terminated. */ if (apt.getStage() != AsyncTask.POST_EXECUTE){ break; } flushResponse(apt); break; } } } /** * Complete the asynchronous request. */ private void flushResponse(AsyncProcessorTask apt){ apt.setStage(AsyncTask.POST_EXECUTE); try{ apt.doTask(); } catch (IllegalStateException ex){ if (logger.isLoggable(Level.FINEST)){ logger.log(Level.FINEST,"flushResponse failed",ex); } } catch (IOException ex) { logger.log(Level.SEVERE,"flushResponse failed",ex); } } /** * Bring the cometContext path target (most probably a Servlet) to the processing * stage we need for Comet request processing. * @param cometContext The CometContext associated with the Servlet * @param apt the AsyncProcessorTask */ private void executeServlet(int continuationType, AsyncProcessorTask apt){ try{ switch (continuationType){ case BEFORE_REQUEST_PROCESSING: apt.setStage(AsyncTask.PRE_EXECUTE); break; case AFTER_SERVLET_PROCESSING: apt.getAsyncExecutor().getProcessorTask().invokeAdapter(); return; case AFTER_RESPONSE_PROCESSING: apt.setStage(AsyncTask.POST_EXECUTE); // Last step, execute directly from here. apt.doTask(); break; default: throw new IllegalStateException("Invalid state"); } /** * We have finished the processing, most probably because we * entered the {@link FileCache} or because we of * the {@link #AFTER_RESPONSE_PROCESSING} configuration. */ if (apt.getStage() == AsyncTask.POST_EXECUTE){ return; } apt.doTask(); } catch (IOException ex){ logger.log(Level.SEVERE,"executeServlet",ex); } } /** * Return the default {@link NotificationHandler} class name. * @return the default {@link NotificationHandler} class name. */ public static String getNotificationHandlerClassName() { return notificationHandlerClassName; } /** * Set the default {@link NotificationHandler} class name. * @param the default {@link NotificationHandler} class name. */ public static void setNotificationHandlerClassName(String aNotificationHandlerClassName) { notificationHandlerClassName = aNotificationHandlerClassName; } /** * Util to load classes using reflection. */ protected final static NotificationHandler loadNotificationHandlerInstance(String className){ Class clazz = null; try{ clazz = Class.forName(className,true, Thread.currentThread().getContextClassLoader()); return (NotificationHandler)clazz.newInstance(); } catch (Throwable t) { logger.log(Level.WARNING,"Invalid NotificationHandler: ",t); } return new DefaultNotificationHandler(); } /** * Return the current logger. */ public final static Logger logger(){ return logger; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy