curioustechizen
curioustechizen

Reputation: 10672

JAX-RS: Providing a "default" Sub-resource locator

I am using JAX-RS to develop a RESTful API. A simplified version of my API is as follows:

GET /appapi/v1.0/users
POST /appapi/v1.1/users

... ... and so on

As you can see, the pattern follows {api_name}/v{major_version}.{minor_version}/{rest_of_the_path}.

I have an additional requirement:

If the version is not specified, then the latest version should be used by default - i.e.,

GET /appapi/users should be equivalent to GET /appapi/v1.1/users (assuming 1.1 is the latest version of users).

This is how I have implemented this using JAX RS.

@Path("/appapi")
public class AppApiRootResource {

    @Path("/{version: [v]\\d+[[.]\\d+]?}/")
    public AppApiSubResource getVersionedSubResource
                            (@PathParam("version") String version){
        System.out.println("Version: "+version);
        String versionString = version.substring(1); //Discard the leading 'v'
        String majorVersion = "";
        String minorVersion = "0";
        if(versionString.contains(".")){
            String [] splits = versionString.split(".");
            majorVersion = splits[0];
            minorVersion = splits[1];
        } else {
            majorVersion = versionString;
        }

        return SubResourceFactory.getSubResource(majorVersion, minorVersion);

    }

    @Path("/{^([v]\\d+[[.]\\d+]?)}/") //Is This Correct??
    public AppApiSubResource getDefaultApiResource(){
        /*
         * Need help defining the regular expression here 
         * so that this is used for anything that doesn't match
         * the "version" regexp above
         */
        System.out.println("API version not specified; Using default version");
        return SubResourceFactory.getLatestVersionSubResource();
    }
}

My Sub-Resource class is defined as follows. I have sub-classes of this class to deal with multiple versions of the API. The implementation of SubResourceFactory is not relevant for the purposes of this discussion. It just returns an instance of AppApiSubResource or its sub-class

public class AppApiSubResource {


    /**
     * Create a new user
     */
    @POST
    @Path("users")
    @Consumes(MediaType.APPLICATION_XML)
    public Response createUser(String newUser, @Context UriInfo uriInfo) {
        URI uri = uriInfo.getAbsolutePathBuilder().path("10")).build();       
        return Response.created(uri).build();
    }

    /**
     * Get a user
     */
    @GET
    @Path("users/{id}")
    @Produces(MediaType.APPLICATION_XML)
    public Response getUser(@PathParam("id") String userId
            ) {

        return Response.ok().entity("<user></user>").build();
    }
}

Problem statement:

  • If I comment out getDefaultApiResource(), then things work as expected when there is a version specifier in the API. However, if I un-comment getDefaultApiResource(), it is always being invoked, irrespective of whether I have the v1.0 in the request or not.
  • Also, if I un-comment getDefaultApiResource(), I get a 404 when I do a GET /appapi/users (i.e, without the version specifier); but things work fine if I use GET /appapi/v1.0/users (i.e., with a version specifier)

So, how do I set up my sub-resource locator paths/regexps such that the method is invoked when there is no version specifier in the request?

I'm using Restlet framework, but this question is implementation-agnostic.

Upvotes: 0

Views: 1442

Answers (1)

VGR
VGR

Reputation: 44338

The reason getDefaultApiResource always gets invoked is that its URI pattern is the same regular expression as that of getVersionedSubResource, and when more than one pattern matches the request URI, the longest pattern (literally, the one with the most characters) wins. ("version: " is not considered part of the pattern.) See section 3.7.2 of the JAX-RS specification for all the details.

I've never tried this, but I think @Path("") will do what you want.

By the way, it appears your regular expression isn't quite right:

[v]\\d+[[.]\\d+]?

That says "lowercase v, followed by one or more digits, optionally followed by a single period, digit, or plus sign." It should be:

[v]\\d+([.]\\d+)?

Upvotes: 2

Related Questions