Skip to content

Commit

Permalink
Include all resource classes in endpoint output of DropwizardResource…
Browse files Browse the repository at this point in the history
…Config

JAX-RS resource classes which inherited from another resource class or which implemented
an annotated interface weren't included in the resource list which DropwizardResourceConfig
logs on startup.

This changeset is based on PR #728.

Fixes #726, closes #728.
  • Loading branch information
joschi committed Jan 3, 2015
1 parent 892e39a commit 5a92faf
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 95 deletions.
Expand Up @@ -2,10 +2,11 @@


import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.jersey2.InstrumentedResourceMethodApplicationListener; import com.codahale.metrics.jersey2.InstrumentedResourceMethodApplicationListener;
import com.google.common.collect.ImmutableList; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet; import com.google.common.base.Joiner;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import io.dropwizard.jersey.caching.CacheControlledResponseFeature; import io.dropwizard.jersey.caching.CacheControlledResponseFeature;
import io.dropwizard.jersey.guava.OptionalMessageBodyWriter; import io.dropwizard.jersey.guava.OptionalMessageBodyWriter;
import io.dropwizard.jersey.guava.OptionalParameterInjectionBinder; import io.dropwizard.jersey.guava.OptionalParameterInjectionBinder;
Expand All @@ -25,12 +26,13 @@


import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.Provider;
import java.lang.annotation.Annotation;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;


public class DropwizardResourceConfig extends ResourceConfig { public class DropwizardResourceConfig extends ResourceConfig {
private static final String NEWLINE = String.format("%n");
private static final Logger LOGGER = LoggerFactory.getLogger(DropwizardResourceConfig.class); private static final Logger LOGGER = LoggerFactory.getLogger(DropwizardResourceConfig.class);
private static final String NEWLINE = String.format("%n");


private String urlPattern; private String urlPattern;


Expand Down Expand Up @@ -70,8 +72,8 @@ public static DropwizardResourceConfig forTesting(MetricRegistry metricRegistry)
} }


public void logComponents() { public void logComponents() {
LOGGER.debug("resources = {}", logResources()); LOGGER.debug("resources = {}", canonicalNamesByAnnotation(Path.class));
LOGGER.debug("providers = {}", logProviders()); LOGGER.debug("providers = {}", canonicalNamesByAnnotation(Provider.class));
LOGGER.info(logEndpoints()); LOGGER.info(logEndpoints());
} }


Expand All @@ -83,113 +85,110 @@ public void setUrlPattern(String urlPattern) {
this.urlPattern = urlPattern; this.urlPattern = urlPattern;
} }


private Set<String> logResources() { /**
final ImmutableSet.Builder<String> builder = ImmutableSet.builder(); * Combines types of getClasses() and getSingletons in one Set.

*
for (Class<?> klass : getClasses()) { * @return all registered types
if (klass.isAnnotationPresent(Path.class)) { */
builder.add(klass.getCanonicalName()); @VisibleForTesting
} Set<Class<?>> allClasses() {
} final Set<Class<?>> allClasses = Sets.newHashSet(getClasses());

for (Object singleton : getSingletons()) {
for (Object o : getSingletons()) { allClasses.add(singleton.getClass());
if (o.getClass().isAnnotationPresent(Path.class)) { }
builder.add(o.getClass().getCanonicalName()); return allClasses;
}
}

return builder.build();
} }


private Set<String> logProviders() { private Set<String> canonicalNamesByAnnotation(final Class<? extends Annotation> annotation) {
final ImmutableSet.Builder<String> builder = ImmutableSet.builder(); final Set<String> result = Sets.newHashSet();

for (Class<?> clazz : getClasses()) {
for (Class<?> klass : getClasses()) { if (clazz.isAnnotationPresent(annotation)) {
if (klass.isAnnotationPresent(Provider.class)) { result.add(clazz.getCanonicalName());
builder.add(klass.getCanonicalName());
} }
} }

return result;
for (Object o : getSingletons()) {
if (o.getClass().isAnnotationPresent(Provider.class)) {
builder.add(o.getClass().getCanonicalName());
}
}

return builder.build();
} }


