Monday Aug 18, 2008

Integrating Jersey and Spring: Take 3

Just in case it got hidden in the message of the 0.8 release Jersey now supplies Spring support via the spring maven module that was contributed by Martin.

So, rather than copying some code from my blog, you can depend on this module and reference the Spring servlet:

com.sun.jersey.spi.spring.container.servlet.SpringServlet 

in the web.xml. That is it. Martin describes this in more detail here. Note that after this was written some changes were made to the package names as Martin describes here.

I am quite happy with it as we managed to solve a knotty issue of referencing Spring beans in constructor/method parameters that Jersey is responsible for invoking, thus JAX-RS/Jersey-based annotated parameters can be intermixed with Spring-based annotated or referenced parameters. Having said that i think we may be able to make the integration even smoother in two areas when using Spring-based annotation configuration: 1) inferring the life-cycle Jersey requires; and 2) reusing @Autowired for constructor/method parameters.

The mechanisms by which Jersey integrates with Spring can equally apply to Guice or WebBeans or a more specialized integration.

Friday Feb 08, 2008

Jersey Client API

Yesterday i finished refactoring the Jersey Client API and sprinkling it with some JavaDoc. This API was originally being used for Jersey unit testing but i think it is applicable in the larger context of a general RESTful client API.

With some HTTP-based client APIs you have to do a lot of crufty work to make even the simplest request and process a response. In addition they do not really capture the concept of resources and the uniform interface. I think this may be one reason why client-side code generation for creating a 'proxy' to a HTTP-based service is more prevalent than i think it should be. With this client API it is very easy to produce requests and consume responses with resources and the uniform interface at the fore of the API. Hopefully if you play with this API you will see how easy it is to use and why code-generation (and in general relying statically on specific server-side artifacts) is of less importance in this respect.

At the end of this blog is a complete example showing the basic features of the client-side API. This works with the latest Jersey 0.6 build. It contains a resource, called PropertiesResource, a message body reader/writer for reading and writing properties, and some client code in the Main.run method. When this code is executed it should complete successfully with no exception thrown. The example tests simple create, read, update and delete of properties where the server is in control of the property names and the client chooses the property values.

Let's go through the statements of the Main.run method.

Setting up the client:

ClientConfig cc = new DefaultClientConfig();
cc.getProviderClasses().add(PropertiesProvider.class);
Client c = Client.create(cc);

The client can utilize message body readers and writers just like the server-side (in addition to IoC frameworks). I have yet to implement the client-scanning parts so for now it is necessary to explicitly register readers and writers.

Create a resource proxy to the properties resource:

ResourceProxy pResource = c.proxy("http://localhost:9998/");

Now we can start calling methods on the resource proxy:

Properties p = pResource.get(Properties.class);
assertTrue(p.size() == 0);

A GET request should return a Properties instance that has no properties. Notice how the HTTP method is used a bit like RMI but since the interface is uniform it never varies for standard HTTP-based resources. Like the server-side the client side can easily avail of Java types for representations.

Creating a new property resource:

ClientResponse cr = pResource.
  type("text/plain").
  post(ClientResponse.class, "some stuff");
assertTrue(cr.getStatus() == 201);

A POST method is called on the properties resource. The builder pattern is used to set the content type of the request (it is possible for the client to specify what is acceptable using the accept method) and the post method is invoked with the requested return type and the request entity (a String instance). The status code of the response should be 201 (Created). Notice that the client can choose to get all the response information or the representation previously shown for the GET request. The same methods are used as it is the return type that specifies such behavior.

Verify the created property resource: 

ResourceProxy vResource = c.proxy(cr.getLocation());
String content = vResource.get(String.class);
assertTrue("some stuff".equals(content)); 

The response to the POST request (if successful) will contain the location to the newly created property resource. This can be used to create a new resource proxy. A get method is called on that resource proxy and it should return the same information that was POSTed.

Verify the contents of the created property resource are also present in the properties resource:

p = pResource.get(Properties.class);
assertTrue(p.size() == 1);
assertTrue(p.contains("some stuff"));

Delete the created resource:

try {
    vResource.delete();
} catch (UniformInterfaceException e) {
    assertTrue(false);
}

The DELETE method on the property resource is invoked. In this case the client does not send anything and does not request a response. If something other than a successful response occurs an exception will be thrown.

Verify that the property resource has been deleted: 

try {
    content = vResource.get(String.class);
} catch (UniformInterfaceException e) {
    assertTrue(e.getResponse().getStatus() == 404);
}

In this case we expect an exception to be thrown. The ClientResponse can be obtained from the exception to check the status code, which should be 404 (Not Found). But we can also do it like this if we wish:

cr = vResource.get(ClientResponse.class);
assertTrue(cr.getStatus() == 404);

So it is possible to verify the status code explicitly or work under the assumption that success is the norm and errors are the exception.

Hopefully that gives you a flavor of how to use the API from a resource and uniform interface perspective. Given the recent blogs on Jersey and Abdera (see here and here) it should be easy to apply Abdera on the Jersey client side as it was on the server side.

If you want you can still build client requests independently and pass them to the Client instance. You can also add filters for say performing authentication. There is a lot that still can be done. For example, a client requesting a CreatedResponse could verify the status code and location header and throw an exception if it does not conform. Asynchronous requests may be possible by the client requesting say Future<String> or Future<ClientResponse>. I already have a feature request to add request/response progress listeners. Since services may be clients too there is lots of opportunity for such clients to share resources with the server making things more efficient and scalable. So much to do! If you would like to help out just contact me :-)


import com.sun.net.httpserver.HttpServer;
import com.sun.ws.rest.api.client.Client;
import com.sun.ws.rest.api.client.ClientResponse;
import com.sun.ws.rest.api.client.ResourceProxy;
import com.sun.ws.rest.api.client.UniformInterfaceException;
import com.sun.ws.rest.api.client.config.ClientConfig;
import com.sun.ws.rest.api.client.config.DefaultClientConfig;
import com.sun.ws.rest.api.container.httpserver.HttpServerFactory;
import com.sun.ws.rest.spi.resource.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Properties;
import java.util.UUID;
import javax.ws.rs.ConsumeMime;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

public class Main {

    @ProduceMime("text/plain")
    @Provider
    public static class PropertiesProvider implements
            MessageBodyWriter<Properties>,
            MessageBodyReader<Properties> {

        public void writeTo(Properties p, MediaType mediaType,
                MultivaluedMap<String, Object> headers, OutputStream out)
                throws IOException {
            p.store(out, null);
        }

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

        public long getSize(Properties p) {
            return -1;
        }

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

        public Properties readFrom(Class<Properties> type, MediaType mediaType,
                MultivaluedMap<String, String> headers, InputStream in)
                throws IOException {
            Properties p = new Properties();
            p.load(in);
            return p;
        }
    }
   
    @Path("/")
    @ProduceMime("text/plain")
    @ConsumeMime("text/plain")
    @Singleton
    public static class PropertiesResource {
        @HttpContext UriInfo uriInfo;
       
        Properties p = new Properties();
       
        @GET public Properties get() {
            return p;
        }
       
        @POST public Response post(String in) {
            String id = UUID.randomUUID().toString();
           
            p.setProperty(id, in);
           
            URI u = uriInfo.getAbsolutePathBuilder().path(id).build();
            return Response.created(u).build();
        }
       
        @Path("{id}") @GET public synchronized String getContent(
                @PathParam("id") String id) {
            String content = p.getProperty(id);
            if (content == null) throw new WebApplicationException(404);
           
            return content;
        }
       
        @Path("{id}") @PUT public synchronized void updateContent(
                @PathParam("id") String id) {
            String content = p.getProperty(id);
            if (content == null) throw new WebApplicationException(404);
           
            p.setProperty(id, content);
        }
       
        @Path("{id}") @DELETE public synchronized void deleteContent(
                @PathParam("id") String id) {
            String content = p.getProperty(id);
            if (content == null) throw new WebApplicationException(404);
           
            p.remove(id);
        }
    }
   
