Is it possible to list all exposed/available endpoints of RestEasy service in a simple way?
RESTEasy is a JBoss / Red Hat project that provides various frameworks to help you build RESTful Web Services and RESTful Java applications. It is an implementation of the Jakarta RESTful Web Services, an Eclipse Foundation specification that provides a Java API for RESTful Web Services over the HTTP protocol.
Resteasy supports both Jackson 1.9. x and Jackson 2.2.
Jackson, RESTEasy. RESTEasy is JBOSS provided implementation of JAX-RS specification for building RESTful Web Services and RESTful Java applications. Though this is not limited to be used in JBOSS only, and you can use with other servers also.
There is a RestEasy plugin, "stats", which exposes .../resteasy/registry
.
It needs to be registered in web.xml
:
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value>
</context-param>
Example response:
<registry>
<resource uriTemplate="/resource">
<delete class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="delete"
invocations="0"/>
<head class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="head" invocations="0"/>
</resource>
<resource uriTemplate="/locator">
<locator class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="getLocator"/>
</resource>
<resource uriTemplate="/resteasy/registry">
<get class="org.jboss.resteasy.plugins.stats.RegistryStatsResource" method="get" invocations="2">
<produces>application/xml</produces>
<produces>application/json</produces>
</get>
</resource>
<resource uriTemplate="/entry/{foo:.*}">
<post class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="post" invocations="0">
<produces>text/xml</produces>
<consumes>application/json</consumes>
</post>
<put class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="put" invocations="0">
<produces>text/xml</produces>
<consumes>application/json</consumes>
</put>
</resource>
</registry>
Maven dependency:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>3.0.8.Final</version>
</dependency>
See eg. EAP docs and this EAP 7 Jira
I had to adjust the "cleaner" example which was excellent to begin with. I'm using RestEasy 3.07 and wanted to also have each method's Path annotation value. I hope this modification can be of help to others.
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
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.ResourceMethodInvoker;
import org.jboss.resteasy.core.ResourceMethodRegistry;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@Component
@Path("/overview")
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, ResourceMethodInvoker method)
{
String produces = mostPreferredOrNull(method.getProduces());
String consumes = mostPreferredOrNull(method.getConsumes());
for (String verb : method.getHttpMethods())
{
calls.add(new MethodDescription(verb, path, produces, consumes));
}
}
private static String mostPreferredOrNull(MediaType[] mediaTypes)
{
if (mediaTypes == null || mediaTypes.length < 1)
{
return null;
}
else
{
return mediaTypes[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)
{
Method aMethod = ((ResourceMethodInvoker) entry.getValue().get(0)).getMethod();
String basePath = aMethod.getDeclaringClass().getAnnotation(Path.class).value();
if (!descriptions.containsKey(basePath))
{
descriptions.put(basePath, new ResourceDescription(basePath));
}
for (ResourceInvoker invoker : entry.getValue())
{
ResourceMethodInvoker method = (ResourceMethodInvoker) invoker;
String subPath = null;
for(Annotation annotation : method.getMethodAnnotations())
{
if(annotation.annotationType().equals(Path.class))
{
subPath = ((Path) annotation).value();
break;
}
}
descriptions.get(basePath).addMethod(basePath + subPath, 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.getBounded().entrySet());
}
@GET
@Path("/")
@Produces(MediaType.TEXT_HTML)
public Response getAvailableEndpointsHtml(@Context Dispatcher dispatcher)
{
StringBuilder sb = new StringBuilder();
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
List<ResourceDescription> descriptions = ResourceDescription.fromBoundResourceInvokers(registry.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();
}
}
(On another note, perhaps there is something available, or I can begin work on, to model the resource listing and description that ServiceStack does so nicely: http://mono.servicestack.net/Content/Images/MetadataIndex.png)
EDIT:
See this gist for a "cleaner" example: https://gist.github.com/wonderb0lt/10731371
Yes, it's possible. Perhaps you would like to know how? :)
Here's a "quick-n-dirty" example:
import org.jboss.resteasy.annotations.providers.jaxb.Formatted;
import org.jboss.resteasy.annotations.providers.jaxb.Wrapped;
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.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Test;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PrintAllResourcesTest {
@Test
public void name_StateUnderTest_ExpectedBehavior() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(new MetaService());
dispatcher.getRegistry().addSingletonResource(new Service());
MockHttpResponse response = new MockHttpResponse();
MockHttpRequest request = MockHttpRequest.get("/meta")
.accept(MediaType.APPLICATION_XML);
dispatcher.invoke(request, response);
/*<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<resource method="GET">/service/</resource>
<resource method="POST">/service/</resource>
</resources>*/
String result = response.getContentAsString();
}
@XmlRootElement(name = "resource")
public static final class JaxRsResource {
@XmlAttribute String method;
@XmlValue String uri;
public JaxRsResource() {}
public JaxRsResource(String method, String uri) {
this.method = method;
this.uri = uri;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JaxRsResource that = (JaxRsResource) o;
if (method != null ? !method.equals(that.method) : that.method != null) return false;
if (uri != null ? !uri.equals(that.uri) : that.uri != null) return false;
return true;
}
@Override
public int hashCode() {
int result = method != null ? method.hashCode() : 0;
result = 31 * result + (uri != null ? uri.hashCode() : 0);
return result;
}
}
@Path("/service")
public static final class Service {
@GET
@Path("/")
public String getStuff(){
return "";
}
@POST
@Path("/")
public String postStuff(){
return "";
}
}
@Path("/meta")
public static final class MetaService {
@Context Dispatcher dispatcher;
@GET
@Path("/")
@Wrapped(element = "resources")
@Formatted
public Set<JaxRsResource> getAllResources(){
Set<JaxRsResource> resources = new HashSet<JaxRsResource>();
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
for (Map.Entry<String, List<ResourceInvoker>> entry : registry.getRoot().getBounded().entrySet()) {
for (ResourceInvoker invoker : entry.getValue()) {
ResourceMethod method = (ResourceMethod) invoker;
if(method.getMethod().getDeclaringClass() == getClass()){
continue;
}
for (String verb : method.getHttpMethods()) {
String uri = entry.getKey();
resources.add(new JaxRsResource(verb, uri));
}
}
}
return resources;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With