private String logEndpoints() { @VisibleForTesting
String logEndpoints() {
final StringBuilder msg = new StringBuilder(1024); final StringBuilder msg = new StringBuilder(1024);
msg.append("The following paths were found for the configured resources:"); msg.append("The following paths were found for the configured resources:");
msg.append(NEWLINE).append(NEWLINE); msg.append(NEWLINE).append(NEWLINE);


final ImmutableList.Builder<Class<?>> builder = ImmutableList.builder(); final Set<Class<?>> allResources = Sets.newHashSet();
for (Object o : getSingletons()) { for (Class<?> clazz : allClasses()) {
if (o.getClass().isAnnotationPresent(Path.class)) { if (!clazz.isInterface() && Resource.from(clazz) != null) {
builder.add(o.getClass()); allResources.add(clazz);
} }
} }
for (Class<?> klass : getClasses()) {
if (klass.isAnnotationPresent(Path.class)) {
builder.add(klass);
}
}

String rootPath = urlPattern;
if (rootPath.endsWith("/*")) {
rootPath = rootPath.substring(0, rootPath.length() - 1);
}


for (Class<?> klass : builder.build()) { if (!allResources.isEmpty()) {
final List<String> endpoints = Lists.newArrayList(); for (Class<?> klass : allResources) {
populateEndpoints(endpoints, rootPath, klass, false); Joiner.on(NEWLINE).appendTo(msg, new EndpointLogger(urlPattern, klass).getEndpoints());

msg.append(NEWLINE);
for (String line : Ordering.natural().sortedCopy(endpoints)) {
msg.append(line).append(NEWLINE);
} }
} else {
msg.append(" NONE").append(NEWLINE);
} }


return msg.toString(); return msg.toString();
} }


private void populateEndpoints(List<String> endpoints, String basePath, Class<?> klass,
boolean isLocator) {
populateEndpoints(endpoints, basePath, klass, isLocator, Resource.from(klass));
}


private void populateEndpoints(List<String> endpoints, String basePath, Class<?> klass, /**
boolean isLocator, Resource resource) { * Takes care of recursively creating all registered endpoints and providing them as Collection of lines to log
if (!isLocator) { * on application start.
basePath = normalizePath(basePath, resource.getPath()); */
private static class EndpointLogger {
private final String rootPath;
private final List<String> endpoints = Lists.newArrayList();

public EndpointLogger(String urlPattern, Class<?> klass) {
this.rootPath = urlPattern.endsWith("/*") ? urlPattern.substring(0, urlPattern.length() - 1) : urlPattern;

populateEndpoints(rootPath, klass, false);
} }


for (ResourceMethod method : resource.getResourceMethods()) { private void populateEndpoints(String basePath, Class<?> klass, boolean isLocator) {
endpoints.add(formatEndpoint(method.getHttpMethod(), basePath, klass)); populateEndpoints(basePath, klass, isLocator, Resource.from(klass));
} }


for (Resource childResource : resource.getChildResources()) { private void populateEndpoints(String basePath, Class<?> klass, boolean isLocator, Resource resource) {
for (ResourceMethod method : childResource.getResourceMethods()) { if (!isLocator) {
if (method.getType() == ResourceMethod.JaxrsType.RESOURCE_METHOD) { basePath = normalizePath(basePath, resource.getPath());
final String path = normalizePath(basePath, childResource.getPath()); }
endpoints.add(formatEndpoint(method.getHttpMethod(), path, klass));
} else if (method.getType() == ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR) { for (ResourceMethod method : resource.getResourceMethods()) {
final String path = normalizePath(basePath, childResource.getPath()); endpoints.add(formatEndpoint(method.getHttpMethod(), basePath, klass));
populateEndpoints(endpoints, path, method.getInvocable().getRawResponseType(), true); }

for (Resource childResource : resource.getChildResources()) {
for (ResourceMethod method : childResource.getResourceMethods()) {
if (method.getType() == ResourceMethod.JaxrsType.RESOURCE_METHOD) {
final String path = normalizePath(basePath, childResource.getPath());
endpoints.add(formatEndpoint(method.getHttpMethod(), path, klass));
} else if (method.getType() == ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR) {
final String path = normalizePath(basePath, childResource.getPath());
populateEndpoints(path, method.getInvocable().getRawResponseType(), true);
}
} }
} }
} }
}


private String formatEndpoint(String method, String path, Class<?> klass) { private String formatEndpoint(String method, String path, Class<?> klass) {
return String.format(" %-7s %s (%s)", method, path, klass.getCanonicalName()); return String.format(" %-7s %s (%s)", method, path, klass.getCanonicalName());
} }