    public static void run() throws Exception {
        // Create the client
        ClientConfig cc = new DefaultClientConfig();
        // Include the properties provider
        cc.getProviderClasses().add(PropertiesProvider.class);
        Client c = Client.create(cc);
       
        // Create the resource proxy to the resource
        ResourceProxy pResource = c.proxy("http://localhost:9998/");
       
        // Get the current properties and verify it is empty
        Properties p = pResource.get(Properties.class);       
        assertTrue(p.size() == 0);
       
        // Create a new property
        ClientResponse cr = pResource.
                type("text/plain").
                post(ClientResponse.class, "some stuff");
        assertTrue(cr.getStatus() == 201);

        // Verify the created resource
        ResourceProxy vResource = c.proxy(cr.getLocation());
        String content = vResource.get(String.class);
        assertTrue("some stuff".equals(content));
       
        // Get the current properties and verify the contents
        p = pResource.get(Properties.class);
        assertTrue(p.size() == 1);
        assertTrue(p.contains("some stuff"));
       
        // Delete the created resource
        try {
            vResource.delete();
        } catch (UniformInterfaceException e) {
            // This will occur when a status >= 300 occurs
            assertTrue(false);
        }
       
        // Verify resource is deleted
        try {
            content = vResource.get(String.class);
        } catch (UniformInterfaceException e) {
            // This will occur when a status >= 300 occurs
            assertTrue(e.getResponse().getStatus() == 404);
        }
       
        // Verify resource is deleted using ClientResponse
        cr = vResource.get(ClientResponse.class);
        assertTrue(cr.getStatus() == 404);
       
        // Verify properties resource contains no properties
        p = pResource.get(Properties.class);
        assertTrue(p.size() == 0);       
    }
   
    private static void assertTrue(boolean v) {
        if (v == false) throw new RuntimeException();
    }
   
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServerFactory.create("http://localhost:9998/");
        server.start();
       
        try {
            run();
        } finally {
            server.stop(0);
        }
    }
}

Thursday Feb 07, 2008

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.

Tuesday Feb 05, 2008

JavaOne and Jazoon

We submitted a technical session on JAX-RS and a BOF on Jersey to JavaOne and both have been accepted.

In terms of the technical session our current thinking is we would like to describe the REST style (grounded in the Resource Oriented Architecture terminology), how it connects to the JAX-RS API,  and how you code to that API in a series of logical steps (for example the Resource Oriented Architecture properties) to build an example. I think this would be better than lots of talk (cue hand waving) followed by a 5 to 10 minute demo. Intermixing these three things is a tricky balancing act but i think its worth the effort to better communicate about JAX-RS. We need to choose an example, if you have any ideas on what you would like to see, write a comment or send me some email.

In terms of the BOF i deliberately made the abstract "loose" and would like to drive the content based on feedback before or at the BOF. We can come up with some pre-prepared slides on the Jersey architecture, additional features, JAX-RS, and some demos but i would really like things driven by what developers want to see. So again if you have any ideas/requests write a comment or send me some email.

I have also been invited to present at Jazoon08 (and very much enjoyed Jazoon07 last year). I suspect we are going to reuse a lot of material that was prepared for JavaOne :-) but we will endeavor to improve it based on feedback we receive.

Friday Feb 01, 2008

Integrating Jersey and Spring: Take 2

Marc previously described how to integrate Spring with Jersey 0.4 for the instantiation of root resources. This was an great first step but it fell short in a couple of areas:

  • there was some initialization code that could not be performed at initialization stage;
  • it was necessary to annotate (or specify the default provider for) all resources with a Spring specific life-cycle annotation, thus it was not possible to write 'vanilla' resources for use with Spring; and
  • this was only applicable to root resource classes. Jersey has other components, such as instances of MessageBodyReader/Writer, and it would be useful if those components could also be Spring-enabled.

I have spent this week unifing the instantiation of components (in addition to removing the requirement of META-INF/services for registration, it is all dynamic like for root resource classes). Instantiation of any component managed in Jersey is deferred to a ComponentProvider. By default Jersey provides a basic implementation but it is possible to  provide an application-specific implementation for say Spring. Jersey will then adapt that implementation so that Spring-registered and non-Spring-registered components can be instantiated.

