11/*
22 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
33 *
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.
55 *
66 * The contents of this file are subject to the terms of either the GNU
77 * General Public License Version 2 only ("GPL") or the Common Development
3737 * only if the new code is made subject to such option by the copyright
3838 * holder.
3939 */
40+
4041package org .glassfish .jersey .simple ;
4142
4243import java .io .IOException ;
4748import java .security .Principal ;
4849import java .util .List ;
4950import java .util .Map ;
51+ import java .util .concurrent .ScheduledExecutorService ;
52+ import java .util .concurrent .ScheduledFuture ;
53+ import java .util .concurrent .ScheduledThreadPoolExecutor ;
5054import java .util .concurrent .TimeUnit ;
55+ import java .util .concurrent .atomic .AtomicReference ;
5156import java .util .logging .Level ;
5257import java .util .logging .Logger ;
5358
7075import org .glassfish .jersey .server .internal .ContainerUtils ;
7176import org .glassfish .jersey .server .spi .Container ;
7277import org .glassfish .jersey .server .spi .ContainerResponseWriter ;
78+ import org .glassfish .jersey .server .spi .ContainerResponseWriter .TimeoutHandler ;
7379import org .glassfish .jersey .server .spi .RequestScopedInitializer ;
7480
7581import org .glassfish .hk2 .api .ServiceLocator ;
7682import org .glassfish .hk2 .api .TypeLiteral ;
7783import org .glassfish .hk2 .utilities .binding .AbstractBinder ;
7884
85+ import org .simpleframework .common .thread .DaemonFactory ;
7986import org .simpleframework .http .Address ;
8087import org .simpleframework .http .Request ;
8188import org .simpleframework .http .Response ;
8289import org .simpleframework .http .Status ;
8390
8491/**
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}.
8694 *
8795 * @author Arul Dhesiaseelan ([email protected] ) 8896 * @author Marek Potociar (marek.potociar at oracle.com)
@@ -120,48 +128,57 @@ public SimpleResponseReferencingFactory(final Provider<Ref<Response>> referenceF
120128 }
121129
122130 /**
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.
125133 */
126134 private static class SimpleBinder extends AbstractBinder {
127135
128136 @ Override
129137 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 );
141149 }
142150 }
143151
152+ private volatile ScheduledExecutorService scheduler ;
144153 private volatile ApplicationHandler appHandler ;
145154
146- private static final class Writer implements ContainerResponseWriter {
155+ private static final class ResponseWriter implements ContainerResponseWriter {
147156
157+ private final AtomicReference <TimeoutTimer > reference ;
158+ private final ScheduledExecutorService scheduler ;
148159 private final Response response ;
149160
150- Writer (final Response response ) {
161+ ResponseWriter (final Response response , final ScheduledExecutorService scheduler ) {
162+ this .reference = new AtomicReference <TimeoutTimer >();
151163 this .response = response ;
164+ this .scheduler = scheduler ;
152165 }
153166
154167 @ 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 {
157170 final javax .ws .rs .core .Response .StatusType statusInfo = context .getStatusInfo ();
158171
159172 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 )
161175 : statusInfo .getReasonPhrase ();
162176 response .setCode (code );
163177 response .setDescription (reason );
164- response .setContentLength (contentLength );
178+
179+ if (contentLength != -1 ) {
180+ response .setContentLength (contentLength );
181+ }
165182 for (final Map .Entry <String , List <String >> e : context .getStringHeaders ().entrySet ()) {
166183 for (final String value : e .getValue ()) {
167184 response .addValue (e .getKey (), value );
@@ -176,13 +193,41 @@ public OutputStream writeResponseStatusAndHeaders(final long contentLength, fina
176193 }
177194
178195 @ 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+ }
181216 }
182217
183218 @ 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+ }
186231 }
187232
188233 @ Override
@@ -196,6 +241,10 @@ public void commit() {
196241 }
197242 }
198243
244+ public boolean isSuspended () {
245+ return reference .get () != null ;
246+ }
247+
199248 @ Override
200249 public void failure (final Throwable error ) {
201250 try {
@@ -231,19 +280,64 @@ private void rethrow(final Throwable error) {
231280
232281 }
233282
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+
234332 @ Override
235333 public void handle (final Request request , final Response response ) {
236- final Writer responseWriter = new Writer (response );
334+ final ResponseWriter responseWriter = new ResponseWriter (response , scheduler );
237335 final URI baseUri = getBaseUri (request );
238336 final URI requestUri = getRequestUri (request , baseUri );
239337
240338 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 ());
247341 requestContext .setEntityStream (request .getInputStream ());
248342 for (final String headerName : request .getNames ()) {
249343 requestContext .headers (headerName , request .getValue (headerName ));
@@ -261,7 +355,9 @@ public void initialize(final ServiceLocator locator) {
261355 } catch (final Exception ex ) {
262356 throw new RuntimeException (ex );
263357 } finally {
264- close (response );
358+ if (!responseWriter .isSuspended ()) {
359+ close (response );
360+ }
265361 }
266362 }
267363
@@ -282,7 +378,8 @@ private URI getRequestUri(final Request request, final URI baseUri) {
282378 }
283379
284380 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 ();
286383 }
287384
288385 private URI getBaseUri (final Request request ) {
@@ -294,7 +391,8 @@ private URI getBaseUri(final Request request) {
294391 return new URI (scheme + "://" + hostHeader + "/" );
295392 } else {
296393 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 );
298396 }
299397 } catch (final URISyntaxException ex ) {
300398 throw new IllegalArgumentException (ex );
@@ -316,7 +414,7 @@ public boolean isSecure() {
316414
317415 @ Override
318416 public Principal getUserPrincipal () {
319- return request . getSecuritySession (). getLocalPrincipal () ;
417+ return null ;
320418 }
321419
322420 @ Override
@@ -349,6 +447,7 @@ public void reload(final ResourceConfig configuration) {
349447 appHandler .onShutdown (this );
350448
351449 appHandler = new ApplicationHandler (configuration .register (new SimpleBinder ()));
450+ scheduler = new ScheduledThreadPoolExecutor (2 , new DaemonFactory (TimeoutDispatcher .class ));
352451 appHandler .onReload (this );
353452 appHandler .onStartup (this );
354453 }
@@ -360,7 +459,7 @@ public ApplicationHandler getApplicationHandler() {
360459
361460 /**
362461 * Inform this container that the server has been started.
363- *
462+ * <p/>
364463 * This method must be implicitly called after the server containing this container is started.
365464 */
366465 void onServerStart () {
@@ -369,29 +468,34 @@ void onServerStart() {
369468
370469 /**
371470 * Inform this container that the server is being stopped.
372- *
471+ * <p/>
373472 * This method must be implicitly called before the server containing this container is stopped.
374473 */
375474 void onServerStop () {
376475 appHandler .onShutdown (this );
476+ scheduler .shutdown ();
377477 }
378478
379479 /**
380480 * Create a new Simple framework HTTP container.
381481 *
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.
383484 * @param parentLocator parent HK2 service locator.
384485 */
385486 SimpleContainer (final Application application , final ServiceLocator parentLocator ) {
386487 this .appHandler = new ApplicationHandler (application , new SimpleBinder (), parentLocator );
488+ this .scheduler = new ScheduledThreadPoolExecutor (2 , new DaemonFactory (TimeoutDispatcher .class ));
387489 }
388490
389491 /**
390492 * Create a new Simple framework HTTP container.
391493 *
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.
393496 */
394497 SimpleContainer (final Application application ) {
395498 this .appHandler = new ApplicationHandler (application , new SimpleBinder ());
499+ this .scheduler = new ScheduledThreadPoolExecutor (2 , new DaemonFactory (TimeoutDispatcher .class ));
396500 }
397501}
0 commit comments