diff --git a/governator-jersey/src/main/java/com/netflix/governator/guice/jersey/GovernatorServletContainer.java b/governator-jersey/src/main/java/com/netflix/governator/guice/jersey/GovernatorServletContainer.java index df6d2cf9..ba586d47 100644 --- a/governator-jersey/src/main/java/com/netflix/governator/guice/jersey/GovernatorServletContainer.java +++ b/governator-jersey/src/main/java/com/netflix/governator/guice/jersey/GovernatorServletContainer.java @@ -52,11 +52,10 @@ protected void initiate(ResourceConfig config, WebApplication webapp) { // Make sure all root resources are created eagerly so they're fully initialized // by the time the injector was created, instead of being created lazily at the // first request. - LOG.info("Creating resources : {}", config.getRootResourceClasses()); for (Class resource : config.getRootResourceClasses()) { if (resource.isAnnotationPresent(com.google.inject.Singleton.class) || resource.isAnnotationPresent(javax.inject.Singleton.class)) { - factory.getComponentProvider(resource).getInstance(); + LOG.warn("Class {} should be annotated with Jersey's com.sun.jersey.spi.resource.Singleton. Also make sure that any JAX-RS clasese (such as UriInfo) are injected using Jersey's @Context instead of @Inject.", resource); } } } diff --git a/governator-jersey/src/test/java/com/netflix/governator/guice/jersey/JerseyServerTest.java b/governator-jersey/src/test/java/com/netflix/governator/guice/jersey/JerseyServerTest.java index 5d4ffaca..98c83f98 100644 --- a/governator-jersey/src/test/java/com/netflix/governator/guice/jersey/JerseyServerTest.java +++ b/governator-jersey/src/test/java/com/netflix/governator/guice/jersey/JerseyServerTest.java @@ -1,14 +1,36 @@ package com.netflix.governator.guice.jersey; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Proxy; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.function.UnaryOperator; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.Assert; +import org.junit.Test; + +import com.google.common.io.CharStreams; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.util.Modules; import com.netflix.governator.InjectorBuilder; import com.netflix.governator.LifecycleInjector; import com.netflix.governator.ShutdownHookModule; -import com.netflix.governator.guice.jersey.GovernatorJerseySupportModule; import com.netflix.governator.guice.jetty.DefaultJettyConfig; -import com.netflix.governator.guice.jetty.GovernatorServletContainer; import com.netflix.governator.guice.jetty.JettyConfig; import com.netflix.governator.guice.jetty.JettyModule; import com.netflix.governator.guice.jetty.resources3.SampleResource; @@ -17,24 +39,9 @@ import com.sun.jersey.api.core.ResourceConfig; import com.sun.jersey.guice.JerseyServletModule; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; -import java.util.function.UnaryOperator; - -import javax.inject.Named; -import javax.inject.Singleton; - public class JerseyServerTest { @Test - public void confirmResourceLoadedWithoutBinding() throws InterruptedException, MalformedURLException, IOException { + public void confirmResourceLoadedWithoutBinding() throws IOException { // Create the injector and autostart Jetty try (LifecycleInjector injector = InjectorBuilder.fromModules( new ShutdownHookModule(), @@ -86,10 +93,9 @@ JettyConfig getConfig() { } @Test - public void confirmFailedToCreateWithoutRootResources() throws InterruptedException, MalformedURLException, IOException { + public void confirmFailedToCreateWithoutRootResources() { // Create the injector and autostart Jetty - try { - InjectorBuilder.fromModules( + try (LifecycleInjector injector = InjectorBuilder.fromModules( new ShutdownHookModule(), new GovernatorJerseySupportModule(), new JerseyServletModule() { @@ -109,7 +115,7 @@ JettyConfig getConfig() { return new DefaultJettyConfig().setPort(0); } })) - .createInjector(); + .createInjector()) { Assert.fail(); } catch (Exception e) { Assert.assertTrue(e.getCause().getMessage().contains("The ResourceConfig instance does not contain any root resource classes")); @@ -118,8 +124,7 @@ JettyConfig getConfig() { @Test public void confirmNoResourceLoadedFromGuiceBinding() throws InterruptedException, MalformedURLException, IOException { - try { - InjectorBuilder.fromModules( + try (LifecycleInjector injector = InjectorBuilder.fromModules( new ShutdownHookModule(), new GovernatorJerseySupportModule(), new JerseyServletModule() { @@ -140,10 +145,173 @@ JettyConfig getConfig() { return new DefaultJettyConfig().setPort(0); } })) - .createInjector(); + .createInjector()) { Assert.fail(); } catch (Exception e) { Assert.assertTrue(e.getCause().getMessage().contains("The ResourceConfig instance does not contain any root resource classes")); } } + + @Path("/") + @Singleton + public static class FieldInjectionResource { + private static int createCount = 0; + + public FieldInjectionResource() { + createCount++; + } + + @Inject + private String foo; + + @Context + UriInfo uri; + + @GET + public String get() { + return foo; + } + } + + @Test + public void confirmNonJerseySingletonNotEagerOrASingleton() { + // Create the injector and autostart Jetty + try (LifecycleInjector injector = InjectorBuilder.fromModules( + new ShutdownHookModule(), + new GovernatorJerseySupportModule(), + new JerseyServletModule() { + protected void configureServlets() { + serve("/*").with(GovernatorServletContainer.class); + + bind(String.class).toInstance("foo"); + } + + @Advises + @Singleton + @Named("governator") + UnaryOperator getResourceConfig() { + return config -> { + config.getClasses().add(FieldInjectionResource.class); + return config; + }; + } + }, + Modules.override(new JettyModule()) + .with(new AbstractModule() { + @Override + protected void configure() { + } + + @Provides + JettyConfig getConfig() { + // Use emphemeral ports + return new DefaultJettyConfig().setPort(0); + } + })) + .createInjector()) { + + Assert.assertEquals(0, FieldInjectionResource.createCount); + + // Determine the emphermal port from jetty + Server server = injector.getInstance(Server.class); + int port = ((ServerConnector)server.getConnectors()[0]).getLocalPort(); + + System.out.println("Listening on port : "+ port); + + URL url = new URL(String.format("http://localhost:%d/", port)); + { + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + Assert.assertEquals(200, conn.getResponseCode()); + + Assert.assertEquals(1, FieldInjectionResource.createCount); + } + + { + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + Assert.assertEquals(200, conn.getResponseCode()); + + Assert.assertEquals(2, FieldInjectionResource.createCount); + } + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(e.getMessage()); + } + } + + @Path("/") + @com.sun.jersey.spi.resource.Singleton + public static class UriContextIsRequestScoped { + @Context + UriInfo uri; + + @GET + @Path("{subResources:.*}") + public String get(@PathParam("subResources") String subResources) { + Assert.assertTrue(Proxy.isProxyClass(uri.getClass())); + return uri.getPath(); + } + } + + @Test + public void confirmUriInfoContextIsRequestScoped() { + // Create the injector and autostart Jetty + try (LifecycleInjector injector = InjectorBuilder.fromModules( + new ShutdownHookModule(), + new GovernatorJerseySupportModule(), + new JerseyServletModule() { + protected void configureServlets() { + serve("/*").with(GovernatorServletContainer.class); + + bind(String.class).toInstance("foo"); + } + + @Advises + @Singleton + @Named("governator") + UnaryOperator getResourceConfig() { + return config -> { + config.getClasses().add(UriContextIsRequestScoped.class); + return config; + }; + } + }, + Modules.override(new JettyModule()) + .with(new AbstractModule() { + @Override + protected void configure() { + } + + @Provides + JettyConfig getConfig() { + // Use emphemeral ports + return new DefaultJettyConfig().setPort(0); + } + })) + .createInjector()) { + + // Determine the emphermal port from jetty + Server server = injector.getInstance(Server.class); + int port = ((ServerConnector)server.getConnectors()[0]).getLocalPort(); + + System.out.println("Listening on port : "+ port); + + { + URL url = new URL(String.format("http://localhost:%d/path1", port)); + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + Assert.assertEquals(200, conn.getResponseCode()); + Assert.assertEquals("path1", CharStreams.toString(new InputStreamReader(conn.getInputStream()))); + } + + { + URL url = new URL(String.format("http://localhost:%d/path2", port)); + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + Assert.assertEquals(200, conn.getResponseCode()); + Assert.assertEquals("path2", CharStreams.toString(new InputStreamReader(conn.getInputStream()))); + } + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(e.getMessage()); + } + } + }