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

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

There is a newer version: 1.9.65
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.http.SelectorThread;
import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The main object used by {@link CometHandler}. 
 * The {@link CometContext} is always available for {@link CometHandler}
 * and can be used to notify other {@link CometHandler}.
 *
 * Attributes can be added/removed the same way HttpServletSession 
 * is doing. It is not recommended to use attributes if this 
 * {@link CometContext} is not shared amongs multiple
 * context path (uses HttpServletSession instead).
 *
 * @author Jeanfrancois Arcand
 */
public class CometContext {
    
    /**
     * Generic error message
     */
    protected final static String INVALID_COMET_HANDLER = "CometHandler cannot be null. " 
            + "This CometHandler was probably resumed and an invalid " 
            +  "reference was made to it.";
    
    /**
     * Main logger
     */
    private final static Logger logger = SelectorThread.logger();  
 
     
    /**
     * Attributes placeholder.
     */
    private ConcurrentHashMap attributes;
    
    
    /**
     * The context path associated with this instance.
     */
    private String contextPath;
    
    
    /**
     * Is the {@link CometContext} instance been cancelled.
     */
    protected boolean cancelled = false;
    
    
    /**
     * The list of registered {@link CometHandler}
     */
    protected ConcurrentHashMap handlers;
    
    
    /**
     * The {@link CometSelector} used to register {@link SelectionKey}
     * for upcoming bytes.
     */
    private CometSelector cometSelector;
    
    
    /**
     * The {@link CometContext} continuationType. See {@link CometEngine}
     */
    protected int continuationType = CometEngine.AFTER_SERVLET_PROCESSING;
    
    
    /**
     * The default delay expiration before a {@link CometContext}'s
     * {@link CometHandler} are interrupted.
     */
    private long expirationDelay = 30 * 1000;
    
    
    /**
     * true if the caller of CometContext.notify should block when 
     * notifying other CometHandler.
     */
    protected boolean blockingNotification = false;

    
    /**
     * The default NotificationHandler.
     */
    protected NotificationHandler notificationHandler; 
    
    
    /**
     * SelectionKey that are in the process of being parked.
     */
    private static ConcurrentLinkedQueue inProgressSelectionKey = null;
     
    
    /**
     * Current associated list of {@link CometTask}
     */
    private ConcurrentLinkedQueue activeTasks =
            new ConcurrentLinkedQueue();
    
