Initial Contribution
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/tests/integration/servlet-3-async/pom.xml b/tests/integration/servlet-3-async/pom.xml
new file mode 100644
index 0000000..a959490
--- /dev/null
+++ b/tests/integration/servlet-3-async/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.tests.integration</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>servlet-3-async</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-tests-integration-servlet-3-async</name>
+
+ <description>Servlet integration test - servlet-3-async - configured via jax-rs application annotated with @javax.ws.rs.ApplicationPath</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-external</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncServletResource.java b/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncServletResource.java
new file mode 100644
index 0000000..2f96055
--- /dev/null
+++ b/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncServletResource.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.integration.servlet_3_async;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+/**
+ * Asynchronous servlet-deployed resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("async")
+public class AsyncServletResource {
+ /**
+ * Hello world message.
+ */
+ public static final String HELLO_ASYNC_WORLD = "Hello Async World!";
+ public static final String CANCELED = "Canceled";
+
+ private static BlockingQueue<CanceledRequest> cancelingQueue = new ArrayBlockingQueue<CanceledRequest>(5);
+
+ private static class CanceledRequest {
+ private final String id;
+ private final AsyncResponse asyncResponse;
+
+ private CanceledRequest(String id, AsyncResponse asyncResponse) {
+ this.id = id;
+ this.asyncResponse = asyncResponse;
+ }
+ }
+
+ /**
+ * Get the async "Hello World" message.
+ */
+ @GET
+ @Produces("text/plain")
+ public void get(@Suspended final AsyncResponse ar) {
+ Executors.newSingleThreadExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(100);
+ ar.resume(HELLO_ASYNC_WORLD);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ /**
+ * Get a canceled request.
+ *
+ * @param id request id.
+ * @throws InterruptedException in case of not being able to put the request
+ * to an internal queue for canceling.
+ */
+ @GET
+ @Path("canceled")
+ public void getCanceled(@Suspended final AsyncResponse ar, @QueryParam("id") final String id) throws InterruptedException {
+ cancelingQueue.put(new CanceledRequest(id, ar));
+ }
+
+ /**
+ * Cancel a request that is on top of the canceling queue.
+ *
+ * @return notification message about successful request canceling.
+ * @throws InterruptedException in case of not being able to take a cancelled request
+ * from an internal canceling queue.
+ */
+ @POST
+ @Produces("text/plain")
+ @Path("canceled")
+ public String cancel(String requestId) throws InterruptedException {
+ final CanceledRequest canceledRequest = cancelingQueue.take();
+ canceledRequest.asyncResponse.cancel();
+
+ return CANCELED + " " + canceledRequest.id + " by POST " + requestId;
+ }
+
+}
diff --git a/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncTimeoutResource.java b/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncTimeoutResource.java
new file mode 100644
index 0000000..23c000b
--- /dev/null
+++ b/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncTimeoutResource.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.integration.servlet_3_async;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.container.TimeoutHandler;
+
+/**
+ * Asynchronous servlet-deployed resource for testing {@link javax.ws.rs.container.AsyncResponse async response} timeouts.
+ *
+ * @author Michal Gajdos
+ */
+@Path("timeout")
+public class AsyncTimeoutResource {
+
+ private static final BlockingQueue<AsyncResponse> queue = new ArrayBlockingQueue<AsyncResponse>(1);
+
+ @GET
+ @Path("suspend")
+ public void suspend(@Suspended final AsyncResponse asyncResponse) {
+ queue.add(asyncResponse);
+ }
+
+ @POST
+ @Path("timeout")
+ public void setTimeOut(final Integer millis) throws InterruptedException {
+ final AsyncResponse asyncResponse = queue.take();
+
+ final boolean timeout1 = asyncResponse.setTimeout(millis, TimeUnit.MILLISECONDS);
+ asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+ @Override
+ public void handleTimeout(final AsyncResponse asyncResponse) {
+ final boolean timeout2 = asyncResponse.setTimeout(millis, TimeUnit.MILLISECONDS);
+ asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+ @Override
+ public void handleTimeout(final AsyncResponse asyncResponse) {
+ asyncResponse.resume("timeout1=" + timeout1 + "_timeout2=" + timeout2 + "_handled");
+ asyncResponse.cancel();
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/Servlet3Async.java b/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/Servlet3Async.java
new file mode 100644
index 0000000..5787a1d
--- /dev/null
+++ b/tests/integration/servlet-3-async/src/main/java/org/glassfish/jersey/tests/integration/servlet_3_async/Servlet3Async.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.integration.servlet_3_async;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ * Asynchronous servlet-deployed resource application.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ApplicationPath("/")
+public class Servlet3Async extends Application {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ final Set<Class<?>> hashSet = new HashSet<Class<?>>();
+ hashSet.add(AsyncServletResource.class);
+ hashSet.add(AsyncTimeoutResource.class);
+ return hashSet;
+ }
+}
diff --git a/tests/integration/servlet-3-async/src/test/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncServletResourceITCase.java b/tests/integration/servlet-3-async/src/test/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncServletResourceITCase.java
new file mode 100644
index 0000000..6cdfb9a
--- /dev/null
+++ b/tests/integration/servlet-3-async/src/test/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncServletResourceITCase.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.integration.servlet_3_async;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Asynchronous servlet-deployed resource test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class AsyncServletResourceITCase extends JerseyTest {
+ private static final Logger LOGGER = Logger.getLogger(AsyncServletResourceITCase.class.getName());
+
+ private static class ResponseRecord {
+ final int status;
+ final String message;
+
+ private ResponseRecord(int status, String message) {
+ this.status = status;
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return status + " : \"" + message + '\"';
+ }
+ }
+
+ @Override
+ protected Application configure() {
+ return new Application();
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new ExternalTestContainerFactory();
+ }
+
+ /**
+ * Test asynchronous servlet-deployed resource.
+ *
+ * @throws InterruptedException in case the waiting for all requests to complete was interrupted.
+ */
+ @Test
+ public void testAsyncServlet() throws InterruptedException {
+ final WebTarget resourceTarget = target("async");
+ resourceTarget.register(LoggingFeature.class);
+ final String expectedResponse = AsyncServletResource.HELLO_ASYNC_WORLD;
+
+ final int MAX_MESSAGES = 50;
+ final int LATCH_WAIT_TIMEOUT = 10 * getAsyncTimeoutMultiplier();
+ final boolean debugMode = false;
+ final boolean sequentialGet = false;
+ final Object sequentialGetLock = new Object();
+
+ final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("async-resource-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ final Map<Integer, ResponseRecord> getResponses = new ConcurrentHashMap<Integer, ResponseRecord>();
+
+ final CountDownLatch getRequestLatch = new CountDownLatch(MAX_MESSAGES);
+
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ final int requestId = i;
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (debugMode || sequentialGet) {
+ synchronized (sequentialGetLock) {
+ get();
+ }
+ } else {
+ get();
+ }
+ }
+
+ private void get() {
+ try {
+ final Response response = resourceTarget.request().get();
+ getResponses.put(
+ requestId,
+ new ResponseRecord(response.getStatus(), response.readEntity(String.class)));
+ } catch (Throwable t) {
+ t.printStackTrace();
+ } finally {
+ getRequestLatch.countDown();
+ }
+ }
+ });
+ }
+
+ //noinspection ConstantConditions
+ if (debugMode) {
+ getRequestLatch.await();
+ } else {
+ assertTrue("Waiting for all GET requests to complete has timed out.", getRequestLatch.await(LATCH_WAIT_TIMEOUT,
+ TimeUnit.SECONDS));
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ StringBuilder messageBuilder = new StringBuilder();
+ for (Map.Entry<Integer, ResponseRecord> getResponseEntry : getResponses.entrySet()) {
+ messageBuilder.append("GET response for message ")
+ .append(getResponseEntry.getKey()).append(": ")
+ .append(getResponseEntry.getValue().toString()).append('\n');
+ }
+ LOGGER.info(messageBuilder.toString());
+
+ assertEquals(MAX_MESSAGES, getResponses.size());
+ for (Map.Entry<Integer, ResponseRecord> entry : getResponses.entrySet()) {
+ assertEquals(
+ "Unexpected GET response status for request " + entry.getKey(),
+ 200, entry.getValue().status);
+ assertEquals(
+ "Unexpected GET response message for request " + entry.getKey(),
+ expectedResponse, entry.getValue().message);
+ }
+ }
+
+ /**
+ * Test canceling of an async request to a servlet-deployed resource.
+ *
+ * @throws InterruptedException in case the waiting for all requests to complete was interrupted.
+ */
+ @Test
+ public void testAsyncRequestCanceling() throws InterruptedException {
+ final WebTarget resourceTarget = target("async/canceled");
+ resourceTarget.register(LoggingFeature.class);
+
+ final int MAX_MESSAGES = 10;
+ final int LATCH_WAIT_TIMEOUT = 10 * getAsyncTimeoutMultiplier();
+ final boolean debugMode = false;
+ final boolean sequentialGet = false;
+ final boolean sequentialPost = false;
+ final Object sequentialGetLock = new Object();
+ final Object sequentialPostLock = new Object();
+
+ final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("async-canceled-resource-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ final Map<Integer, String> postResponses = new ConcurrentHashMap<Integer, String>();
+ final Map<Integer, String> getResponses = new ConcurrentHashMap<Integer, String>();
+
+ final CountDownLatch postRequestLatch = new CountDownLatch(MAX_MESSAGES);
+ final CountDownLatch getRequestLatch = new CountDownLatch(MAX_MESSAGES);
+
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ final int requestId = i;
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (debugMode || sequentialGet) {
+ synchronized (sequentialGetLock) {
+ get();
+ }
+ } else {
+ get();
+ }
+ }
+
+ private void get() {
+ try {
+ final String response = resourceTarget.queryParam("id", requestId).request().get(String.class);
+ getResponses.put(requestId, response);
+ } catch (WebApplicationException ex) {
+ final Response response = ex.getResponse();
+ getResponses.put(requestId, response.getStatus() + ": " + response.readEntity(String.class));
+ } finally {
+ getRequestLatch.countDown();
+ }
+ }
+ });
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (debugMode || sequentialPost) {
+ synchronized (sequentialPostLock) {
+ post();
+ }
+ } else {
+ post();
+ }
+ }
+
+ private void post() throws ProcessingException {
+ try {
+ final String response = resourceTarget.request().post(Entity.text("" + requestId), String.class);
+ postResponses.put(requestId, response);
+ } finally {
+ postRequestLatch.countDown();
+ }
+ }
+ });
+ }
+
+ //noinspection ConstantConditions
+ if (debugMode) {
+ postRequestLatch.await();
+ getRequestLatch.await();
+ } else {
+ assertTrue("Waiting for all POST requests to complete has timed out.",
+ postRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS));
+ assertTrue("Waiting for all GET requests to complete has timed out.", getRequestLatch.await(LATCH_WAIT_TIMEOUT,
+ TimeUnit.SECONDS));
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ StringBuilder messageBuilder = new StringBuilder();
+ for (Map.Entry<Integer, String> postResponseEntry : postResponses.entrySet()) {
+ messageBuilder.append("POST response for message ")
+ .append(postResponseEntry.getKey()).append(": ")
+ .append(postResponseEntry.getValue()).append('\n');
+ }
+ messageBuilder.append('\n');
+ for (Map.Entry<Integer, String> getResponseEntry : getResponses.entrySet()) {
+ messageBuilder.append("GET response for message ")
+ .append(getResponseEntry.getKey()).append(": ")
+ .append(getResponseEntry.getValue()).append('\n');
+ }
+ LOGGER.info(messageBuilder.toString());
+
+ assertEquals(MAX_MESSAGES, postResponses.size());
+ for (Map.Entry<Integer, String> postResponseEntry : postResponses.entrySet()) {
+ assertTrue("Unexpected POST notification response for message " + postResponseEntry.getKey(),
+ postResponseEntry.getValue().startsWith(AsyncServletResource.CANCELED));
+ }
+
+ assertEquals(MAX_MESSAGES, getResponses.size());
+ final Collection<Integer> getResponseKeys = getResponses.keySet();
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ assertTrue("Detected a GET message response loss: " + i, getResponseKeys.contains(i));
+ final String getResponseEntry = getResponses.get(i);
+ assertTrue("Unexpected canceled GET response status for request " + i,
+ getResponseEntry.startsWith("503: "));
+ }
+ }
+}
diff --git a/tests/integration/servlet-3-async/src/test/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncTimeoutResourceITCase.java b/tests/integration/servlet-3-async/src/test/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncTimeoutResourceITCase.java
new file mode 100644
index 0000000..8bfb646
--- /dev/null
+++ b/tests/integration/servlet-3-async/src/test/java/org/glassfish/jersey/tests/integration/servlet_3_async/AsyncTimeoutResourceITCase.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.integration.servlet_3_async;
+
+import java.util.concurrent.Future;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Asynchronous servlet-deployed resource for testing {@link javax.ws.rs.container.AsyncResponse async response} timeouts.
+ *
+ * @author Michal Gajdos
+ */
+public class AsyncTimeoutResourceITCase extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new Application();
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new ExternalTestContainerFactory();
+ }
+
+ @Test
+ public void testTimeout() throws Exception {
+ final WebTarget resourceTarget = target("timeout");
+ resourceTarget.register(LoggingFeature.class);
+ final Future<Response> responseFuture = resourceTarget.path("suspend").request().async().get();
+
+ // Set timeout.
+ assertThat(resourceTarget.path("timeout").request().post(Entity.text("500")).getStatus(), equalTo(204));
+
+ // Wait for it.
+ assertThat(responseFuture.get().readEntity(String.class), equalTo("timeout1=true_timeout2=true_handled"));
+ }
+}