· jersey

Jersey: Listing all resources, paths, verbs to build an entry point/index for an API

I’ve been playing around with Jersey over the past couple of days and one thing I wanted to do was create an entry point or index which listed all my resources, the available paths and the verbs they accepted.

Guido Simone explained a neat way of finding the paths and verbs for a specific resource using Jersey’s http://grepcode.com/file/repo1.maven.org/maven2/com.sun.jersey/jersey-server/1.0.3/com/sun/jersey/server/impl/modelapi/annotation/IntrospectionModeller.java:

AbstractResource resource = IntrospectionModeller.createResource(JacksonResource.class);
System.out.println("Path is " + resource.getPath().getValue());

String uriPrefix = resource.getPath().getValue();
for (AbstractSubResourceMethod srm :resource.getSubResourceMethods())
{
    String uri = uriPrefix + "/" + srm.getPath().getValue();
    System.out.println(srm.getHttpMethod() + " at the path " + uri + " return " + srm.getReturnType().getName());
}

If we run that against j4-minimal's https://github.com/mneedham/j4-minimal/blob/master/src/main/java/com/g414/j4/minimal/JacksonResource.java class we get the following output:

Path is /jackson
GET at the path /jackson/{who} return com.g414.j4.minimal.JacksonResource$Greeting
GET at the path /jackson/awesome/{who} return javax.ws.rs.core.Response

That’s pretty neat but I didn’t want to have to manually list all my resources since I’ve already done that using Guice .

I needed a way to programatically get hold of them and I partially found the way to do this from this post which suggests using Application.getSingletons().

I actually ended up using Application.getClasses() and I ended up with https://github.com/mneedham/j4-minimal/blob/249078d2c8e982b81ae810310eb2340fa4fd909f/src/main/java/com/g414/j4/minimal/ResourceListingResource.java:

@Path("/")
public class ResourceListingResource
{
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response showAll( @Context Application application,
                             @Context HttpServletRequest request)
    {
        String basePath = request.getRequestURL().toString();

        ObjectNode root = JsonNodeFactory.instance.objectNode();
        ArrayNode resources = JsonNodeFactory.instance.arrayNode();

        root.put( "resources", resources );

        for ( Class<?> aClass : application.getClasses() )
        {
            if ( isAnnotatedResourceClass( aClass ) )
            {
                AbstractResource resource = IntrospectionModeller.createResource( aClass );
                ObjectNode resourceNode = JsonNodeFactory.instance.objectNode();
                String uriPrefix = resource.getPath().getValue();

                for ( AbstractSubResourceMethod srm : resource.getSubResourceMethods() )
                {
                    String uri = uriPrefix + "/" + srm.getPath().getValue();
                    addTo( resourceNode, uri, srm, joinUri(basePath, uri) );
                }

                for ( AbstractResourceMethod srm : resource.getResourceMethods() )
                {
                    addTo( resourceNode, uriPrefix, srm, joinUri( basePath, uriPrefix ) );
                }

                resources.add( resourceNode );
            }

        }


        return Response.ok().entity( root ).build();
    }

    private void addTo( ObjectNode resourceNode, String uriPrefix, AbstractResourceMethod srm, String path )
    {
        if ( resourceNode.get( uriPrefix ) == null )
        {
            ObjectNode inner = JsonNodeFactory.instance.objectNode();
            inner.put("path", path);
            inner.put("verbs", JsonNodeFactory.instance.arrayNode());
            resourceNode.put( uriPrefix, inner );
        }

        ((ArrayNode) resourceNode.get( uriPrefix ).get("verbs")).add( srm.getHttpMethod() );
    }


    private boolean isAnnotatedResourceClass( Class rc )
    {
        if ( rc.isAnnotationPresent( Path.class ) )
        {
            return true;
        }

        for ( Class i : rc.getInterfaces() )
        {
            if ( i.isAnnotationPresent( Path.class ) )
            {
                return true;
            }
        }

        return false;
    }

}

The only change I’ve made from Guido Simone’s solution is that I also call resource.getResourceMethods() because resource.getSubResourceMethods() only returns methods which have a @Path annotation.

Since we’ll sometimes define our path at the class level and then define different verbs that operate on that resource it misses some methods out.

If we run a cURL command (piped through python to make it look nice) against the root we get the following output:

$ curl http://localhost:8080/ -w "\n" 2>/dev/null | python -mjson.tool

{
    "resources": [
        {
            "/bench": {
                "path": "http://localhost:8080/bench",
                "verbs": [
                    "GET",
                    "POST",
                    "PUT",
                    "DELETE"
                ]
            }
        },
        {
            "/sample/{who}": {
                "path": "http://localhost:8080/sample/{who}",
                "verbs": [
                    "GET"
                ]
            }
        },
        {
            "/jackson/awesome/{who}": {
                "path": "http://localhost:8080/jackson/awesome/{who}",
                "verbs": [
                    "GET"
                ]
            },
            "/jackson/{who}": {
                "path": "http://localhost:8080/jackson/{who}",
                "verbs": [
                    "GET"
                ]
            }
        },
        {
            "/": {
                "path": "http://localhost:8080/",
                "verbs": [
                    "GET"
                ]
            }
        }
    ]
}
  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket