Dynamic CDI Producer Methods

I’m exploring some of my configuration ideas and ran into a pattern that I recognize from prior experience and wanted to document how to resolve it here.

I started off by implementing things without regard to CDI.  So just programmatic APIs, getters, setters, builders—that sort of thing.

In my case, I have a set of Converters.  A Converter is something that can take a String in and convert it to a different kind of Object.  The Converter also stores the Type that it serves as a converter for.

So I have indexed these Converters in a Map under their Type.  If you have a Type, you can get a Converter and use it to transform a raw configuration value.  Simple.

The Converters come from a META-INF/services/com.foo.bar.Converter entry, so they are effectively chosen by the user.

How could I integrate this subsystem into CDI? That is, instead of making the user get a Type and do a programmatic lookup (anytime you see the word “lookup” you should be suspicious), can I allow her to just ask CDI to inject a properly converted value?

Well, the first thought is: producer methods.  A Converter can produce some kind of object, and the kind of object it can produce is encoded in its Type, so the pseudocode looks like this:

@Produces
@ConfigurationValue // or whatever
public  T produceConvertedValue(final InjectionPoint injectionPoint) {
  final Type type = injectionPoint.getType();
  final Converter converter = getTheRightConverter(type);
  return converter.convert(getTheRightStringValue(injectionPoint));
}

Ah, but producer methods must return a concrete type!  That is, the method above can’t return T: it has to return Integer, or Long, or Double, or whatever the actual type is.

But if our converters are stored as META-INF/services/com.foo.bar.Converter entries, we don’t know when we’re writing this method what the concrete types are!  How are we supposed to write a number of producer methods for a set of Types when we don’t know how many Types there are?

This is one of those patterns that should ring the portable extension bell.  Any time you have some “setup” to do based on user-supplied configuration or information that you don’t have available to you when you are writing code—any time this occurs, you should be thinking portable extension.  As I (previously) and Antoine Sabot-Durand have shown, they are not difficult or esoteric or something that only experts can write.  They are simply how you do things dynamically in CDI—where you do setup work before the container comes up and is locked down and cannot be tweaked further.  This is one of those times.

Let’s think about what we want to do.  We want to first make sure this fledgling configuration framework is available in CDI—i.e. that the thing that houses the Set of Types and their related Converters is available as a CDI bean.  As I said above, I designed things originally so that they have nothing to do with CDI, so no annotations, so that means we’ll have to programmatically add the main Converter housing object (called Configurations, as it happens) as a bean. That’s really easy.

Then we are going to want to get its set of Types somehow and do some work for each of them.  Specifically, for each of them we want to install the equivalent of a producer method that returns objects of that Type.  We also want these producer methods to be able to satisfy injection points like the following:

@Inject
@ConfigurationValue("frobnicationInterval")
private Integer frobnicationInterval;

If we do all this successfully, then the user can just ask for a configuration value of a particular type, and if the String that is the configuration value can be converted to that type, then it will be, and the user will simply get the converted value. If the user changes the META-INF/services/com.foo.bar.Converter entries in some sort of bad way, like by removing an entry for IntegerConverter, then the CDI container will detect this at container startup time. This is all good stuff and very CDI-ish.

So now we can survey our toolbox. We’re in portable extension land, so we should be looking at container lifecycle events and the BeanManager.

The object that houses the Converters is called Configurations in my fledgling framework, so we need to make it be a CDI bean in a particular scope. We can get it to be added to the container by programmatically sticking an ApplicationScoped annotation on it (since ApplicationScoped is a bean-defining annotation):

private final void addConfigurations(@Observes final BeforeBeanDiscovery event) {
  if (event != null) {
    event.addAnnotatedType(Configurations.class, "configurations")
      .add(ApplicationScoped.Literal.INSTANCE);
  }
}

