/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2013-2017 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.e2e; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; 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.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; import javax.inject.Named; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Application; import javax.ws.rs.core.Response; import org.glassfish.jersey.client.ClientAsyncExecutor; import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler; import org.glassfish.jersey.server.ManagedAsync; import org.glassfish.jersey.server.ManagedAsyncExecutor; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.spi.ExecutorServiceProvider; import org.glassfish.jersey.test.JerseyTest; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * {@link org.glassfish.jersey.spi.ExecutorServiceProvider} E2E tests. * * @author Marek Potociar (marek.potociar at oracle.com) */ public class ExecutorServiceProviderTest extends JerseyTest { @Path("resource") @Produces("text/plain") public static class TestResourceA { @GET public String getSync() { return "resource"; } @GET @Path("async") @ManagedAsync public String getAsync() { return "async-resource-" + ExecutorServiceProviderTest.getResponseOnThread(); } } // A separate resource class for the custom-async sub-path to ensure that // the named ExecutorService injection is only performed for the "/resource/custom-async" path. @Path("resource") @Produces("text/plain") public static class TestResourceB { @Inject @Named("custom") ExecutorService executorService; @GET @Path("custom-async") public void getCustomAsync(@Suspended final AsyncResponse asyncResponse) { executorService.submit(new Runnable() { @Override public void run() { asyncResponse.resume("custom-async-resource-" + ExecutorServiceProviderTest.getResponseOnThread()); } }); } } static String getResponseOnThread() { final String threadName = Thread.currentThread().getName(); if (threadName.startsWith("async-request-")) { return "passed"; } else { return "error - unexpected custom thread name: " + threadName; } } @ClientAsyncExecutor @ManagedAsyncExecutor @Named("custom") public static class CustomExecutorProvider implements ExecutorServiceProvider { private final Set<ExecutorService> executors = Collections.newSetFromMap(new IdentityHashMap<>()); private volatile int executorCreationCount = 0; private volatile int executorReleaseCount = 0; public void reset() { for (ExecutorService executor : executors) { executor.shutdownNow(); } executors.clear(); executorCreationCount = 0; executorReleaseCount = 0; } @Override public ExecutorService getExecutorService() { return new CustomExecutorService(); } @Override public void dispose(final ExecutorService executorService) { executorService.shutdownNow(); } private class CustomExecutorService implements ExecutorService { private final ExecutorService delegate; private final AtomicBoolean isCleanedUp; public CustomExecutorService() { this.isCleanedUp = new AtomicBoolean(false); this.delegate = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() .setNameFormat("async-request-%d") .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()) .build()); executorCreationCount++; executors.add(this); } @Override public void shutdown() { tryCleanUp(); delegate.shutdown(); } @Override public List<Runnable> shutdownNow() { tryCleanUp(); return delegate.shutdownNow(); } @Override public boolean isShutdown() { return delegate.isShutdown(); } @Override public boolean isTerminated() { return delegate.isTerminated(); } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return delegate.awaitTermination(timeout, unit); } @Override public <T> Future<T> submit(Callable<T> task) { return delegate.submit(task); } @Override public <T> Future<T> submit(Runnable task, T result) { return delegate.submit(task, result); } @Override public Future<?> submit(Runnable task) { return delegate.submit(task); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { return delegate.invokeAll(tasks); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { return delegate.invokeAll(tasks, timeout, unit); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { return delegate.invokeAny(tasks); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate.invokeAny(tasks, timeout, unit); } private void tryCleanUp() { if (isCleanedUp.compareAndSet(false, true)) { executors.remove(this); executorReleaseCount++; } } @Override public void execute(Runnable command) { delegate.execute(command); } } } private static final CustomExecutorProvider serverExecutorProvider = new CustomExecutorProvider(); @Override protected Application configure() { // enable(TestProperties.LOG_TRAFFIC); // enable(TestProperties.DUMP_ENTITY); return new ResourceConfig(TestResourceA.class, TestResourceB.class).register(serverExecutorProvider); } /** * Reproducer for JERSEY-2205 (client-side). * * @throws Exception in case of a test error. */ @Test public void testCustomClientExecutorsInjectionAndReleasing() throws Exception { final CustomExecutorProvider provider = new CustomExecutorProvider(); Client client = ClientBuilder.newClient().register(provider); Response response = client.target(getBaseUri()).path("resource").request().get(); assertEquals(200, response.getStatus()); assertEquals("resource", response.readEntity(String.class)); // no executors should be created or released at this point yet assertEquals("Unexpected number of created client executors", 0, provider.executorCreationCount); assertEquals("Unexpected number of released client executors", 0, provider.executorReleaseCount); assertEquals("Unexpected number of client executors stored in the set.", 0, provider.executors.size()); Future<Response> fr = client.target(getBaseUri()).path("resource").request().async().get(); response = fr.get(); assertEquals(200, response.getStatus()); assertEquals("resource", response.readEntity(String.class)); // single executor should be created but not released at this point yet assertEquals("Unexpected number of created client executors", 1, provider.executorCreationCount); assertEquals("Unexpected number of released client executors", 0, provider.executorReleaseCount); assertEquals("Unexpected number of client executors stored in the set.", 1, provider.executors.size()); client.close(); // the created executor needs to be released by now; no more executors should be created assertEquals("Unexpected number of created client executors", 1, provider.executorCreationCount); assertEquals("Unexpected number of released client executors", 1, provider.executorReleaseCount); assertEquals("Unexpected number of client executors stored in the set.", 0, provider.executors.size()); } /** * Reproducer for JERSEY-2205 (server-side). * * @throws Exception in case of a test error. */ @Test public void testCustomServerExecutorsInjectionAndReleasing() throws Exception { // reset server executor statistics to avoid data pollution from other test methods serverExecutorProvider.reset(); Response response = target("resource").request().get(); assertEquals(200, response.getStatus()); assertEquals("resource", response.readEntity(String.class)); // no executors should be created or released at this point yet assertEquals("Unexpected number of created server executors", 0, serverExecutorProvider.executorCreationCount); assertEquals("Unexpected number of released server executors", 0, serverExecutorProvider.executorReleaseCount); assertEquals("Unexpected number of server executors stored in the set.", 0, serverExecutorProvider.executors.size()); response = target("resource/async").request().get(); assertEquals(200, response.getStatus()); assertEquals("async-resource-passed", response.readEntity(String.class)); // single executor should be created but not released at this point yet assertEquals("Unexpected number of created server executors", 1, serverExecutorProvider.executorCreationCount); assertEquals("Unexpected number of released server executors", 0, serverExecutorProvider.executorReleaseCount); assertEquals("Unexpected number of server executors stored in the set.", 1, serverExecutorProvider.executors.size()); tearDown(); // stopping test container // the created executor needs to be released by now; no more executors should be created assertEquals("Unexpected number of created server executors", 1, serverExecutorProvider.executorCreationCount); assertEquals("Unexpected number of released server executors", 1, serverExecutorProvider.executorReleaseCount); assertEquals("Unexpected number of server executors stored in the set.", 0, serverExecutorProvider.executors.size()); setUp(); // re-starting test container to ensure proper post-test tearDown. } /** * Test named custom executor injection and release mechanism. * * @throws Exception in case of a test error. */ @Test public void testCustomNamedServerExecutorsInjectionAndReleasing() throws Exception { serverExecutorProvider.reset(); Response response = target("resource/custom-async").request().get(); assertEquals(200, response.getStatus()); assertEquals("custom-async-resource-passed", response.readEntity(String.class)); // single executor should be created but not released at this point yet assertEquals("Unexpected number of created server executors", 1, serverExecutorProvider.executorCreationCount); assertEquals("Unexpected number of released server executors", 0, serverExecutorProvider.executorReleaseCount); assertEquals("Unexpected number of server executors stored in the set.", 1, serverExecutorProvider.executors.size()); tearDown(); // stopping test container // the created executor needs to be released by now; no more executors should be created assertEquals("Unexpected number of created server executors", 1, serverExecutorProvider.executorCreationCount); assertEquals("Unexpected number of released server executors", 1, serverExecutorProvider.executorReleaseCount); assertEquals("Unexpected number of server executors stored in the set.", 0, serverExecutorProvider.executors.size()); setUp(); // re-starting test container to ensure proper post-test tearDown. } }