1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.server;
20
21import javax.servlet.AsyncContext;
22import javax.servlet.AsyncEvent;
23import javax.servlet.AsyncListener;
24import javax.servlet.RequestDispatcher;
25import javax.servlet.ServletException;
26
27import java.util.ArrayList;
28import java.util.List;
29
30import javax.servlet.ServletContext;
31import javax.servlet.ServletRequest;
32import javax.servlet.ServletResponse;
33import javax.servlet.http.HttpServletRequest;
34
35import org.eclipse.jetty.continuation.Continuation;
36import org.eclipse.jetty.continuation.ContinuationThrowable;
37import org.eclipse.jetty.continuation.ContinuationListener;
38import org.eclipse.jetty.io.AsyncEndPoint;
39import org.eclipse.jetty.io.EndPoint;
40import org.eclipse.jetty.server.handler.ContextHandler;
41import org.eclipse.jetty.server.handler.ContextHandler.Context;
42import org.eclipse.jetty.util.URIUtil;
43import org.eclipse.jetty.util.log.Log;
44import org.eclipse.jetty.util.log.Logger;
45import org.eclipse.jetty.util.thread.Timeout;
46
47/* ------------------------------------------------------------ */
48/** Implementation of Continuation and AsyncContext interfaces
49 *
50 */
51public class AsyncContinuation implements AsyncContext, Continuation
52{
53    private static final Logger LOG = Log.getLogger(AsyncContinuation.class);
54
55    private final static long DEFAULT_TIMEOUT=30000L;
56
57    private final static ContinuationThrowable __exception = new ContinuationThrowable();
58
59    // STATES:
60    //               handling()    suspend()     unhandle()    resume()       complete()  doComplete()
61    //                             startAsync()                dispatch()
62    // IDLE          DISPATCHED
63    // DISPATCHED                  ASYNCSTARTED  UNCOMPLETED
64    // ASYNCSTARTED                              ASYNCWAIT     REDISPATCHING  COMPLETING
65    // REDISPATCHING                             REDISPATCHED
66    // ASYNCWAIT                                               REDISPATCH     COMPLETING
67    // REDISPATCH    REDISPATCHED
68    // REDISPATCHED                ASYNCSTARTED  UNCOMPLETED
69    // COMPLETING    UNCOMPLETED                 UNCOMPLETED
70    // UNCOMPLETED                                                                        COMPLETED
71    // COMPLETED
72    private static final int __IDLE=0;         // Idle request
73    private static final int __DISPATCHED=1;   // Request dispatched to filter/servlet
74    private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container
75    private static final int __REDISPATCHING=3;// resumed while dispatched
76    private static final int __ASYNCWAIT=4;    // Suspended and parked
77    private static final int __REDISPATCH=5;   // Has been scheduled
78    private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
79    private static final int __COMPLETING=7;   // complete while dispatched
80    private static final int __UNCOMPLETED=8;  // Request is completable
81    private static final int __COMPLETED=9;    // Request is complete
82
83    /* ------------------------------------------------------------ */
84    protected AbstractHttpConnection _connection;
85    private List<AsyncListener> _lastAsyncListeners;
86    private List<AsyncListener> _asyncListeners;
87    private List<ContinuationListener> _continuationListeners;
88
89    /* ------------------------------------------------------------ */
90    private int _state;
91    private boolean _initial;
92    private boolean _resumed;
93    private boolean _expired;
94    private volatile boolean _responseWrapped;
95    private long _timeoutMs=DEFAULT_TIMEOUT;
96    private AsyncEventState _event;
97    private volatile long _expireAt;
98    private volatile boolean _continuation;
99
100    /* ------------------------------------------------------------ */
101    protected AsyncContinuation()
102    {
103        _state=__IDLE;
104        _initial=true;
105    }
106
107    /* ------------------------------------------------------------ */
108    protected void setConnection(final AbstractHttpConnection connection)
109    {
110        synchronized(this)
111        {
112            _connection=connection;
113        }
114    }
115
116    /* ------------------------------------------------------------ */
117    public void addListener(AsyncListener listener)
118    {
119        synchronized(this)
120        {
121            if (_asyncListeners==null)
122                _asyncListeners=new ArrayList<AsyncListener>();
123            _asyncListeners.add(listener);
124        }
125    }
126
127    /* ------------------------------------------------------------ */
128    public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
129    {
130        synchronized(this)
131        {
132            // TODO handle the request/response ???
133            if (_asyncListeners==null)
134                _asyncListeners=new ArrayList<AsyncListener>();
135            _asyncListeners.add(listener);
136        }
137    }
138
139    /* ------------------------------------------------------------ */
140    public void addContinuationListener(ContinuationListener listener)
141    {
142        synchronized(this)
143        {
144            if (_continuationListeners==null)
145                _continuationListeners=new ArrayList<ContinuationListener>();
146            _continuationListeners.add(listener);
147        }
148    }
149
150    /* ------------------------------------------------------------ */
151    public void setTimeout(long ms)
152    {
153        synchronized(this)
154        {
155            _timeoutMs=ms;
156        }
157    }
158
159    /* ------------------------------------------------------------ */
160    public long getTimeout()
161    {
162        synchronized(this)
163        {
164            return _timeoutMs;
165        }
166    }
167
168    /* ------------------------------------------------------------ */
169    public AsyncEventState getAsyncEventState()
170    {
171        synchronized(this)
172        {
173            return _event;
174        }
175    }
176
177    /* ------------------------------------------------------------ */
178    /**
179     * @see org.eclipse.jetty.continuation.Continuation#keepWrappers()
180     */
181
182    /* ------------------------------------------------------------ */
183    /**
184     * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
185     */
186    public boolean isResponseWrapped()
187    {
188        return _responseWrapped;
189    }
190
191    /* ------------------------------------------------------------ */
192    /* (non-Javadoc)
193     * @see javax.servlet.ServletRequest#isInitial()
194     */
195    public boolean isInitial()
196    {
197        synchronized(this)
198        {
199            return _initial;
200        }
201    }
202
203    public boolean isContinuation()
204    {
205        return _continuation;
206    }
207
208    /* ------------------------------------------------------------ */
209    /* (non-Javadoc)
210     * @see javax.servlet.ServletRequest#isSuspended()
211     */
212    public boolean isSuspended()
213    {
214        synchronized(this)
215        {
216            switch(_state)
217            {
218                case __ASYNCSTARTED:
219                case __REDISPATCHING:
220                case __COMPLETING:
221                case __ASYNCWAIT:
222                    return true;
223
224                default:
225                    return false;
226            }
227        }
228    }
229
230    /* ------------------------------------------------------------ */
231    public boolean isSuspending()
232    {
233        synchronized(this)
234        {
235            switch(_state)
236            {
237                case __ASYNCSTARTED:
238                case __ASYNCWAIT:
239                    return true;
240
241                default:
242                    return false;
243            }
244        }
245    }
246
247    /* ------------------------------------------------------------ */
248    public boolean isDispatchable()
249    {
250        synchronized(this)
251        {
252            switch(_state)
253            {
254                case __REDISPATCH:
255                case __REDISPATCHED:
256                case __REDISPATCHING:
257                case __COMPLETING:
258                    return true;
259
260                default:
261                    return false;
262            }
263        }
264    }
265
266    /* ------------------------------------------------------------ */
267    @Override
268    public String toString()
269    {
270        synchronized (this)
271        {
272            return super.toString()+"@"+getStatusString();
273        }
274    }
275
276    /* ------------------------------------------------------------ */
277    public String getStatusString()
278    {
279        synchronized (this)
280        {
281            return
282            ((_state==__IDLE)?"IDLE":
283                (_state==__DISPATCHED)?"DISPATCHED":
284                    (_state==__ASYNCSTARTED)?"ASYNCSTARTED":
285                        (_state==__ASYNCWAIT)?"ASYNCWAIT":
286                            (_state==__REDISPATCHING)?"REDISPATCHING":
287                                (_state==__REDISPATCH)?"REDISPATCH":
288                                    (_state==__REDISPATCHED)?"REDISPATCHED":
289                                        (_state==__COMPLETING)?"COMPLETING":
290                                            (_state==__UNCOMPLETED)?"UNCOMPLETED":
291                                                (_state==__COMPLETED)?"COMPLETE":
292                                                    ("UNKNOWN?"+_state))+
293            (_initial?",initial":"")+
294            (_resumed?",resumed":"")+
295            (_expired?",expired":"");
296        }
297    }
298
299    /* ------------------------------------------------------------ */
300    /**
301     * @return false if the handling of the request should not proceed
302     */
303    protected boolean handling()
304    {
305        synchronized (this)
306        {
307            _continuation=false;
308
309            switch(_state)
310            {
311                case __IDLE:
312                    _initial=true;
313                    _state=__DISPATCHED;
314                    if (_lastAsyncListeners!=null)
315                        _lastAsyncListeners.clear();
316                    if (_asyncListeners!=null)
317                        _asyncListeners.clear();
318                    else
319                    {
320                        _asyncListeners=_lastAsyncListeners;
321                        _lastAsyncListeners=null;
322                    }
323                    return true;
324
325                case __COMPLETING:
326                    _state=__UNCOMPLETED;
327                    return false;
328
329                case __ASYNCWAIT:
330                    return false;
331
332                case __REDISPATCH:
333                    _state=__REDISPATCHED;
334                    return true;
335
336                default:
337                    throw new IllegalStateException(this.getStatusString());
338            }
339        }
340    }
341
342    /* ------------------------------------------------------------ */
343    /* (non-Javadoc)
344     * @see javax.servlet.ServletRequest#suspend(long)
345     */
346    private void doSuspend(final ServletContext context,
347            final ServletRequest request,
348            final ServletResponse response)
349    {
350        synchronized (this)
351        {
352            switch(_state)
353            {
354                case __DISPATCHED:
355                case __REDISPATCHED:
356                    _resumed=false;
357                    _expired=false;
358
359                    if (_event==null || request!=_event.getSuppliedRequest() || response != _event.getSuppliedResponse() || context != _event.getServletContext())
360                        _event=new AsyncEventState(context,request,response);
361                    else
362                    {
363                        _event._dispatchContext=null;
364                        _event._pathInContext=null;
365                    }
366                    _state=__ASYNCSTARTED;
367                    List<AsyncListener> recycle=_lastAsyncListeners;
368                    _lastAsyncListeners=_asyncListeners;
369                    _asyncListeners=recycle;
370                    if (_asyncListeners!=null)
371                        _asyncListeners.clear();
372                    break;
373
374                default:
375                    throw new IllegalStateException(this.getStatusString());
376            }
377        }
378
379        if (_lastAsyncListeners!=null)
380        {
381            for (AsyncListener listener : _lastAsyncListeners)
382            {
383                try
384                {
385                    listener.onStartAsync(_event);
386                }
387                catch(Exception e)
388                {
389                    LOG.warn(e);
390                }
391            }
392        }
393    }
394
395    /* ------------------------------------------------------------ */
396    /**
397     * Signal that the HttpConnection has finished handling the request.
398     * For blocking connectors, this call may block if the request has
399     * been suspended (startAsync called).
400     * @return true if handling is complete, false if the request should
401     * be handled again (eg because of a resume that happened before unhandle was called)
402     */
403    protected boolean unhandle()
404    {
405        synchronized (this)
406        {
407            switch(_state)
408            {
409                case __REDISPATCHED:
410                case __DISPATCHED:
411                    _state=__UNCOMPLETED;
412                    return true;
413
414                case __IDLE:
415                    throw new IllegalStateException(this.getStatusString());
416
417                case __ASYNCSTARTED:
418                    _initial=false;
419                    _state=__ASYNCWAIT;
420                    scheduleTimeout(); // could block and change state.
421                    if (_state==__ASYNCWAIT)
422                        return true;
423                    else if (_state==__COMPLETING)
424                    {
425                        _state=__UNCOMPLETED;
426                        return true;
427                    }
428                    _initial=false;
429                    _state=__REDISPATCHED;
430                    return false;
431
432                case __REDISPATCHING:
433                    _initial=false;
434                    _state=__REDISPATCHED;
435                    return false;
436
437                case __COMPLETING:
438                    _initial=false;
439                    _state=__UNCOMPLETED;
440                    return true;
441
442                default:
443                    throw new IllegalStateException(this.getStatusString());
444            }
445        }
446    }
447
448    /* ------------------------------------------------------------ */
449    public void dispatch()
450    {
451        boolean dispatch=false;
452        synchronized (this)
453        {
454            switch(_state)
455            {
456                case __ASYNCSTARTED:
457                    _state=__REDISPATCHING;
458                    _resumed=true;
459                    return;
460
461                case __ASYNCWAIT:
462                    dispatch=!_expired;
463                    _state=__REDISPATCH;
464                    _resumed=true;
465                    break;
466
467                case __REDISPATCH:
468                    return;
469
470                default:
471                    throw new IllegalStateException(this.getStatusString());
472            }
473        }
474
475        if (dispatch)
476        {
477            cancelTimeout();
478            scheduleDispatch();
479        }
480    }
481
482    /* ------------------------------------------------------------ */
483    protected void expired()
484    {
485        final List<ContinuationListener> cListeners;
486        final List<AsyncListener> aListeners;
487        synchronized (this)
488        {
489            switch(_state)
490            {
491                case __ASYNCSTARTED:
492                case __ASYNCWAIT:
493                    cListeners=_continuationListeners;
494                    aListeners=_asyncListeners;
495                    break;
496                default:
497                    cListeners=null;
498                    aListeners=null;
499                    return;
500            }
501            _expired=true;
502        }
503
504        if (aListeners!=null)
505        {
506            for (AsyncListener listener : aListeners)
507            {
508                try
509                {
510                    listener.onTimeout(_event);
511                }
512                catch(Exception e)
513                {
514                    LOG.debug(e);
515                    _connection.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
516                    break;
517                }
518            }
519        }
520        if (cListeners!=null)
521        {
522            for (ContinuationListener listener : cListeners)
523            {
524                try
525                {
526                    listener.onTimeout(this);
527                }
528                catch(Exception e)
529                {
530                    LOG.warn(e);
531                }
532            }
533        }
534
535        synchronized (this)
536        {
537            switch(_state)
538            {
539                case __ASYNCSTARTED:
540                case __ASYNCWAIT:
541                    dispatch();
542                    break;
543
544                default:
545                    if (!_continuation)
546                        _expired=false;
547            }
548        }
549
550        scheduleDispatch();
551    }
552
553    /* ------------------------------------------------------------ */
554    /* (non-Javadoc)
555     * @see javax.servlet.ServletRequest#complete()
556     */
557    public void complete()
558    {
559        // just like resume, except don't set _resumed=true;
560        boolean dispatch=false;
561        synchronized (this)
562        {
563            switch(_state)
564            {
565                case __DISPATCHED:
566                case __REDISPATCHED:
567                    throw new IllegalStateException(this.getStatusString());
568
569                case __ASYNCSTARTED:
570                    _state=__COMPLETING;
571                    return;
572
573                case __ASYNCWAIT:
574                    _state=__COMPLETING;
575                    dispatch=!_expired;
576                    break;
577
578                default:
579                    throw new IllegalStateException(this.getStatusString());
580            }
581        }
582
583        if (dispatch)
584        {
585            cancelTimeout();
586            scheduleDispatch();
587        }
588    }
589
590    /* ------------------------------------------------------------ */
591    /* (non-Javadoc)
592     * @see javax.servlet.ServletRequest#complete()
593     */
594    public void errorComplete()
595    {
596        // just like complete except can overrule a prior dispatch call;
597        synchronized (this)
598        {
599            switch(_state)
600            {
601                case __REDISPATCHING:
602                case __ASYNCSTARTED:
603                    _state=__COMPLETING;
604                    _resumed=false;
605                    return;
606
607                case __COMPLETING:
608                    return;
609
610                default:
611                    throw new IllegalStateException(this.getStatusString());
612            }
613        }
614    }
615
616    /* ------------------------------------------------------------ */
617    @Override
618    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
619    {
620        try
621        {
622            // TODO inject
623            return clazz.newInstance();
624        }
625        catch(Exception e)
626        {
627            throw new ServletException(e);
628        }
629    }
630
631
632    /* ------------------------------------------------------------ */
633    /* (non-Javadoc)
634     * @see javax.servlet.ServletRequest#complete()
635     */
636    protected void doComplete(Throwable ex)
637    {
638        final List<ContinuationListener> cListeners;
639        final List<AsyncListener> aListeners;
640        synchronized (this)
641        {
642            switch(_state)
643            {
644                case __UNCOMPLETED:
645                    _state=__COMPLETED;
646                    cListeners=_continuationListeners;
647                    aListeners=_asyncListeners;
648                    break;
649
650                default:
651                    cListeners=null;
652                    aListeners=null;
653                    throw new IllegalStateException(this.getStatusString());
654            }
655        }
656
657        if (aListeners!=null)
658        {
659            for (AsyncListener listener : aListeners)
660            {
661                try
662                {
663                    if (ex!=null)
664                    {
665                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
666                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,ex.getMessage());
667                        listener.onError(_event);
668                    }
669                    else
670                        listener.onComplete(_event);
671                }
672                catch(Exception e)
673                {
674                    LOG.warn(e);
675                }
676            }
677        }
678        if (cListeners!=null)
679        {
680            for (ContinuationListener listener : cListeners)
681            {
682                try
683                {
684                    listener.onComplete(this);
685                }
686                catch(Exception e)
687                {
688                    LOG.warn(e);
689                }
690            }
691        }
692    }
693
694    /* ------------------------------------------------------------ */
695    protected void recycle()
696    {
697        synchronized (this)
698        {
699            switch(_state)
700            {
701                case __DISPATCHED:
702                case __REDISPATCHED:
703                    throw new IllegalStateException(getStatusString());
704                default:
705                    _state=__IDLE;
706            }
707            _initial = true;
708            _resumed=false;
709            _expired=false;
710            _responseWrapped=false;
711            cancelTimeout();
712            _timeoutMs=DEFAULT_TIMEOUT;
713            _continuationListeners=null;
714        }
715    }
716
717    /* ------------------------------------------------------------ */
718    public void cancel()
719    {
720        synchronized (this)
721        {
722            cancelTimeout();
723            _continuationListeners=null;
724        }
725    }
726
727    /* ------------------------------------------------------------ */
728    protected void scheduleDispatch()
729    {
730        EndPoint endp=_connection.getEndPoint();
731        if (!endp.isBlocking())
732        {
733            ((AsyncEndPoint)endp).asyncDispatch();
734        }
735    }
736
737    /* ------------------------------------------------------------ */
738    protected void scheduleTimeout()
739    {
740        EndPoint endp=_connection.getEndPoint();
741        if (_timeoutMs>0)
742        {
743            if (endp.isBlocking())
744            {
745                synchronized(this)
746                {
747                    _expireAt = System.currentTimeMillis()+_timeoutMs;
748                    long wait=_timeoutMs;
749                    while (_expireAt>0 && wait>0 && _connection.getServer().isRunning())
750                    {
751                        try
752                        {
753                            this.wait(wait);
754                        }
755                        catch (InterruptedException e)
756                        {
757                            LOG.ignore(e);
758                        }
759                        wait=_expireAt-System.currentTimeMillis();
760                    }
761
762                    if (_expireAt>0 && wait<=0 && _connection.getServer().isRunning())
763                    {
764                        expired();
765                    }
766                }
767            }
768            else
769            {
770                ((AsyncEndPoint)endp).scheduleTimeout(_event._timeout,_timeoutMs);
771            }
772        }
773    }
774
775    /* ------------------------------------------------------------ */
776    protected void cancelTimeout()
777    {
778        EndPoint endp=_connection.getEndPoint();
779        if (endp.isBlocking())
780        {
781            synchronized(this)
782            {
783                _expireAt=0;
784                this.notifyAll();
785            }
786        }
787        else
788        {
789            final AsyncEventState event=_event;
790            if (event!=null)
791            {
792                ((AsyncEndPoint)endp).cancelTimeout(event._timeout);
793            }
794        }
795    }
796
797    /* ------------------------------------------------------------ */
798    public boolean isCompleting()
799    {
800        synchronized (this)
801        {
802            return _state==__COMPLETING;
803        }
804    }
805
806    /* ------------------------------------------------------------ */
807    boolean isUncompleted()
808    {
809        synchronized (this)
810        {
811            return _state==__UNCOMPLETED;
812        }
813    }
814
815    /* ------------------------------------------------------------ */
816    public boolean isComplete()
817    {
818        synchronized (this)
819        {
820            return _state==__COMPLETED;
821        }
822    }
823
824
825    /* ------------------------------------------------------------ */
826    public boolean isAsyncStarted()
827    {
828        synchronized (this)
829        {
830            switch(_state)
831            {
832                case __ASYNCSTARTED:
833                case __REDISPATCHING:
834                case __REDISPATCH:
835                case __ASYNCWAIT:
836                    return true;
837
838                default:
839                    return false;
840            }
841        }
842    }
843
844
845    /* ------------------------------------------------------------ */
846    public boolean isAsync()
847    {
848        synchronized (this)
849        {
850            switch(_state)
851            {
852                case __IDLE:
853                case __DISPATCHED:
854                case __UNCOMPLETED:
855                case __COMPLETED:
856                    return false;
857
858                default:
859                    return true;
860            }
861        }
862    }
863
864    /* ------------------------------------------------------------ */
865    public void dispatch(ServletContext context, String path)
866    {
867        _event._dispatchContext=context;
868        _event.setPath(path);
869        dispatch();
870    }
871
872    /* ------------------------------------------------------------ */
873    public void dispatch(String path)
874    {
875        _event.setPath(path);
876        dispatch();
877    }
878
879    /* ------------------------------------------------------------ */
880    public Request getBaseRequest()
881    {
882        return _connection.getRequest();
883    }
884
885    /* ------------------------------------------------------------ */
886    public ServletRequest getRequest()
887    {
888        if (_event!=null)
889            return _event.getSuppliedRequest();
890        return _connection.getRequest();
891    }
892
893    /* ------------------------------------------------------------ */
894    public ServletResponse getResponse()
895    {
896        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
897            return _event.getSuppliedResponse();
898        return _connection.getResponse();
899    }
900
901    /* ------------------------------------------------------------ */
902    public void start(final Runnable run)
903    {
904        final AsyncEventState event=_event;
905        if (event!=null)
906        {
907            _connection.getServer().getThreadPool().dispatch(new Runnable()
908            {
909                public void run()
910                {
911                    ((Context)event.getServletContext()).getContextHandler().handle(run);
912                }
913            });
914        }
915    }
916
917    /* ------------------------------------------------------------ */
918    public boolean hasOriginalRequestAndResponse()
919    {
920        synchronized (this)
921        {
922            return (_event!=null && _event.getSuppliedRequest()==_connection._request && _event.getSuppliedResponse()==_connection._response);
923        }
924    }
925
926    /* ------------------------------------------------------------ */
927    public ContextHandler getContextHandler()
928    {
929        final AsyncEventState event=_event;
930        if (event!=null)
931            return ((Context)event.getServletContext()).getContextHandler();
932        return null;
933    }
934
935
936    /* ------------------------------------------------------------ */
937    /**
938     * @see Continuation#isResumed()
939     */
940    public boolean isResumed()
941    {
942        synchronized (this)
943        {
944            return _resumed;
945        }
946    }
947    /* ------------------------------------------------------------ */
948    /**
949     * @see Continuation#isExpired()
950     */
951    public boolean isExpired()
952    {
953        synchronized (this)
954        {
955            return _expired;
956        }
957    }
958
959    /* ------------------------------------------------------------ */
960    /**
961     * @see Continuation#resume()
962     */
963    public void resume()
964    {
965        dispatch();
966    }
967
968
969
970    /* ------------------------------------------------------------ */
971    protected void startAsync(final ServletContext context,
972            final ServletRequest request,
973            final ServletResponse response)
974    {
975        synchronized (this)
976        {
977            _responseWrapped=!(response instanceof Response);
978            doSuspend(context,request,response);
979            if (request instanceof HttpServletRequest)
980            {
981                _event._pathInContext = URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo());
982            }
983        }
984    }
985
986    /* ------------------------------------------------------------ */
987    protected void startAsync()
988    {
989        _responseWrapped=false;
990        _continuation=false;
991        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());
992    }
993
994
995    /* ------------------------------------------------------------ */
996    /**
997     * @see Continuation#suspend()
998     */
999    public void suspend(ServletResponse response)
1000    {
1001        _continuation=true;
1002        _responseWrapped=!(response instanceof Response);
1003        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),response);
1004    }
1005
1006    /* ------------------------------------------------------------ */
1007    /**
1008     * @see Continuation#suspend()
1009     */
1010    public void suspend()
1011    {
1012        _responseWrapped=false;
1013        _continuation=true;
1014        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());
1015    }
1016
1017    /* ------------------------------------------------------------ */
1018    /**
1019     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
1020     */
1021    public ServletResponse getServletResponse()
1022    {
1023        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
1024            return _event.getSuppliedResponse();
1025        return _connection.getResponse();
1026    }
1027
1028    /* ------------------------------------------------------------ */
1029    /**
1030     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
1031     */
1032    public Object getAttribute(String name)
1033    {
1034        return _connection.getRequest().getAttribute(name);
1035    }
1036
1037    /* ------------------------------------------------------------ */
1038    /**
1039     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
1040     */
1041    public void removeAttribute(String name)
1042    {
1043        _connection.getRequest().removeAttribute(name);
1044    }
1045
1046    /* ------------------------------------------------------------ */
1047    /**
1048     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
1049     */
1050    public void setAttribute(String name, Object attribute)
1051    {
1052        _connection.getRequest().setAttribute(name,attribute);
1053    }
1054
1055    /* ------------------------------------------------------------ */
1056    /**
1057     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
1058     */
1059    public void undispatch()
1060    {
1061        if (isSuspended())
1062        {
1063            if (LOG.isDebugEnabled())
1064                throw new ContinuationThrowable();
1065            else
1066                throw __exception;
1067        }
1068        throw new IllegalStateException("!suspended");
1069    }
1070
1071    /* ------------------------------------------------------------ */
1072    /* ------------------------------------------------------------ */
1073    public class AsyncTimeout extends Timeout.Task implements Runnable
1074    {
1075            @Override
1076            public void expired()
1077            {
1078                AsyncContinuation.this.expired();
1079            }
1080
1081            @Override
1082            public void run()
1083            {
1084                AsyncContinuation.this.expired();
1085            }
1086    }
1087
1088    /* ------------------------------------------------------------ */
1089    /* ------------------------------------------------------------ */
1090    public class AsyncEventState extends AsyncEvent
1091    {
1092        private final ServletContext _suspendedContext;
1093        private ServletContext _dispatchContext;
1094        private String _pathInContext;
1095        private Timeout.Task _timeout=  new AsyncTimeout();
1096
1097        public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
1098        {
1099            super(AsyncContinuation.this, request,response);
1100            _suspendedContext=context;
1101            // Get the base request So we can remember the initial paths
1102            Request r=_connection.getRequest();
1103
1104            // If we haven't been async dispatched before
1105            if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
1106            {
1107                // We are setting these attributes during startAsync, when the spec implies that
1108                // they are only available after a call to AsyncContext.dispatch(...);
1109
1110                // have we been forwarded before?
1111                String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
1112                if (uri!=null)
1113                {
1114                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
1115                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
1116                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
1117                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
1118                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
1119                }
1120                else
1121                {
1122                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI());
1123                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath());
1124                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath());
1125                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo());
1126                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString());
1127                }
1128            }
1129        }
1130
1131        public ServletContext getSuspendedContext()
1132        {
1133            return _suspendedContext;
1134        }
1135
1136        public ServletContext getDispatchContext()
1137        {
1138            return _dispatchContext;
1139        }
1140
1141        public ServletContext getServletContext()
1142        {
1143            return _dispatchContext==null?_suspendedContext:_dispatchContext;
1144        }
1145
1146        public void setPath(String path)
1147        {
1148            _pathInContext=path;
1149        }
1150
1151        /* ------------------------------------------------------------ */
1152        /**
1153         * @return The path in the context
1154         */
1155        public String getPath()
1156        {
1157            return _pathInContext;
1158        }
1159    }
1160}
1161