Jersey and Abdera with a twist of JSON

Marc recently blogged on integrating Jersey with Abdera. Marc shows how easy it is to integrate, especially if using the latest Jersey 0.6 builds (see the comments) as there is no need to use the messy and error prone META-INF/services for registration of message body readers/writes.

I am going to extend Marc's example to add a twist of JSON to the mix. James shows here how to use Abdera with JSON (in the larger context of mapping between Atom documents encoded in XML and JSON). If you want to play with this code you will need to use the latest Jersey 0.6 build.

Lets refactor the AbderaSupport class to be as follows:

public abstract class BaseAbderaSupport 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;
            write(feed.getDocument(), outputStream);
        } else {
            Entry entry = (Entry) feedOrEntry;
            write(entry.getDocument(), 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 = read(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());
        }
    } 
    protected abstract void write(Document<? extends Base> doc,
            OutputStream outputStream) throws IOException;

    protected abstract Document<Element> read(InputStream inputStream)
            throws IOException;
}

Notice the protected abstract methods at the end. Then lets extend this base class for XML support:

@Provider
@ProduceMime("application/atom+xml")
@ConsumeMime("application/atom+xml")
public class XMLAbderaSupport extends BaseAbderaSupport {

   protected void write(Document<? extends Base> doc,
           OutputStream outputStream)  throws IOException {
       doc.writeTo(outputStream);
   }
  
   protected Document<Element> read(InputStream inputStream) throws IOException {
       return getAbdera().getParser().parse(inputStream);      
   }
}

and extend for JSON support:

@Provider
@ProduceMime("application/json")
@ConsumeMime("application/json")
public class JSONAbderaSupport extends BaseAbderaSupport {

   protected void write(Document<? extends Base> doc,
           OutputStream outputStream)  throws IOException {
       Writer w = getAbdera().getWriterFactory().getWriter("json");
       doc.writeTo(w, outputStream);
   }
   
   protected Document<Element> read(InputStream inputStream) throws IOException {
       Parser p = getAbdera().getParserFactory().getParser("json");
       return p.parse(inputStream);       
   }
}

Notice that the JSONAbderaSupport class uses the "application/json" media type.

Now lets create a very simple resource that returns an empty Atom feed:

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

   @GET
   public Feed getFeed() {
       Feed f = BaseAbderaSupport.getAbdera().getFactory().newFeed();
       f.setTitle("TITLE");
       URI feedLink = uriInfo.getRequestUri();
       f.addLink(feedLink.toString(),"self");      
       return f;
   }
}

Notice that the feed resource declares that it produces the "application/atom+xml" and the "application/json" media types. 

Penultimately lets deploy all this using the light weight HTTP server:

HttpServer server = HttpServerFactory.create("http://localhost:9998/");
server.start();

Notice that there is no need to explicitly reference the classes XMLAbderaSupport, JSONAbderaSupport and FeedResource. By default they are found automatically by scanning the Java classes files in the java.class.path for the appropriate annotations.

Finally lets test the feed resource using curl (i have formatted the output):

curl -v -H "Accept: application/atom+xml" http://localhost:9998/myfeed
\* About to connect() to localhost port 9998
\*   Trying 127.0.0.1... connected
\* Connected to localhost (127.0.0.1) port 9998
> GET /myfeed HTTP/1.1
> User-Agent: curl/7.15.5 (i386-pc-solaris2.11) libcurl/7.15.5
OpenSSL/0.9.8a zlib/1.2.3 libidn/0.6.8
> Host: localhost:9998
> Accept: application/atom+xml
>
< HTTP/1.1 200 OK
< Date: Thu, 07 Feb 2008 10:48:35 GMT
< Content-type: application/atom+xml
< Transfer-encoding: chunked
\* Connection #0 to host localhost left intact
\* Closing connection #0
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">TITLE</title>
<link href="http://localhost:9998/myfeed" rel="self" />
</feed>


curl -v -H "Accept: application/json" http://localhost:9998/myfeed
\* About to connect() to localhost port 9998
\*   Trying 127.0.0.1... connected
\* Connected to localhost (127.0.0.1) port 9998
> GET /myfeed HTTP/1.1
> User-Agent: curl/7.15.5 (i386-pc-solaris2.11) libcurl/7.15.5
OpenSSL/0.9.8a zlib/1.2.3 libidn/0.6.8
> Host: localhost:9998
> Accept: application/json
>
< HTTP/1.1 200 OK
< Date: Thu, 07 Feb 2008 10:49:02 GMT
< Content-type: application/json
< Transfer-encoding: chunked
\* Connection #0 to host localhost left intact
\* Closing connection #0
{
"contributors":[],
"title":"TITLE","categories":[],
"entries":[],
"authors":[],
"links":[
{
"href":"http://localhost:9998/myfeed",
"rel":"self"
}]
}

In less than an hour of playing around and writing this blog I have XML and JSON support for servicing Atom documents. Of course it helps that Abdera made it easy to integrate. My first impressions of playing around with Jersey and Abdera are that they are very complimentary for building Atom-based services.

Comments:

Sweet. One minor change.. Instead of:

Writer w = getAbdera().getWriterFactory().getWriter("json");
doc.writeTo(w, outputStream);

You can do:

doc.writeTo("json",outputStream);

Also, there is no json parser yet so getAbdera().getParserFactory().getParser("json"); won't return anything

Posted by James Snell on February 07, 2008 at 08:52 AM CET #

Hi James,

Thanks for the API tip. Before i wrote the writing stuff i searched the online JavaDoc for the nicer method but i could not find it on the Base interface (i may have missed it). So i assumed it was not in 0.3, but i did not check by writing code...

As you can tell i never got around to verifying the parsing side :-)

Paul.

Posted by Paul Sandoz on February 08, 2008 at 01:19 AM CET #

Yeah, one thing we haven't been too good at is keeping the online docs up to date. The stuff on the site is 0.3.0. The new api is part of 0.4.0. We really should get the site updated.

Posted by James Snell on February 13, 2008 at 05:00 PM CET #

Post a Comment:
  • HTML Syntax: NOT allowed
About

sandoz

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today