3 Replies Latest reply on Jan 26, 2010 2:46 PM by Pete Muir

    XML Extension

    Stuart Douglas Master

      I have started on XML configuration for CDI beans, it can be checked out from svn here. As yet there is no real documentation, so I though I would post a few notes here in case any one would like to try it out. There are also plenty of examples in the src/test/java directory. If you wish to build it you should run it against the trunk of Weld, as there are several bugfixes to the SPI in trunk that are not in 1.0.0.


      Lets start with a simple example:


      <?xml version="1.0" encoding="UTF-8"?>
      <Beans xmlns="urn:seam:core"
                xmlns:test="urn:java:org.jboss.seam.xml.test.injection">
             
          <test:ProducerQualifier>
               <Qualifier/>
          </test:ProducerQualifier>
          
          <test:ProducerBean>
              <test:value>
                  <Produces/>
                  <test:ProducerQualifier/>
                  <value>hello world</value>
              </test:value>
          </test:ProducerBean>
      
         <test:RecieverBean>
                   <test:value>
                        <test:ProducerQualifier/>
                        <Inject/>
                   </test:value>
         </test:RecieverBean>
         
      </Beans>
      
      



      From the top:


      the root element of the file has to be <Beans/>, and the root namespace should be 'urn:seam:core'. You will also notice that there is a second namespace defined, 'urn:java:org.jboss.seam.xml.test.injection', this namesapce is used to resolve classes in the java package 'org.jboss.seam.xml.test.injection'.


          <test:ProducerQualifier>
              <Qualifier/>
          </test:ProducerQualifier>
      



      The first entry in the file defines a new qualifier. ProducerQualifier is an annotation in the package 'org.jboss.seam.xml.test.injection'.



          <test:ProducerBean>
              <test:value>
                  <Produces/>
                  <test:ProducerQualifier/>
                  <value>hello world</value>
              </test:value>
          </test:ProducerBean>
      



      The next entry in the file is a bean declaration. The bean class is org.jboss.seam.xml.test.injection.ProducerBean. It is important to note that this declaration does not change the existing declaration of ProducerBean, instead it installs a new bean. In this instance there will be two ProducerBean CDI beans.


      This bean has a field called 'value', this field is configured to be a producer field using XML (it is also possible to configure producer methods, more on this later). The <test:value/> declaration has several child elements. The <Produces/> element tells the container that this is a producer field. <test:ProducerQualifier/> element defines a qualifier for the producer field. The <value> element defines an initial value for the field.


      Child elements of fields, methods and classes that resolve to Annotation types are considered to be annotations on the corresponding element, so the corresponding java declaration for the XML above would be:


      public class ProducerBean
      {
         @Produces
         @ProducerQualifier
         public String value = "hello world";
      }
      



         <test:RecieverBean>
              <test:value>
                   <test:ProducerQualifier/>
                   <Inject/>
              </test:value>
         </test:RecieverBean>
      



      The XML above declares a new Bean that injects the value that was produced above. In this case the @Inject annotation is applied instead of @Produces and no initial value is set.


      Replacing existing bean declarations



      It is possible to prevent an existing bean being installed using the <veto/> tag:


        <veto>
          <test:ProducerBean/>
          <test:RecieverBean/>
        </veto>
      



      The code above would prevent the ProducerBean and RecieverBean that was discovered in the bean discovery phase from being installed. Using the veto tag it is possible to replace beans instead of just installing new ones. I am planning on adding a way of adding additional annotations to existing Beans at some point in the future.


      The root namespace



      The root namesapce can contain the following elements:



      • Beans

      • veto

      • value

      • key

      • entry

      • array

      • e (alias for entry)

      • v (alias for value)

      • k (alias for key)



      as well as classes from the following packages:



      • java.lang

      • java.util

      • javax.annotation

      • javax.inject

      • javax.enterprise.inject

      • javax.enterprise.context

      • javax.enterprise.event

      • javax.decorator

      • javax.interceptor

      • javax.persistence

      • javax.xml.ws

      • javax.jms

      • javax.sql



      So the <Produces> element above actually resolved to java.enterprise.inject.Produces and the @Inject annotation resolved to javax.inject.Inject.


      Initial field values



      Inital field values can be set in two different ways, in addition to the <value> element shown above it can be set as follows:



       <test:someField>hello world</test:someField>
      


      using this method prevents you from adding any annotations to the field.


      It is possible to set Map,Array and Collection field values. Some examples:


          <test:ArrayFieldValue>
              <test:iarray>
                   <value>1</value>
                   <value>2</value>
              </test:iarray>
              <test:carray>
                   <value>java.lang.Integer</value>
                   <value>java.lang.Long</value>
              </test:carray>
              <test:sarray>
                   <value>hello</value>
                   <value>world</value>
              </test:sarray>
          </test:ArrayFieldValue>
       
          <test:MapFieldValue>
              <test:map1>
                   <entry><key>1</key><value>hello</value></entry>
                   <entry><key>2</key><value>world</value></entry>
              </test:map1>
              <test:map2>
                  <e><k>1</k><v>java.lang.Integer</v></e>
                  <e><k>2</k><v>java.lang.Long</v></e>
              </test:map2>
            
          </test:MapFieldValue>
      
      



      Type conversion is done automatically for all primitives and primitive wrappers, Date, Calendar,Enum and Class fields. In this instance ArrayFieldValue.carray is actually an array of classes, not an array of Strings.



      Configuring methods



      It is also possible to configure methods in a similar way to configuring fields:


      <?xml version="1.0" encoding="UTF-8"?>
      <Beans xmlns="urn:seam:core"
                xmlns:test="urn:java:org.jboss.seam.xml.test.method">
            
          <test:MethodBean>
              <test:method>
                  <Produces/>
              </test:method>
              
              <test:method>
                  <Produces/>
                  <test:Qualifier1/>
                  <test:MethodValueBean>
                      <test:Qualifier2/>
                  </test:MethodValueBean>
              </test:method>
          </test:MethodBean>
       
      
      </Beans>
      
      public class MethodBean
      {
      
         public int method()
         {
            return 1;
         }
      
         public int method(MethodValueBean bean)
         {
            return bean.value + 1;
         }
      
      }
      



      In this instance MethodBean has two methods, both of them rather imaginatively named 'method'. The first <test:method> entry in the XML file configures the method that takes no arguments. The <Produces> element makes it into a producer method. The next entry in the file configures the Method that takes a MethoValueBean as a parameter. When configuring methods non-annotation classes are considered to represent method paramters. If these parameters have annotation children they are taken to be annotations on the parameter. In this instance the corresponding java declaration would be:


        @Produces
        @Qualifier1
        public int method(@Qualifier2 MethodValueBean param) {//method body}
      



      Array parameters can be represented using the <array> element, with a child element to represent the type of the array. E.g.


      int method(MethodValueBean[] param);
      



      could be configured via xml using the following:


        <test:method>
          <array>
            <test:MethodValueBean/>
          </array>
        </test:method>
      



      Annotation members



      It is also possible to set the value of annotation members by. For example:


      
      public @interface OtherQualifier
      {
         String value1();
      
         int value2();
      
         QualifierEnum value();
      }
      
      
      <test:QualifiedBean1>
              <test:OtherQualifier value1="AA" value2="1">A</test:OtherQualifier>
      </test:QualifiedBean1>
          
      <test:QualifiedBean2>
              <test:OtherQualifier value1="BB" value2="2" value="B" />
      </test:QualifiedBean2>
      



      The value member can be set using the inner text of the node, as seen in the first example.


      Still to come



      There is still a lot of work to be done, some of the things that are still to come include



      • Configuration of remote and local EJB's

      • Configuration of JMS resource

      • Ability to redefine beans, instead of just adding and removing them

      • Ability to redefine beans based on type or annotation (e.g. add an interceptor to all beans that implement a certain interface, prevent all beans with a particular annotation from being installed etc)

      • Ability to configure primitive types as beans



      And more. If you want more information have a look at the tests, also the JSR-299 public review draft section on XML Configuration was the base for this extension, so it may also be worthwhile reading.