diff src/org/eclipse/jetty/server/AsyncContinuation.java @ 802:3428c60d7cfc

replace jetty jars with source
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 07 Sep 2016 21:15:48 -0600
parents
children 8e9db0bbf4f9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/eclipse/jetty/server/AsyncContinuation.java	Wed Sep 07 21:15:48 2016 -0600
@@ -0,0 +1,1160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationThrowable;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/* ------------------------------------------------------------ */
+/** Implementation of Continuation and AsyncContext interfaces
+ * 
+ */
+public class AsyncContinuation implements AsyncContext, Continuation
+{
+    private static final Logger LOG = Log.getLogger(AsyncContinuation.class);
+
+    private final static long DEFAULT_TIMEOUT=30000L;
+    
+    private final static ContinuationThrowable __exception = new ContinuationThrowable();
+    
+    // STATES:
+    //               handling()    suspend()     unhandle()    resume()       complete()  doComplete()
+    //                             startAsync()                dispatch()   
+    // IDLE          DISPATCHED      
+    // DISPATCHED                  ASYNCSTARTED  UNCOMPLETED
+    // ASYNCSTARTED                              ASYNCWAIT     REDISPATCHING  COMPLETING
+    // REDISPATCHING                             REDISPATCHED  
+    // ASYNCWAIT                                               REDISPATCH     COMPLETING
+    // REDISPATCH    REDISPATCHED
+    // REDISPATCHED                ASYNCSTARTED  UNCOMPLETED
+    // COMPLETING    UNCOMPLETED                 UNCOMPLETED
+    // UNCOMPLETED                                                                        COMPLETED
+    // COMPLETED
+    private static final int __IDLE=0;         // Idle request
+    private static final int __DISPATCHED=1;   // Request dispatched to filter/servlet
+    private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container
+    private static final int __REDISPATCHING=3;// resumed while dispatched
+    private static final int __ASYNCWAIT=4;    // Suspended and parked
+    private static final int __REDISPATCH=5;   // Has been scheduled
+    private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
+    private static final int __COMPLETING=7;   // complete while dispatched
+    private static final int __UNCOMPLETED=8;  // Request is completable
+    private static final int __COMPLETED=9;    // Request is complete
+    
+    /* ------------------------------------------------------------ */
+    protected AbstractHttpConnection _connection;
+    private List<AsyncListener> _lastAsyncListeners;
+    private List<AsyncListener> _asyncListeners;
+    private List<ContinuationListener> _continuationListeners;
+
+    /* ------------------------------------------------------------ */
+    private int _state;
+    private boolean _initial;
+    private boolean _resumed;
+    private boolean _expired;
+    private volatile boolean _responseWrapped;
+    private long _timeoutMs=DEFAULT_TIMEOUT;
+    private AsyncEventState _event;
+    private volatile long _expireAt;    
+    private volatile boolean _continuation;
+    
+    /* ------------------------------------------------------------ */
+    protected AsyncContinuation()
+    {
+        _state=__IDLE;
+        _initial=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void setConnection(final AbstractHttpConnection connection)
+    {
+        synchronized(this)
+        {
+            _connection=connection;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addListener(AsyncListener listener)
+    {
+        synchronized(this)
+        {
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<AsyncListener>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
+    {
+        synchronized(this)
+        {
+            // TODO handle the request/response ???
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<AsyncListener>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addContinuationListener(ContinuationListener listener)
+    {
+        synchronized(this)
+        {
+            if (_continuationListeners==null)
+                _continuationListeners=new ArrayList<ContinuationListener>();
+            _continuationListeners.add(listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeout(long ms)
+    {
+        synchronized(this)
+        {
+            _timeoutMs=ms;
+        }
+    } 
+
+    /* ------------------------------------------------------------ */
+    public long getTimeout()
+    {
+        synchronized(this)
+        {
+            return _timeoutMs;
+        }
+    } 
+
+    /* ------------------------------------------------------------ */
+    public AsyncEventState getAsyncEventState()
+    {
+        synchronized(this)
+        {
+            return _event;
+        }
+    } 
+   
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#keepWrappers()
+     */
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
+     */
+    public boolean isResponseWrapped()
+    {
+        return _responseWrapped;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#isInitial()
+     */
+    public boolean isInitial()
+    {
+        synchronized(this)
+        {
+            return _initial;
+        }
+    }
+    
+    public boolean isContinuation()
+    {
+        return _continuation;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#isSuspended()
+     */
+    public boolean isSuspended()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __REDISPATCHING:
+                case __COMPLETING:
+                case __ASYNCWAIT:
+                    return true;
+                    
+                default:
+                    return false;   
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isSuspending()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __ASYNCWAIT:
+                    return true;
+                    
+                default:
+                    return false;   
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isDispatchable()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __REDISPATCH:
+                case __REDISPATCHED:
+                case __REDISPATCHING:
+                case __COMPLETING:
+                    return true;
+                    
+                default:
+                    return false;   
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        synchronized (this)
+        {
+            return super.toString()+"@"+getStatusString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getStatusString()
+    {
+        synchronized (this)
+        {
+            return
+            ((_state==__IDLE)?"IDLE":
+                (_state==__DISPATCHED)?"DISPATCHED":
+                    (_state==__ASYNCSTARTED)?"ASYNCSTARTED":
+                        (_state==__ASYNCWAIT)?"ASYNCWAIT":
+                            (_state==__REDISPATCHING)?"REDISPATCHING":
+                                (_state==__REDISPATCH)?"REDISPATCH":
+                                    (_state==__REDISPATCHED)?"REDISPATCHED":
+                                        (_state==__COMPLETING)?"COMPLETING":
+                                            (_state==__UNCOMPLETED)?"UNCOMPLETED":
+                                                (_state==__COMPLETED)?"COMPLETE":
+                                                    ("UNKNOWN?"+_state))+
+            (_initial?",initial":"")+
+            (_resumed?",resumed":"")+
+            (_expired?",expired":"");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return false if the handling of the request should not proceed
+     */
+    protected boolean handling()
+    {
+        synchronized (this)
+        {
+            _continuation=false;
+            
+            switch(_state)
+            {
+                case __IDLE:
+                    _initial=true;
+                    _state=__DISPATCHED;
+                    if (_lastAsyncListeners!=null)
+                        _lastAsyncListeners.clear();
+                    if (_asyncListeners!=null)
+                        _asyncListeners.clear();
+                    else
+                    {
+                        _asyncListeners=_lastAsyncListeners;
+                        _lastAsyncListeners=null;
+                    }
+                    return true;
+                    
+                case __COMPLETING:
+                    _state=__UNCOMPLETED;
+                    return false;
+
+                case __ASYNCWAIT:
+                    return false;
+                    
+                case __REDISPATCH:
+                    _state=__REDISPATCHED;
+                    return true;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#suspend(long)
+     */
+    private void doSuspend(final ServletContext context,
+            final ServletRequest request,
+            final ServletResponse response)
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __DISPATCHED:
+                case __REDISPATCHED:
+                    _resumed=false;
+                    _expired=false;
+
+                    if (_event==null || request!=_event.getSuppliedRequest() || response != _event.getSuppliedResponse() || context != _event.getServletContext())
+                        _event=new AsyncEventState(context,request,response);
+                    else
+                    {
+                        _event._dispatchContext=null;
+                        _event._pathInContext=null;
+                    }
+                    _state=__ASYNCSTARTED;
+                    List<AsyncListener> recycle=_lastAsyncListeners;
+                    _lastAsyncListeners=_asyncListeners;
+                    _asyncListeners=recycle;
+                    if (_asyncListeners!=null)
+                        _asyncListeners.clear();
+                    break;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (_lastAsyncListeners!=null)
+        {
+            for (AsyncListener listener : _lastAsyncListeners)
+            {
+                try
+                {
+                    listener.onStartAsync(_event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Signal that the HttpConnection has finished handling the request.
+     * For blocking connectors, this call may block if the request has
+     * been suspended (startAsync called).
+     * @return true if handling is complete, false if the request should 
+     * be handled again (eg because of a resume that happened before unhandle was called)
+     */
+    protected boolean unhandle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __REDISPATCHED:
+                case __DISPATCHED:
+                    _state=__UNCOMPLETED;
+                    return true;
+
+                case __IDLE:
+                    throw new IllegalStateException(this.getStatusString());
+
+                case __ASYNCSTARTED:
+                    _initial=false;
+                    _state=__ASYNCWAIT;
+                    scheduleTimeout(); // could block and change state.
+                    if (_state==__ASYNCWAIT)
+                        return true;
+                    else if (_state==__COMPLETING)
+                    {
+                        _state=__UNCOMPLETED;
+                        return true;
+                    }         
+                    _initial=false;
+                    _state=__REDISPATCHED;
+                    return false; 
+
+                case __REDISPATCHING:
+                    _initial=false;
+                    _state=__REDISPATCHED;
+                    return false; 
+
+                case __COMPLETING:
+                    _initial=false;
+                    _state=__UNCOMPLETED;
+                    return true;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch()
+    {
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                    _state=__REDISPATCHING;
+                    _resumed=true;
+                    return;
+
+                case __ASYNCWAIT:
+                    dispatch=!_expired;
+                    _state=__REDISPATCH;
+                    _resumed=true;
+                    break;
+                    
+                case __REDISPATCH:
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (dispatch)
+        {
+            cancelTimeout();
+            scheduleDispatch();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expired()
+    {
+        final List<ContinuationListener> cListeners;
+        final List<AsyncListener> aListeners;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __ASYNCWAIT:
+                    cListeners=_continuationListeners;
+                    aListeners=_asyncListeners;
+                    break;
+                default:
+                    cListeners=null;
+                    aListeners=null;
+                    return;
+            }
+            _expired=true;
+        }
+        
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    listener.onTimeout(_event);
+                }
+                catch(Exception e)
+                {
+                    LOG.debug(e);
+                    _connection.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+                    break;
+                }
+            }
+        }
+        if (cListeners!=null)
+        {
+            for (ContinuationListener listener : cListeners)
+            {
+                try
+                {
+                    listener.onTimeout(this);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __ASYNCWAIT:
+                    dispatch();
+                    break;
+                    
+                default:
+                    if (!_continuation)
+                        _expired=false;
+            }
+        }
+
+        scheduleDispatch();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#complete()
+     */
+    public void complete()
+    {
+        // just like resume, except don't set _resumed=true;
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __DISPATCHED:
+                case __REDISPATCHED:
+                    throw new IllegalStateException(this.getStatusString());
+
+                case __ASYNCSTARTED:
+                    _state=__COMPLETING;
+                    return;
+                    
+                case __ASYNCWAIT:
+                    _state=__COMPLETING;
+                    dispatch=!_expired;
+                    break;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (dispatch)
+        {
+            cancelTimeout();
+            scheduleDispatch();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#complete()
+     */
+    public void errorComplete()
+    {
+        // just like complete except can overrule a prior dispatch call;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __REDISPATCHING:
+                case __ASYNCSTARTED:
+                    _state=__COMPLETING;
+                    _resumed=false;
+                    return;
+                    
+                case __COMPLETING:
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException 
+    {
+        try
+        {
+            // TODO inject
+            return clazz.newInstance();
+        }
+        catch(Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#complete()
+     */
+    protected void doComplete(Throwable ex)
+    {
+        final List<ContinuationListener> cListeners;
+        final List<AsyncListener> aListeners;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __UNCOMPLETED:
+                    _state=__COMPLETED;
+                    cListeners=_continuationListeners;
+                    aListeners=_asyncListeners;
+                    break;
+                    
+                default:
+                    cListeners=null;
+                    aListeners=null;
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    if (ex!=null)
+                    {
+                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
+                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,ex.getMessage());
+                        listener.onError(_event);
+                    }
+                    else
+                        listener.onComplete(_event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        if (cListeners!=null)
+        {
+            for (ContinuationListener listener : cListeners)
+            {
+                try
+                {
+                    listener.onComplete(this);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void recycle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __DISPATCHED:
+                case __REDISPATCHED:
+                    throw new IllegalStateException(getStatusString());
+                default:
+                    _state=__IDLE;
+            }
+            _initial = true;
+            _resumed=false;
+            _expired=false;
+            _responseWrapped=false;
+            cancelTimeout();
+            _timeoutMs=DEFAULT_TIMEOUT;
+            _continuationListeners=null;
+        }
+    }    
+    
+    /* ------------------------------------------------------------ */
+    public void cancel()
+    {
+        synchronized (this)
+        {
+            cancelTimeout();
+            _continuationListeners=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void scheduleDispatch()
+    {
+        EndPoint endp=_connection.getEndPoint();
+        if (!endp.isBlocking())
+        {
+            ((AsyncEndPoint)endp).asyncDispatch();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void scheduleTimeout()
+    {
+        EndPoint endp=_connection.getEndPoint();
+        if (_timeoutMs>0)
+        {
+            if (endp.isBlocking())
+            {
+                synchronized(this)
+                {
+                    _expireAt = System.currentTimeMillis()+_timeoutMs;
+                    long wait=_timeoutMs;
+                    while (_expireAt>0 && wait>0 && _connection.getServer().isRunning())
+                    {
+                        try
+                        {
+                            this.wait(wait);
+                        }
+                        catch (InterruptedException e)
+                        {
+                            LOG.ignore(e);
+                        }
+                        wait=_expireAt-System.currentTimeMillis();
+                    }
+
+                    if (_expireAt>0 && wait<=0 && _connection.getServer().isRunning())
+                    {
+                        expired();
+                    }
+                }            
+            }
+            else
+            {
+                ((AsyncEndPoint)endp).scheduleTimeout(_event._timeout,_timeoutMs);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void cancelTimeout()
+    {
+        EndPoint endp=_connection.getEndPoint();
+        if (endp.isBlocking())
+        {
+            synchronized(this)
+            {
+                _expireAt=0;
+                this.notifyAll();
+            }
+        }
+        else 
+        {
+            final AsyncEventState event=_event;
+            if (event!=null)
+            {
+                ((AsyncEndPoint)endp).cancelTimeout(event._timeout);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCompleting()
+    {
+        synchronized (this)
+        {
+            return _state==__COMPLETING;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    boolean isUncompleted()
+    {
+        synchronized (this)
+        {
+            return _state==__UNCOMPLETED;
+        }
+    } 
+    
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
+    {
+        synchronized (this)
+        {
+            return _state==__COMPLETED;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsyncStarted()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __REDISPATCHING:
+                case __REDISPATCH:
+                case __ASYNCWAIT:
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsync()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __IDLE:
+                case __DISPATCHED:
+                case __UNCOMPLETED:
+                case __COMPLETED:
+                    return false;
+
+                default:
+                    return true;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch(ServletContext context, String path)
+    {
+        _event._dispatchContext=context;
+        _event.setPath(path);
+        dispatch();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch(String path)
+    {
+        _event.setPath(path);
+        dispatch();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Request getBaseRequest()
+    {
+        return _connection.getRequest();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ServletRequest getRequest()
+    {
+        if (_event!=null)
+            return _event.getSuppliedRequest();
+        return _connection.getRequest();
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getResponse()
+    {
+        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
+            return _event.getSuppliedResponse();
+        return _connection.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void start(final Runnable run)
+    {
+        final AsyncEventState event=_event;
+        if (event!=null)
+        {
+            _connection.getServer().getThreadPool().dispatch(new Runnable()
+            {
+                public void run()
+                {
+                    ((Context)event.getServletContext()).getContextHandler().handle(run);
+                }
+            });
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean hasOriginalRequestAndResponse()
+    {
+        synchronized (this)
+        {
+            return (_event!=null && _event.getSuppliedRequest()==_connection._request && _event.getSuppliedResponse()==_connection._response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler getContextHandler()
+    {
+        final AsyncEventState event=_event;
+        if (event!=null)
+            return ((Context)event.getServletContext()).getContextHandler();
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#isResumed()
+     */
+    public boolean isResumed()
+    {
+        synchronized (this)
+        {
+            return _resumed;
+        }
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#isExpired()
+     */
+    public boolean isExpired()
+    {
+        synchronized (this)
+        {
+            return _expired;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#resume()
+     */
+    public void resume()
+    {
+        dispatch();
+    }
+    
+
+
+    /* ------------------------------------------------------------ */
+    protected void startAsync(final ServletContext context,
+            final ServletRequest request,
+            final ServletResponse response)
+    {
+        synchronized (this)
+        {
+            _responseWrapped=!(response instanceof Response);
+            doSuspend(context,request,response);
+            if (request instanceof HttpServletRequest)
+            {
+                _event._pathInContext = URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void startAsync()
+    {
+        _responseWrapped=false;
+        _continuation=false;
+        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());  
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#suspend()
+     */
+    public void suspend(ServletResponse response)
+    {
+        _continuation=true;
+        _responseWrapped=!(response instanceof Response);
+        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),response); 
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#suspend()
+     */
+    public void suspend()
+    {
+        _responseWrapped=false;
+        _continuation=true;
+        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());       
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
+     */
+    public ServletResponse getServletResponse()
+    {
+        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
+            return _event.getSuppliedResponse();
+        return _connection.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _connection.getRequest().getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _connection.getRequest().removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _connection.getRequest().setAttribute(name,attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
+     */
+    public void undispatch()
+    {
+        if (isSuspended())
+        {
+            if (LOG.isDebugEnabled())
+                throw new ContinuationThrowable();
+            else
+                throw __exception;
+        }
+        throw new IllegalStateException("!suspended");
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class AsyncTimeout extends Timeout.Task implements Runnable
+    {
+            @Override
+            public void expired()
+            {
+                AsyncContinuation.this.expired();
+            }
+
+            @Override
+            public void run()
+            {
+                AsyncContinuation.this.expired();
+            }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class AsyncEventState extends AsyncEvent
+    {
+        private final ServletContext _suspendedContext;
+        private ServletContext _dispatchContext;
+        private String _pathInContext;
+        private Timeout.Task _timeout=  new AsyncTimeout();
+        
+        public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
+        {
+            super(AsyncContinuation.this, request,response);
+            _suspendedContext=context;
+            // Get the base request So we can remember the initial paths
+            Request r=_connection.getRequest();
+ 
+            // If we haven't been async dispatched before
+            if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+            {
+                // We are setting these attributes during startAsync, when the spec implies that 
+                // they are only available after a call to AsyncContext.dispatch(...);
+                
+                // have we been forwarded before?
+                String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+                if (uri!=null)
+                {
+                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+                }
+                else
+                {
+                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI());
+                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath());
+                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath());
+                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo());
+                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString());
+                }
+            }
+        }
+        
+        public ServletContext getSuspendedContext()
+        {
+            return _suspendedContext;
+        }
+        
+        public ServletContext getDispatchContext()
+        {
+            return _dispatchContext;
+        }
+        
+        public ServletContext getServletContext()
+        {
+            return _dispatchContext==null?_suspendedContext:_dispatchContext;
+        }
+        
+        public void setPath(String path)
+        {
+            _pathInContext=path;
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The path in the context
+         */
+        public String getPath()
+        {
+            return _pathInContext;
+        }
+    }
+}