Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit ce5d952

Browse files
ngallagherpavelbucek
authored andcommitted
JERSEY-2982: AsyncResponse support for the Simple HTTP container.
Change-Id: I206754922b63b7182c45670a786289c1f3501185
1 parent 20814ae commit ce5d952

File tree

14 files changed

+893
-168
lines changed

14 files changed

+893
-168
lines changed

containers/simple-http/pom.xml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<!--
33
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
44
5-
Copyright (c) 2011-2015 Oracle and/or its affiliates. All rights reserved.
5+
Copyright (c) 2011-2016 Oracle and/or its affiliates. All rights reserved.
66
77
The contents of this file are subject to the terms of either the GNU
88
General Public License Version 2 only ("GPL") or the Common Development
@@ -39,7 +39,8 @@
3939
holder.
4040
4141
-->
42-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
42+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
43+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4344
<modelVersion>4.0.0</modelVersion>
4445

4546
<parent>
@@ -61,11 +62,18 @@
6162
</dependency>
6263
<dependency>
6364
<groupId>org.simpleframework</groupId>
64-
<artifactId>simple</artifactId>
65+
<artifactId>simple-http</artifactId>
66+
</dependency>
67+
<dependency>
68+
<groupId>org.simpleframework</groupId>
69+
<artifactId>simple-transport</artifactId>
70+
</dependency>
71+
<dependency>
72+
<groupId>org.simpleframework</groupId>
73+
<artifactId>simple-common</artifactId>
6574
</dependency>
6675
</dependencies>
6776

68-
6977
<build>
7078
<plugins>
7179
<plugin>

containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java

Lines changed: 144 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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
@@ -37,6 +37,7 @@
3737
* only if the new code is made subject to such option by the copyright
3838
* holder.
3939
*/
40+
4041
package org.glassfish.jersey.simple;
4142

4243
import java.io.IOException;
@@ -47,7 +48,11 @@
4748
import java.security.Principal;
4849
import java.util.List;
4950
import java.util.Map;
51+
import java.util.concurrent.ScheduledExecutorService;
52+
import java.util.concurrent.ScheduledFuture;
53+
import java.util.concurrent.ScheduledThreadPoolExecutor;
5054
import java.util.concurrent.TimeUnit;
55+
import java.util.concurrent.atomic.AtomicReference;
5156
import java.util.logging.Level;
5257
import java.util.logging.Logger;
5358

@@ -70,19 +75,22 @@
7075
import org.glassfish.jersey.server.internal.ContainerUtils;
7176
import org.glassfish.jersey.server.spi.Container;
7277
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
78+
import org.glassfish.jersey.server.spi.ContainerResponseWriter.TimeoutHandler;
7379
import org.glassfish.jersey.server.spi.RequestScopedInitializer;
7480

7581
import org.glassfish.hk2.api.ServiceLocator;
7682
import org.glassfish.hk2.api.TypeLiteral;
7783
import org.glassfish.hk2.utilities.binding.AbstractBinder;
7884

85+
import org.simpleframework.common.thread.DaemonFactory;
7986
import org.simpleframework.http.Address;
8087
import org.simpleframework.http.Request;
8188
import org.simpleframework.http.Response;
8289
import 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 (aruld@acm.org)
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

Comments
 (0)