Help

In the comments thread for this post, Bill got me thinking again about a problem that's been bugging me for a while.

The problem is that there are certain kinds of dependent objects (in the sense of the Web Beans @Dependent scope) that need to know something about the object or injection point into which they are injected in order to be able to do what they do. For example:

  • the log category for a Logger depends upon the class of the object that owns it
  • injection of a HTTP parameter or header value depends upon what parameter or header name was specified at the injection point
  • injection of the result of an EL expression evaluation depends upon the expression that was specified at the injection point

The solution I've come up with is to allow injection of a special metadata object into any injected dependent object.

(Note that this functionality is specifically for @Dependent scope Web Beans - objects with normal scopes are shared between many clients and many injection points, so their behavior cannot depend upon anything relating to the client.)

Let's look at an example:

class LogFactory {

   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }

}

This clever little producer method lets you inject a JDK Logger with the right log category. Instead of typing:

Logger log = Logger.getLogger(MyClass.class.getName());

You can write:

@Current Logger log;

which is shorter, and less vulnerable to refactoring problems.

Not convinced? OK then, let's see a second example...

To inject HTTP parameters, we need to define a binding type:

@BindingType
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @annotation HttpParam {
   @NonBinding public String value();
}

We would use this binding type at injection points as follows:

@HttpParam("username") String username;
@HttpParam("password") String password;

The following producer method does the work:

class HttpParams

   @Produces @HttpParam("")
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      return request.getParameter(ip.getAnnotation(HttpParam.class).value());
   }

}

I suppose this stuff is mainly useful for framework-type development, but it might also have other applications.

So how hard is it to add this feature to Web Beans? Not hard at all! The Web Beans specification would define the following API:

public interface InjectionPoint { 
   public Type getType();
   public Set<Annotation> getBindingTypes();
   public Object getInstance(); 
   public Bean<?> getBean(); 
   public Member getMember(): 
   public <T extends Annotation> T getAnnotation(Class<T> annotation); 
   public Set<T extends Annotation> getAnnotations(); 
}

And the Web Bean manager would be required to provide a built-in Web Bean that implements this interface.

What do you think?

13 comments:
 
12. Nov 2008, 09:08 CET | Link
Stefan Schubert
This is some really evil magic you're suggesting here.

Well ... I for myself like it ;-)

When it comes to start-up and runtime performance concerning I'm a litte mit more suspicious. But I'm sure you guys know that this is an important challenge: What happens if a tomcat app with a million lines of code is web-beaned?

Thanks for your efforts to make web development easier, Gavin!

Stefan
ReplyQuote
 
12. Nov 2008, 10:10 CET | Link

As Stefan says, evil but likable evil magic ;)

Is that empty string inside @HttpParam just some ugly annotation syntax we need to live with or does it have some meaning ? i.e. how would the container know that it should not do injection work for the getParamValue but only for the @HttpParam(morethanemptystring) fields ?

 

--max

 
12. Nov 2008, 15:26 CET | Link
Max Andersen wrote on Nov 12, 2008 10:10:
Is that empty string inside @HttpParam just some ugly annotation syntax we need to live with or does it have some meaning ? i.e. how would the container know that it should not do injection work for the getParamValue but only for the @HttpParam(morethanemptystring) fields?

The value() member is annotated @NonBinding, so it is ignored for the purposes of Web Beans injection resolution. However, it doesn't have a default value, and unfortunately annotation members can't be null, so we need to specify something. Yeah, a bit ugly, but no uglier than stuff like: String value() default "", which we see all over the place.

By the way, another option would be to default to the name of the field if no value is explicitly specified. For example, let's write in a default value:

@BindingType
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @annotation HttpParam {
   @NonBinding public String value() default "";
}

And change the producer method like so:

class HttpParams

   @Produces @HttpParam
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      String name=ip.getAnnotation(HttpParam.class).value();
      if ("".equals(name)) name = ip.getMember().getName();
      return request.getParameter(name);
   }

}

And now we can do:

@HttpParam String username;
@HttpParam String password;

:-)

12. Nov 2008, 16:12 CET | Link
Clint Popetz | cpopetz(AT)gmail.com

I think it's a very useful idiom, and I don't think it's limited to framework concerns. It makes it possible to have dependent scoped objects that cooperate instead of just being used. I see code like this pretty frequently:

@In Service service;

...
{ 
  service.startUsingMe(this);
  ...
}

because the service has something that is not only context dependent, but host dependent.

And yeah, any magic is dangerous. (c.f. perl(1)) But we're all adults here, and I don't think it would lead to unmaintainable code, which is my primary metric for things like this.

 
12. Nov 2008, 18:29 CET | Link

You win me over at @Current Logger log;

I'm all for it, Gavin.

 
13. Nov 2008, 09:58 CET | Link
Michael Brackx

Fist of all, JAX-RS should be implementable with Web Beans, so something is needed.

(Note that this functionality is specifically for @Dependent scope Web Beans - objects with normal scopes are shared between many clients and many injection points, so their behavior cannot depend upon anything relating to the client.)

Why only for @Dependent scope Web Beans? The factory/producer must know something about the injection point, but the object can be independent/reusable. Also with normal injection with binding annotations which object is injected is dependent on the injection point

For me

@Pill("red") Pill pill1;
@Pill("blue") Pill pill2;

could be similar to

@PillRed Pill pill1;
@PillBlue Pill pill2;
 
13. Nov 2008, 12:26 CET | Link
Stephane Epardaud

In the case of JAX-RS the @HttpParam name defaults to the name of the injected parameter:

public void foo(@HttpParam bar);

Would inject the value of the bar HTTP parameter. I didn't see at first glance how method parameters where abstracted in the InjectionPoint interface. Are they bean members too or is that limited to methods and fields?

 
13. Nov 2008, 14:21 CET | Link
Michael Brackx wrote on Nov 13, 2008 09:58:
Why only for @Dependent scope Web Beans?

Because objects with normal scopes are shared b/w many clients and many injection points. Their behavior should not depend upon any particular one of those injection points.

 
13. Nov 2008, 14:25 CET | Link
Stephane Epardaud wrote on Nov 13, 2008 12:26:
In the case of JAX-RS the @HttpParam name defaults to the name of the injected parameter:

That's impossible to implement. The Java compiler doesn't store the names of parameters in the .class file.

I didn't see at first glance how method parameters where abstracted in the InjectionPoint interface. Are they bean members too or is that limited to methods and fields?

The Member is the method. The annotations are annotations of the parameter.

 
13. Nov 2008, 16:58 CET | Link
Michael Brackx
@Pill("red") Pill pill;

should be able to give me the session scoped red pill in the same way as

@PillRed Pill pill;

could.

The pill itself is not dependent on the injection point, no more than it is with a binding annotation. The annotation instance dependent injection could be generalized to other scopes, not the instance/bean/member dependency.

 
13. Nov 2008, 17:13 CET | Link

Already supported. See here.

 
17. Nov 2008, 22:20 CET | Link

Great, I was waiting for it! Do you think it will be possible for WebBeans implementation to eat WebBeans own dog food and do the @EJB injector has a Producer with InjectionPoint?

This stuff really makes WebBeans providers packed in a jar accessible for all. WebBeans Modules

 
19. Nov 2008, 21:34 CET | Link
Fred wrote on Nov 17, 2008 22:20:
Great, I was waiting for it! Do you think it will be possible for WebBeans implementation to eat WebBeans own dog food and do the @EJB injector has a Producer with InjectionPoint?

It's a possibility that we're discussing with some folks from the EE group.

Post Comment