Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a JAX-RS service where the sub-resource @Path doesn't have a leading slash

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?

like image 996
Matt Avatar asked Apr 15 '10 10:04

Matt


2 Answers

Old topic, but this is something I solved recently using Jersey; perhaps it'll help someone else.

Jersey supports specifying the accepted content type as a file extension in the URI through the use of request filters. Jersey supplies a UriConnegFilter (URI Content Negotiation Filter) object that you extend to translate specific extensions to content types. You then include that filter as an initial parameter to the Jersey application.

Since that all sounds so vague, here's a concrete example from my project:

I wanted to be able to interpret ".json" and ".xml" at the end of the URL as meaning that the client wanted JSON-formatted or XML-formatted content, respectively. To that end, I extended UriConnegFilter like so:

package my.filter.package;

import com.sun.jersey.api.container.filter.UriConnegFilter;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.core.MediaType;


public class MediaTypeFilter extends UriConnegFilter {
  private static final Map<String, MediaType> mappedMediaTypes = new HashMap<String, MediaType>(2);

  static {
    mappedMediaTypes.put("json", MediaType.APPLICATION_JSON_TYPE);
    mappedMediaTypes.put("xml", MediaType.APPLICATION_XML_TYPE);
  }

  public MediaTypeFilter() {
    super(mappedMediaTypes);
  }
}

Then, since I am using Jersey as a Servlet, I added MediaTypeFilter to my web.xml:

<servlet>
  <servlet-name>My Jersey Servlet</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>my.resource.package</param-value>
  </init-param>
  <init-param>
    <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
    <param-value>com.sun.jersey.api.container.filter.LoggingFilter;
                 my.filter.package.MediaTypeFilter;
                 com.sun.jersey.api.container.filter.PostReplaceFilter;
                 com.sun.jersey.api.container.filter.GZIPContentEncodingFilter</param-value>
  </init-param>
  <init-param>
    <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
    <param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
                 com.sun.jersey.api.container.filter.LoggingFilter</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

With that in place, Jersey translates the extension on the URI into the specified media type, and removes the extension. This works for root resources and sub-resources because it operates on the whole URI. For your particular example, /people/Bob.xml would become /people/Bob and the Accept header in the request would be changed to "application/xml" (overriding any existing Accept header).

hth,

-Peter

like image 98
Peter Avatar answered Oct 13 '22 13:10

Peter


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.

like image 27
Robert Wilson Avatar answered Oct 13 '22 14:10

Robert Wilson