I'm working on an internal project that involves adding Atom Publishing Protocolsupport 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 AbderaFeed instance and returns it in response to aGET request on the myfeed URI. TheaddEntry method is called with an AbderaEntry instance that corresponds to the body of aPOST 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 implementedMessageBodyReader<Object> rather thanMessageBodyReader<Entry> andMessageBodyReader<Feed>. This allowed me to support both types in the same class. Same forMessageBodyWriter. The isReadable andisWriteable 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.