31 July 2009

A RESTful Web service storing DNA sequences: Jersey, my notebook


Jersey is the open source JAX-RS implementation for building RESTful Web services. JAX-RS uses java annotations to simplify the development and deployment of web service clients and endpoints. In this post I'll describe how I've implemented a naive RESTful web service for storing and querying a DNA database. This code was tested and deployed under netbeans 6.1.

The service is defined in a class named FastaRest. An annotation @Path gives the root of our services. The value of UriInfo is injected by @Context (this interface provides an access to the application and requests information about the current URI). Our DNA database is just an associative array mapping the DNA sequence to its id (integer).

Building the Resource


@Path("/fastadb/") /** application path */
public class FastaRest {
/** injectable interface that provides access to application and request URI information */
@Context
private UriInfo context;

/** our DNA database */
private static final Map<Integer,String> SEQUENCES= new HashMap<Integer, String>();
(...)
}

Adding a DNA sequence


A DNA is inserted using a PUT method. The relative path must be /add and two parameters (@FormParam= id, an integer and @FormParam= seq, the DNA sequence) must be provided. A JSON response is returned with a message telling if the new sequence was added.
@PUT
@Path("/add")
@ConsumeMime("application/x-www-form-urlencoded")
@ProduceMime("application/json")
public String putSequence(
@FormParam("id")int id,
@FormParam("seq")String seq
)
{
if(!seq.matches("[atgcATGC]+"))
{
return "{'status':'error','message':'illegal sequence'}";
}

/** the static instance of SEQUENCE may be shared by multiple service. Let's lock it */
synchronized(SEQUENCES)
{
if (SEQUENCES.containsKey(id))
{
return "{'status':'error','message','already exists'}";
}
else
{
SEQUENCES.put(id, seq);
return "{'status':'ok','id',"+id+"}";
}
}
}

Retrieving a DNA sequence


The Fasta sequence of a given DNA is returned to the client using a GET method with the relative path /seq/{id-of-the-sequence}.
@GET
@Path("/seq/{id}")
@ProduceMime("text/plain")
public String getSequenceById(@PathParam("id") int id)
{
String seq=null;
synchronized(SEQUENCES)
{
seq=SEQUENCES.get(id);
}
if(seq!=null)
{
return ">"+id+"\n"+seq+"\n";
}
return "";
}

Dump all the DNA sequences

The fasta sequences of all the DNA is returned to the client using a GET method with the relative path /seqs. As this result may be huge, a StreamingOutput object is returned rather than a String.
@GET
@Path("/seqs")
@ProduceMime("text/plain")
public StreamingOutput getSequences()
{
return new StreamingOutput()
{
public void write(OutputStream out) throws IOException, WebApplicationException
{
synchronized(SEQUENCES)
{
PrintStream w= new PrintStream(out);

for(Integer id: SEQUENCES.keySet())
{
w.print(">"+id+"\n"+SEQUENCES.get(id)+"\n");
}
w.flush();
}
}
};
}

Test


Add a sequence
curl -X PUT -d 'id=1&seq=AAATAGCTAGTCGACGATCGTAG' "http://localhost:17370/REST01/resources/fastadb/add"
{'status':'ok','id',1}

Add a second sequence
curl -X PUT -d 'id=2&seq=AGCTAGAGCGGCTATATGC' "http://localhost:17370/REST01/resources/fastadb/add"
{'status':'ok','id',2}

Try to re-insert sequence id=2
curl -X PUT -d 'id=2&seq=AAAA' "http://localhost:17370/REST01/resources/fastadb/add"
{'status':'error','message','already exists'}

Try to insert something that is not a DNA sequence
curl -X PUT -d 'id=3&seq=NotADnaSequence' "http://localhost:17370/REST01/resources/fastadb/add"
{'status':'error','message':'illegal sequence'}

Retrieve sequence id=1
curl "http://localhost:17370/REST01/resources/fastadb/seq/1"
>1
AAATAGCTAGTCGACGATCGTAG

Fetch all
curl "http://localhost:17370/REST01/resources/fastadb/seqs"
>1
AAATAGCTAGTCGACGATCGTAG
>2
AGCTAGAGCGGCTATATGC



That's it !

Source code

import com.sun.jersey.api.representation.FormParam;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.ConsumeMime;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;

@Path("/fastadb/")
public class FastaRest {
@Context
private UriInfo context;
private static final Map<Integer,String> SEQUENCES= new HashMap<Integer, String>();

public FastaRest() {
}


@GET
@Path("/seq/{id}")
@ProduceMime("text/plain")
public String getSequenceById(@PathParam("id") int id)
{
String seq=null;
synchronized(SEQUENCES)
{
seq=SEQUENCES.get(id);
}
if(seq!=null)
{
return ">"+id+"\n"+seq+"\n";
}
return "";
}


@GET
@Path("/seqs")
@ProduceMime("text/plain")
public StreamingOutput getSequences()
{
return new StreamingOutput()
{
public void write(OutputStream out) throws IOException, WebApplicationException
{
synchronized(SEQUENCES)
{
PrintStream w= new PrintStream(out);

for(Integer id: SEQUENCES.keySet())
{
w.print(">"+id+"\n"+SEQUENCES.get(id)+"\n");
}
w.flush();
}
}
};
}




@PUT
@Path("/add")
@ConsumeMime("application/x-www-form-urlencoded")
@ProduceMime("application/json")
public String putSequence(@FormParam("id")int id,@FormParam("seq")String seq)
{
if(!seq.matches("[atgcATGC]+"))
{
return "{'status':'error','message':'illegal sequence'}";
}


synchronized(SEQUENCES)
{
if (SEQUENCES.containsKey(id))
{
return "{'status':'error','message','already exists'}";
}
else
{
SEQUENCES.put(id, seq);
return "{'status':'ok','id',"+id+"}";
}
}
}
}

web.xml :

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

No comments: