views:

191

answers:

2

I've created a JAX-RS service (MyService) that has a number of sub resources, each of which is a subclass of MySubResource. The sub resource class being chosen is picked based on the parameters given in the MyService path, for example:

@Path("/") @Provides({"text/html", "text/xml"}) 
public class MyResource {
  @Path("people/{id}") public MySubResource getPeople(@PathParam("id") String id) {
    return new MyPeopleSubResource(id);
  }
  @Path("places/{id}") public MySubResource getPlaces(@PathParam("id") String id) {
    return new MyPlacesSubResource(id);
  }
}

where MyPlacesSubResource and MyPeopleSubResource are both sub-classes of MySubResource.

MySubResource is defined as:

public abstract class MySubResource {
  protected abstract Results getResults();

  @GET public Results get() { return getResults(); }

  @GET @Path("xml") 
  public Response getXml() {
    return Response.ok(getResults(), MediaType.TEXT_XML_TYPE).build();  
  }

  @GET @Path("html") 
  public Response getHtml() {
    return Response.ok(getResults(), MediaType.TEXT_HTML_TYPE).build();  
  }
}

Results is processed by corresponding MessageBodyWriters depending on the mimetype of the response.

While this works it results in paths like /people/Bob/html or /people/Bob/xml where what I really want is /people/Bob.html or /people/Bob.xml

Does anybody know how to accomplish what I want to do?

A: 

You could possibly write some servlet routing to work it out. Realistically though, you should be using Content Types to do this.

@GET @Path("/") @Produces(MediaType.APPLICATION_XML)
public Response getXml() { ... }

@GET @Path("/") @Produces(MediaType.APPLICATION_HTML)
public Response getHtml() { ... }

The JAX-RS provider will then work out what to call based on the client's request. Even better, you could us JAXB and RestEASY to do it all for you!

@GET
@Produces(MediaType.APPLICATION_XML)
@Path("/{id}")
public MyObject getXml(@PathParam("typeDocument") String id) {
 myObjectService.get(id);
}


@XmlRootElement(name="myObject")
public class MyObject {
// Some properties
}

See http://java.dzone.com/articles/resteasy-spring for a good example with Spring.

Robert Wilson
Thanks for the response, this is actually the way I originally implemented it. Unfortunately some browsers don't send a compatible Accept header and so get shown the xml instead of html representation.Using servlet routing may work though haven't written one of those before. Come to think of it using routing would probably break the UriBuilder.getPath methods which I use exclusively in the html views.Guess I'd better keep looking...
Matt
A: 

One way to solve this is you can probably use a regular expression capture in your @javax.ws.rs.Path.

@Path("people/{id:[^/]+?}{format:(\\.[^/]*?)?}")
@GET
public MySubResource getPeople(@PathParam("id") String id, @PathParam("format") String format) {
    // remove the "." from the start of "format" if it is not null
    return new MySubResource(id, format);
}

Then in your sub-resource:

public abstract class MySubResource {
    final protected String format;

    protected MySubResource(String id, String format) {
        this.format = format;
    }

    protected abstract Results getResults();

    @GET
    public Response get() {
       return Response.ok(getResults(), this.format).build();  
    }
}

Be careful with the regular expressions. I gave an example but you may way to tighten the expression to make sure that nothing slips through.

Another way to solve this is to change where your {id} is being captured and use the regular expression there. Instead of having @Path("id") MySubResource public getPeople(@PathParam("id") String id) capture the id, remove the id capture from getPeople() and change MySubResource like so:

 @Path("people")
 public MySubResource getPeople() {
    return new MyPeopleSubResource();
 }

public abstract class MySubResource {
  protected abstract Results getResults();

  @GET
  @Path("{id}")
  public Results get() { return getResults(); }

  @GET
  @Path("{id}.xml") 
  public Response getXml() {
    return Response.ok(getResults(), MediaType.TEXT_XML_TYPE).build();  
  }

  @GET
  @Path("{id}.html") 
  public Response getHtml() {
    return Response.ok(getResults(), MediaType.TEXT_HTML_TYPE).build();  
  }
}

There are tradeoffs in either case depending on how your data structures are organized and when you need to know the "id" parameter. I'm not particular fond of regular expressions since they're really hard to get right but it's a possibility in this case.

Bryant Luk
Thanks for the answer. I was hoping to avoid doing it like this because moving the logic for choosing response types into the parent resource has two drawbacks: 1) it means that I need to copy the functionality to everywhere a sub resource is returned and 2) if one of my sub resource can return an extra type (say application/json) then this needs to be pushed into the parent resource; both of these I think are leaky abstractions. Unfortunately, so far, this seems to be the only solution and I'll just have to lump the disadvantages.
Matt