/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * http://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package org.glassfish.jersey.tests.api; import java.net.URI; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import org.glassfish.jersey.server.ManagedAsync; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; import org.glassfish.jersey.test.util.runner.ConcurrentRunner; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; /** * Test if the location relativer URI is correctly resolved within asynchronous processing cases. * * @author Adam Lindenthal (adam.lindenthal at oracle.com) */ @RunWith(ConcurrentRunner.class) public class LocationHeaderAsyncTest extends JerseyTest { private static final Logger LOGGER = Logger.getLogger(LocationHeaderAsyncTest.class.getName()); static ExecutorService executor; private static final AtomicBoolean executorComparisonFailed = new AtomicBoolean(false); private static final AtomicBoolean interrupted = new AtomicBoolean(false); @Override protected ResourceConfig configure() { enable(TestProperties.LOG_TRAFFIC); return new ResourceConfig(ResponseTest.class); } /** * Prepare test infrastructure. * * In this case it prepares executor thread pool of size one and initializes the thread. * @throws Exception */ @Override public void setUp() throws Exception { super.setUp(); /* thread pool for custom executor async test */ LocationHeaderAsyncTest.executor = Executors.newFixedThreadPool(1); // Force the thread to be eagerly instantiated - this prevents the instantiation later and ensures, that the thread // will not be a child thread of the request handling thread, so the thread-local baseUri variable will not be inherited. LocationHeaderAsyncTest.executor.submit(new Runnable() { @Override public void run() { LOGGER.info("Thread pool initialized."); } }); } /** * Test JAX-RS resource */ @SuppressWarnings("VoidMethodAnnotatedWithGET") @Path(value = "/ResponseTest") public static class ResponseTest { /* injected request URI for assertions in the resource methods */ @Context private UriInfo uriInfo; /** * Asynchronous resource method for testing if the URI is absolutized also in case of asynchronous processing; * * The response is created in the separate thread. This tests, that the thread still has access to the request baseUri * thread-local variable in {@link org.glassfish.jersey.message.internal.OutboundJaxrsResponse.Builder}. */ @GET @Path("locationAsync") public void locationAsync(@Suspended final AsyncResponse asyncResponse) { new Thread(new Runnable() { @Override public void run() { final URI uri = getUriBuilder().segment("locationAsync").build(); final Response result = Response.created(uri).build(); final URI location = result.getLocation(); if (uriInfo.getAbsolutePath().equals(location)) { asyncResponse.resume(result); } else { asyncResponse.resume(Response.serverError().entity(location.toString()).build()); } } }).start(); } /** * Resource method for async test with custom executor. * * It runs in a thread that was not created within the request scope, so it does not inherit the baseUri thread-local * variable value. * In this case, URI will not be absolutized until calling {@link AsyncResponse#resume(Object)}. */ @GET @Path("executorAsync") @ManagedAsync public void executorAsync(@Suspended final AsyncResponse asyncResponse) { LocationHeaderAsyncTest.executor.submit(new Runnable() { @Override public void run() { final URI uri = getUriBuilder().segment("executorAsync").build(); final Response result = Response.created(uri).build(); asyncResponse.resume(result); if (!uriInfo.getAbsolutePath().equals(result.getLocation())) { executorComparisonFailed.set(true); } } }); } /** * Placeholder for the suspended async responses; * For the current test a simple static field would be enough, but this is easily extensible; * * This is inspired by the {@link AsyncResponse} javadoc example */ private static final BlockingQueue<AsyncResponse> suspended = new ArrayBlockingQueue<>(5); /** * Start of the async test - stores the asynchronous response object */ @GET @Path("locationAsyncStart") public void locationAsyncStart(@Suspended final AsyncResponse asyncResponse) { new Thread(new Runnable() { @Override public void run() { try { suspended.put(asyncResponse); } catch (final InterruptedException e) { asyncResponse.cancel(); Thread.currentThread().interrupt(); interrupted.set(true); } } }).start(); } /** * Finish of the async test - creates a response, checks the location header and resumes the asyncResponse * @return true if the URI was correctly absolutized, false if the URI is relative or differs from the expected URI */ @GET @Path("locationAsyncFinish") public Boolean locationAsyncFinish() throws InterruptedException { final AsyncResponse asyncResponse = suspended.poll(2000, TimeUnit.MILLISECONDS); final URI uri = getUriBuilder().segment("locationAsyncFinish").build(); final Response result = Response.created(uri).build(); final boolean wasEqual = result.getLocation().equals(uriInfo.getAbsolutePath()); asyncResponse.resume(result); return wasEqual; } /** Return UriBuilder with base pre-set {@code /ResponseTest} uri segment for this resource. * * @return UriBuilder */ private UriBuilder getUriBuilder() { return UriBuilder.fromResource(ResponseTest.class); } } /** * Basic asynchronous testcase; checks if the URI is correctly absolutized also within a separate thread during * async processing */ @Test public void testAsync() { final String expectedUri = getBaseUri() + "ResponseTest/locationAsync"; final Response response = target().path("ResponseTest/locationAsync").request().get(Response.class); final String msg = String.format("Comparison failed in the resource method. \nExpected: %1$s\nActual: %2$s", expectedUri, response.readEntity(String.class)); assertNotEquals(msg, response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); final String location = response.getHeaderString(HttpHeaders.LOCATION); LOGGER.info("Location resolved from response > " + location); assertEquals(expectedUri, location); } /** * Test with a thread from thread-pool (created out of request scope) */ @Test public void testExecutorAsync() { final Response response = target().path("ResponseTest/executorAsync").request().get(Response.class); final String location = response.getHeaderString(HttpHeaders.LOCATION); LOGGER.info("Location resolved from response > " + location); assertFalse("The comparison failed in the resource method.", executorComparisonFailed.get()); assertEquals(getBaseUri() + "ResponseTest/executorAsync", location); } /** * Asynchronous testcase split over two distinct requests */ @Test public void testSeparatedAsync() throws ExecutionException, InterruptedException { final Future<Response> futureResponse = target().path("ResponseTest/locationAsyncStart").request().async().get(); final Boolean result = target().path("ResponseTest/locationAsyncFinish").request().get(Boolean.class); assertFalse("Thread was interrupted on inserting into blocking queue.", interrupted.get()); assertTrue(result); final Response response = futureResponse.get(); final String location = response.getHeaderString(HttpHeaders.LOCATION); assertEquals(getBaseUri() + "ResponseTest/locationAsyncFinish", location); } }