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
19
20package org.eclipse.jetty.continuation;
21
22import java.util.ArrayList;
23
24import javax.servlet.ServletRequest;
25import javax.servlet.ServletResponse;
26import javax.servlet.ServletResponseWrapper;
27
28import org.eclipse.jetty.continuation.ContinuationFilter.FilteredContinuation;
29
30
31/* ------------------------------------------------------------ */
32/**
33 * A blocking implementation of Continuation.
34 * This implementation of Continuation is used by the {@link ContinuationFilter}
35 * when there are is no native or asynchronous continuation type available.
36 */
37class FauxContinuation implements FilteredContinuation
38{
39    // common exception used for all continuations.
40    // Turn on debug in ContinuationFilter to see real stack trace.
41    private final static ContinuationThrowable __exception = new ContinuationThrowable();
42
43    private static final int __HANDLING=1;   // Request dispatched to filter/servlet
44    private static final int __SUSPENDING=2;   // Suspend called, but not yet returned to container
45    private static final int __RESUMING=3;     // resumed while suspending
46    private static final int __COMPLETING=4;   // resumed while suspending or suspended
47    private static final int __SUSPENDED=5;    // Suspended and parked
48    private static final int __UNSUSPENDING=6;
49    private static final int __COMPLETE=7;
50
51    private final ServletRequest _request;
52    private ServletResponse _response;
53
54    private int _state=__HANDLING;
55    private boolean _initial=true;
56    private boolean _resumed=false;
57    private boolean _timeout=false;
58    private boolean _responseWrapped=false;
59    private  long _timeoutMs=30000; // TODO configure
60
61    private ArrayList<ContinuationListener> _listeners;
62
63    FauxContinuation(final ServletRequest request)
64    {
65        _request=request;
66    }
67
68    /* ------------------------------------------------------------ */
69    public void onComplete()
70    {
71        if (_listeners!=null)
72            for (ContinuationListener l:_listeners)
73                l.onComplete(this);
74    }
75
76    /* ------------------------------------------------------------ */
77    public void onTimeout()
78    {
79        if (_listeners!=null)
80            for (ContinuationListener l:_listeners)
81                l.onTimeout(this);
82    }
83
84    /* ------------------------------------------------------------ */
85    /**
86     * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
87     */
88    public boolean isResponseWrapped()
89    {
90        return _responseWrapped;
91    }
92
93    /* ------------------------------------------------------------ */
94    public boolean isInitial()
95    {
96        synchronized(this)
97        {
98            return _initial;
99        }
100    }
101
102    /* ------------------------------------------------------------ */
103    public boolean isResumed()
104    {
105        synchronized(this)
106        {
107            return _resumed;
108        }
109    }
110
111    /* ------------------------------------------------------------ */
112    public boolean isSuspended()
113    {
114        synchronized(this)
115        {
116            switch(_state)
117            {
118                case __HANDLING:
119                    return false;
120                case __SUSPENDING:
121                case __RESUMING:
122                case __COMPLETING:
123                case __SUSPENDED:
124                    return true;
125                case __UNSUSPENDING:
126                default:
127                    return false;
128            }
129        }
130    }
131
132    /* ------------------------------------------------------------ */
133    public boolean isExpired()
134    {
135        synchronized(this)
136        {
137            return _timeout;
138        }
139    }
140
141    /* ------------------------------------------------------------ */
142    public void setTimeout(long timeoutMs)
143    {
144        _timeoutMs = timeoutMs;
145    }
146
147    /* ------------------------------------------------------------ */
148    public void suspend(ServletResponse response)
149    {
150        _response=response;
151        _responseWrapped=response instanceof ServletResponseWrapper;
152        suspend();
153    }
154
155    /* ------------------------------------------------------------ */
156    public void suspend()
157    {
158        synchronized (this)
159        {
160            switch(_state)
161            {
162                case __HANDLING:
163                    _timeout=false;
164                    _resumed=false;
165                    _state=__SUSPENDING;
166                    return;
167
168                case __SUSPENDING:
169                case __RESUMING:
170                    return;
171
172                case __COMPLETING:
173                case __SUSPENDED:
174                case __UNSUSPENDING:
175                    throw new IllegalStateException(this.getStatusString());
176
177                default:
178                    throw new IllegalStateException(""+_state);
179            }
180
181        }
182    }
183
184
185    /* ------------------------------------------------------------ */
186    /* (non-Javadoc)
187     * @see org.mortbay.jetty.Suspendor#resume()
188     */
189    public void resume()
190    {
191        synchronized (this)
192        {
193            switch(_state)
194            {
195                case __HANDLING:
196                    _resumed=true;
197                    return;
198
199                case __SUSPENDING:
200                    _resumed=true;
201                    _state=__RESUMING;
202                    return;
203
204                case __RESUMING:
205                case __COMPLETING:
206                    return;
207
208                case __SUSPENDED:
209                    fauxResume();
210                    _resumed=true;
211                    _state=__UNSUSPENDING;
212                    break;
213
214                case __UNSUSPENDING:
215                    _resumed=true;
216                    return;
217
218                default:
219                    throw new IllegalStateException(this.getStatusString());
220            }
221        }
222
223    }
224
225
226    /* ------------------------------------------------------------ */
227    public void complete()
228    {
229        // just like resume, except don't set _resumed=true;
230        synchronized (this)
231        {
232            switch(_state)
233            {
234                case __HANDLING:
235                    throw new IllegalStateException(this.getStatusString());
236
237                case __SUSPENDING:
238                    _state=__COMPLETING;
239                    break;
240
241                case __RESUMING:
242                    break;
243
244                case __COMPLETING:
245                    return;
246
247                case __SUSPENDED:
248                    _state=__COMPLETING;
249                    fauxResume();
250                    break;
251
252                case __UNSUSPENDING:
253                    return;
254
255                default:
256                    throw new IllegalStateException(this.getStatusString());
257            }
258        }
259    }
260
261    /* ------------------------------------------------------------ */
262    /**
263     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
264     */
265    public boolean enter(ServletResponse response)
266    {
267        _response=response;
268        return true;
269    }
270
271    /* ------------------------------------------------------------ */
272    /**
273     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
274     */
275    public ServletResponse getServletResponse()
276    {
277        return _response;
278    }
279
280
281    /* ------------------------------------------------------------ */
282    void handling()
283    {
284        synchronized (this)
285        {
286            _responseWrapped=false;
287            switch(_state)
288            {
289                case __HANDLING:
290                    throw new IllegalStateException(this.getStatusString());
291
292                case __SUSPENDING:
293                case __RESUMING:
294                    throw new IllegalStateException(this.getStatusString());
295
296                case __COMPLETING:
297                    return;
298
299                case __SUSPENDED:
300                    fauxResume();
301                case __UNSUSPENDING:
302                    _state=__HANDLING;
303                    return;
304
305                default:
306                    throw new IllegalStateException(""+_state);
307            }
308
309        }
310    }
311
312    /* ------------------------------------------------------------ */
313    /**
314     * @return true if handling is complete
315     */
316    public boolean exit()
317    {
318        synchronized (this)
319        {
320            switch(_state)
321            {
322                case __HANDLING:
323                    _state=__COMPLETE;
324                    onComplete();
325                    return true;
326
327                case __SUSPENDING:
328                    _initial=false;
329                    _state=__SUSPENDED;
330                    fauxSuspend(); // could block and change state.
331                    if (_state==__SUSPENDED || _state==__COMPLETING)
332                    {
333                        onComplete();
334                        return true;
335                    }
336
337                    _initial=false;
338                    _state=__HANDLING;
339                    return false;
340
341                case __RESUMING:
342                    _initial=false;
343                    _state=__HANDLING;
344                    return false;
345
346                case __COMPLETING:
347                    _initial=false;
348                    _state=__COMPLETE;
349                    onComplete();
350                    return true;
351
352                case __SUSPENDED:
353                case __UNSUSPENDING:
354                default:
355                    throw new IllegalStateException(this.getStatusString());
356            }
357        }
358    }
359
360    /* ------------------------------------------------------------ */
361    protected void expire()
362    {
363        // just like resume, except don't set _resumed=true;
364
365        synchronized (this)
366        {
367            _timeout=true;
368        }
369
370        onTimeout();
371
372        synchronized (this)
373        {
374            switch(_state)
375            {
376                case __HANDLING:
377                    return;
378
379                case __SUSPENDING:
380                    _timeout=true;
381                    _state=__RESUMING;
382                    fauxResume();
383                    return;
384
385                case __RESUMING:
386                    return;
387
388                case __COMPLETING:
389                    return;
390
391                case __SUSPENDED:
392                    _timeout=true;
393                    _state=__UNSUSPENDING;
394                    break;
395
396                case __UNSUSPENDING:
397                    _timeout=true;
398                    return;
399
400                default:
401                    throw new IllegalStateException(this.getStatusString());
402            }
403        }
404    }
405
406    private void fauxSuspend()
407    {
408        long expire_at = System.currentTimeMillis()+_timeoutMs;
409        long wait=_timeoutMs;
410        while (_timeoutMs>0 && wait>0)
411        {
412            try
413            {
414                this.wait(wait);
415            }
416            catch (InterruptedException e)
417            {
418                break;
419            }
420            wait=expire_at-System.currentTimeMillis();
421        }
422
423        if (_timeoutMs>0 && wait<=0)
424            expire();
425    }
426
427    private void fauxResume()
428    {
429        _timeoutMs=0;
430        this.notifyAll();
431    }
432
433    @Override
434    public String toString()
435    {
436        return getStatusString();
437    }
438
439    String getStatusString()
440    {
441        synchronized (this)
442        {
443            return
444            ((_state==__HANDLING)?"HANDLING":
445                    (_state==__SUSPENDING)?"SUSPENDING":
446                        (_state==__SUSPENDED)?"SUSPENDED":
447                            (_state==__RESUMING)?"RESUMING":
448                                (_state==__UNSUSPENDING)?"UNSUSPENDING":
449                                    (_state==__COMPLETING)?"COMPLETING":
450                                    ("???"+_state))+
451            (_initial?",initial":"")+
452            (_resumed?",resumed":"")+
453            (_timeout?",timeout":"");
454        }
455    }
456
457
458    public void addContinuationListener(ContinuationListener listener)
459    {
460        if (_listeners==null)
461            _listeners=new ArrayList<ContinuationListener>();
462        _listeners.add(listener);
463
464    }
465
466    /* ------------------------------------------------------------ */
467    /**
468     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
469     */
470    public Object getAttribute(String name)
471    {
472        return _request.getAttribute(name);
473    }
474
475    /* ------------------------------------------------------------ */
476    /**
477     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
478     */
479    public void removeAttribute(String name)
480    {
481        _request.removeAttribute(name);
482    }
483
484    /* ------------------------------------------------------------ */
485    /**
486     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
487     */
488    public void setAttribute(String name, Object attribute)
489    {
490        _request.setAttribute(name,attribute);
491    }
492
493    /* ------------------------------------------------------------ */
494    /**
495     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
496     */
497    public void undispatch()
498    {
499        if (isSuspended())
500        {
501            if (ContinuationFilter.__debug)
502                throw new ContinuationThrowable();
503            throw __exception;
504        }
505        throw new IllegalStateException("!suspended");
506
507    }
508}
509