private String normalizePath(String basePath, String path) {
if (basePath.endsWith("/")) {
return path.startsWith("/") ? basePath + path.substring(1) : basePath + path;
}
return path.startsWith("/") ? basePath + path : basePath + "/" + path;
}


private String normalizePath(String basePath, String path) { public List<String> getEndpoints() {
if (basePath.endsWith("/")) { return Ordering.natural().sortedCopy(endpoints);
return path.startsWith("/") ? basePath + path.substring(1) : basePath + path;
} }
return path.startsWith("/") ? basePath + path : basePath + "/" + path;
} }


private static class ComponentLoggingListener implements ApplicationEventListener { private static class ComponentLoggingListener implements ApplicationEventListener {
Expand All @@ -201,15 +200,14 @@ public ComponentLoggingListener(DropwizardResourceConfig config) {


@Override @Override
public void onEvent(ApplicationEvent event) { public void onEvent(ApplicationEvent event) {
if (event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED) if (event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED) {
this.config.logComponents(); this.config.logComponents();

}
} }


@Override @Override
public RequestEventListener onRequest(RequestEvent requestEvent) { public RequestEventListener onRequest(RequestEvent requestEvent) {
return null; return null;
} }

} }
} }
Expand Up @@ -3,44 +3,96 @@
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import io.dropwizard.jersey.dummy.DummyResource; import io.dropwizard.jersey.dummy.DummyResource;
import io.dropwizard.logging.LoggingFactory; import io.dropwizard.logging.LoggingFactory;

import org.junit.Before;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Test; import org.junit.Test;


import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;


import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;


@SuppressWarnings("unchecked")
public class DropwizardResourceConfigTest { public class DropwizardResourceConfigTest {
static { static {
LoggingFactory.bootstrap(); LoggingFactory.bootstrap();
} }


private DropwizardResourceConfig rc;

@Before
public void setUp() {
rc = DropwizardResourceConfig.forTesting(new MetricRegistry());
}

@Test @Test
public void findsResourceClassInPackage() { public void findsResourceClassInPackage() {
ResourceConfig rc = DropwizardResourceConfig.forTesting(new MetricRegistry()); rc.packages(DummyResource.class.getPackage().getName());
rc = rc.packages(DummyResource.class.getPackage().getName());


assertThat(rc.getClasses()).contains(DummyResource.class); assertThat(rc.getClasses()).contains(DummyResource.class);
} }


@Test @Test
public void findsResourceClassesInPackageAndSubpackage() { public void findsResourceClassesInPackageAndSubpackage() {
ResourceConfig rc = DropwizardResourceConfig.forTesting(new MetricRegistry()); rc.packages(getClass().getPackage().getName());
rc = rc.packages(getClass().getPackage().getName());
assertThat(rc.getClasses()).contains(
DummyResource.class,
TestResource.class,
ResourceInterface.class);
}

@Test
public void combinesAlRegisteredClasses() {
rc.register(new TestResource());
rc.registerClasses(ResourceInterface.class, ImplementingResource.class);

assertThat(rc.allClasses()).contains(
TestResource.class,
ResourceInterface.class,
ImplementingResource.class
);
}

@Test
public void logsNoInterfaces() {
rc.packages(getClass().getPackage().getName());

assertThat(rc.logEndpoints()).doesNotContain("io.dropwizard.jersey.DropwizardResourceConfigTest.ResourceInterface");
}


assertThat(rc.getClasses()) @Test
.contains public void logsNoEndpointsWhenNoResourcesAreRegistered() {
(DummyResource.class, TestResource.class); assertThat(rc.logEndpoints()).contains(" NONE");
} }


@Test
public void logsEndpoints() {
rc.register(TestResource.class);
rc.register(ImplementingResource.class);

assertThat(rc.logEndpoints())
.contains("GET /dummy (io.dropwizard.jersey.DropwizardResourceConfigTest.TestResource)")
.contains("GET /another (io.dropwizard.jersey.DropwizardResourceConfigTest.ImplementingResource)");
}


@Path("/dummy") @Path("/dummy")
public static class TestResource { public static class TestResource {
@GET @GET
public String foo() { public String foo() {
return "bar"; return "bar";
} }
} }

@Path("/another")
public static interface ResourceInterface {
@GET
public String bar();
}

public static class ImplementingResource implements ResourceInterface {
@Override
public String bar() {
return "";
}
}
} }

0 comments on commit 5a92faf

Please sign in to comment.