List all exposed/available endpoints of RestEasy service?
Asked Answered
M

6

20

Is it possible to list all exposed/available endpoints of RestEasy service in a simple way?

Meingoldas answered 5/11, 2013 at 17:45 Comment(0)
H
8

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

Housekeeping answered 26/9, 2016 at 10:40 Comment(1)
Here's an updated link to the RegistryStatsResource javadoc (not that there's much in it): access.redhat.com/webassets/avalon/d/…Evadnee
H
6

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)

Halophyte answered 4/9, 2014 at 2:4 Comment(0)
B
5

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;
        }

    }
}
Baggott answered 5/11, 2013 at 19:17 Comment(2)
Thanks ... couldn't find something ... helped me alot.Meingoldas
Hi, sorry to post on such an old answer! I found your answer helpful and have posted a less "quick-n-dirty" method based on your code and wanted to share it with the world: gist.github.com/wonderb0lt/10731371Pradeep
B
0

Even it is an old post, I give my answer here.

Here is the implementation from RestEasy shipped with JBoss. You can use it, or you can write your own. The implementation returns an object with an array property where you can find a uriTemplate String for each RestEasy Resource.

You need to iterate over all entries and get the info you need:

RegistryData.entries.get(index).uriTemplate

The implementation of org.jboss.resteasy.plugins.stats.RegistryStatsResource.get method:

public RegistryData get() throws JAXBException {
    ResourceMethodRegistry registry = (ResourceMethodRegistry)ResteasyProviderFactory.getContextData(Registry.class);
    RegistryData data = new RegistryData();
    Iterator i$ = registry.getRoot().getBounded().keySet().iterator();

    label85:
    while(i$.hasNext()) {
        String key = (String)i$.next();
        List<ResourceInvoker> invokers = (List)registry.getRoot().getBounded().get(key);
        RegistryEntry entry = new RegistryEntry();
        data.getEntries().add(entry);
        entry.setUriTemplate(key);
        Iterator i$ = invokers.iterator();

        while(true) {
            while(true) {
                if (!i$.hasNext()) {
                    continue label85;
                }

                ResourceInvoker invoker = (ResourceInvoker)i$.next();
                if (invoker instanceof ResourceMethod) {
                    ResourceMethod rm = (ResourceMethod)invoker;

                    Object method;
                    for(Iterator i$ = rm.getHttpMethods().iterator(); i$.hasNext(); entry.getMethods().add(method)) {
                        String httpMethod = (String)i$.next();
                        method = null;
                        if (httpMethod.equals("GET")) {
                            method = new GetResourceMethod();
                        } else if (httpMethod.equals("PUT")) {
                            method = new PutResourceMethod();
                        } else if (httpMethod.equals("DELETE")) {
                            method = new DeleteResourceMethod();
                        } else if (httpMethod.equals("POST")) {
                            method = new PostResourceMethod();
                        } else if (httpMethod.equals("OPTIONS")) {
                            method = new OptionsResourceMethod();
                        } else if (httpMethod.equals("TRACE")) {
                            method = new TraceResourceMethod();
                        } else if (httpMethod.equals("HEAD")) {
                            method = new HeadResourceMethod();
                        }

                        ((ResourceMethodEntry)method).setClazz(rm.getResourceClass().getName());
                        ((ResourceMethodEntry)method).setMethod(rm.getMethod().getName());
                        AtomicLong stat = (AtomicLong)rm.getStats().get(httpMethod);
                        if (stat != null) {
                            ((ResourceMethodEntry)method).setInvocations(stat.longValue());
                        } else {
                            ((ResourceMethodEntry)method).setInvocations(0L);
                        }

                        MediaType[] arr$;
                        int len$;
                        int i$;
                        MediaType mediaType;
                        if (rm.getProduces() != null) {
                            arr$ = rm.getProduces();
                            len$ = arr$.length;

                            for(i$ = 0; i$ < len$; ++i$) {
                                mediaType = arr$[i$];
                                ((ResourceMethodEntry)method).getProduces().add(mediaType.toString());
                            }
                        }

                        if (rm.getConsumes() != null) {
                            arr$ = rm.getConsumes();
                            len$ = arr$.length;

                            for(i$ = 0; i$ < len$; ++i$) {
                                mediaType = arr$[i$];
                                ((ResourceMethodEntry)method).getConsumes().add(mediaType.toString());
                            }
                        }
                    }
                } else {
                    ResourceLocator rl = (ResourceLocator)invoker;
                    SubresourceLocator locator = new SubresourceLocator();
                    locator.setClazz(rl.getMethod().getDeclaringClass().getName());
                    locator.setMethod(rl.getMethod().getName());
                    entry.setLocator(locator);
                }
            }
        }
    }

    return data;
}

See also: WildFly management - list/detect REST endpoints deployed in WildFly

Beichner answered 13/11, 2018 at 13:18 Comment(0)
K
0

In Resteasy 6.2 the above by Ondra Žižka mentioned solution caused a ClassCastException:

[2022-10-13 02:45:58,640] Artifact RegistryStatsResource:war: java.lang.Exception: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"xyz.war\".undertow-deployment" => "java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.stats.RegistryStatsResource from [Module \"deployment.xyz.war\" from Service Module Loader]
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.stats.RegistryStatsResource from [Module \"deployment.xyz.war\" from Service Module Loader]
Caused by: java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.stats.RegistryStatsResource from [Module \"deployment.xyz.war\" from Service Module Loader]"}}

I was able to resolve the issue by adding the resteasy-stats dependency:

<!-- pom.xml -->
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-stats</artifactId>
    <version>6.2.0.Final</version>
</dependency>

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jaxb-provider</artifactId>
    <version>6.2.0.Final</version>
</dependency>

For completeness, the web.xml

<web-app>
    <context-param>
        <param-name>resteasy.resources</param-name>
        <param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value>
    </context-param>
</web-app>

The services can be accessed via:

curl http://<AS-ip>:<AS-port>/<web context>/<rest activator>/resteasy/registry
Kyanite answered 13/10, 2022 at 13:20 Comment(0)
P
-1

In case anyone is still looking hit "/resteasy/registry" on your app and it

provides XML output of all registered endpoints, associated classes/methods etc

FYI resteasy-jaxb-provider provides this functionality

Pilch answered 7/11, 2014 at 12:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.