Skip to content

Commit

Permalink
Merge pull request #347 from elandau/jersey_eager_singleton
Browse files Browse the repository at this point in the history
Warn of use of non Jersey Singleton
  • Loading branch information
elandau authored Nov 16, 2016
2 parents c47cbbe + 6867140 commit ae98d56
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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(),
Expand Down Expand Up @@ -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() {
Expand All @@ -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"));
Expand All @@ -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() {
Expand All @@ -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<DefaultResourceConfig> 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<DefaultResourceConfig> 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());
}
}

}

0 comments on commit ae98d56

Please sign in to comment.