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"
]
}
}
]
}
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.