(All code referenced below only works with the latest build of Jersey.)

I am still pondering the best way to declare an application-specific ComponentProvider but for now i am experimenting specifically with a Spring aware Servlet. Jersey ships with a servlet that can be extended to configure the WebApplication, which makes it very easy to extend for Spring support. Below is the code for the SpringServlet:

public class SpringServlet extends ServletContainer {
   
    private static class SpringComponentProvider implements ComponentProvider {
        private ApplicationContext springContext;

        SpringComponentProvider(ApplicationContext springContext) {
            this.springContext = springContext;
        }
       
        private String getBeanName(Class c) {
            String names[] = springContext.getBeanNamesForType(c);
            if (names.length == 0) {
                return null;
            } else if (names.length > 1) {
                throw new RuntimeException("Multiple configured beans for "
                        + c.getName());
            }
            return names[0];           
        }
       
        public Object getInstance(Scope scope, Class c)
                throws InstantiationException, IllegalAccessException {           
            String beanName = getBeanName(c);
            if (beanName == null) return null;
           
            if (scope == Scope.WebApplication &&
                    springContext.isSingleton(beanName)) {
                return springContext.getBean(beanName, c);
            } else if (scope == Scope.ApplicationDefined &&
                    springContext.isPrototype(beanName) &&
                    !springContext.isSingleton(beanName)) {
                return springContext.getBean(beanName, c);
            } else {
                return null;
            }
        }

        public Object getInstance(Scope scope, Constructor contructor,
                Object[] parameters)
                throws InstantiationException, IllegalArgumentException,
                IllegalAccessException, InvocationTargetException {
            return null;
        }

        public void inject(Object instance) {
        }       
    };
   
    @Override
    protected void initiate(ResourceConfig rc, WebApplication wa) {
        ApplicationContext springContext = WebApplicationContextUtils.
                getRequiredWebApplicationContext(getServletContext());
       
        wa.initiate(rc, new SpringComponentProvider(springContext));
    }       
}

Notice that SpringServlet extends ServletContainer and the initiate method is overridden. This method creates an ApplicationContext and then initiates the WebApplication by passing in an instance of the static inner class SpringComponentProvider. This class implements ComponentProvider and the getInstance method will attempt to obtain a Spring bean that is present and matches the requested scope, if so then the bean instance is returned otherwise null is returned. (Note that the getInstance method with a Constructor type parameter is not implemented, this is because we have not determined how to support constructors with Spring beans).

The SpringServlet can be used in a web.xml as follows:

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
    <listener>
        <description>Spring listener that initializes the ApplicationContext in ServletContext</description>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>   
    <servlet>
        <servlet-name>Jersey Spring</servlet-name>
        <servlet-class>test.spring.SpringServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Spring</servlet-name>
        <url-pattern>/\*</url-pattern>
    </servlet-mapping>
</web-app>

Then i can create two root resource classes. The first, SingletonResource, uses the Jersey supplied singleton life-cycle:

@Path("singleton")
@Singleton
public class SingletonResource {
   
    private String name;
   
    private int uses = 0;
   
    private synchronized int getCount() {
        return ++uses;
    }
   
    public SingletonResource() {
        name = "unset";
    }

    public String getName() {
        return name;
    }
   
    public void setName(String name) {
        this.name = name;
    }
   
    @GET
    @ProduceMime("text/plain")
    public String getDescription() {
        return "Name: " + getName() + ", Uses: " + Integer.toString(getCount());
    }
}

and the second, PerRequestResource, uses the per-request life-cycle:

@Path("request")
public class PerRequestResource {
   
    private String name;
   
    private int uses = 0;
   
    private synchronized int getCount() {
        return ++uses;
    }
   
    public PerRequestResource() {
        name = "unset";
    }

    public String getName() {
        return name;
    }
   
    public void setName(String name) {
        this.name = name;
    }
   
    @GET
    @ProduceMime("text/plain")
    public String getDescription() {
        return "Name: " + getName() + ", Uses: " + Integer.toString(getCount());
    }
}

