1
1
/*
2
2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
3
*
4
- * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
4
+ * Copyright (c) 2010-2016 Oracle and/or its affiliates. All rights reserved.
5
5
*
6
6
* The contents of this file are subject to the terms of either the GNU
7
7
* General Public License Version 2 only ("GPL") or the Common Development
37
37
* only if the new code is made subject to such option by the copyright
38
38
* holder.
39
39
*/
40
+
40
41
package org .glassfish .jersey .simple ;
41
42
42
43
import java .io .IOException ;
47
48
import java .security .Principal ;
48
49
import java .util .List ;
49
50
import java .util .Map ;
51
+ import java .util .concurrent .ScheduledExecutorService ;
52
+ import java .util .concurrent .ScheduledFuture ;
53
+ import java .util .concurrent .ScheduledThreadPoolExecutor ;
50
54
import java .util .concurrent .TimeUnit ;
55
+ import java .util .concurrent .atomic .AtomicReference ;
51
56
import java .util .logging .Level ;
52
57
import java .util .logging .Logger ;
53
58
70
75
import org .glassfish .jersey .server .internal .ContainerUtils ;
71
76
import org .glassfish .jersey .server .spi .Container ;
72
77
import org .glassfish .jersey .server .spi .ContainerResponseWriter ;
78
+ import org .glassfish .jersey .server .spi .ContainerResponseWriter .TimeoutHandler ;
73
79
import org .glassfish .jersey .server .spi .RequestScopedInitializer ;
74
80
75
81
import org .glassfish .hk2 .api .ServiceLocator ;
76
82
import org .glassfish .hk2 .api .TypeLiteral ;
77
83
import org .glassfish .hk2 .utilities .binding .AbstractBinder ;
78
84
85
+ import org .simpleframework .common .thread .DaemonFactory ;
79
86
import org .simpleframework .http .Address ;
80
87
import org .simpleframework .http .Request ;
81
88
import org .simpleframework .http .Response ;
82
89
import org .simpleframework .http .Status ;
83
90
84
91
/**
85
- * Jersey {@code Container} implementation based on Simple framework {@link org.simpleframework.http.core.Container}.
92
+ * Jersey {@code Container} implementation based on Simple framework
93
+ * {@link org.simpleframework.http.core.Container}.
86
94
*
87
95
* @author Arul Dhesiaseelan (aruld@acm.org)
88
96
* @author Marek Potociar (marek.potociar at oracle.com)
@@ -120,48 +128,57 @@ public SimpleResponseReferencingFactory(final Provider<Ref<Response>> referenceF
120
128
}
121
129
122
130
/**
123
- * An internal binder to enable Simple HTTP container specific types injection.
124
- * This binder allows to inject underlying Grizzly HTTP request and response instances.
131
+ * An internal binder to enable Simple HTTP container specific types injection. This binder allows
132
+ * to inject underlying Grizzly HTTP request and response instances.
125
133
*/
126
134
private static class SimpleBinder extends AbstractBinder {
127
135
128
136
@ Override
129
137
protected void configure () {
130
- bindFactory (SimpleRequestReferencingFactory .class ).to (Request .class )
131
- . proxy ( true ) .proxyForSameScope (false ).in (RequestScoped .class );
132
- bindFactory (ReferencingFactory .<Request >referenceFactory ()). to ( new TypeLiteral < Ref < Request >>() {
133
- })
134
- .in (RequestScoped .class );
135
-
136
- bindFactory (SimpleResponseReferencingFactory .class ).to (Response .class )
137
- . proxy ( true ) .proxyForSameScope (false ).in (RequestScoped .class );
138
- bindFactory (ReferencingFactory .<Response >referenceFactory ()). to ( new TypeLiteral < Ref < Response >>() {
139
- })
140
- .in (RequestScoped .class );
138
+ bindFactory (SimpleRequestReferencingFactory .class ).to (Request .class ). proxy ( true )
139
+ .proxyForSameScope (false ).in (RequestScoped .class );
140
+ bindFactory (ReferencingFactory .<Request >referenceFactory ())
141
+ . to ( new TypeLiteral < Ref < Request >>() {
142
+ }) .in (RequestScoped .class );
143
+
144
+ bindFactory (SimpleResponseReferencingFactory .class ).to (Response .class ). proxy ( true )
145
+ .proxyForSameScope (false ).in (RequestScoped .class );
146
+ bindFactory (ReferencingFactory .<Response >referenceFactory ())
147
+ . to ( new TypeLiteral < Ref < Response >>() {
148
+ }) .in (RequestScoped .class );
141
149
}
142
150
}
143
151
152
+ private volatile ScheduledExecutorService scheduler ;
144
153
private volatile ApplicationHandler appHandler ;
145
154
146
- private static final class Writer implements ContainerResponseWriter {
155
+ private static final class ResponseWriter implements ContainerResponseWriter {
147
156
157
+ private final AtomicReference <TimeoutTimer > reference ;
158
+ private final ScheduledExecutorService scheduler ;
148
159
private final Response response ;
149
160
150
- Writer (final Response response ) {
161
+ ResponseWriter (final Response response , final ScheduledExecutorService scheduler ) {
162
+ this .reference = new AtomicReference <TimeoutTimer >();
151
163
this .response = response ;
164
+ this .scheduler = scheduler ;
152
165
}
153
166
154
167
@ Override
155
- public OutputStream writeResponseStatusAndHeaders (final long contentLength , final ContainerResponse context )
156
- throws ContainerException {
168
+ public OutputStream writeResponseStatusAndHeaders (final long contentLength ,
169
+ final ContainerResponse context ) throws ContainerException {
157
170
final javax .ws .rs .core .Response .StatusType statusInfo = context .getStatusInfo ();
158
171
159
172
final int code = statusInfo .getStatusCode ();
160
- final String reason = statusInfo .getReasonPhrase () == null ? Status .getDescription (code )
173
+ final String reason = statusInfo .getReasonPhrase () == null
174
+ ? Status .getDescription (code )
161
175
: statusInfo .getReasonPhrase ();
162
176
response .setCode (code );
163
177
response .setDescription (reason );
164
- response .setContentLength (contentLength );
178
+
179
+ if (contentLength != -1 ) {
180
+ response .setContentLength (contentLength );
181
+ }
165
182
for (final Map .Entry <String , List <String >> e : context .getStringHeaders ().entrySet ()) {
166
183
for (final String value : e .getValue ()) {
167
184
response .addValue (e .getKey (), value );
@@ -176,13 +193,41 @@ public OutputStream writeResponseStatusAndHeaders(final long contentLength, fina
176
193
}
177
194
178
195
@ Override
179
- public boolean suspend (final long timeOut , final TimeUnit timeUnit , final TimeoutHandler timeoutHandler ) {
180
- throw new UnsupportedOperationException ("Method suspend is not supported by the container." );
196
+ public boolean suspend (final long timeOut , final TimeUnit timeUnit ,
197
+ final TimeoutHandler timeoutHandler ) {
198
+ try {
199
+ TimeoutTimer timer = reference .get ();
200
+
201
+ if (timer == null ) {
202
+ TimeoutDispatcher task = new TimeoutDispatcher (this , timeoutHandler );
203
+ ScheduledFuture <?> future =
204
+ scheduler .schedule (task , timeOut == 0 ? Integer .MAX_VALUE : timeOut ,
205
+ timeOut == 0 ? TimeUnit .SECONDS : timeUnit );
206
+ timer = new TimeoutTimer (scheduler , future , task );
207
+ reference .set (timer );
208
+ return true ;
209
+ }
210
+ return false ;
211
+ } catch (final IllegalStateException ex ) {
212
+ return false ;
213
+ } finally {
214
+ logger .debugLog ("suspend(...) called" );
215
+ }
181
216
}
182
217
183
218
@ Override
184
- public void setSuspendTimeout (final long timeOut , final TimeUnit timeUnit ) throws IllegalStateException {
185
- throw new UnsupportedOperationException ("Method suspend is not supported by the container." );
219
+ public void setSuspendTimeout (final long timeOut , final TimeUnit timeUnit )
220
+ throws IllegalStateException {
221
+ try {
222
+ TimeoutTimer timer = reference .get ();
223
+
224
+ if (timer == null ) {
225
+ throw new IllegalStateException ("Response has not been suspended" );
226
+ }
227
+ timer .reschedule (timeOut , timeUnit );
228
+ } finally {
229
+ logger .debugLog ("setTimeout(...) called" );
230
+ }
186
231
}
187
232
188
233
@ Override
@@ -196,6 +241,10 @@ public void commit() {
196
241
}
197
242
}
198
243
244
+ public boolean isSuspended () {
245
+ return reference .get () != null ;
246
+ }
247
+
199
248
@ Override
200
249
public void failure (final Throwable error ) {
201
250
try {
@@ -231,19 +280,64 @@ private void rethrow(final Throwable error) {
231
280
232
281
}
233
282
283
+ private static final class TimeoutTimer {
284
+
285
+ private final AtomicReference <ScheduledFuture <?>> reference ;
286
+ private final ScheduledExecutorService service ;
287
+ private final TimeoutDispatcher task ;
288
+
289
+ public TimeoutTimer (ScheduledExecutorService service , ScheduledFuture <?> future ,
290
+ TimeoutDispatcher task ) {
291
+ this .reference = new AtomicReference <ScheduledFuture <?>>();
292
+ this .service = service ;
293
+ this .task = task ;
294
+ }
295
+
296
+ public void reschedule (long timeOut , TimeUnit timeUnit ) {
297
+ ScheduledFuture <?> future = reference .getAndSet (null );
298
+
299
+ if (future != null ) {
300
+ if (future .cancel (false )) {
301
+ future = service .schedule (task , timeOut == 0 ? Integer .MAX_VALUE : timeOut ,
302
+ timeOut == 0 ? TimeUnit .SECONDS : timeUnit );
303
+ reference .set (future );
304
+ }
305
+ } else {
306
+ future = service .schedule (task , timeOut == 0 ? Integer .MAX_VALUE : timeOut ,
307
+ timeOut == 0 ? TimeUnit .SECONDS : timeUnit );
308
+ reference .set (future );
309
+ }
310
+ }
311
+ }
312
+
313
+ private static final class TimeoutDispatcher implements Runnable {
314
+
315
+ private final ResponseWriter writer ;
316
+ private final TimeoutHandler handler ;
317
+
318
+ public TimeoutDispatcher (ResponseWriter writer , TimeoutHandler handler ) {
319
+ this .writer = writer ;
320
+ this .handler = handler ;
321
+ }
322
+
323
+ public void run () {
324
+ try {
325
+ handler .onTimeout (writer );
326
+ } catch (Exception e ) {
327
+ logger .log (Level .INFO , "Failed to call timeout handler" , e );
328
+ }
329
+ }
330
+ }
331
+
234
332
@ Override
235
333
public void handle (final Request request , final Response response ) {
236
- final Writer responseWriter = new Writer (response );
334
+ final ResponseWriter responseWriter = new ResponseWriter (response , scheduler );
237
335
final URI baseUri = getBaseUri (request );
238
336
final URI requestUri = getRequestUri (request , baseUri );
239
337
240
338
try {
241
- final ContainerRequest requestContext = new ContainerRequest (
242
- baseUri ,
243
- requestUri ,
244
- request .getMethod (),
245
- getSecurityContext (request ),
246
- new MapPropertiesDelegate ());
339
+ final ContainerRequest requestContext = new ContainerRequest (baseUri , requestUri ,
340
+ request .getMethod (), getSecurityContext (request ), new MapPropertiesDelegate ());
247
341
requestContext .setEntityStream (request .getInputStream ());
248
342
for (final String headerName : request .getNames ()) {
249
343
requestContext .headers (headerName , request .getValue (headerName ));
@@ -261,7 +355,9 @@ public void initialize(final ServiceLocator locator) {
261
355
} catch (final Exception ex ) {
262
356
throw new RuntimeException (ex );
263
357
} finally {
264
- close (response );
358
+ if (!responseWriter .isSuspended ()) {
359
+ close (response );
360
+ }
265
361
}
266
362
}
267
363
@@ -282,7 +378,8 @@ private URI getRequestUri(final Request request, final URI baseUri) {
282
378
}
283
379
284
380
private String getServerAddress (final URI baseUri ) throws URISyntaxException {
285
- return new URI (baseUri .getScheme (), null , baseUri .getHost (), baseUri .getPort (), null , null , null ).toString ();
381
+ return new URI (baseUri .getScheme (), null , baseUri .getHost (), baseUri .getPort (), null , null ,
382
+ null ).toString ();
286
383
}
287
384
288
385
private URI getBaseUri (final Request request ) {
@@ -294,7 +391,8 @@ private URI getBaseUri(final Request request) {
294
391
return new URI (scheme + "://" + hostHeader + "/" );
295
392
} else {
296
393
final Address address = request .getAddress ();
297
- return new URI (address .getScheme (), null , address .getDomain (), address .getPort (), "/" , null , null );
394
+ return new URI (address .getScheme (), null , address .getDomain (), address .getPort (), "/" , null ,
395
+ null );
298
396
}
299
397
} catch (final URISyntaxException ex ) {
300
398
throw new IllegalArgumentException (ex );
@@ -316,7 +414,7 @@ public boolean isSecure() {
316
414
317
415
@ Override
318
416
public Principal getUserPrincipal () {
319
- return request . getSecuritySession (). getLocalPrincipal () ;
417
+ return null ;
320
418
}
321
419
322
420
@ Override
@@ -349,6 +447,7 @@ public void reload(final ResourceConfig configuration) {
349
447
appHandler .onShutdown (this );
350
448
351
449
appHandler = new ApplicationHandler (configuration .register (new SimpleBinder ()));
450
+ scheduler = new ScheduledThreadPoolExecutor (2 , new DaemonFactory (TimeoutDispatcher .class ));
352
451
appHandler .onReload (this );
353
452
appHandler .onStartup (this );
354
453
}
@@ -360,7 +459,7 @@ public ApplicationHandler getApplicationHandler() {
360
459
361
460
/**
362
461
* Inform this container that the server has been started.
363
- *
462
+ * <p/>
364
463
* This method must be implicitly called after the server containing this container is started.
365
464
*/
366
465
void onServerStart () {
@@ -369,29 +468,34 @@ void onServerStart() {
369
468
370
469
/**
371
470
* Inform this container that the server is being stopped.
372
- *
471
+ * <p/>
373
472
* This method must be implicitly called before the server containing this container is stopped.
374
473
*/
375
474
void onServerStop () {
376
475
appHandler .onShutdown (this );
476
+ scheduler .shutdown ();
377
477
}
378
478
379
479
/**
380
480
* Create a new Simple framework HTTP container.
381
481
*
382
- * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP container.
482
+ * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP
483
+ * container.
383
484
* @param parentLocator parent HK2 service locator.
384
485
*/
385
486
SimpleContainer (final Application application , final ServiceLocator parentLocator ) {
386
487
this .appHandler = new ApplicationHandler (application , new SimpleBinder (), parentLocator );
488
+ this .scheduler = new ScheduledThreadPoolExecutor (2 , new DaemonFactory (TimeoutDispatcher .class ));
387
489
}
388
490
389
491
/**
390
492
* Create a new Simple framework HTTP container.
391
493
*
392
- * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP container.
494
+ * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP
495
+ * container.
393
496
*/
394
497
SimpleContainer (final Application application ) {
395
498
this .appHandler = new ApplicationHandler (application , new SimpleBinder ());
499
+ this .scheduler = new ScheduledThreadPoolExecutor (2 , new DaemonFactory (TimeoutDispatcher .class ));
396
500
}
397
501
}
0 commit comments