View Javadoc

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