5 Replies Latest reply on May 6, 2010 1:13 PM by Pete Muir

    How do I register a producer for subtypes dynamically?

    Dan Allen Master

      I'm trying to figure out how to register a producer method that will match injection points of any subtype of a particular class.


      While the following is not legal, it depicts what I am trying to accomplish:


      @Produces @ClientId
      public <T extends UIComponent> T findComponent(InjectionPoint ip) { ... }



      I'll explain what is going on here. I want to look for any UIComponent in the current JSF UI component tree and return it. The @ClientId qualifier provides the search expression and the type determines the corresponding producer.


      @Inject @ClientId("register") UICommand registerButton;



      The component returned by UIViewRoot#findComponent() will always descend from the base class UIComponent. However, I don't want to have to create a producer method for every possible type, because that is



      • ridiculous

      • a closed set



      What I would rather do is scan the classpath for all descendants of UIComponent (or have some configuration files that list them out) and create a producer method that maps to that type. It's just that I can't figure out how to register the beans dynamically.


      I figure I need to start with a producer that uses type parameters:


      public class UIComponentProducer<T extends UIComponent>
      {
         @Produces @ClientId
         public T findComponent(InjectionPoint ip) { ... }
      }



      One approach is to observe AfterBeanDiscovery and register a reified bean for every UIComponent subtype based on the generic producer, but I still have to statically link to the types:


      public void addUIComponentProducers(@Observes AfterBeanDiscovery e)
      {
         final Bean b = manager.getBeans(UIComponentProducer.class).iterator().next();
         e.addBean(new Bean<UIComponentProducer<UIForm>>() {
      
            public Set<Type> getTypes()
            {
               Set<Type> types = new HashSet<Type>();
               types.add(new TypeLiteral<UIComponentProducer<UIForm>>() {}.getType());
               return types;
            }
      
            public Set<Annotation> getQualifiers()
            {
               return b.getQualifiers();
            }
      
            public Class<? extends Annotation> getScope()
            {
               return b.getScope();
            }
      
            public String getName()
            {
               return b.getName();
            }
      
            public Set<Class<? extends Annotation>> getStereotypes()
            {
               return b.getStereotypes();
            }
      
            public Class<?> getBeanClass()
            {
               return UIComponentProducer.class;
            }
      
            public boolean isAlternative()
            {
               return b.isAlternative();
            }
      
            public boolean isNullable()
            {
               return b.isNullable();
            }
      
            public Set<InjectionPoint> getInjectionPoints()
            {
               return b.getInjectionPoints();
            }
      
            public UIComponentProducer<UIForm> create(CreationalContext<UIComponentProducer<UIForm>> creationalContext)
            {
               return new UIComponentProducer<UIForm>();
            }
      
            public void destroy(UIComponentProducer<UIForm> instance, CreationalContext<UIComponentProducer<UIForm>> creationalContext)
            {
            }
         });
      }



      Any idea how to do this registration dynamically? Because if I have to do all this, I might as well just create a producer class corresponding to every type.

        • 1. Re: How do I register a producer for subtypes dynamically?
          Dan Allen Master

          I forgot to consider that I need to add the @Typed annotation to limit the return type to the exact type produced, otherwise you won't be able to inject parent types. Here's an example:


          @Produces @ClientId @Typed(UIForm.class)
          public UIForm findFormComponent(InjectionPoint ip) { ... }



          Without the typed, this injection would fail w/ an ambiguous dependency exception:


          @Inject @ClientId("id") UIComponent component;

          • 2. Re: How do I register a producer for subtypes dynamically?
            Stuart Douglas Master

            I think that you are going about this the wrong way. On one page a @ClientId(register) may be a button, on another it may be a textbox.


            I think the easiest way to get the functionality you want is to make weld treat all these injection points as UIComponents. Try something like this (requires weld-extensions snapshot, as overrideFieldType was added recently ):


            
               public void processAnnotatedType(@Observes ProcessAnnotatedType<?> event)
               {
                  NewAnnotatedTypeBuilder builder = new NewAnnotatedTypeBuilder(event.getAnnotatedType());
                  for(AnnotatedField<?> f : event.getAnnotatedType().getFields())
                  {
                     if(f.isAnnotationPresent(ClientId.class))
                     {
                        builder.overrideFieldType(f.getJavaMember(), UIComponent.class);
                     }
                  }
                  
                  event.setAnnotatedType(builder.create());
               }
            
            



            This means that any UIComponent will be eligible for injection into these points. If you return the wrong type then a ClassCastException would result, but you will probably want to throw your own exception in this case anyway.

            • 3. Re: How do I register a producer for subtypes dynamically?
              Lincoln Baxter III Master

              Ok, so I'm trying to implement this, but I'm getting some weird problems. Because of this extension, I get broken injection all over the app:


              Caused by: org.jboss.weld.DeploymentException: Injection point has unstatisfied dependencies. Injection point: field org.jboss.seam.faces.component.FormFieldProducer.log; Qualifiers: @javax.enterprise.inject.Default()


              Any thoughts? I've debugged, and this only overrides the fields I've actually annotated. It shouldn't be impacting everything else... Injection is just failing.


              public class FormFieldTypingExtension implements Extension
              {
              
                 public <T> void processAnnotatedType(@Observes final ProcessAnnotatedType<T> event)
                 {
                    AnnotatedTypeBuilder<T> builder = AnnotatedTypeBuilder.newInstance(event.getAnnotatedType());
                    for (AnnotatedField<?> f : event.getAnnotatedType().getFields())
                    {
                       if (f.isAnnotationPresent(InputField.class))
                       {
                          builder.overrideFieldType(f.getJavaMember(), Object.class);
                       }
                    }
                    event.setAnnotatedType(builder.create());
                 }
              }


              • 4. Re: How do I register a producer for subtypes dynamically?
                Lincoln Baxter III Master

                Resolved:



                Needed to call:



                      builder.readAnnotationsFromUnderlyingType();



                Not a bug.


                public class FormFieldTypingExtension implements Extension
                {
                
                   public <T> void processAnnotatedType(@Observes final ProcessAnnotatedType<T> event)
                   {
                      AnnotatedTypeBuilder<T> builder = AnnotatedTypeBuilder.newInstance(event.getAnnotatedType());
                      builder.readAnnotationsFromUnderlyingType();
                
                      for (AnnotatedField<?> f : event.getAnnotatedType().getFields())
                      {
                         if (f.isAnnotationPresent(InputField.class))
                         {
                            builder.overrideFieldType(f.getJavaMember(), Object.class);
                         }
                      }
                      event.setAnnotatedType(builder.create());
                   }
                }

                • 5. Re: How do I register a producer for subtypes dynamically?
                  Pete Muir Master

                  Sorry, my bad, I altered the API since this last post.


                  I will try to write javadocs over the next few days for all the public API of WeldX