The root resources are almost identical except that the former is annotated with @Singleton, but they should return different output (as shown later).

Then i can create the Spring applicationContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans">
    <bean id="bean1" scope="singleton" class="test.spring.SingletonResource">
        <property name="name" value="Mr. Singleton Bean"/>
    </bean>
    <bean id="bean2" scope="prototype" class="test.spring.PerRequestResource">
        <property name="name" value="Mr. PerRequest Bean"/>
    </bean>
</beans>

Where the root resource classes are registered (with the appropriate scope) and a property is specified for each.

Then i can deploy and test using curl (the '>' character is my shell prompt): 

> curl http://localhost:8080/SpringResourceWebApp/singleton ; echo
Name: Mr. Singleton Bean, Uses: 1
> curl http://localhost:8080/SpringResourceWebApp/request ; echo
Name: Mr. PerRequest Bean, Uses: 1
>
> curl http://localhost:8080/SpringResourceWebApp/singleton ; echo
Name: Mr. Singleton Bean, Uses: 2
> curl http://localhost:8080/SpringResourceWebApp/request ; echo
Name: Mr. PerRequest Bean, Uses: 1
>
> curl http://localhost:8080/SpringResourceWebApp/singleton ; echo
Name: Mr. Singleton Bean, Uses: 3
> curl http://localhost:8080/SpringResourceWebApp/request ; echo
Name: Mr. PerRequest Bean, Uses: 1

Notice that the response to the URI containing the 'singleton' path segment returns the 'Mr. Singleton Bean' property as specified in the applicationContext.xml, and similarly the URI containing the 'request' path segment returns the 'Mr. PerRequest Bean' property. Also notice that the Uses value for the singleton-based URI increments for each request where as the one for the request-based URI does not. It works!

Having to edit  applicationContext.xml is something i would prefer to avoid and instead utilize some Spring specific annotations. The ComponentProvider interface could probably do with some tweaks to make things more efficient for per-request life-cycle. But in general it seems to work reasonably well and i am sure the same concepts for Spring integration would apply to Guice.

Friday Dec 21, 2007

Jersey using Grizzly 1.7

One might be laboring under the misconception that Grizzly should be hibernating at this time, but no, Grizzly 1.7 is wide awake. Just updated Jersey to use 1.7, and the 0.5 release will use this version.

Next up... try to figure out how to connect Grizzly to Jersey for 100 (Continue) and Comet support. I believe it should be possible, although the mechanism to do this in Java using anonymous inner classes might not be pretty, much better in Scala :-)

I also wonder if it may be possible to utilize Grizzly's UDP support (how about someone getting reliable Multicast integrated into Grizzly?) to get Jersey working over UDP i know that is not HTTP on the wire but it could be interesting to experiment with granular publish/subscribe like what the Microsoft's RESTful robotics framework does.

Wednesday Dec 12, 2007

Dynamic WADL generation in Jersey

Current stable versions of Jersey require an awkward tool time step to identify the set of root resource classes and then generate a WADL document, from that set, that is accessible from the Web application.

For the next release, 0.5, everything will be dynamic. And we have just finished adding dynamic WADL support to the trunk. So the same URI, <base URI>/application.wadl,  to access the WADL of the Web application is supported but no tool-time configuration is required. There is a runtime requirement that the JAXB jars be in the class path, if not then WADL support is turned off but the Web application will still work.

In addition there is experimental support for GETing the WADL of a specific resource. At deployment the runtime only knows about root resource classes but a Web application can dynamically return other resources. This allows a client to GET WADL for such resources enabling the client to "WADL" through the resources :-) I like this approach, it fits more closely with the view of WADL as hypermedia and using WADL in a much more loosely coupled way rather than a way to describe the whole Web application.

Currently WADL is obtained by a client performing a GET request on a URI and setting the Accept header to include the media type "application/vnd.sun.wadl+xml". I am not sure i like this approach as it can interfere with application specific GET methods (it is implemented so application specific GET methods take priority, thus WADL may not always be returned).



