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