users@jersey.java.net

Re: [Jersey] Unifying injection

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Fri, 23 May 2008 12:52:15 +0200

Paul Sandoz wrote:
> Next steps:
>
> - plug in parameter-based injectable providers for parameters of
> methods/constructors. Thus moving fully over to the unified model.

Done (with some minor changes to the Injectable interface).


> Only use thread local proxied injectable providers for non-per-request
> resources (performance enhancement).
>

TODO.


> - allow use of @Provider on InjectableProvider. Thus it is no longer
> necessary to register via WebApplication. They become components that
> can also be managed by an IoC.
>

Done.

Is is now possible to have constructor/method signatures as follows:

   @GET
   String get(
      @Inject Foo foo,
      @MyAnnotation Thing t,
      @QueryParam("l") Map<String, String> m);

Where @Inject defers to the ComponentProvider.

Where @MyAnnotation is provided by (say) a developer-declared singleton
injectable provider.

Where @QueryParam annotated Map<String, String> is provided by (say) a
developer-declared per-request injectable provider that overrides
support for @QueryParam thus one can support Java types that don't
conform to the basic 311 parameter rules. See below for code that is
used by a unit test in this case.

In this respect i think we have completed our previously enumerated
injection-based goals we set.

Paul.

     @Provider
     public static class QueryParamInjectableProvider implements
             InjectableProvider<QueryParam, Parameter,
PerRequestInjectable<Map<String, String>>> {
         public PerRequestInjectable<Map<String, String>>
getInjectable(InjectableContext ic,
                 QueryParam a, Parameter c) {
             if (Map.class != c.getParameterClass())
                 return null;

             final String name = c.getSourceName();
             return new PerRequestInjectable<Map<String, String>>() {
                 public Map<String, String> getValue(HttpContext c) {
                     String value =
c.getUriInfo().getQueryParameters().getFirst(name);

                     Map<String, String> m = new LinkedHashMap<String,
String>();
                     String[] kvs = value.split(",");
                     for (String kv : kvs) {
                         String[] nv = kv.split("=");
                         m.put(nv[0].trim(), nv[1].trim());
                     }

                     return m;
                 }
             };
         }
     }

     @Path("/")
     public static class MethodInjected {
         @GET
         public String get(@QueryParam("l") Map<String, String> kv)
throws Exception {
             String v = "";
             for (Map.Entry<String, String> e : kv.entrySet())
                 v += e.getKey() + e.getValue();
             return v;
         }
     }

     public void testMethodInjected() throws IOException {
         initiateWebApplication(MethodInjected.class,
QueryParamInjectableProvider.class);

         URI u = UriBuilder.fromPath("").
                         queryParam("l", "1=2, 3=4, 5=6").build();
         assertEquals("123456", resource("/").uri(u).get(String.class));
     }

> - injection meta class to perform injection to avoid use of reflection
> each time, a performance enhancement for per-request resources.
>
> - support injection onto fields of per-request resources for per-request
> injectable providers.
>
> - support bean setter methods.
>
>
> A nice consequence: when completed one should be able to add their own
> injectable providers for @*Param that go beyond the existing rules of
> 311 e.g. using joda date time class. Another possible nice consequence:
> it could make the support of @FormParam easier :-)
>
> Paul.
>
>
>
> wa.addInjectable(new InjectableProvider<PersistenceUnit, Type,
> SingletonInjectable>() {
> public SingletonInjectable<EntityManagerFactory>
> getInjectable(InjectableContext ic, PersistenceUnit pu, Type c) {
> if (!c.equals(EntityManagerFactory.class))
> return null;
>
> // TODO localize error message
> if (!persistenceUnits.containsKey(pu.unitName()))
> throw new ContainerException("Persistence unit '"+
> pu.unitName()+
> "' is not configured as a servlet parameter
> in web.xml");
> String jndiName = persistenceUnits.get(pu.unitName());
> ThreadLocalNamedInvoker<EntityManagerFactory> emfHandler =
> new
> ThreadLocalNamedInvoker<EntityManagerFactory>(jndiName);
> final EntityManagerFactory emf = (EntityManagerFactory)
> Proxy.newProxyInstance(
> EntityManagerFactory.class.getClassLoader(),
> new Class[] {EntityManagerFactory.class },
> emfHandler);
>
> return new SingletonInjectable<EntityManagerFactory>() {
> public EntityManagerFactory getValue() {
> return emf;
> }
> };
> }
> });
>
>
> Paul Sandoz wrote:
>> Hi,
>>
>> Currently the injection support is split between the Injectable
>> abstract class and hard-wired support for the @*Param.
>>
>> There are a number of use-cases we need to support:
>>
>> - pluggable injection on parameters of constructors and methods,
>> so then we can support the inclusion of IoC based parameters as
>> part of the signature.
>>
>> - 311 requirement of using injection with setter methods, for example:
>>
>> @QueryParam("name")
>> public void setName(String name) { ... }
>>
>> @Context
>> public void setUriInfo(UriInfo name) { ... }
>>
>> - 311 requirement for injection of @*Param on fields of per-request
>> resources.
>>
>> - as a consequence this should make it easier to optimize the creation
>> and injection on per-request resources as well as the method
>> invocation on resource methods and sub-resource locators of
>> per-request or non-per-request resources (with the long term view of
>> injectables providing generated byte code "snippets")
>>
>> Such cases force the demand for unifying the pluggable injection
>> model, and abstracting it from where stuff gets injected (field,
>> parameter, or setter method) and constraining what can be injected
>> based on the life-cycle of the resource, the life-cycle of what is
>> injected and the context of injection.
>>
>>
>> More technical details follow....
>>
>> I am proposing that there be an InjectableProvider that provides
>> Injectable instances per the context, annotation and type (same
>> pattern as the ResourceProvider concept). Concrete Injectable
>> instances can be either one of SingletonInjectable, where the
>> injectable value is independent of the request, or
>> PerRequestInjectable, where the injectable value is dependent on the
>> request (see below for proposed classes, i am sure they will change
>> when implementation starts).
>>
>> This approach is a little different to the current mechanism of
>> Injectable, instances of which are essentially singletons. Instead
>> Jersey will analyze the fields, setter, constructor/method parameters
>> of resource classes and loop through the InjectableProvider's until it
>> finds one that returns null. So essentially Jersey will create an
>> artifact of the runtime model to perform specific injection for a
>> resource class (this should speed up per-request life-cycle injection
>> for fields and setters) (such artifacts are already created for
>> constructor and method invocation).
>>
>> For the longer term i envisage a ByteCodeGenerator interface that
>> could be implemented by Injectable implementations. To generate the
>> snippet of byte code related to obtaining the injectable value. In
>> general this would also extend to the actual invocation of resource
>> methods.
>>
>> Paul.
>>
>> // Provide an injectable instance for a particular
>> // annotation and type.
>> interface InjectableProvider<A extends Annotation,
>> I extends Injectable> {
>> enum Context { Field, Parameter, Setter };
>>
>> A getAnnotation();
>>
>> // return null if not supported
>> I<A> getInjectable(Context c, A a, Type t)
>> }
>>
>> abstract class Injectable<A extends Annotation> {
>> public Injectable(A a, Type t) { ... }
>> }
>>
>> class SingletonInjectable<A extends Annotation, T>
>> extends Injectable {
>>
>> public abstract T getValue();
>> }
>>
>> class PerRequestInjectable<A extends Annotation, T>
>> extends Injectable {
>>
>> public abstract T getValue(HttpContext context);
>> }
>>
>

-- 
| ? + ? = To question
----------------\
    Paul Sandoz
         x38109
+33-4-76188109