Skip to main content

Integrating Jersey and Abdera

Posted by mhadley on February 5, 2008 at 11:38 AM PST

I'm working on an internal project that involves adding Atom Publishing Protocol support to a data store. Naturally, I'm using Jersey for the HTTP side of things and decided to give Apache Abdera a try for simplifying working with feeds and entries.

With JAX-RS I can write a feed resource pretty easily:

import java.net.URI;
import java.util.Date;
import javax.ws.rs.ConsumeMime;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.UriParam;
import javax.ws.rs.core.HttpContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;

@ProduceMime("application/atom+xml")
@ConsumeMime("application/atom+xml")
@Path("myfeed")
public class FeedResource {
   @HttpContext
   private UriInfo uriInfo;

   @GET
   public Feed getFeed() {
       Feed f = AbderaSupport.getAbdera().getFactory().newFeed();
       f.setTitle("...");
       f.setId(...);
       f.addAuthor(...);
       f.setUpdated(...);
       URI feedLink = uriInfo.getRequestUri();
       f.addLink(feedLink.toString(),"self");
       for (...) {
           Entry e = f.addEntry();
           URI entryLink = ...
       f.addLink(entryLink.toString(),"alternate");
           ...
       }
       return f;
   }

   @POST
   public Response addEntry(Entry e) {
       Entry newEntry = AbderaSupport.getAbdera().newEntry();
       URI entryLink = ...;
       ...
       return Response.created(entryLink).entity(newEntry).build();
   }
}

The getFeed method creates an Abdera Feed instance and returns it in response to a GET request on the myfeed URI. The addEntry method is called with an Abdera Entry instance that corresponds to the body of a POST request. It does whatever is necessary to persist the new entry and then creates and returns a 201 Created response with a Location header and a new Entry corresponding to the persisted entry as the body of the response.

To allow use of Abdera's Entry and Feed types in resource method arguments and return types I had to implement the JAX-RS interfaces MessageBodyReader and MessageBodyWriter for those types. This turned out to be quite simple with Abdera:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.apache.abdera.Abdera;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;

@Provider
@ProduceMime("application/atom+xml")
@ConsumeMime("application/atom+xml")
public class AbderaSupport implements MessageBodyWriter<Object>,
       MessageBodyReader<Object> {

   private final static Abdera abdera = new Abdera();

   public static Abdera getAbdera() {
       return abdera;
   }

   public long getSize(Object arg0) {
       return -1;
   }

   public boolean isWriteable(Class<?> type) {
       return (Feed.class.isAssignableFrom(type) || Entry.class.isAssignableFrom(type));
   }

   public void writeTo(Object feedOrEntry, MediaType mediaType,
           MultivaluedMap<String, Object> headers,
           OutputStream outputStream) throws IOException {
       if (feedOrEntry instanceof Feed) {
           Feed feed = (Feed)feedOrEntry;
           Document<Feed> doc = feed.getDocument();
           doc.writeTo(outputStream);
       } else {
           Entry entry = (Entry)feedOrEntry;
           Document<Entry> doc = entry.getDocument();
           doc.writeTo(outputStream);
       }
   }

   public boolean isReadable(Class<?> type) {
       return (Feed.class.isAssignableFrom(type) || Entry.class.isAssignableFrom(type));
   }

   public Object readFrom(Class<Object> feedOrEntry, MediaType mediaType,
           MultivaluedMap<String, String> headers,
           InputStream inputStream) throws IOException {
       Document<Element> doc = getAbdera().getParser().parse(inputStream);
       Element el = doc.getRoot();
       if (feedOrEntry.isAssignableFrom(el.getClass())) {
           return el;
       } else {
           throw new IOException("Unexpected payload, expected "+feedOrEntry.getName()+
               ", received "+el.getClass().getName());
       }
   }
}

Notice I actually implemented MessageBodyReader<Object> rather than MessageBodyReader<Entry> and MessageBodyReader<Feed>. This allowed me to support both types in the same class. Same for MessageBodyWriter. The isReadable and isWriteable methods allow a class to reject types it doesn't support.

Abdera is shaping up to be a nice API for working with Atom documents. Hopefully the above demonstrates how easy it is to integrate support for new formats into Jersey.

Related Topics >>

Comments

thanks for the tutorial. But my choice is serializing JSON with JAXB providers...

Very Nice! I have extended Marc's example to support both JSON and XML, see here

If you are using Jersey trunk code you shouldn't need to do anything else, Jersey will locate the reader/writer using the @Provider annotation. I'm guessing you are using the 0.5 snapshot which requires an additional step I forgot to mention. In that case you need to create two files in META-INF/services: javax.ws.rs.ext.MessageBodyReader and javax.ws.rs.ext.MessageBodyWriter. Add a single line to each file: "whatever.package.name.you.use.AbderaSupport". The EntityProvider sample that is included with the Jersey release shows how to register custom message body readers and writers in more detail. Sorry about that.

Hi, thanks for the tutorial, it really is great!. But I get this error when trying to run the above code: A message body writer for Java type, class org.apache.abdera.parser.stax.FOMFeed, and MIME media type, application/atom+xml, was not found Any idea what might be causing it?

Yay! I'm glad you did this - now I don't have to :-)

I posted a complementing article on using the Jersey client API with Abdera here: http://blog.beuchelt.org/2008/07/18/Using+Abdera+And+The+Jersey+Client+A...