Two other approaches may be more appropriate:

  1. Return WADL as the representation of HTTP OPTIONS response. The OPTIONS method is automatically supported to return the allowed HTTP methods in the Allow response header.
  2. Return WADL using a sub-resource, say "/application.wadl".
Approach 1 seems kind of natural and is the least intrusive but i am not sure how widely supported OPTIONS is by clients. Approach 2 is less intrusive that the currently implemented approach but it does require the client to know something specific to construct the WADL URI.

Friday Sep 21, 2007

Ohloh API

I noticed that Ohloh has REST-based API to GET representations in XML. Looks like a great start. The URI structure looks good and you are presented with URIs like the following:

http://www.ohloh.net/projects/{project_id}/factoids/{factoid_id}.xml 
http://www.ohloh.net/projects/{project_id}/factoids/{factoid_id}.xml
http://www.ohloh.net/projects/{project_id}/factoids.xml

But, from the perspective of a consumer, this API could be improved:

  1. The <response><status>...</status> fragment at the beginning of a representation is redundant and forces the client to unnecessarily process additional XML. Instead document the HTTP status codes. The HTTP status code 200 (OK) is sufficient for signaling success so rather than the root element being <response> it could be <projects> when getting a collection of projects and <project> when getting a project. The HTTP status code 400 (Bad Request) is sufficient for signaling a client error with an appropriate machine processable representation detailing the error.
  2. URIs are not present in the representations. This forces the client to construct URIs from identifiers in the representation, for example <project><id>1</id>...</project>, which increases the coupling between the client and server and makes it harder for the server to evolve the URIs (if need be) without breaking existing clients.

Thursday Sep 20, 2007

More philosophical content negotiation

In a previous blog entry i showed a way to support platonic and distinct URIs (with a redirection twist). An alternative way to do this (motivated by a comment from Patrick Mueller) is to use a URI template variable that captures the suffix corresponding to the media type, do some initialization work in a constructor and then use one HTTP method, as follows:

@UriTemplate("/helloworld{media}")
public class HelloWorldResource {
    static final MediaType XML = new MediaType("application/xml");
    static final MediaType JSON = new MediaType("application/json");
    static final List<MediaType> acceptableList = Arrays.asList(XML, JSON);
   
    @HttpContext
    HttpContextAccess context;

    @HttpContext
    UriInfo uriInfo;
   
    MediaType contentType;
   
    public HelloWorldResource(@UriParam("media") String media) {
        if (media.equals(".xml")) {
            contentType = XML;
        } else if (media.equals(".json")) {
            contentType = JSON;
        } else if (media.length() == 0) {
            contentType = null;
        } else {
            throw new NotFoundException();           
        }
    }
   
