Integrating Jersey and Abdera
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.
- Login or register to post comments
- Printer-friendly version
- mhadley's blog
- 8464 reads






Comments
by sotuzun - 2008-08-28 06:48
thanks for the tutorial. But my choice is serializing JSON with JAXB providers...by sandoz - 2008-02-07 04:10
Very Nice! I have extended Marc's example to support both JSON and XML, see here
by mhadley - 2008-02-06 06:53
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.by vzafzal - 2008-02-06 04:16
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?by dandiep - 2008-02-05 15:37
Yay! I'm glad you did this - now I don't have to :-)by beuchelt - 2008-08-13 08:35
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...