    // ---------------------------------------------------------------------- //
    
    
    /**
     * Create a new instance
     * @param contextPath the context path 
     * @param type when the Comet processing will happen (see CometEngine).
     */
    public CometContext(String contextPath, int continuationType) {
        this.contextPath = contextPath;
        this.continuationType = continuationType;
        attributes = new ConcurrentHashMap();
        handlers = new ConcurrentHashMap();
        inProgressSelectionKey = new ConcurrentLinkedQueue();
    }

    
    /**
     * Get the context path associated with this instance.
     * @return contextPath the context path associated with this instance
     */
    public String getContextPath(){
        return contextPath;
    }
    
    
    /**
     * Add an attibute.
     * @param key the key
     * @param value the value
     */
    public void addAttribute(Object key, Object value){
        attributes.put(key,value);
    }

    
    /**
     * Retrive an attribute.
     * @param key the key
     * @return Object the value.
     */
    public Object getAttribute(Object key){
        return attributes.get(key);
    }    
    
    
    /**
     * Remove an attribute.
     * @param key the key
     * @return Object the value
     */
    public Object removeAttribute(Object key){
        return attributes.remove(key);
    }  
    
    
    /**
     * Add a {@link CometHandler}. Client of this method might
     * make sure the {@link CometHandler} is removed when the 
     * CometHandler.onInterrupt is invoked.
     * @param handler a new {@link CometHandler}
     * @param completeExecution Add the Comethandler but don't block waiting
     *        for event.
     */
    public int addCometHandler(CometHandler handler, boolean completeExecution){
        Long threadId = Thread.currentThread().getId();
        SelectionKey key = CometEngine.getEngine().
                activateContinuation(threadId,this,completeExecution);

        if (key == null){
            throw new 
               IllegalStateException("Grizzly Comet hasn't been registered");
        }
        
        if (handler == null){
            throw new 
               IllegalStateException(INVALID_COMET_HANDLER);
        }

        if (!completeExecution){
            handlers.putIfAbsent(handler,key);
        } else {
            handlers.putIfAbsent(handler,new SelectionKey() {
                public void cancel() {
                }
                public SelectableChannel channel() {
                    throw new IllegalStateException();
                }
                public int interestOps() {
                    throw new IllegalStateException();
                }
                public SelectionKey interestOps(int ops) {
                    throw new IllegalStateException();
                }
                public boolean isValid() {
                    return true;
                }
                public int readyOps() {
                    throw new IllegalStateException();
                }
                public Selector selector() {
                    throw new IllegalStateException();
                }
            });
        }
        return handler.hashCode();
    }
    
    
    /**
     * Add a {@link CometHandler}. Client on this method might
     * make sure the {@link CometHandler} is removed when the 
     * CometHandler.onInterrupt is invoked.
     * @param handler a new {@link CometHandler}
     */
    public int addCometHandler(CometHandler handler){
        return addCometHandler(handler,false);
    }
    
    
    /**
     * Retrive a {@link CometHandler} using its hashKey;
     */
    public CometHandler getCometHandler(int hashCode){
        Iterator iterator = handlers.keySet().iterator();
        CometHandler cometHandler = null;
        while (iterator.hasNext()){
            cometHandler = iterator.next();
            if ( cometHandler.hashCode() == hashCode ){
               return cometHandler;
            }
        }
        return null;
    }   
    
    
    /**
     * Retrive a {@link CometHandler} using its SelectionKey. The 
     * {@link SelectionKey} is not exposed to the Comet API, hence this
     * method must be protected.
     */
    protected CometHandler getCometHandler(SelectionKey key){
        Iterator iterator = handlers.keySet().iterator();
        CometHandler cometHandler = null;
        while (iterator.hasNext()){
            cometHandler = iterator.next();
            if (handlers.get(cometHandler) == key){
               return cometHandler;
            }
        }
        return null;        
    }
    
    
    /**
     * Notify all {@link CometHandler}. The attachment can be null.
     * The type will determine which code>CometHandler 
     * method will be invoked:
     * 

     * CometEvent.INTERRUPT -> CometHandler.onInterrupt
     * CometEvent.NOTIFY -> CometHandler.onEvent
     * CometEvent.INITIALIZE -> CometHandler.onInitialize
     * CometEvent.TERMINATE -> CometHandler.onTerminate
     * CometEvent.READ -> CometHandler.onEvent
     * CometEvent.WRITE -> CometHandler.onEvent
     * 
* @param attachment An object shared amongst {@link CometHandler}. * @param type The type of notification. * @param key The SelectionKey associated with the CometHandler. */ protected void notify(CometEvent event, int eventType, SelectionKey key) throws IOException{ CometHandler cometHandler = getCometHandler(key); if (cometHandler == null){ throw new IllegalStateException(INVALID_COMET_HANDLER); } event.setCometContext(CometContext.this); cometHandler.onEvent(event); } /** * Remove a {@link CometHandler}. If the continuation (connection) * associated with this {@link CometHandler} no longer have * {@link CometHandler} associated to it, it will be resumed. */ public void removeCometHandler(CometHandler handler){ removeCometHandler(handler,true); } /** * Remove a {@link CometHandler}. If the continuation (connection) * associated with this {@link CometHandler} no longer have * {@link CometHandler} associated to it, it will be resumed. * @param handler The CometHandler to remove. * @param resume True is the connection can be resumed if no CometHandler * are associated with the underlying SelectionKey. */ private void removeCometHandler(CometHandler handler,boolean resume){ SelectionKey key = handlers.remove(handler); if (resume && !handlers.containsValue(key)){ CometEngine.getEngine().resume(key); } } /** * Remove a {@link CometHandler} based on its hashcode. */ public void removeCometHandler(int hashCode){ Iterator iterator = handlers.keySet().iterator(); CometHandler cometHandler = null; while (iterator.hasNext()){ cometHandler = iterator.next(); if (cometHandler.hashCode() == hashCode){ SelectionKey key = handlers.get(cometHandler); if (key == null){ throw new IllegalStateException("Invalid CometHandler"); } if (inProgressSelectionKey.contains(key)){ throw new IllegalStateException("Cannot resume an in progress connection."); } iterator.remove(); return; } } } /** * Resume the Comet request and remove it from the active CometHandler list. Once resumed, * a CometHandler should never manipulate the HttpServletRequest or HttpServletResponse as * those are recycled. If you cache them for later reuse by another thread there is a * possibility to introduce corrupted responses next time a request is made. */ public void resumeCometHandler(CometHandler handler){ resumeCometHandler(handler,true); } /** * Resume the Comet request. * @param handler The CometHandler associated with the current continuation. * @param remove true if the CometHandler needs to be removed. */ protected void resumeCometHandler(CometHandler handler, boolean remove){ SelectionKey key = handlers.get(handler); if (key == null){ throw new IllegalStateException("Invalid CometHandler"); } if (inProgressSelectionKey.contains(key)){ throw new IllegalStateException("Cannot resume an in progress connection"); } if (remove){ removeCometHandler(handler,false); } // Retrieve the CometSelector key. SelectionKey cometKey = cometSelector.cometKeyFor(key.channel()); if (cometKey != null){ CometTask task = (CometTask)cometKey.attachment(); if (task != null){ activeTasks.remove(task); } cometKey.attach(null); cometKey.cancel(); } CometEngine.getEngine().resume(key); } /** * Return true if this CometHandler is still active, e.g. there is * still a continuation associated with it. */ public boolean isActive(CometHandler cometHandler){ if (cometHandler == null){ throw new IllegalStateException(INVALID_COMET_HANDLER); } SelectionKey key = handlers.get(cometHandler); return (key != null && !inProgressSelectionKey.contains(key)); } /** * Notify all {@link CometHandler}. The attachment can be null. All * CometHandler.onEvent() will be invoked. * @param attachment An object shared amongst {@link CometHandler}. */ public void notify(final E attachment) throws IOException{ CometEvent event = new CometEvent(); event.setType(CometEvent.NOTIFY); event.attach(attachment); event.setCometContext(CometContext.this); Iterator iterator = handlers.keySet().iterator(); notificationHandler.setBlockingNotification(blockingNotification); notificationHandler.notify(event,iterator); resetSuspendIdleTimeout(); } /** * Notify a single {@link CometHandler}. The attachment can be null. * The type will determine which code>CometHandler * method will be invoked: *

     * CometEvent.INTERRUPT -> CometHandler.onInterrupt
     * CometEvent.NOTIFY -> CometHandler.onEvent
     * CometEvent.INITIALIZE -> CometHandler.onInitialize
     * CometEvent.TERMINATE -> CometHandler.onTerminate
     * CometEvent.READ -> CometHandler.onEvent
     * 
* @param attachment An object shared amongst {@link CometHandler}. * @param type The type of notification. * @param cometHandlerID Notify a single CometHandler. */ public void notify(final E attachment,final int eventType,final int cometHandlerID) throws IOException{ CometHandler cometHandler = getCometHandler(cometHandlerID); if (cometHandler == null){ throw new IllegalStateException(INVALID_COMET_HANDLER); } CometEvent event = new CometEvent(); event.setType(eventType); event.attach(attachment); event.setCometContext(CometContext.this); notificationHandler.setBlockingNotification(blockingNotification); notificationHandler.notify(event,cometHandler); if (event.getType() == CometEvent.TERMINATE || event.getType() == CometEvent.INTERRUPT) { resumeCometHandler(cometHandler); } else { resetSuspendIdleTimeout(); } } /** * Initialize the newly added {@link CometHandler}. * * @param attachment An object shared amongst {@link CometHandler}. * @param type The type of notification. * @param key The SelectionKey representing the CometHandler. */ protected void initialize(SelectionKey key) throws IOException { CometEvent event = new CometEvent(); event.setType(CometEvent.INITIALIZE); event.setCometContext(this); Iterator iterator = handlers.keySet().iterator(); CometHandler cometHandler = null; while(iterator.hasNext()){ cometHandler = iterator.next(); if(handlers.get(cometHandler).equals(key)){ cometHandler.onInitialize(event); break; } } } /** * Notify all {@link CometHandler}. The attachment can be null. * The type will determine which code>CometHandler * method will be invoked: *

     * CometEvent.INTERRUPT -> CometHandler.onInterrupt
     * CometEvent.NOTIFY -> CometHandler.onEvent
     * CometEvent.INITIALIZE -> CometHandler.onInitialize
     * CometEvent.TERMINATE -> CometHandler.onTerminate
     * CometEvent.READ -> CometHandler.onEvent
     * 
* @param attachment An object shared amongst {@link CometHandler}. * @param type The type of notification. */ public void notify(final E attachment,final int eventType) throws IOException{ // XXX Use a pool of CometEvent instance. CometEvent event = new CometEvent(); event.setType(eventType); event.attach(attachment); event.setCometContext(CometContext.this); Iterator iterator = handlers.keySet().iterator(); notificationHandler.setBlockingNotification(blockingNotification); notificationHandler.notify(event,iterator); if (event.getType() == CometEvent.TERMINATE || event.getType() == CometEvent.INTERRUPT) { while(iterator.hasNext()){ resumeCometHandler(iterator.next()); } } else { resetSuspendIdleTimeout(); } } /** * Reset the current timestamp on a suspended connection. */ protected synchronized void resetSuspendIdleTimeout(){ CometTask cometTask; Iterator it = activeTasks.iterator(); while(it.hasNext()){ cometTask = it.next(); cometTask.setExpireTime(System.currentTimeMillis()); } } /** * Register for asynchronous read. If your client supports http pipelining, * invoking this method might result in a state where your CometHandler * is invoked with a {@link CometRead} that will read the next http request. In that * case, it is strongly recommended to not use that method unless your * CometHandler can handle the http request. * @oaram handler The CometHandler that will be invoked. */ public boolean registerAsyncRead(CometHandler handler){ SelectionKey key = null; if (handler != null) { key = handlers.get(handler); } if (handler == null || key == null) { throw new IllegalStateException(INVALID_COMET_HANDLER); } // Retrieve the CometSelector key. SelectionKey cometKey = cometSelector.cometKeyFor(key.channel()); if (cometKey != null){ cometKey.interestOps(cometKey.interestOps() | SelectionKey.OP_READ); if (cometKey.attachment() != null){ ((CometTask)cometKey.attachment()).setAsyncReadSupported(true); } return true; } else { return false; } } /** * Register for asynchronous write. */ public boolean registerAsyncWrite(CometHandler handler){ SelectionKey key = null; if (handler != null) { key = handlers.get(handler); } if (handler == null || key == null) { throw new IllegalStateException(INVALID_COMET_HANDLER); } // Retrieve the CometSelector key. SelectionKey cometKey = cometSelector.cometKeyFor(key.channel()); if (cometKey != null){ cometKey.interestOps(cometKey.interestOps() | SelectionKey.OP_WRITE); return true; } else { return false; } } /** * Recycle this object. */ protected void recycle(){ handlers.clear(); attributes.clear(); cancelled = false; activeTasks.clear(); } /** * Is this instance beeing cancelled by the {@link CometSelector} * @return boolean cancelled or not. */ protected boolean isCancelled() { return cancelled; } /** * Cancel this object or "uncancel". * @param cancelled true or false. */ protected void setCancelled(boolean cancelled) { this.cancelled = cancelled; } /** * Set the {@link CometSelector} associated with this instance. * @param CometSelector the {@link CometSelector} associated with * this instance. */ protected void setCometSelector(CometSelector cometSelector) { this.cometSelector = cometSelector; } /** * Helper. */ @Override public String toString(){ return contextPath; } /** * Return the long delay before a request is resumed. * @return long the long delay before a request is resumed. */ public long getExpirationDelay() { return expirationDelay; } /** * Set the long delay before a request is resumed. * @param long the long delay before a request is resumed. */ public void setExpirationDelay(long expirationDelay) { this.expirationDelay = expirationDelay; } /** * Interrupt a {@link CometHandler} by invoking {@link CometHandler#onInterrupt} */ protected void interrupt(CometTask task){ CometEvent event = new CometEvent(); event.setType(CometEvent.INTERRUPT); event.attach(null); event.setCometContext(this); Iterator iterator = handlers.keySet().iterator(); CometHandler handler; while(iterator.hasNext()){ handler = iterator.next(); if (handlers.get(handler).equals(task.getSelectionKey()) ){ try{ handler.onInterrupt(event); iterator.remove(); } catch (IOException ex){ logger.log(Level.WARNING,"Exception: ",ex); } break; } } activeTasks.remove(task); } /** * Add a {@link CometTask} to the active list. * @param cometTask */ protected void addActiveCometTask(CometTask cometTask){ activeTasks.offer(cometTask); } /** * Return true if the invoker of notify() should block when * notifying Comet Handlers. */ public boolean isBlockingNotification() { return blockingNotification; } /** * Set to true if the invoker of notify() should block when * notifying Comet Handlers. */ public void setBlockingNotification(boolean blockingNotification) { this.blockingNotification = blockingNotification; } /** * Set the current {@link NotificationHandler} * @param notificationHandler */ public void setNotificationHandler(NotificationHandler notificationHandler){ this.notificationHandler = notificationHandler; } /** * Return the associated {@link NotificationHandler} * @return */ public NotificationHandler getNotificationHandler(){ return notificationHandler; } /** * Add a {@link SelectionKey} to the list of current operations. * @param key */ protected static void addInProgressSelectionKey(SelectionKey key){ inProgressSelectionKey.add(key); } /** * Remove a {@link SelectionKey} to the list of current operations. * @param key * @return */ protected static boolean removeInProgressSelectionKey(SelectionKey key){ return inProgressSelectionKey.remove(key); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy