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);        
        }
    }
}
Comments:

[Trackback] Bookmarked your post over at Blog Bookmarker.com!

Posted by stuff on June 27, 2008 at 11:56 PM CEST #

i'm very interested in Jersey, but how to make Jersey works in OSGi container?

Posted by gembin on July 05, 2008 at 08:25 AM CEST #

Hi Gembin,

[I have just got back from a 2 week holiday]

We need to OSGi'ify Jersey as part of the integration work for Glassfish v3. At the moment OSGI related information is not declared in the manifest files.

Posted by Paul Sandoz on July 21, 2008 at 03:59 AM CEST #

when will the integration work be finished?

Posted by gembin on July 22, 2008 at 02:58 AM CEST #

Not exactly sure, but it needs to get it done by 1.0, which is currently scheduled for the end of September.

In the interim i am wondering if the following may help you:

http://wiki.ops4j.org/confluence/display/ops4j/Pax+URL+-+wrap

which i found from reading:

http://netzooid.com/blog/2008/07/02/osgi-is-a-pita-bundles/
http://netzooid.com/blog/2008/07/03/mixing-jars-and-osgi-bundles-with-maven/

Posted by Paul Sandoz on July 22, 2008 at 03:45 AM CEST #

This looks interesting to me. Is this an alternative to using client stubs? How would I reproduce your services in NB 6.5?

Posted by Jeff Rubinoff on August 07, 2008 at 05:37 AM CEST #

The client API was partly designed to avoid the need for client stub generation.

What is presented here is not part of the Jersey distribution so if you want to do this you have to copy the code, which should work if using Jersey 0.8.

If people think this is useful enough i can put it into the Jersey code base.

Paul.

Posted by Paul Sandoz on August 07, 2008 at 10:54 AM CEST #

Hi Paul,

I´ve implemented the example you gave above and it works fine.

However, we work with various environments and therefore i can only compute the url of the service i asynchronicaly use at runtime.

Unfortunately the compiler does not allow the usage of computed values in annotations and gives the following error: "The value for annotation attribute WebSourceRef.value must be a constant expression".

Do you have any idea how i solve this problem?

Posted by Jeffrey on August 27, 2008 at 04:26 PM CEST #

Hi Jeffery,

Annotation values are fixed at compile time.

You can use the client API directly if you wish. In the annotated @GET method you will need to create a Client and a WebResource or AsyncWebResource.

Another solution is to resort to that age old computing trick of abstraction and create logical name for URIs and then have a runtime mapping from logical name to URI. The logical name can be declared in the annotation.

Notice that the URI of the WebResource is not fixed. It is possible to add further path segments (or query parameters, using the uri method, but i need to improve this).

Paul.

Posted by Paul Sandoz on August 28, 2008 at 12:49 AM CEST #

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