3 Replies Latest reply on Jul 12, 2010 11:52 AM by pmuir

    Weld - pull from MC

    kabirkhan
      I have experimented with an alternate implementation of the weld-mc integration as discussed in Boston. I still need to write more tests but wanted to let the Weld guys know where I am at.
      The idea is to "pull" data from the MC in special cases, which is different from the "push" we do at the moment for MC beans annotated with @WeldEnabled. I had a look at using the InjectionServices interface, but this has the following problems:
      -It does not get triggered when calling constructors
      -If using a separate @McInject annotation instead of @Inject to do injection the annotated field does not appear in invocation context's injection points meaning all the fields etc. need to be parsed from invocation context's target's class (I am not sure if the target will always be non-null?)
      -If using the @Inject annotation with an additional @Mc annotation (to look up the particular injection point in the MC) falls over when calling proceed(), also again a problem is that you need to iterate over all fields etc. to look for the members. This also fails in the validation phase.
      As mentioned the ability to do this injection from MC is something that most Weld applications would not want to do. What I have locally now is an extra service that can handle this:
      package org.jboss.temp.weld.spi;
       
      import java.lang.annotation.Annotation;
      import java.util.Iterator;
       
      import org.jboss.weld.bootstrap.api.Service;
       
       
      public interface ExternalLookupRegistryService extends Service
      {
         Iterable<ExternalLookup<?>> getExternalLookups();
         
         void addExternalLookup(ExternalLookup<?> externalLookup);
         
         boolean removeExternalLookup(Class<? extends Annotation> clazz);
      }
      
      And then a service to handle particular annotations
      package org.jboss.temp.weld.spi;
       
      import java.lang.annotation.Annotation;
      import java.lang.reflect.Type;
      import java.util.Set;
       
      public interface ExternalLookup<T extends Annotation>
      {
         Class<T> getAnnotation();
         
         boolean isValid(Type type, Set<Annotation> qualifiers);
         
         Object lookupValue(Type type, Set<Annotation> qualifiers);
      }
      
      I'm hoping to be able to push these interfaces into the main spi, the temp location I am using is just while playing.
      Then I have modified the Validator and Field-/ParameterInjectionPoint to look for this service when validating/injecting:
      [kabir ~/sourcecontrol/weld/core/subversion]
      $svn diff impl/src/main/java/org/jboss/weld/bootstrap/Validator.java
      Index: impl/src/main/java/org/jboss/weld/bootstrap/Validator.java
      ===================================================================
      --- impl/src/main/java/org/jboss/weld/bootstrap/Validator.java     (revision 6577)
      +++ impl/src/main/java/org/jboss/weld/bootstrap/Validator.java     (working copy)
      @@ -83,6 +83,8 @@
      import javax.inject.Scope;
      import org.jboss.interceptor.model.InterceptionModel;
      +import org.jboss.temp.weld.spi.ExternalLookup;
      +import org.jboss.temp.weld.spi.ExternalLookupRegistryService;
      import org.jboss.weld.bean.AbstractClassBean;
      import org.jboss.weld.bean.AbstractProducerBean;
      import org.jboss.weld.bean.DisposalMethod;
      @@ -288,6 +290,16 @@
             Set<?> resolvedBeans = beanManager.getBeanResolver().resolve(beanManager.getBeans(ij));
             if (!isInjectionPointSatisfied(ij, resolvedBeans, beanManager))
             {
      +         ExternalLookupRegistryService service = beanManager.getServices().get(ExternalLookupRegistryService.class);
      +         if (service != null)
      +         {
      +            for (ExternalLookup<?> lookup : service.getExternalLookups())
      +            {
      +               if (ij.getAnnotated().isAnnotationPresent(lookup.getAnnotation()))
      +                  if (lookup.isValid(ij.getType(), ij.getQualifiers()))
      +                     return;
      +            }
      +         }
                throw new DeploymentException(INJECTION_POINT_HAS_UNSATISFIED_DEPENDENCIES, ij, Arrays.toString(bindings));
             }
             if (resolvedBeans.size() > 1 && !ij.isDelegate())
      [kabir ~/sourcecontrol/weld/core/subversion]
      $svn diff impl/src/main/java/org/jboss/weld/injection/
      Index: impl/src/main/java/org/jboss/weld/injection/FieldInjectionPoint.java
      ===================================================================
      --- impl/src/main/java/org/jboss/weld/injection/FieldInjectionPoint.java     (revision 6577)
      +++ impl/src/main/java/org/jboss/weld/injection/FieldInjectionPoint.java     (working copy)
      @@ -36,6 +36,8 @@
      import javax.inject.Inject;
      import org.jboss.interceptor.util.InterceptionUtils;
      +import org.jboss.temp.weld.spi.ExternalLookup;
      +import org.jboss.temp.weld.spi.ExternalLookupRegistryService;
      import org.jboss.weld.exceptions.IllegalStateException;
      import org.jboss.weld.exceptions.InvalidObjectException;
      import org.jboss.weld.introspector.ForwardingWeldField;
      @@ -114,7 +116,23 @@
                   // if declaringInstance is a proxy, unwrap it
                   instanceToInject = InterceptionUtils.getRawInstance(declaringInstance);
                }
      -         delegate().set(instanceToInject, manager.getInjectableReference(this, creationalContext));
      +        
      +         Object value = null;
      +         ExternalLookupRegistryService service = manager.getServices().get(ExternalLookupRegistryService.class);
      +         if (service != null)
      +         {
      +            for (ExternalLookup<?> lookup : service.getExternalLookups())
      +            {
      +               if (getAnnotation(lookup.getAnnotation()) != null)
      +                  value = lookup.lookupValue(getBaseType(), getQualifiers());
      +            }
      +         }
      +        
      +         if (value == null)
      +            value = manager.getInjectableReference(this, creationalContext);
      +
      +        
      +         delegate().set(instanceToInject, value);
             }
             catch (IllegalArgumentException e)
             {
      Index: impl/src/main/java/org/jboss/weld/injection/ParameterInjectionPoint.java
      ===================================================================
      --- impl/src/main/java/org/jboss/weld/injection/ParameterInjectionPoint.java     (revision 6577)
      +++ impl/src/main/java/org/jboss/weld/injection/ParameterInjectionPoint.java     (working copy)
      @@ -35,6 +35,8 @@
      import javax.enterprise.inject.spi.Bean;
      import javax.enterprise.inject.spi.Decorator;
      +import org.jboss.temp.weld.spi.ExternalLookup;
      +import org.jboss.temp.weld.spi.ExternalLookupRegistryService;
      import org.jboss.weld.exceptions.IllegalStateException;
      import org.jboss.weld.exceptions.InvalidObjectException;
      import org.jboss.weld.exceptions.UnsupportedOperationException;
      @@ -119,6 +121,15 @@
          @SuppressWarnings("unchecked")
          public T getValueToInject(BeanManagerImpl manager, CreationalContext<?> creationalContext)
          {
      +      ExternalLookupRegistryService service = manager.getServices().get(ExternalLookupRegistryService.class);
      +      if (service != null)
      +      {
      +         for (ExternalLookup<?> lookup : service.getExternalLookups())
      +         {
      +            if (getAnnotation(lookup.getAnnotation()) != null)
      +               return (T)lookup.lookupValue(getBaseType(), getQualifiers());
      +         }
      +      }
             return (T) manager.getInjectableReference(this, creationalContext);
          }
      In my test I have this implementation of the service classes
      public class TestExternalLookup implements ExternalLookup<External>
      {
       
         @Override
         public boolean isValid(Type type, Set<Annotation> qualifiers)
         {
            if (type == Bar.class)
               return true;
            return false;
         }
       
         @Override
         public Class<External> getAnnotation()
         {
            return External.class;
         }
       
         @Override
         public Object lookupValue(Type type, Set<Annotation> qualifiers)
         {
            if (type == Bar.class)
               return new BarImpl();
            return null;
         }
         
      }
       
      public class TestExternalLookupRegistryService implements ExternalLookupRegistryService
      {
         private Map<Class<? extends Annotation>, ExternalLookup<?>> lookups = Collections.synchronizedMap(new HashMap<Class<? extends Annotation>, ExternalLookup<?>>());
       
         public void addExternalLookup(ExternalLookup<?> externalLookup)
         {
            lookups.put(externalLookup.getAnnotation(), externalLookup);
         }
       
         public Iterable<ExternalLookup<?>> getExternalLookups()
         {
            synchronized (lookups)
            {
               return new HashSet<ExternalLookup<?>>(lookups.values());
            }
         }
       
         public boolean removeExternalLookup(Class<? extends Annotation> clazz)
         {
            return lookups.remove(clazz) != null;
         }
       
         public void cleanup()
         {
            lookups.clear();
         }
      }
      
      I register this service with the deployment and add the TestExternalLookup to it, meaning that when I create an instance of ExternalConstructorInjected the constructor parameter is found in TestExternalLookup.
      public class ExternalConstructorInjected
      {
         
         Bar bar;
         
         @Inject
         public ExternalConstructorInjected(@External Bar bar)
         {
            this.bar = bar;
         }
      }
      
      I think Pete is on vacation, so after adding a few more tests I'll add this to a branch somewhere so that it can be included once he okays it.
        • 1. Re: Weld - pull from MC
          kabirkhan

          Attaching svn diff, since trying to test decorators with this approach I run into some problems. I've also got some problems with decorators in the "push" approach.

          • 2. Re: Weld - pull from MC
            kabirkhan

            So decorators are not supported for external beans no matter whether they are pushed or pulled.

             

            Also, I found that when we have a "pushed" external bean with a producer method which is wrapped with an alternative Producer implementation (such as our ExisitingInstanceMethodProducer), I can't see anything in the CDI api to be able to figure out which bean manager the call came from. i.e. there does not seem to be anything in the ProcessProducer event or in the CreationContext passed in to Producer.produce(). This means it is impossible to have producer methods with injected parameters in these producer methods coming from external beans.

             

            This works out of the box with the "pull" approach.

            • 3. Re: Weld - pull from MC
              pmuir
              -If using a separate @McInject annotation instead of @Inject to do injection the annotated field does not appear in invocation context's injection points meaning all the fields etc. need to be parsed from invocation context's target's class (I am not sure if the target will always be non-null?)

              It will always be non-null, otherwise we will be trying to inject into a null instance...

               

              -It does not get triggered when calling constructors

              Correct, but it is probably reasonable to extend it support this for Weld 1.1

               

              -If using the @Inject annotation with an additional @Mc annotation (to look up the particular injection point in the MC) falls over when calling proceed(), also again a problem is that you need to iterate over all fields etc. to look for the members. This also fails in the validation pha

              It is possible to overcome this, but I think it just points at underlying problems of this approach.

               

              At the moment I'm pretty unconvinced about adding these extra SPIs because I remain unconvinced of the validity of this approach - essentially you are opening the door for introducing other resolution algorithms which aren't spec'd, which I don't see the value of for users.