1   //========================================================================
2   //Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
3   //------------------------------------------------------------------------
4   //Licensed under the Apache License, Version 2.0 (the "License");
5   //you may not use this file except in compliance with the License.
6   //You may obtain a copy of the License at 
7   //http://www.apache.org/licenses/LICENSE-2.0
8   //Unless required by applicable law or agreed to in writing, software
9   //distributed under the License is distributed on an "AS IS" BASIS,
10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  //See the License for the specific language governing permissions and
12  //limitations under the License.
13  //========================================================================
14  
15  package org.mortbay.jetty;
16  
17  import java.io.IOException;
18  
19  import org.mortbay.io.AsyncEndPoint;
20  import org.mortbay.io.EndPoint;
21  import org.mortbay.log.Log;
22  import org.mortbay.thread.Timeout;
23  
24  public class Suspendable
25  {
26      // STATES:
27      private static final int __IDLE=0; // Idle request
28      private static final int __HANDLING=1;   // Request dispatched to filter/servlet
29      private static final int __SUSPENDING=2;   // Suspend called, but not yet returned to container
30      private static final int __RESUMING=3;     // resumed while suspending
31      private static final int __COMPLETING=4;   // resumed while suspending or suspended
32      private static final int __SUSPENDED=5;    // Suspended and parked
33      private static final int __UNSUSPENDING=6; // Has been scheduled
34      
35      // State table
36      //                       __HANDLE      __UNHANDLE       __SUSPEND        __RESUME   
37      // IDLE */          {  __HANDLING,      __Illegal,      __Illegal,      __Illegal  },    
38      // HANDLING */      {   __Illegal,         __IDLE,   __SUSPENDING,       __Ignore  },
39      // SUSPENDING */    {   __Illegal,    __SUSPENDED,      __Illegal,     __RESUMING  },
40      // RESUMING */      {   __Illegal,      _HANDLING,      __Ignored,       __Ignore  },
41      // COMPLETING */    {   __Illegal,         __IDLE,      __Illegal,       __Illegal },
42      // SUSPENDED */     {  __HANDLING,      __Illegal,      __Illegal, __UNSUSPENDING  },
43      // UNSUSPENDING */  {  __HANDLING,      __Illegal,      __Illegal,       __Ignore  }
44      
45      // State diagram
46      //
47      //   +----->  IDLE  <---------------------> HANDLING
48      //   |                                       ^  |
49      //   |                                       |  |
50      //   |          +--------------------------->|  |
51      //   |          ^                            |  |
52      //   |          |     +--------------------->+  |   
53      //   |          |     ^                ^        |
54      //   |          |     |                |        v
55      //   |          |    SUSPENDED <----------- SUSPENDING
56      //   |          |     |  |             |    |   |
57      //   |          |     |  |             |    |   |
58      //   |          |     v  |             |    v   |
59      //   |     UNSUSPENDING  |            RESUMING  |
60      //   |                   |                |     |
61      //   |                   v                v     v
62      //   +---------------- COMPLETING  <------------+
63      
64      
65      protected HttpConnection _connection;
66      
67      protected int _state;
68      protected boolean _initial;
69      protected boolean _resumed;   // resume called (different to resumed state)
70      protected boolean _timeout;
71      
72      protected long _timeoutMs;
73      protected final Timeout.Task _timeoutTask;
74      
75  
76      /* ------------------------------------------------------------ */
77      public Suspendable(HttpConnection connection)
78      {
79          _connection=connection;
80          _state=__IDLE;
81          _initial=true;
82          _resumed=false;
83              
84          _timeoutTask= new Timeout.Task()
85          {
86              public void expired()
87              {
88                  Suspendable.this.expire();
89              }
90          };
91      }
92  
93  
94  
95      /* ------------------------------------------------------------ */
96      public long getTimeout()
97      {
98          return _timeoutMs;
99      } 
100 
101     
102 
103     /* ------------------------------------------------------------ */
104     /* (non-Javadoc)
105      * @see javax.servlet.ServletRequest#isInitial()
106      */
107     public boolean isInitial()
108     {
109         synchronized(this)
110         {
111             return _initial;
112         }
113     }
114        
115     /* ------------------------------------------------------------ */
116     /* (non-Javadoc)
117      * @see javax.servlet.ServletRequest#isResumed()
118      */
119     public boolean isResumed()
120     {
121         synchronized(this)
122         {
123             return _resumed;
124         }
125     }
126     
127     /* ------------------------------------------------------------ */
128     /* (non-Javadoc)
129      * @see javax.servlet.ServletRequest#isSuspended()
130      */
131     public boolean isSuspended()
132     {
133         synchronized(this)
134         {
135             switch(_state)
136             {
137                 case __IDLE:
138                 case __HANDLING:
139                     return false;
140                 case __SUSPENDING:
141                 case __RESUMING:
142                 case __COMPLETING:
143                 case __SUSPENDED:
144                     return true;
145                 case __UNSUSPENDING:
146                 default:
147                     return false;   
148             }
149         }
150     }
151 
152     
153     /* ------------------------------------------------------------ */
154     /* (non-Javadoc)
155      * @see javax.servlet.ServletRequest#isTimeout()
156      */
157     public boolean isTimeout()
158     {
159         synchronized(this)
160         {
161             return _timeout;
162         }
163     }
164     
165 
166     /* ------------------------------------------------------------ */
167     /* (non-Javadoc)
168      * @see javax.servlet.ServletRequest#suspend()
169      */
170     public void suspend()
171     {
172         long timeout = 30000L;
173         suspend(timeout);
174     }
175 
176     /* ------------------------------------------------------------ */
177     public String toString()
178     {
179         return getStatusString();
180     }
181 
182     /* ------------------------------------------------------------ */
183     public String getStatusString()
184     {
185         synchronized (this)
186         {
187             return
188             ((_state==__IDLE)?"IDLE":
189                 (_state==__HANDLING)?"HANDLING":
190                     (_state==__SUSPENDING)?"SUSPENDING":
191                         (_state==__SUSPENDED)?"SUSPENDED":
192                             (_state==__RESUMING)?"RESUMING":
193                                 (_state==__UNSUSPENDING)?"UNSUSPENDING":
194                                     (_state==__COMPLETING)?"COMPLETING":
195                                     ("???"+_state))+
196             (_initial?",initial":"")+
197             (_resumed?",resumed":"")+
198             (_timeout?",timeout":"");
199         }
200     }
201 
202     /* ------------------------------------------------------------ */
203     /* (non-Javadoc)
204      * @see javax.servlet.ServletRequest#resume()
205      */
206     public void handling()
207     {
208         synchronized (this)
209         {
210             switch(_state)
211             {
212                 case __HANDLING:
213                     throw new IllegalStateException(this.getStatusString());
214 
215                 case __IDLE:
216                     _initial=true;
217                     _state=__HANDLING;
218                     return;
219 
220                 case __SUSPENDING:
221                 case __RESUMING:
222                     throw new IllegalStateException(this.getStatusString());
223 
224                 case __COMPLETING:
225                     return;
226 
227                 case __SUSPENDED:
228                     cancelTimeout();
229                 case __UNSUSPENDING:
230                     _state=__HANDLING;
231                     return;
232 
233                 default:
234                     throw new IllegalStateException(""+_state);
235             }
236 
237         }
238     }
239 
240     /* ------------------------------------------------------------ */
241     /* (non-Javadoc)
242      * @see javax.servlet.ServletRequest#suspend(long)
243      */
244     public void suspend(long timeoutMs)
245     {
246         synchronized (this)
247         {
248             switch(_state)
249             {
250                 case __HANDLING:
251                     _timeout=false;
252                     _resumed=false;
253                     _state=__SUSPENDING;
254                     _timeoutMs = timeoutMs;
255                     return;
256 
257                 case __IDLE:
258                     throw new IllegalStateException(this.getStatusString());
259 
260                 case __SUSPENDING:
261                 case __RESUMING:
262                     if (timeoutMs<_timeoutMs)
263                         _timeoutMs = timeoutMs;
264                     return;
265 
266                 case __COMPLETING:
267                 case __SUSPENDED:
268                 case __UNSUSPENDING:
269                     throw new IllegalStateException(this.getStatusString());
270 
271                 default:
272                     throw new IllegalStateException(""+_state);
273             }
274 
275         }
276     }
277 
278     /* ------------------------------------------------------------ */
279     /**
280      * @return true if handling is complete, false if the request should 
281      * be handled again (eg because of a resume)
282      */
283     public boolean unhandling()
284     {
285         synchronized (this)
286         {
287             switch(_state)
288             {
289                 case __HANDLING:
290                     _state=__IDLE;
291                     return true;
292 
293                 case __IDLE:
294                     throw new IllegalStateException(this.getStatusString());
295 
296                 case __SUSPENDING:
297                     _initial=false;
298                     _state=__SUSPENDED;
299                     scheduleTimeout(); // could block and change state.
300                     if (_state==__SUSPENDED || _state==__COMPLETING)
301                         return true;
302                     _initial=false;
303                     _state=__HANDLING;
304                     return false; 
305 
306                 case __RESUMING:
307                     _initial=false;
308                     _state=__HANDLING;
309                     return false; 
310 
311                 case __COMPLETING:
312                     _initial=false;
313                     _state=__IDLE;
314                     return true;
315 
316                 case __SUSPENDED:
317                 case __UNSUSPENDING:
318                 default:
319                     throw new IllegalStateException(this.getStatusString());
320 
321             }
322 
323         }
324     }
325 
326     /* ------------------------------------------------------------ */
327     public void resume()
328     {
329         boolean dispatch=false;
330         synchronized (this)
331         {
332             switch(_state)
333             {
334                 case __HANDLING:
335                     _resumed=true;
336                     return;
337                     
338                 case __SUSPENDING:
339                     _resumed=true;
340                     _state=__RESUMING;
341                     return;
342 
343                 case __IDLE:
344                 case __RESUMING:
345                 case __COMPLETING:
346                     return;
347                     
348                 case __SUSPENDED:
349                     dispatch=true;
350                     _resumed=true;
351                     _state=__UNSUSPENDING;
352                     break;
353                     
354                 case __UNSUSPENDING:
355                     _resumed=true;
356                     return;
357                     
358                 default:
359                     throw new IllegalStateException(this.getStatusString());
360             }
361         }
362         
363         if (dispatch)
364         {
365             cancelTimeout();
366             scheduleDispatch();
367         }
368     }
369 
370 
371     /* ------------------------------------------------------------ */
372     protected void expire()
373     {
374         // just like resume, except don't set _resumed=true;
375         boolean dispatch=false;
376         synchronized (this)
377         {
378             switch(_state)
379             {
380                 case __HANDLING:
381                     return;
382                     
383                 case __IDLE:
384                     throw new IllegalStateException(this.getStatusString());
385                     
386                 case __SUSPENDING:
387                     _timeout=true;
388                     _state=__RESUMING;
389                     cancelTimeout();
390                     return;
391                     
392                 case __RESUMING:
393                     return;
394                     
395                 case __COMPLETING:
396                     return;
397                     
398                 case __SUSPENDED:
399                     dispatch=true;
400                     _timeout=true;
401                     _state=__UNSUSPENDING;
402                     break;
403                     
404                 case __UNSUSPENDING:
405                     _timeout=true;
406                     return;
407                     
408                 default:
409                     throw new IllegalStateException(this.getStatusString());
410             }
411         }
412         if (dispatch)
413         {
414             scheduleDispatch();
415         }
416     }
417     
418     /* ------------------------------------------------------------ */
419     /* (non-Javadoc)
420      * @see javax.servlet.ServletRequest#complete()
421      */
422     public void complete() throws IOException
423     {
424         // just like resume, except don't set _resumed=true;
425         boolean dispatch=false;
426         synchronized (this)
427         {
428             switch(_state)
429             {
430                 case __HANDLING:
431                     throw new IllegalStateException(this.getStatusString());
432                     
433                 case __IDLE:
434                     return;
435                     
436                 case __SUSPENDING:
437                     _state=__COMPLETING;
438                     break;
439                     
440                 case __RESUMING:
441                     break;
442 
443                 case __COMPLETING:
444                     return;
445                     
446                 case __SUSPENDED:
447                     _state=__COMPLETING;
448                     dispatch=true;
449                     break;
450                     
451                 case __UNSUSPENDING:
452                     return;
453                     
454                 default:
455                     throw new IllegalStateException(this.getStatusString());
456             }
457         }
458         
459         if (dispatch)
460         {
461             cancelTimeout();
462             scheduleDispatch();
463         }
464     }
465 
466 
467     /* ------------------------------------------------------------ */
468     public void reset()
469     {
470         synchronized (this)
471         {
472             _state=(_state==__SUSPENDED||_state==__IDLE)?__IDLE:__HANDLING;
473             _resumed = false;
474             _initial = true;
475             _timeout = false;
476             cancelTimeout();
477         }
478     }
479 
480     /* ------------------------------------------------------------ */
481     protected void scheduleDispatch()
482     {
483         EndPoint endp=_connection.getEndPoint();
484         if (!endp.isBlocking())
485         {
486             ((AsyncEndPoint)endp).dispatch();
487         }
488     }
489 
490     /* ------------------------------------------------------------ */
491     protected void scheduleTimeout()
492     {
493         EndPoint endp=_connection.getEndPoint();
494         if (endp.isBlocking())
495         {
496             synchronized(this)
497             {
498                 long expire_at = System.currentTimeMillis()+_timeoutMs;
499                 long wait=_timeoutMs;
500                 while (_timeoutMs>0 && wait>0)
501                 {
502                     try
503                     {
504                         this.wait(wait);
505                     }
506                     catch (InterruptedException e)
507                     {
508                         Log.ignore(e);
509                     }
510                     wait=expire_at-System.currentTimeMillis();
511                 }
512 
513                 if (_timeoutMs>0 && wait<=0)
514                     expire();
515             }
516             
517         }
518         else
519             _connection.scheduleTimeout(_timeoutTask,_timeoutMs);
520     }
521 
522     /* ------------------------------------------------------------ */
523     protected void cancelTimeout()
524     {
525         EndPoint endp=_connection.getEndPoint();
526         if (endp.isBlocking())
527         {
528             synchronized(this)
529             {
530                 _timeoutMs=0;
531                 this.notifyAll();
532             }
533         }
534         else
535             _connection.cancelTimeout(_timeoutTask);
536     }
537 
538     /* ------------------------------------------------------------ */
539     public boolean isCompleting()
540     {
541         return _state==__COMPLETING;
542     }
543     
544     /* ------------------------------------------------------------ */
545     public boolean shouldHandleRequest()
546     {
547         switch(_state)
548         {
549             case __COMPLETING:
550                 return false;
551                 
552             default:
553             return true;
554         }
555     }
556 
557     /* ------------------------------------------------------------ */
558     public boolean shouldComplete()
559     {
560         switch(_state)
561         {
562             case __RESUMING:
563             case __SUSPENDED:
564             case __SUSPENDING:
565             case __UNSUSPENDING:
566                 return false;
567                 
568             default:
569             return true;
570         }
571     }
572     
573 }