11 Replies Latest reply on Dec 20, 2013 1:29 AM by young_matthewd

    Weld CDI extension to extend AnnotatedType or add method

    young_matthewd

      hej,

       

      Using Weld 1.1.13.Final with Arquillien to build a CDI extension.  What I want to due is alter the bean containing an qualified injection point.  Looks like with SDI that I can't modify a processed bean or processed annotated type in a way that changes the signature (i.e. to add a type closure extending another class or adding a method).  Only add annotations.  Thought about capturing the types I want to replace/alter with the ProcessAnnotatedType event and veto them.  On AfterBeanDiscovery I would add my altered bean.

       

      Basically I have an injection point similar to Spring's Value:

      public class Whatever {

      ...

      @Inject @Value("a.message.property",....)

      private String myMessage;

      ...

      }

       

      The values will be produced in a singleton.  When a "property" value is changed in the singleton I want to fire an event that should be captured by (for example) all of the Whatever beans and update (for example) the myMessage field with the new value.

       

      Problem is that Whatever and other classes can be used outside the CDI and just initiated without going through the BeanManager.

       

      Only thing I can come up with is to still use a custom CDI extension to isolate types that have a @Value injection point then with Javassist adjust the targeted types.

       

      Anybody else needed a similar solution?  Done something different?  

        • 1. Re: Weld CDI extension to extend AnnotatedType or add method
          young_matthewd

          What I have done so far is rather than recreating the managed bean (sort of a headache) I capture the processed annotated type first and try to add a method there is Javassist then with the help of the BeanManager create a new annotated type which is set to the event.  Seeing my new method later on in the ProcessManagedBean managed bean event.  Code is:

          private ClassPool pool = ClassPool.getDefault();

          ....

           

          <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> event, BeanManager bm) {

          final AnnotatedType<T> target = event.getAnnotatedType();

           

          // check only fields for annotation

          for (AnnotatedField<? super T> field : target.getFields())

          if (field.isAnnotationPresent(Value.class)) {

          CtClass wrapper = pool.get(target.getJavaClass().getName());

          CtMethod m = CtNewMethod.make(....);


          wrapper.addMethod(m);

          event.setAnnotatedType(bm.createAnnotatedType(wrapper.toClass()));

          }

          }

           

          <T> void processManagedBean(@Observes ProcessManagedBean<T> event) {

          // check that the method is there

          }

           

          But I get a Deployment Exception for the managed bean that is to be injected into another bean when validating.  Any ideas?

          • 2. Re: Weld CDI extension to extend AnnotatedType or add method
            mkouba

            Hi Matthew,

            what deployment exception do you actually get (message, stack trace)? If I understand it correctly you would like to automatically add an observer method to a bean with a specific injection point (@Inject @Value...)?

            • 3. Re: Weld CDI extension to extend AnnotatedType or add method
              young_matthewd

              @Martin,

               

              Getting the following exception:

                  org.jboss.weld.exceptions.DeploymentException: WELD-001408 Unsatisfied dependencies for type [Taker] with qualifiers [@Default] at injection point [[field] @Inject se.bolagsverket.afg.cdi.Sample.taker]

               

              where Taker looks like:

                 public class Taker {

                     @Inject @Value("stuff")

                     public String whatever;

                 }

               

              And yes I want to add my Javassist method to Taker.  Have been debugging for the past hour.  The code diverges (from the normal case when not creating a new annotated type from the Taker class with Javassist) inside TypeSafeBeanResolver when calling "getBeans(type)".  The following line returns null in that method:

                  List<T> beansForType = beansByType.get().get(type);

               

              The signature for the passed type and what is contained in the beansForType property is identical (in both cases with the Javassist annotated type and the non-Javaassist).  The only thing that differs is the existence of a method.  Which leads me into a gray area in my head.  When Java does the Map look-up by Type for LazyValueHolder how are the types deemed equal?

              • 4. Re: Weld CDI extension to extend AnnotatedType or add method
                young_matthewd

                Type ID number differs of course.  When I come into the extension method watching the ProcessAnnotatedType event I have a Taker class with a Java type that has id say equal to 139.  Then I modify the class with Javassist and get a similar Type but the id is different, say 249.  Thus the look is done on the original 139 which has no Managed Bean corresponding to Taker (it exists but with a Map key compare of 249).

                 

                Is there anyway to register my modified Taker managed bean with the original Type id?

                • 5. Re: Weld CDI extension to extend AnnotatedType or add method
                  mkouba

                  I guess this might be some class loader issue. CtClass.toClass() uses the context class loader of the current thread which might be inappropriate here.

                  1 of 1 people found this helpful
                  • 6. Re: Weld CDI extension to extend AnnotatedType or add method
                    young_matthewd

                    toClass uses the class loader of the current thread.  Not sure this is the problem rather that Javassist likely has to generate a "new" Type since a new method is added.  Working on assumptions here.

                     

                    Can get at the resolver (TypeSafeBeanResolver) through the bean manager but no seeing a way to "cross map" the original Type id to the managed bean keyed of the adjust Type made by Javassist.

                    • 7. Re: Weld CDI extension to extend AnnotatedType or add method
                      young_matthewd

                      Got an idea, just haven't implemented it...

                       

                      The problem is the binding in the InjectionPoint (in my class a Field Injection Point).  The Java Type contained within the InjectionPoint is the "old" (before the Javassist modification).  I can create a map of "old" to "new" annotated types.  Then when processing the ProcessInjectionTarget events switch out the injection target that use my "new" beans which I can find in the InjectionPoint set associated with the InjectionTarget.  If I can create my own InjectionPoint and InjectionTarget then I can set the event with the target.

                      • 8. Re: Weld CDI extension to extend AnnotatedType or add method
                        young_matthewd

                        Okej.  One step forward 2 back.

                         

                        Did the following.  On ProcessInjectionTarget events I get the InjectionTarget and loop through the InjectionPoint associated.  For each InjectionPoint that is a FieldInjectionPoint I get the WeldField and modify the underlying Field with my "new" Type objects (saved in a map when handling ProcessAnnotatedType events):

                            InjectionTarget<T> target = event.getInjectionTarget();

                            Set<InjectionPoint> points = target.getInjectionPoints();

                            for (InjectionPoint point : points) {

                               if (point instanceof FieldInjectionPoint) {

                                  FieldInjectionPoint fip = (FieldInjectionPoint) point;

                                  WeldField wf = fip.getWeldField();

                         

                                  Type current = wf.getBaseType();

                                  Type wrapper = switching.get(current);  // pulling from the map local to my extension

                                 

                                  Field field = wf.getJavaMember();

                                  Field underlyingType = field.getClass().getDeclaredField("type");

                                  underlyingType.setAccessible(true);

                         

                                  if (wrapper != null) {

                                      underlyingType.set(field, wrapper);

                                      WeldField wfOther = WeldFieldImpl.of(field, wf.getDeclaringType(), new ClassTransformer(new TypeStore()));

                                       FieldInjectionPoint fipOther = FieldInjectionPoint.of(fip.getBean(), fip.getInjectionTargetClass(), wfOther);

                                  }

                               }

                         

                               .... // save the new InjectionTarget

                            }

                         

                        Works fine add I get through validation.  BUT.  This doesn't help when somebody else uses the BeanManager to create an annotated type then a managed bean like Arquillien does with it's JUnit tests and uses the real Field.

                         

                        So I got to apply my changes inside the BeanManager if this is really going to work.

                        • 9. Re: Weld CDI extension to extend AnnotatedType or add method
                          young_matthewd

                          Basically nothing can be done since outside of writing a java agent it isn't possible to "replace" a class in a classloader (doesn't matter if one uses ASM or Javassist or CGLIB).  Stuck with fiddling around with the CDI implementation.  Not a great option but.....

                           

                          Best place I can think of starting at is the bean resolver in the bean manager.  TypeSafeBeanResolver.  Specifically proxying the getBeans by type method.

                           

                          Wondering if my corner case is potentially something that ought to exist in the CDI extension?  Allowing somebody to switch out types of resolved injection point on certain conditions (like only adding methods and so forth).

                          • 10. Re: Weld CDI extension to extend AnnotatedType or add method
                            young_matthewd

                            After adjusting the TypeSafeBeanResolver by intercepting the getAllBeans method and replacing the Resolvable rawType, typeClosure with my proxied annotated type I ran into the next problem.  A problem I should have seen coming.  Weld is going to take the resolved bean and do a reflection set on the target's field.  That field is coded to the "old" type not the one I make with Javassist.  Fail.  ;-)

                             

                            Short of the long: this approach won't work.  Back to square one.

                            • 11. Re: Weld CDI extension to extend AnnotatedType or add method
                              young_matthewd

                              TypeSafeBeanResolver is smart.  Just by extending the target class the super type is enough to resolve:

                                   CtClass wrapper = pool.makeClass(target.getJavaClass().getName() + "Proxy");

                                   wrapper.setSuperclass(pool.get(target.getJavaClass().getName()));

                               

                              This has lead me to another problem BUT all beans generated are of type proxy and their injection points are valid.