    @HttpMethod
    @ProduceMime({"application/xml", "application/json"})
    public Response get() {
        if (contentType == XML) {
            contentType = context.getHttpRequestContext().
                getAcceptableMediaType(Arrays.asList(XML));           
        } else if (contentType == JSON) {
            contentType = context.getHttpRequestContext().
                getAcceptableMediaType(Arrays.asList(JSON));           
        } else if (contentType == null) {
            contentType = context.getHttpRequestContext().
                getAcceptableMediaType(acceptableList);           
        }

        if (contentType == null) {
            throw new WebApplicationException(Response.Builder.
                    noContent().status(406).build());
        }
       
        return Response.Builder.representation(contentType.toString(),
                contentType).build();
    }

(I am cheating a bit by not returning actual XML or JSON content as i am too lazy to put some JAXB code together.)  

In this example the platonic URI returns a representation instead of redirecting. To be correct it is necessary to check what is acceptable for distinct URIs and return a Not Acceptable (406) response, for example, if the distinct URI is "/helloworld.xml" but the requested acceptable content is "application/json".

There is still a lot of boiler plate in the application code but the way this code is structured indicates that it should be possible to push such things into the runtime.

Wednesday Sep 19, 2007

Philosophical content negotiation

Arun recently presented an example of how to use JAXB for both XML and JSON representations. In the example Arun uses distinct URIs to access the XML and JSON representations, "/helloworld/xml" and "helloworld/json" respectively.

I thought it would be use to connect things together with a bit of light RESTful philosophy.

Jersey can be used to develop a resource that has one URI to serve XML and JSON representations with content negotiation (see Jakub's example). That URI is referred to as the 'platonic' URI (see my previous blog entries here and here on the subject).

Jersey can also be used to develop a resource that has a platonic URI and distinct URIs. The GET request to the platonic URI will redirect, based on the most acceptable media type, to the distinct URI and a GET request to the distinct URIs can refer back to the platonic URI using the "Content-Location" header.

Below is an example of such a resource that returns XML and JSON representations: 

@UriTemplate("/helloworld")
public class HelloWorldResource {
    static final MediaType XML = new MediaType("application/xml");
    static final MediaType JSON = new MediaType("application/json");
    static final List<MediaType> acceptableList = Arrays.asList(XML, JSON);
   
    @HttpContext
    HttpContextAccess context;

    @HttpContext
    UriInfo uriInfo;
   
    @HttpMethod
    @ProduceMime({"application/xml", "application/json"})
    public Response get() {
        MediaType contentType = context.getHttpRequestContext().
                getAcceptableMediaType(acceptableList);

        UriBuilder ub = uriInfo.getBuilder();
        if (contentType.equals(XML)) {
            ub.path("xml");
        } else {
            ub.path("json");
        }
        URI distinctUri = ub.build();
       
        return Response.Builder.temporaryRedirect(distinctUri).build();
    }

    @HttpMethod
    @ProduceMime("application/xml")
    @UriTemplate("/xml")
    public Response getXML() {
        URI platonicUri = uriInfo.getBaseBuilder().path(this.getClass()).
                build();
               
        return Response.Builder.
                representation("<content>distinct XML</content>").
                contentLocation(platonicUri).
                build();
    }

    @HttpMethod
    @ProduceMime("application/json")
    @UriTemplate("/json")
    public Response getJSON() {
        URI platonicUri = uriInfo.getBaseBuilder().path(this.getClass()).
                build();
               
        return Response.Builder.
                representation("{'content': 'distinct JSON'}").
                contentLocation(platonicUri).
                build();
    }
}

This highlights nicely the building of URIs and responses. I deliberately wrote one method to support the GET request to the platonic URI for testing out the API. I think it shows it requires some improvement as such information could be easily calculated and returned by the runtime without having to create a list of media type. However, one could always do the following instead:

    @HttpMethod 
    @ProduceMime("application/xml")
    public Response getPlatonicXML() {
        URI distinctUri = uriInfo.getBuilder().path("xml").build();
       
        return Response.Builder.temporaryRedirect(distinctUri).build();
    }
   
    @HttpMethod
    @ProduceMime("application/json")
    public Response getPlatonicJSON() {
        URI distinctUri = uriInfo.getBuilder().path("json").build();
       
        return Response.Builder.temporaryRedirect(distinctUri).build();
    }

My guess is it should be possible to support a platonic mapping to such methods automatically by the runtime analyzing the patterns of HTTP methods declared on the Java class. That way developers do not have to explicitly support such functionality.

Wednesday Sep 12, 2007

NetBeans Jersey Tooling on the goggle box

Two videos showing how to build/deploy and test/invoke a RESTful Web service facade over a database using NetBeans and Jersey.

Monday Jul 16, 2007

BBC Web API (beta)

Recently i took a look a the BBC Web API as i was curious to see how "webby" and RESTful or not it was.

In the RESTful Web services book Leonard and Sam, in section 1.5, provide some descriptions of competing Web service architectures. One architecture described (in section 1.5.3) is the REST-RPC hybrid architecture and although the documentation for the API makes no claims whether it be RESTful or RCPful or not i think the API fits firmly into the REST-RPC hybrid category.

The following is the URI used to GET the information of the BBC One channel:

http://www0.rdthdo.bbc.co.uk/cgi-perl/api/query.pl?method=bbc.channel.getInfo&channel_id=BBCOne&format=simple

Note the scoping information in the URI, as the query parameter method, but also the method information in the URI too using the same query parameter:

method=bbc.channel.getInfo.

One way to find out that something is not quite as RESTful as it can be is lop off all the query parameters and perform a GET on the URI and see if it results in an error. If an error occurs it can be an indication of an RPC-based architecture that is focused on URIs as endpoints.

Since the BBC Web API is read only it can be considered RESTful. But, as soon one moves to a read/write API (for example the BBC could offer a way for a user to annotate programs or post/delete comments) then things can start to get problematic (like using GET for deletion).

Furthermore, IMHO, URIs for REST-RPC hybrid architecture are fairly ugly. They tend to become cleaner and more intuitive if the scoping information is placed in the URI path, and therefore cooler. In fact it is very easy to implement the BBC Web API using the JSR 311 API and turn it into a cooler more RESTful Web service.

Consider the channel package that has three methods:

  1. bbc.channel.getInfo
  2. bbc.channel.getLocations
  3. bbc.channel.list

Instead the following URI paths could be used (respectively):

  1. channel/{id} 
  2. channel/{id}/locations
  3. channel 

and an example URI could be:

http://www0.rdthdo.bbc.co.uk/api/channels/BBCOne?format=simple

Notice that the method and channel_id parameters are no longer required. In JSR 311 we could write the following resource to support channels:

@UriTemplate("channel")
class ChannelResource {
    @HttpMethod
    Channels getList(@QueryParam("format") String format) { ... }

    @UriTemplate("{id}")
    @HttpMethod
    Channel getInfo(@QueryParam("format") String format,
        @UriParam("id"} int channelId) { ... }

    @UriTemplate("{id}/locations")
    @HttpMethod
    Locations getLocations(@QueryParam("format") String format,
        @UriParam("id"} int channelId) { ... }
}

assuming that Channels/Channel/Locations types are serializable by the 311 runtime. The same pattern can apply for genre, group and programme packages. The resource could be further improved by removing the query parameter format and instead using content types:

@UriTemplate("channel")
class ChannelResource {
@ProduceMime("application/simpletv+xml")
    @HttpMethod
    Channels getList() { ... }

         @ProduceMime("application/tvanytime+xml")
@HttpMethod
Channels getList() { ... }

...

     }

The 311 expert group is also considering support for the following:

@UriTemplate("channel")
@ProduceMime(["application/simpletv+xml", 
    application/tvanytime+xml"])
class ChannelResource {
    @HttpMethod
    Channels getList() { ... }

...

     }

such that the serializer for Channels is capable of understanding how to serialize in both the simple and TV-Anytime representations, and thereby, reducing the duplication of HTTP methods. While on the topic of the representations... unfortunately the API is missing an important REST constraint, which is "hypermedia is the engine of application state" or more concisely "connectedness", but to get into that detail requires another blog entry.

Wednesday Jun 20, 2007

Announcing Jersey

Jersey

Jersey (@ java.net) is the newly created open source JAX-RS (JSR 311) Reference Implementation for building RESTful Web services with plain old Java objects. But, it is also more than the Reference Implementation. Jersey aims to provide additional APIs and extension points (SPIs) so that developers may extend Jersey to suite their needs (read more...).

Why the name Jersey? Well... Jersey is a restful place to spend a couple of days vacation. The Yellow jersey or Maillot jaune (the project logo) is worn by the overall leader in Le Tour. With modesty i would like this to be the leading open source project for developing RESTful Web services using plain old Java objects. Think of it as the yin and yang of REST and hard work required for success.

There is still some more work to do (notably on how to contribute and including JavaDoc in the distributions) but you can download the first binary and source distributions of Jersey 0.1 Early Access (EA) here. See here for getting started.

Jersey 0.1 EA implements version 0.1 of the 311 API. A new stable version Jersey will be released each time the 311 Expert Group releases a new stable version of the API. The aim is to allow users, developers and Expert Group members to experiment with an implementation of the 311 API to see what works, what does not work, propose changes and new prototype features (possibly implemented), and examples (as use-cases). Of course version 1.0 will be a different (and hopefully a very much better) beast to version 0.1 so expect backward incompatible changes as we evolve to 1.0.

If you are interested in tracking progress or getting more involved please join the project.

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