What’s that "configurations" String doing there?  When you add an annotated type, you specify the Class you’re adding, but remember that this will result in a new AnnotatedType object.  You can have many of these per Class (that’s a little mind-bending, but remember you can add and remove annotations programmatically—that’s the reason), so you need to provide a way to talk about the type in particular that you’re adding here.  That takes the form of a String identifier, which here we’ve just made up—we just use "configurations".  You can read what little information there is on this model of things in the meager discussion around CDI-58.  At any rate, we’re just doing a very simple add of a single AnnotatedType here, and then we’re not actually going to use that identifier again, so in some sense it hardly matters.

So here we’ve “made it look like” Configurations was annotated all along with the ApplicationScoped annotation.  Since this is during the BeforeBeanDiscovery event, this means that now as the container starts its work of locating CDI beans, it will pick this one up.  We’ve accomplished our first goal.

The second goal is trickier.  First, what facilities do we have to work with producer methods?

Part of navigating the CDI API landscape is to think about these things:

  • Are you in portable extension land?
  • Are you reacting to some sort of lifecycle event?
  • Are you doing some sort of programmatic bean lookup?

We know we’re in portable extension land.  We also know that we’ll have to do a programmatic bean lookup, because we need to get the Configurations object (that houses our Converters and Types).  Finally, we know we’re going to need to add some producer methods in some way, so this is usually done in reaction to a lifecycle event (we’ll see which one shortly).

Recall that we’re trying to create producer methods dynamically based off a Set of Types obtainable from the Configurations object.  So first, let’s just get that Set of Types.

To do this, we’ll need a Configurations object.  It would be nice to simply ask for one to be injected, but we’re in portable extension land, and that means the container isn’t really up yet.  We’ve pointed it in the direction of the Configurations class (see the code above), so if it were up, we could inject a Configurations object, but, well, we’re out of luck here.  That means programmatic bean lookup, and so that means BeanManager operations.

The BeanManager is the handle you get to the CDI container itself, even while it’s coming up.  Any portable extension can observe a container lifecycle event, and specify a BeanManager-typed second parameter in its observer method.  If it does this, then the BeanManager will be supplied.  Easy.  Armed with a BeanManager, we can call its createInstance() method, and now we have an Instance, which means that now we can use it to select(Configurations.class).  That will give us an Instance, and then we can call its get() method, and we’ll get a CDI-managed Configurations object.

OK, we’ll file that away: we know now how to look up a Configurations object so we can get its Set of Types.  Then, once we have that Set, we have to loop over it and programmatically add producer methods.

If we’re going to programmatically add beans (producer methods are beans), then there’s really only one container lifecycle event that supports that, and that’s AfterBeanDiscovery. This means the container has completed whatever automatic scanning it was set up to do (which may be none), and, in the absence of any portable extension doing anything, is about to start validating things so it can actually start.

In our case, we are going to do something: we’re going to add a bunch of beans!

Let’s start by making our observer method, and in it let’s get a Configurations object:

Now the hard part.

So the AfterBeanDiscovery event would seem to be our savior.  It has the addBean() method, which returns a BeanConfigurator, which, among other things, has a produceWith(Function) method! And the first parameter to that function is an Instance, which could give us any parameters we might otherwise write in a “normal” producer method! So we could just supply an appropriate function, make a few select() calls, and boom, there’s our dynamic producer method!

Alas.

In our case, if we were writing a “normal” producer method, one of the parameters we would need that method to have is an InjectionPoint describing the site of injection for which the producer method will provide satisfaction.  In the function supplied to the produceWith(Function) method—the candidate dynamic producer method—if you try to do instance.select(InjectionPoint.class), you do not get an InjectionPoint that describes the place for which your dynamic producer function will provide values.  You get some weird InjectionPoint that describes something about the Instance object itself, which is of course completely unsuitable.  So produceWith(Function) is out.

Good grief; so now what?

Let’s write a “normal” producer method, just to make some headway, but we’ll just say it returns Object, and we won’t add the @Produces annotation.  We’ll call this our almost-producer method.  We’ll put this method in our extension class:

If we were to add a @Produces annotation here, the container would happily accept this.  The problem is it would only work for injection points where the type was (exactly) Object, which is not what we want:

@Inject
@ConfigurationValue("frobnicationInterval")
private Object frobnicationInterval; // this will work, but…

But in all other ways, this is a suitable producer method—and hence a suitable CDI bean.  To think about this a little differently, all we need to do is to create a CDI bean with a specific bean type that uses this method as its “producer body”.

Fortunately, the BeanManager has two methods here that will help us out greatly.

The first is the createBeanAttributes(AnnotatedMember) method. To understand this, let’s talk briefly about BeanAttributes.

A CDI bean is fundamentally two things:

  1. A piece of descriptive information that says what its types are, what annotations it has, and so on.  A BeanAttributes represents this part.
  2. Some means of producing its instances so that a CDI Context can manage those instances.  There are various ways of representing this part.

As we said above, for each Type available in our Configurations object, we’re trying to “create a CDI bean with a specific bean type”—namely, that Type—that uses a particular method we’ve already written (see above) as a means of creating its instances.

The createBeanAttributes(AnnotatedMember) method, then, basically introspects a producer method (or an “almost-producer” method, in our case), derives information about it and represents that information in a new BeanAttributes.  Sounds good!

Working backwards, we’ll need to get an AnnotatedMethod to represent that almost-producer method we wrote. Then we can call the createBeanAttributes(AnnotatedMember) method on it. The recipe looks like this:

If we inspect that producerAttributes object, we will see that the return value of its getTypes() method only has Object in it (reflecting the return type of our almost-producer method).  If we could somehow this value to become an arbitrary type of our choosing, then we have all the raw materials we need.  Let’s write a very simple delegating BeanAttributes class:

So now we have the means to create a BeanAttributes describing our new bean (the bean describing the kinds of things produced by our almost-producer method), and we have a method that can actually create things of that kind.

To link them together into a real CDI bean, we need to make use of the BeanManager#createBean(BeanAttributes, Class, ProducerFactory) method. We’ll supply this with a DelegatingBeanAttributes instance that supplies the right Type in the return value of its getTypes() method, our extension class, and…wait, what’s a ProducerFactory?

A ProducerFactory is (very briefly put) a programmatic representation of a producer.  Producers in general come in two flavors: producer fields and producer methods.  Recall that producers are beans: things with metadata (types, annotations, etc.) and means of production.  A producer field is a bean whose metadata comes from details about the field itself (its type, its annotations) and whose means of production is, quite simply, the field itself.  Similarly, a producer method is a bean whose metadata comes from details about the method itself and the return value of the method (its type, the method’s annotations) and whose means of production is an invocation of the method.

So what, do we have to write one ourselves?  No, not exactly.  Once again, we can ask the container for the ProducerFactory it would use behind the scenes if our almost-producer method were in fact a producer method.  You do this by calling the BeanManager#getProducerFactory(AnnotatedMethod, Bean) method.

The first parameter: hey, we know how to get that. We actually have it in our hands already, since we needed an AnnotatedMethod for the container to give us a BeanAttributes for it.

The second parameter: this is the bean housing the producer method you’re trying to get the ProducerFactory for. In our case, our almost-producer method is static, so there’s no instance of any object that has to be created so that this almost-producer method can be invoked on it. So we can pass null here.

Let’s pause in all of this and take stock.

  • We’re in a lifecycle method that lets us add things to the container.
  • We have a set of Types that we’ve dynamically discovered that we want to do things with.
  • We have the means of creating a BeanAttributes representing a producer method of an arbitrary type.
  • We have the means of marrying this BeanAttributes together with a “template method” to create a CDI bean of a particular type.

So let’s do it!

I cannot emphasize enough how powerful this is.

We’ve taken a dynamic set of user-supplied information, and set the container up so that it can ensure that typesafe resolution will apply even over this set.

This general pattern I’m sure can be widely applied and I hope to have more to say on it in the future.  Thanks for reading!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s