Skip to content

Instantly share code, notes, and snippets.

@wonderb0lt
Created April 15, 2014 13:11
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save wonderb0lt/10731371 to your computer and use it in GitHub Desktop.
Save wonderb0lt/10731371 to your computer and use it in GitHub Desktop.
A JAX-RS resource that shows all bound methods in the current context (RestEasy only)
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.ResourceInvoker;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ResourceMethodRegistry;
import org.jboss.resteasy.util.WeightedMediaType;
import org.springframework.stereotype.Service;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sonydadc.wldcs.bo.rest.security.Secure;
/**
* A resource that displays a list of available
* endpoints (which is helpful to see for debugging purposes).
*
* @author Patrick Stegmann
*/
@Service
@Path("/")
public class OverviewResource {
private static final class MethodDescription {
private String method;
private String fullPath;
private String produces;
private String consumes;
public MethodDescription(String method, String fullPath, String produces, String consumes) {
super();
this.method = method;
this.fullPath = fullPath;
this.produces = produces;
this.consumes = consumes;
}
}
private static final class ResourceDescription {
private String basePath;
private List<MethodDescription> calls;
public ResourceDescription(String basePath) {
this.basePath = basePath;
this.calls = Lists.newArrayList();
}
public void addMethod(String path, ResourceMethod method) {
String produces = mostPreferredOrNull(method.getPreferredProduces());
String consumes = mostPreferredOrNull(method.getPreferredConsumes());
for (String verb : method.getHttpMethods()) {
calls.add(new MethodDescription(verb, path, produces, consumes));
}
}
private static String mostPreferredOrNull(List<WeightedMediaType> preferred) {
if (preferred.isEmpty()) {
return null;
}
else {
return preferred.get(0).toString();
}
}
public static List<ResourceDescription> fromBoundResourceInvokers(Set<Map.Entry<String, List<ResourceInvoker>>> bound) {
Map<String, ResourceDescription> descriptions = Maps.newHashMap();
for (Map.Entry<String, List<ResourceInvoker>> entry : bound) {
ResourceMethod aMethod = (ResourceMethod) entry.getValue().get(0);
String basePath = aMethod.getMethod().getDeclaringClass().getAnnotation(Path.class).value();
if (!descriptions.containsKey(basePath)) {
descriptions.put(basePath, new ResourceDescription(basePath));
}
for (ResourceInvoker invoker : entry.getValue()) {
ResourceMethod method = (ResourceMethod) invoker;
descriptions.get(basePath).addMethod(basePath, method);
}
}
return Lists.newLinkedList(descriptions.values());
}
}
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public List<ResourceDescription> getAvailableEndpoints(@Context Dispatcher dispatcher) {
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
return ResourceDescription.fromBoundResourceInvokers(registry.getRoot().getBounded().entrySet());
}
@GET
@Path("/")
@Produces(MediaType.TEXT_HTML)
public Response getAvailableEndpointsHtml(@Context Dispatcher dispatcher) {
// Yeah, yeah, HTML per StringBuilder. I can't be bovvered to make a JSP :D
StringBuilder sb = new StringBuilder();
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
List<ResourceDescription> descriptions = ResourceDescription.fromBoundResourceInvokers(registry.getRoot().getBounded().entrySet());
sb.append("<h1>").append("REST interface overview").append("</h1>");
for (ResourceDescription resource : descriptions) {
sb.append("<h2>").append(resource.basePath).append("</h2>");
sb.append("<ul>");
for (MethodDescription method : resource.calls) {
sb.append("<li> ").append(method.method).append(" ");
sb.append("<strong>").append(method.fullPath).append("</strong>");
sb.append("<ul>");
if (method.consumes != null) {
sb.append("<li>").append("Consumes: ").append(method.consumes).append("</li>");
}
if (method.produces != null) {
sb.append("<li>").append("Produces: ").append(method.produces).append("</li>");
}
sb.append("</ul>");
}
sb.append("</ul>");
}
return Response.ok(sb.toString()).build();
}
}
@christopherthielen
Copy link

Thanks, this is great. I've forked it to get it to work with the version of RESTEasy that we use.

@lesfoster
Copy link

Very helpful. With only minor tweaks, it showed all my deployed services.

@paulmilner
Copy link

Thanks, that's really useful. Works fine with Resteasy 2.3.6.Final.

@Scuilion
Copy link

In new versions of RESTEasy, ResourceMethod has become ResourceMethodInvoker.

@clxmaba
Copy link

clxmaba commented Nov 14, 2018

This is great! Can you please add a open source license which allows to embed this class in a commercial application?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment