3 Replies Latest reply on Jun 16, 2014 5:13 AM by Martin Kouba

    Set a field from a CDI extension to a producer class

    Jeff Mesnil Master

      Hi,

       

      I'm using a CDI extension to provide a producer method for JMSContext object.

      The code is straightforward and works fine:

       

      public class JMSCDIExtension implements Extension {
      
          private final PropertyReplacer propertyReplacer;
      
          public JMSCDIExtension(PropertyReplacer propertyReplacer) {
              this.propertyReplacer = propertyReplacer;
          }
      
      
          void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
              AnnotatedType<JMSContextProducer> producer = bm.createAnnotatedType(JMSContextProducer.class);
              bbd.addAnnotatedType(producer);
          }
      }
      

      The JMSContextProducer is a simple java class with a CDI producer method:

       

      public class JMSContextProducer {
      
          public JMSContextProducer() {
          }
      
          /**
           * CDI Producer method for injected {@link JMSContext}.
           */
          @Produces
          public JMSContext getJMSContext(InjectionPoint injectionPoint) throws NamingException {
             ...
             return new JMSContextImpl(...);
          }
      
      ...
      }
      

       

      However, I now want to change the JMSContextProducer to use a PropertyReplacer field inside its producer method.

      I have a reference on the PropertyReplacer when my CDI extension is instantiated but I can't figure out to set it on the JMSContextProducer object.

       

      public class JMSCDIExtension implements Extension {
      
          private final PropertyReplacer propertyReplacer;
      
          public JMSCDIExtension(PropertyReplacer propertyReplacer) {
              this.propertyReplacer = propertyReplacer;
          }
      
          void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
              // FIXME somehow push the propertyReplace into the JMSContextProducer instance
              AnnotatedType<JMSContextProducer> producer = bm.createAnnotatedType(JMSContextProducer.class);
              bbd.addAnnotatedType(producer);
          }
      }
      

       

      I find a way to having it work by observing for ProcessInjectionTarget event on JMSContextProducer and set the propertyReplacer on the instance:

       

      public void wrapInjectionTarget(@Observes ProcessInjectionTarget<JMSContextProducer> event)
          {
              final InjectionTarget<JMSContextProducer> injectionTarget = event.getInjectionTarget();
              event.setInjectionTarget(new ForwardingInjectionTarget<JMSContextProducer>() {
      
      
                  @Override
                  public void inject(JMSContextProducer instance, CreationalContext<JMSContextProducer> ctx) {
                      super.inject(instance, ctx);
                      instance.setPropertyReplacer(propertyReplacer);
                  }
      
      
                  @Override
                  protected InjectionTarget<JMSContextProducer> delegate() {
                      return injectionTarget;
                  }
              });
          }
      

       

      It looks to work fine but I wonder if that is the best way to do this. Ideally, I would like to create a single JMSContextProducer instance with a propertyReplacer in its constructor and tell CDI to use that instance to produce JMSContext.

       

      What's the best way to do this?

       

      thanks

        • 1. Re: Set a field from a CDI extension to a producer class
          Martin Kouba Master

          Hi Jeff,

          First, I don't think your JMSCDIExtension is legal. At least it's not portable. Any CDI extension is a service provider (see 11.5. Container lifecycle events) and should have a zero-argument constructor (see also ServiceLoader javadoc). I wonder what container do you use? I've just tested with WildFly and it seems its service loader is more tolerant (does not break the deployment) but it does fail to construct the extension instance. Are you sure it really works?

          As to the best way. It depends whether PropertyReplacer is a bean or not. If so I would simply use a producer method parameter injection point (see also 3.3.2. Declaring a producer method). If not I would create a separate producer. But it really depends on where/when/how you get the PropertyReplacer reference.

          • 2. Re: Set a field from a CDI extension to a producer class
            Jeff Mesnil Master

            The JMSCDIExtension is instantiated by WildFly messaging subsystem to provide injection of JMSContext. It works fine and we pass the TCK with it.

            The current version has a 0-arg constructor but I'm changing that since I need to use the propertyReplacer reference.

            I got it from the WildFly deployment API and it is not a bean that I can inject. That's why I went to the solution in my initial post but I'd welcome any suggestion to improve it.

             

            If not I would create a separate producer.

            How can I provide such a separate producer that it instantiated with the propertyReplacer reference?

            • 3. Re: Set a field from a CDI extension to a producer class
              Martin Kouba Master

              What I was trying to say is that normally you don't instantiate the extension instance directly, it's a service loader responsibility. Of course, it's possible if you register the extension directly through the WildFly API. However, there are other downsides of this approach. For each service provider, the container must provide an @ApplicationScoped bean and so it's type should be proxyable. If it's not you can't inject the extension instance. I agree that it's not such a problem for this use case.

              Still I would remove the constructor and use package-private getter/setter for propertyReplacer instead. Thereafter you can simply inject the JMSCDIExtension instance into JMSContextProducer#getJMSContext().

              However your initial solution also looks good. You can also observe the ProcessProducer event and wrap the producer (similar solution).