Friday Aug 28, 2009

Guice 2.0 is almost there, but not quite

Guice 2.0 is a clean and well-thought-out framework for modularization and dependency injection. It is really good, but it could be really really good with a couple of tweaks.

James Strachan created GuiceyFruit in an effort to move Guice from "really good" to "really really good", or say from 95% there to 99% there. Some patches from GuiceFruit got accepted in Guice 2.0 but some are still pending so GuiceyFruit maintains a patched version of Guice 2.0 for it's own needs.

Jersey depends on GuiceyFruit and the patched Guice 2.0 for it's Guice support (although it is possible to use "native" Guice if you are not using any GuiceyFruit features).

Currently with Guice 2.0 i cannot integrate support for the binding of JAX-RS types and annotations. I would like to be able to do the the following with Guice/Jersey/JAX-RS:

@RequestScoped
@Encoded
public class MyResource {
  private final String q;

  @Inject
  public class MyResource(@DefaultValue("foo") @QueryParam("q") String q) {
    this.q = q;
  }

}

But, currently as far as i am aware, it is not possible for two reasons:

  1. a provider binding does not have any context to what it is providing to; and
  2. an annotation that is not meta-annotated with @BindingAnnotation cannot be utilized as a binding annotation.

A good example of the complexity that results due to lack of support for 1) is presented in the section on Custom Injections of the Guice User's Guide. This section presents an example of how to support injection of a org.apache,log4j.Logger instance with fields annotated with the custom annotation @InjectLogger. The logger instance is initiated with the declaring class of the field.

The developer is required to implement a TypeListener that iterates through the fields of the class to find an appropriate field to inject a Logger, and a MembersInjector that injects the instance of the Logger onto the appropriate field.

While these interfaces are useful they are over-complex for such a use-case and the developer cannot use @Inject with Logger for constructor, method and field injection.

The injection of Logger can be reduced to a provider method of a module if Guice could support the injection of InjectionPoint:

 @Provides
 public Logger providerLogger(InjectionPoint ip) {
   return Logger.getLogger(ip.getMember().getDeclaringClass());
 }

With respect to the JAX-RS example a provider of the String type annotated with @QueryParam requires access to the annotations declared on the constructor parameter and the annotations declared on the defining class (it does not appear possible to get access to the former with InjectionPoint so that would require some tweaks as well).

Moving on to reason 2). If Guice supported the following binding in a module:

declareAnnotationAsBindingAnnotation(QueryParam.class);
bind(String.class)
  .annotatedWith(QueryParam.class)
  .toProvider(QueryParamProvider.class);

then it would be possible for Jersey to supply a Jersey-specific Guice module that supported all the Jersey and JAX-RS annotations in addition to enabling developers to easily add their own providers for such annotations.

If Guice could do that it would be 99% there IMHO and would be a really really good dependency injection framework.

Thursday Aug 07, 2008

Parameter beans

In the JAX-RS EG we modified the target of the @Query/Path/MatrixParam annotations so that fields can be annotated on resource classes or on Java beans in general. I was skeptical at first that this would be a good idea (mainly around the life-cycle aspects of resource classes), but Bill Burke convinced me and the EG it would be a good idea. For example, consider a large set of query parameters that keep repeating, having to duplicate that set as method parameters is not ideal.

I finally started experimenting with the idea of supporting such beans in Jersey. To my pleasant surprise there proved to be a rather simple and elegant solution: treat such beans as resource classes.

An InjectableProvider can operate on an annotation, say ResourceParam as follows:

@Provider
public static class ResourceParamInjector implements 
        InjectableProvider<ResourceParam, Type> {
    @Context ResourceContext rc;        

    public Scope getScope() {
        return Scope.PerRequest;
    }

    public Injectable getInjectable(ComponentContext ic, 
            ResourceParam a, Type t) {
        if (!(t instanceof Class)) return null;           
        final Class c = (Class)t;
        if (c.isPrimitive()) return null;

        return new Injectable<Object>() {
            public Object getValue(HttpContext context) {
                return rc.getResource(c);
            }
        };
   }       
}

Essentially the injected ResourceContext instance is created to obtain the resource class instance.

A resource class could look like this:

@Path("/")
public static class Resource {
    public static class QueryBean {
        @DefaultValue("abcd") @QueryParam("foo") String foo;
        @DefaultValue("9999") @QueryParam("bar") int bar;
    }       
    @GET
    public String get(@ResourceParam QueryBean qb) {
        return qb.foo + qb.bar;
    }
}

The fields foo and bar on the QueryBean class behave in the same manner as if they were parameters on the method get or fields on the class Resource.

A side effect of this approach is that the QueryBean class has the same life-cycle rules of a resource class, thus one could annotate it with say @Singleton, which i am not sure is very useful (since @\*Param annotations are not allowed on fields of singletons).

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

Thursday Jun 26, 2008

@EJB injection

Jersey has a simple injection provider SPI for injecting instances for annotated fields, method/constructor parameters or setter methods. So it is possible to plug-in your own injection functionality.

Out of the box there is currently no EE-based functionality supported in Jersey but using the injection provider SPI it is very easy to have basic support for @EJB in one small class.

If you have the following resource:

@Path("foo")
public class FooResource {
    @EJB private FooSessionRemote fooSessionRemoteBean;
    
    @GET
    @ProduceMime("text/plain")
    public String get() throws Exception {
        return fooSessionRemoteBean.fooMethod();
    }
}

then the field fooSessionRemoteBean should get injected with an instance of the remote EJB interface.

The following provider class supports this behaviour:

@Provider
public class EJBProvider implements InjectableProvider<EJB, Type> {

    public Scope getScope() {
        return Scope.Singleton;
    }

    public Injectable getInjectable(ComponentContext cc, EJB ejb, Type t) {
        if (!(t instanceof Class)) return null;
        
        try {
            Class c = (Class)t;        
            Context ic = new InitialContext();

            final Object o = ic.lookup(c.getName());
            
            return new Injectable<Object>() {
                public Object getValue(HttpContext c) {
                    return o;
                }                    
            };            
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

The EJB reference is looked up using JNDI then an Injectable instance is returned with that reference. This is efficient for per-request resources as the JNDI look up is only performed once. Since the above class is annotated with @Provider it will get picked up and treated like any other provider.

Obviously this could do with some polish to fully implement @EJB injection functionality but i think this example is instructive how one could support other annotations.

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