1 2 3 Previous Next 32 Replies Latest reply on Dec 23, 2009 12:48 PM by Kabir Khan

    Supporting qualifiers in MC

    Kabir Khan Master

      Following our Skype chat I have had a quick look at Supply/Demand and how to support qualifiers. We could do something like:

      *As you mentioned add a enum type to Supply/DemandMetaData so we can see which supplies/demands are qualifiers.

      *SearchClassContextDependencyItem is used when injecting BY_CLASS. This could be expanded to take qualifiers into account when searching for the bean to inject. Maybe rather than expanding SearchClassContextDependencyItem.resolve() and AbstractInjectionValueMetaData.getValue(), we should add another method to AbstractKernelController to take qualifiers into account?
      -Initially the Demands/qualifiers will be for the whole bean (coarse-grained), but should somehow (I've not looked how yet) be expanded so we can qualify a particular injection point (fine-grained).
      -For the coarse-grained model, what do we do if the following bean has the demand qualifier @Test

      class Bean
      {
       @Inject Something field;
      }
      

      And there is a bean in the controller of type Something, but it does not supply @Test? I think coarse-grained needs to work slightly differently from fine-grained. Fine-grained needs a bean with all the qualifiers specified at the injection point. Coarse-grained needs a bean with as many as possible of the qualifiers specified at bean level.

      When looking into this, I noticed that AbstractInjectionValueMetaData takes the LookupStrategy into account when searching for the context, while the SearchClassContextDependencyItem does not appear to do so.

        • 1. Re: Supporting qualifiers in MC
          Ales Justin Master

          Actually, I think we should consider MDR as a proper place for qualifiers.
          At least that's what Adrian and me are discussing wrt LDAP filtering and service properties in OSGi.
          - http://www.jboss.org/index.html?module=bb&op=viewtopic&p=4265098#4265098

          Where our qualifiers are nothing more then properties,
          it's just that they are not all strings.

          This way we would have a single mechanism for lookup,
          regardless of what and where qualifiers/properties are/came,
          or what the filtering mechanism is.

          e.g.
          Weld/jsr330 would have different filtering of MDR data than OSGi LDAP.

          • 2. Re: Supporting qualifiers in MC
            Ales Justin Master

             

            "kabir.khan@jboss.com" wrote:

            And there is a bean in the controller of type Something, but it does not supply @Test? I think coarse-grained needs to work slightly differently from fine-grained. Fine-grained needs a bean with all the qualifiers specified at the injection point. Coarse-grained needs a bean with as many as possible of the qualifiers specified at bean level.

            We just need to make sure we have exact rules, whatever those might be.
            The stuff we discussed could be categorized as pseudo. :-)

            I guess the real examples and commons sense will flush those rules out.

            "kabir.khan@jboss.com" wrote:

            When looking into this, I noticed that AbstractInjectionValueMetaData takes the LookupStrategy into account when searching for the context, while the SearchClassContextDependencyItem does not appear to do so.

            Could be an oversight.


            • 3. Re: Supporting qualifiers in MC
              Kabir Khan Master

              I have something working for qualifiers now. The demanded qualifiers are currently only on bean-level (coarse-grained). I've added these methods to BeanMetaData:

               /**
               * Get the qualifers exposed by this bean
               *
               * @return a set of the qualifiers
               */
               Set<QualifierMetaData> getQualifiers();
              
               /**
               * Get the qualifiers that will be used by default for properties and parameters
               * that don't have explicit qualifiers on their value dependency metadata
               *
               * @return a set of the qualifiers
               */
               Set<QualifierMetaData> getDefaultQualifiers();
              

              QuailifierMetaData:
              /**
               * MetaData describing the qualifiers
               *
               * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
               * @version $Revision: 1.1 $
               */
              public interface QualifierMetaData extends JBossInterface, MetaDataVisitorNode
              {
               /**
               * Get the qualifier
               * @return the qualifier
               */
               Object getQualifier();
              }
              


              If a bean uses autowiring for it's injection and has "default qualifiers" a ClassAndQualifierKey is used to do the lookups from AbstractInjectionValue and SearchQualifiedClassDependencyItem (similar to SearchClassDependencyItem, but takes the ClassAndQualifierKey instead of the plain class. The calls from AIV and SQCPI end up here in AbstractKernelController
               public KernelRegistryEntry getEntry(Object name)
               {
               List<KernelControllerContext> list;
              
              + if (name instanceof ClassAndQualifierKey)
              + return ((ClassAndQualifierKey)name).search(this);
               if (name instanceof Matcher)
               list = matchSupplies((Matcher)name);
               else
               list = suppliers.get(name);
              
               if (list != null && list.isEmpty() == false)
               return list.get(0);
               else if (name instanceof Class)
               return getContextByClass((Class<?>) name);
               else
               return null;
               }
              


              The ClassAndQualifierKey contains the logic to find the context with the qualifiers, and here briefly is what it does
              class ClassAndQualifierKey
              {
               Class<?> type;
               ControllerState dependentState;
               Set<Object> qualifiers;
              
               public KernelControllerContext search(KernelController controller)
               {
               Set<KernelControllerContext> contexts = controller.getContexts(type, dependentState);
               for (KernelControllerContext context : contexts)
               {
               for (Object myqualifier : qualifiers)
               {
               for (QualifierMetaData qualifier : context.getQualifierMetaData())
               {
               //check myqualifier vs qualifier.getQualifier()
               }
               }
               }
               }
              }
              


              As you can see I am not using MDR at all yet at this stage. Where do you see MDR fitting in? Is the idea to add all contexts that have a qualifier to somewhere in MDR and to be able to query that to avoid iterating over all contexts, or do you have something else in mind? I think I am missing what you guys mean by filtering in that other thread

              • 4. Re: Supporting qualifiers in MC
                Kabir Khan Master

                 

                "kabir.khan@jboss.com" wrote:
                I
                As you can see I am not using MDR at all yet at this stage. Where do you see MDR fitting in? Is the idea to add all contexts that have a qualifier to somewhere in MDR and to be able to query that to avoid iterating over all contexts, or do you have something else in mind? I think I am missing what you guys mean by filtering in that other thread


                Aha :-) I found page 2&3 of the discussion. I guess you mean something similar to ServiceTracker? To clarify, do you have any examples of what OSGi needs?

                • 5. Re: Supporting qualifiers in MC
                  Ales Justin Master

                   

                  "kabir.khan@jboss.com" wrote:

                  do you have any examples of what OSGi needs?

                  OSGi has a notion of LDAP filtering; class + ldap-string filter.
                  e.g. org.acme.FooBar + (a=b)
                  Which means we're looking for service registry with FooBar impl
                  which also contains explicit property; key a, value b.
                  * http://anonsvn.jboss.org/repos/jbossas/projects/jboss-osgi/trunk/reactor/framework/src/test/java/org/jboss/test/osgi/service/GetServiceReferencesUnitTestCase.java

                  • 6. Re: Supporting qualifiers in MC
                    Kabir Khan Master

                    The requirements for the usage of MDR are clearer after our team call. For the record, supplied qualifiers should be read from the MDR so that qualifiers can be applied on for example deployment level.

                    I've stumbled upon this though when trying to populate the MDR with qualifers, that the mutable scope has not been created yet:

                     public void describeVisit(MetaDataVisitor vistor)
                     {
                     super.describeVisit(vistor);
                     KernelControllerContext context = vistor.getControllerContext();
                    
                     MetaDataRetrieval retrieval = context.getKernel().getMetaDataRepository().getMetaDataRepository().getMetaDataRetrieval(context.getScopeInfo().getMutableScope());
                     if (retrieval instanceof MutableMetaData)
                     {
                     /At this stage retrieval is null
                     }
                    


                    I'm still a bit wooly on the MDR stuff, so I might be doing something wrong

                    • 7. Re: Supporting qualifiers in MC
                      Kabir Khan Master

                      Forgot to mention, the above method is from a MetaDataVisitorNode class

                      • 8. Re: Supporting qualifiers in MC
                        Kabir Khan Master

                        It seems the metadata for the context is created after the context is described. From some breakpoints

                        QualifierMetaData.describeVisit(MetaDataVisitor) line: 59
                        DescribedMetaDataVisitor(AbstractMetaDataVisitor).internalDescribeVisit(MetaDataVisitorNode) line: 137
                        DescribedMetaDataVisitor(AbstractMetaDataVisitor).describeVisit(MetaDataVisitorNode) line: 87
                        AbstractBeanMetaData(AbstractFeatureMetaData).describeVisit(MetaDataVisitor) line: 106
                        DescribedMetaDataVisitor.run() line: 53
                        AccessController.doPrivileged(PrivilegedAction<T>) line: not available [native method]
                        AbstractKernelControllerContext.infoprocessMetaData() line: 249
                        AbstractKernelControllerContext.setBeanInfo(BeanInfo) line: 180
                        PreInstallAction.installActionInternal(KernelControllerContext) line: 89 <<<
                        PreInstallAction(InstallsAwareAction).installAction(KernelControllerContext) line: 54
                        


                        BasicMetaDataRepository.addMetaDataRetrieval(MetaDataRetrieval) line: 109
                        KernelScopeInfo(AbstractScopeInfo).addMetaData(MutableMetaDataRepository, ControllerContext) line: 126
                        BasicKernelMetaDataRepository.addMetaData(ControllerContext) line: 70
                        PreInstallAction.installActionInternal(KernelControllerContext) line: 95 <<<
                        PreInstallAction(InstallsAwareAction).installAction(KernelControllerContext) line: 54
                        


                        • 9. Re: Supporting qualifiers in MC
                          Kabir Khan Master

                          The problem is that in PreInstallAction, KernelControllerContext.setBeanInfo() adds the bean info and also describeVisits the metadata, while KernelMetaDataRepository.addMetaData() creates the MDR entry and updates the annotations. So with the way it is there is no MDR at the time we are in describeVisit.

                           protected void installActionInternal(KernelControllerContext context) throws Throwable
                           {
                           KernelController controller = (KernelController)context.getController();
                           Kernel kernel = controller.getKernel();
                           KernelConfigurator configurator = kernel.getConfigurator();
                          
                           BeanMetaData metaData = context.getBeanMetaData();
                           if (metaData.getBean() != null)
                           {
                           BeanInfo info = configurator.getBeanInfo(metaData);
                           context.setBeanInfo(info);
                           KernelMetaDataRepository repository = controller.getKernel().getMetaDataRepository();
                           ClassLoader oldCL = SecurityActions.setContextClassLoader(context);
                           try
                           {
                           repository.addMetaData(context); //1
                           }
                           finally
                           {
                           SecurityActions.resetContextClassLoader(oldCL);
                           }
                           try
                           {
                           applyScoping(context);
                           }
                           catch (Throwable t)
                           {
                           removeMetaData(context);
                           throw t;
                           }
                           }
                           }
                          

                          If I call KCC.setBeanInfo() after KMDR.addMetaData() some of the exisiting tests like ClassAnnotationTestCase break. Now, the problem is that the internal calls to update the annotations return early since there is no bean info. I have worked around this with the following change locally:
                           protected void installActionInternal(KernelControllerContext context) throws Throwable
                           {
                           KernelController controller = (KernelController)context.getController();
                           Kernel kernel = controller.getKernel();
                           KernelConfigurator configurator = kernel.getConfigurator();
                          
                           BeanMetaData metaData = context.getBeanMetaData();
                           if (metaData.getBean() != null)
                           {
                           BeanInfo info = configurator.getBeanInfo(metaData);
                           context.setBeanInfo(info);
                           KernelMetaDataRepository repository = controller.getKernel().getMetaDataRepository();
                           ClassLoader oldCL = SecurityActions.setContextClassLoader(context);
                           try
                           {
                           repository.addMetaData(context);
                           }
                           finally
                           {
                           SecurityActions.resetContextClassLoader(oldCL);
                           }
                           try
                           {
                           applyScoping(context);
                           }
                           catch (Throwable t)
                           {
                           removeMetaData(context);
                           throw t;
                           }
                           context.processMetaData();
                           }
                           }
                          


                          KCC.setBeanInfo() now does not describeVisit() the metadata, instead an explicit call is needed to KCC.processMetaData()
                          $svn diff kernel/src/main/java/org/jboss/kernel/plugins/dependency/AbstractKernelControllerContext.java
                          Index: kernel/src/main/java/org/jboss/kernel/plugins/dependency/AbstractKernelControllerContext.java
                          ===================================================================
                          --- kernel/src/main/java/org/jboss/kernel/plugins/dependency/AbstractKernelControllerContext.java (revision 96585)
                          +++ kernel/src/main/java/org/jboss/kernel/plugins/dependency/AbstractKernelControllerContext.java (working copy)
                          @@ -177,10 +177,15 @@
                           public void setBeanInfo(BeanInfo info)
                           {
                           this.info = info;
                          - infoprocessMetaData();
                           flushJBossObjectCache();
                           }
                          
                          + public void processMetaData()
                          + {
                          + infoprocessMetaData();
                          + flushJBossObjectCache();
                          + }
                          +
                           public BeanMetaData getBeanMetaData()
                           {
                           return metaData;
                          



                          • 10. Re: Supporting qualifiers in MC
                            Kabir Khan Master

                            Following Ales's suggestions what I now have is:

                            Qualifier metadata is implemented as RelatedClassMetaData, where the qualifier objects are stored in RCMD.getEnabled(). To pick out the RCMDs that specify qualifiers I use 'RCMB#SUPPLIED_QUALIFIER' as the classname, and 'RCMB#WANTED_QUALIFIER' as the classname for wanted qualifiers specified at bean level.

                            Following the population of the metadata and beaninfo in PreInstallAction (and in InstallAction if beaninfo was null) I populate the MDR with sets of qualifiers from the metadata. These are stored in the INSTANCE level metadata under 'RCMB#SUPPLIED_QUALIFIER' and 'RCMB#WANTED_QUALIFIER' respectively.

                            The search algorithm works much the same as before. ClassAndQualifierKey queries for all the beans implementing a class, and then narrows it down depending on the wanted qualifiers and the required qualifiers.
                            EDIT: The qualifiers now are looked up from the MDR for the contexts involved

                            Before I commit, I want to add some xml configuration of qualifiers. I propose

                            <bean name="bean1" class="SomeBean">
                             <qualifier>a</qualifier>
                            </bean>
                            <bean name="bean2"class="SomeBean">
                             <qualifier>a</qualifier>
                             </qualifier>b</qualifier>
                            </bean>
                            <bean name="bean3" class="OtherBean">
                             <qualifier type="default">a</qualifier>
                            </bean>
                            


                            Meaning that the bean1 supplies qualifier 'a', bean2 supplies qualifiers 'a', 'b' and bean3 uses 'a' by default when looking for other beans to inject into it. So when injecting SomeBean instances into bean3, bean1 will be chosen.

                            Next, I want to look at somehow supporting fine-grained qualifiers injection. If we have two instances of
                            class Bean{}
                            

                            where instance 1 supplies qualifier 'A' and instance 2 supplies qualifier 'B' and we have

                            class Target
                            {
                             Bean a;
                            
                             Bean b;
                            
                             @Constructor
                             Target(@Inject Bean a)
                             {
                             this.a = a;
                             }
                            
                             @Inject
                             void setB()
                             {
                             this.b = b;
                             }
                            }
                            

                            It should be possible to specify qualifiers for injected parameters and properties, so that in this example we can say the constructor wants the Bean with qualifier 'A' while the setter wants the Bean with the qualifier 'B'. I'll see if I can use/expand AbstractInjectionValueMetaData to use qualifiers.

                            • 11. Re: Supporting qualifiers in MC
                              Kabir Khan Master

                               

                              "kabir.khan@jboss.com" wrote:

                              The search algorithm works much the same as before. ClassAndQualifierKey queries for all the beans implementing a class, and then narrows it down depending on the wanted qualifiers and the required qualifiers.
                              EDIT: The qualifiers now are looked up from the MDR for the contexts involved

                              I forgot to mention that in the case of qualifier metadata existing at a higher level in MDR than instance level, the search takes that into account and merges whatever it can find at all levels. e.g., if ["a", "b"] exists at JVM level and ["c", "d"] at INSTANCE level, when doing the lookup for an instance ["a", "b", "c", "d"] is returned.

                              "kabir.khan@jboss.com" wrote:

                              It should be possible to specify qualifiers for injected parameters and properties, so that in this example we can say the constructor wants the Bean with qualifier 'A' while the setter wants the Bean with the qualifier 'B'. I'll see if I can use/expand AbstractInjectionValueMetaData to use qualifiers.


                              I've done this. This differs from the bean-level qualifiers in that if a qualifier exists on an injection point, then the bean-level qualifiers are ignored, so the property level ones completely override the bean level ones e.g.

                              class TargetBean
                              {
                               Bean bean1;
                              
                               Bean bean2;
                              }
                              


                              If the bean has RelatedClassMetaData specifying that it's wanted qualifiers are 'A', and the property bean2 specifies that the qualifiers are 'B', for bean1 we will inject a bean that supplies the qualifier 'A', while for bean2 a bean that supplies the qualifier 'B' will be injected.

                              I don't know if the qualifiers for the injection points should be read from the MDR or not? At first glance doing that does not make sense since the component metadata lives below instance level, but I might be wrong. Especially if my previous assumption that injection point qualifiers should completely override the bean-level wanted qualifiers is wrong?

                              I'll add some xml configuration for everything before I commit

                              • 12. Re: Supporting qualifiers in MC
                                Kabir Khan Master

                                I am able to add qualifiers to the bean level via xml easily, e.g.:

                                <bean xmlns="urn:jboss:bean-deployer:2.0" class="Dummy">
                                 <qualifier>
                                 <javabean xmlns="urn:jboss:javabean:1.0" class="java.util.Date"/>
                                 <javabean xmlns="urn:jboss:javabean:1.0" class="java.lang.String"/>
                                 </qualifier>
                                </bean>
                                <bean xmlns="urn:jboss:bean-deployer:2.0" class="Dummy">
                                 <qualifier type="Wanted">aop</qualifier>
                                </bean>
                                

                                This is handled by a sub class of AbstractRelatedClassMetadata which hardcodes the name of the class depending on the qualifier type being Wanted or Supplied (Default).

                                Now, trying to add this for injection points, I get the following to parse:
                                <bean xmlns="urn:jboss:bean-deployer:2.0" class="Dummy">
                                 <property name="test">
                                 <inject>
                                 <qualifier>
                                 <javabean xmlns="urn:jboss:javabean:1.0" class="java.util.Date"/>
                                 <javabean xmlns="urn:jboss:javabean:1.0" class="java.lang.String"/>
                                 </qualifier>
                                 </inject>
                                 </property>
                                </bean>
                                


                                But since I also want to be able to add these qualifiers directly programatically whereby I have been doing:
                                 AbstractInjectionValueMetaData inject = new AbstractInjectionValueMetaData();
                                 inject.setInjectionPointQualifiers(new HashSet<Object>(Arrays.asList(qualifiers)));
                                


                                I end up with two sets of properties in AbstractInjectionValueMetaData for the same thing:
                                 /**
                                 * Get the injectionPointQualifiers
                                 * @return the injectionPointQualifiers
                                 */
                                 public Set<Object> getInjectionPointQualifiers()
                                 {
                                 return injectionPointQualifiers;
                                 }
                                
                                 /**
                                 * Set the injectionPointQualifiers
                                 * @param injectionPointQualifiers the injectionPointQualifiers to set
                                 */
                                 public void setInjectionPointQualifiers(Set<Object> injectionPointQualifiers)
                                 {
                                 this.injectionPointQualifiers = injectionPointQualifiers;
                                 }
                                
                                 /**
                                 * Get the injectionPointQualifierMetaData
                                 * @return the injectionPointQualifierMetaData
                                 */
                                 public Set<RelatedClassMetaData> getInjectionPointQualifierMetaData()
                                 {
                                 return injectionPointQualifierMetaData;
                                 }
                                
                                 /**
                                 * Set the injectionPointQualifierMetaData
                                 * @param injectionPointQualifierMetaData the injectionPointQualifierMetaData to set
                                 */
                                 @XmlElement(name="qualifier", type=AbstractQualifierMetaData.class) //Sub-class of AbstractRelatedClassMetaData
                                 public void setInjectionPointQualifierMetaData(Set<RelatedClassMetaData> injectionPointQualifierMetaData)
                                 {
                                 this.injectionPointQualifierMetaData = injectionPointQualifierMetaData;
                                 }
                                


                                I like the convenience of being able to call setInjectionPointQualifiers() directly when creating this programatically without having to wrap it up in an AbstractQualifierMetaData, but I guess that can be hidden in the BeanMetaDataBuilder. So unless somebody knows how to avoid that, I'll take that route.

                                • 14. Re: Supporting qualifiers in MC
                                  Kabir Khan Master

                                  Speaking to Ales he mentioned that:

                                  "@Inject should pick up qualifiers from annotations"

                                  I think this should also be done when inject is done via xml/beanmetadata. Since most qualifiers I've seen (in jsr-299 and jsr-330) are picked out using annotations on the annotation, I propose adding something to BeanAnnotationAdapter to handle meta-annotations.

                                  1 2 3 Previous Next