Friday Jun 27, 2008

Services are clients too

RESTful services can also be clients that make RESTful requests to other services. Jersey has a client API and resource classes can use that to efficiently make such requests.

Following on from my last blog entry on injection i can combine injection with a client API to make things a little easier. So i can have a resource class that does the following:

    @Path("/")
    public static class Resource {        
        @GET
        public String get(
                @WebResourceRef("http://localhost:9999/one") AsyncWebResource r1,
                @WebResourceRef("http://localhost:9999/two") AsyncWebResource r2) 
                throws InterruptedException, ExecutionException { 
            Future<String> c1 = r1.path("foo").get(String.class);
            Future<String> c2 = r2.path("bar").get(String.class);
            return c1.get() + c2.get();
        }       
        @GET
        @Path("one/{stuff}")
        public String getOne(@PathParam("stuff") String stuff) { 
            return stuff;
        }       
        @GET
        @Path("two/{stuff}")
        public String getTwo(@PathParam("stuff") String stuff) { 
            return stuff;
        }
    }

The get method has two parameters that are annotated with @WebResourceRef that declares client-based URIs. I wrote an InjectableProvider for that annotation that creates instances of AsyncWebResource or WebResource (see end of this entry for complete source that should work with Jersey 0.8) based on the URI. When that method is invoked it makes two asynchronous requests, to sub-resource methods of the same resource class, and returns the result.

I think this is interesting enough to include support for this in Jersey.

import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.client.AsyncWebResource;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import com.sun.jersey.spi.service.ComponentContext;
import com.sun.jersey.spi.service.ComponentProvider.Scope;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.ext.Provider;

public class Main {
    @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface WebResourceRef {
        String value();
    }

    @Provider
    public static class WebResourceProvider implements InjectableProvider<WebResourceRef, Type> {
        private final Client client;        
        public WebResourceProvider() {
            this.client = Client.create();
        }
        public Scope getScope() { 
            return Scope.PerRequest;
        }

        public Injectable getInjectable(ComponentContext cc, final WebResourceRef wrr, Type t) {
            if (!(t instanceof Class)) return null;
            Class c = (Class)t;            
            if (WebResource.class == t) {
                return new Injectable<WebResource>() {
                    public WebResource getValue(HttpContext c) {
                        return client.resource(wrr.value());
                    }                    
                };                            
            } else if (AsyncWebResource.class == t) {
                return new Injectable<AsyncWebResource>() {
                    public AsyncWebResource getValue(HttpContext c) {
                        return client.asyncResource(wrr.value());
                    }
                };                                            
            } else {
                return null;
            }            
        }
    }
    @Path("/")
    public static class Resource {        
        @GET
        public String get(
                @WebResourceRef("http://localhost:9999/one") AsyncWebResource r1,
                @WebResourceRef("http://localhost:9999/two") AsyncWebResource r2) 
                throws InterruptedException, ExecutionException { 
            Future<String> c1 = r1.path("foo").get(String.class);
            Future<String> c2 = r2.path("bar").get(String.class);
            
            return c1.get() + c2.get();
        }
        @GET
        @Path("one/{stuff}")
        public String getOne(@PathParam("stuff") String stuff) { 
            return stuff;
        }        
        @GET
        @Path("two/{stuff}")
        public String getTwo(@PathParam("stuff") String stuff) { 
            return stuff;
        }
    }    
    public static void main(String[] args) throws Exception {        
        SelectorThread st = GrizzlyServerFactory.create("http://localhost:9999/");        
        try {
            Client c = Client.create();
            WebResource r = c.resource("http://localhost:9999/");
            String s = r.get(String.class);
            System.out.println(s);
        } catch (UniformInterfaceException e) {
            System.out.println(e.getResponse().getStatus());
            e.printStackTrace();
        } finally {
            st.stopEndpoint();
            System.exit(0);        
        }
    }
}

Tuesday Oct 30, 2007

Testing TLC

Last week i spent some time giving some needed TLC to Jersey's unit test infrastructure.

There was some really dreadful crufty code hacked together (the kind that you write in 5 minutes just to try something out and and you say i will change it later but somehow you never do and it gets used more and you spend silly amounts of time dealing with it) that was used to enable the unit testing of resource classes in-memory without depending on a HTTP container.

In addition there were unit tests using the light weight HTTP server, which used crufty HttpURLConnection code.

So there were two different sets of crufty code test infrastructure essentially doing the same thing, namely making HTTP-based client requests. Finally i got so fed up with this state of affairs i decided to do something about it.

The result is a simple RESTful client side API for making HTTP requests and processing HTTP responses that reuses classes and concepts from the JAX-RS API and Jersey. See here for a unit test that tests accept processing in memory, and see here for a unit test that tests matrix parameters using the Light Weight HTTP server. Notice that once a ResourceProxy has been obtained the code uses the same API.

Now that there is a common client-side API for making HTTP requests the next step is to further abstract out the configuration/deployment mechanism such that we can have one way of defining a unit test for testing one or more resource classes and be able to deploy them in any container (in-memory, LW HTTP server, or servlet).

Currently this client side API is part of the Jersey implementation at this location (and not the Jersey API). It was very instructive (and also very tedious!) to convert all the relevant unit tests over to using this API as it resulted in many little improvements and ideas to make further improvements (so it can be considered somewhat battle tested in the context of being used as a testing infrastructure API). Once those further improvements have been applied i plan to document it and move it over to the Jersey API for general use. But, if you are feeling brave you can still use it in the latest release. If you do let me know what you think.

While on the subject of testing TLC i watched a great interview on the Scoble show with ZFS inventors Jeff Bonwick and Bill Moore. At some point during the interview Jeff mentions that the testing code coverage of ZFS is over 90% (i cannot recall the exact number, it could be close to 99%) and it allowed them to make major changes to the ZFS code base without the fear of not knowing they had broken something. That point really resonated with me as i have found the Jersey unit tests have given me the confidence to make major internal changes (soon we will need to do some major refactoring of the URI dispatching). However, the code coverage of the Jersey unit tests is not at 99%. A Jersey release is built, using Hudson, every time the source code changes and that release is tested by running the unit tests. Emma code coverage is integrated and below is the trend graph generated by Hudson:

 

As you can see some more TLC is required to increase the code coverage. I have been told that the code coverage is not bad for a newish project, but still it makes me very nervous that at least 40% of methods are not being tested. Even if Jersey is early access we should strive for the highest quality possible for stable releases.

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