Jaxb: Adding Behaviors

You can add behaviors to jaxb generated classes by subclassing and overriding methods.  This technique is useful when integrating the generated classes into an existing codebase, and when data alteration is needed post unmarshalling or during marshalling.   Currently there is no mechanism to alter data during the  unmarshalling process.  The unmarshalling process entails writing the data into the jaxb generated class object (i.e. superclass) of any subclass you provide, however each subclass  will be able to access the  data from its superclass post unmarshalling.

 

There are several implementation rules that must be followed when adding behaviors to jaxb generated classes.

  • You must create a subclass of the jaxb generated ObjectFactory  class and it must be tagged with the @XmlRegistry annotation.
  • You must override any of the methods which are to provide a new subclass.
  • Your subclasses may use the same package name as the jaxb generated classes however it is recommend to put them in a separate package and it not be the empty package.
  • Your new ObjectFactory class will be identified to the JAXContext  by setting property "com.sun.xml.bind.ObjectFactory" to an instance of this class.
Here is a simple example.  We have a simple schema for a book inventory, books.xsd.  xjc is used to generate the classes from the schema. A package name of gbooks.inv is defined for these classes.  The output is written into the b directory.

                 mkdir b
                 xjc -d b -p gbooks.inv books.xsd

 


   books.xsd

      <?xml version="1.0"?>
      <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
         xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
         jxb:version="2.0">

         <xsd:element name="BookInventory">
             <xsd:complexType>
                 <xsd:sequence>
                     <xsd:element name="Book" type="bookType"
                         minOccurs="0" maxOccurs="unbounded" />
                 </xsd:sequence>
             </xsd:complexType>
         </xsd:element>

         <xsd:complexType name="bookType">
             <xsd:sequence>
                 <xsd:element name="isbin" type="xsd:string" />
                 <xsd:element name="title" type="xsd:string" />
                 <xsd:element name="price" type="xsd:double" />
             </xsd:sequence>
         </xsd:complexType>
      </xsd:schema>



Three classes are generated from this schema, ObjectFactory, BookInventory, and BookType.


gbooks/inv/ObjectFactory.java

      @XmlRegistry
      public class ObjectFactory {

         public BookInventory createBookInventory() { return new BookInventory(); }
         public BookType createBookType() { return new BookType(); }
      }


   gbooks/inv/BookInventory.java

      public class BookInventory {
         public List<BookType> getBook() {
            if (book == null) { book = new ArrayList<BookType>(); }
            return this.book;
         }
      }


   gbooks/inv/BookType.java

      public class BookType {

          public String getIsbin() { return isbin; }
          public void setIsbin(String value) { this.isbin = value; }
          public String getTitle() { return title; }
          public void setTitle(String value) { this.title = value; }
          public float getPrice() { return price; }
          public void setPrice(float value) { this.price = value; }
      }


This example adds behaviors to BookType.  Class RM_BookType is extended from  the jaxb generated class gbooks.inv.BookType and the methods of interest are overridden.  Text is prepended to the isbin string and written back into the superclass, BookType.  Any future references to this value in object BookType will be the new value.  10% is added to the original price. This change occurs only when class object RM_BookType is referenced.


   RM_BookType

      package read.more;

      public class RM_BookType extends gbooks.inv.BookType {

          @Override
          public void setIsbin(String value) {
                //- check for initial instance of this class;
                //- parent would not have a value for isbin.
                if (super.getIsbin() == null)
                        super.setIsbin(value);
                else
                        super.setIsbin(value + super.getIsbin());
          }

          @Override
          public double getPrice() {
                // add a 10% handling fee to book
                return  super.getPrice() \* 0.1 ;
          }
      }


Next we must subclass from the jaxb generated ObjectFactory.  RM_ObjectFactory extends gbooks.inv.ObjectFactory.  It is annotated with @XmlRegistry as is required.  It has a package name of read.more. Method createBookType is overridden in order to provide an instance of RM_BookType.


   RM_ObjectFactory

      package read.more;

      import javax.xml.bind.annotation.XmlRegistry;
      import gbooks.inv.BookType;

      @XmlRegistry
      public class RM_ObjectFactory extends gbooks.inv.ObjectFactory {

          @Override
          public BookType createBookType() {
                return new RM_BookType();
          }
      }


Here is a program to demonstrate the use of these new classes.  We have hardcoded the input file name, line 2.  Line 4, the JAXBContext is defined with the jaxb generated classes as one normally would; the JAXBContext is not define pointing to the package with your subclasses.  Line 6 identifies RM_ObjectFactory for jaxb to use. Line 8 we add a new book to the inventory. Lines 14 and 15 demonstrates the new functionality that is implemented.


1      public class Test {
2        public static final String XML_TEST_FILE = "BookList.xml";
3        public static void main(String[] args) throws Exception {

4           JAXBContext context = JAXBContext.newInstance("gbooks.inv");
5           Unmarshaller u = context.createUnmarshaller();

6           u.setProperty("com.sun.xml.bind.ObjectFactory",new RM_ObjectFactory());

7           BookInventory bi = (BookInventory)u.unmarshal(new File(XML_TEST_FILE));

            //- add something new to the list
8           RM_BookType rmb = new RM_BookType();
9           rmb.setIsbin("9932-3423");
10          rmb.setTitle("Partial Math");
11          rmb.setPrice(0.40);
12          bi.getBook().add(rmb);

13          for(BookType b: bi.getBook()){
14             b.setIsbin("ACCT_");
15             System.out.println("isbin: " + b.getIsbin() + "\\tprice: " +
               b.getPrice());
            }
         }
      }


Here is some sample data


    BookList.xml

      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
          <BookInventory>
              <Book>
                  <isbin>1209-01</isbin>
                  <title>New Math</title>
                  <price>40.00</price>
              </Book>
              <Book>
                  <isbin>2209-01</isbin>
                  <title>Old Math</title>
                  <price>4.00</price>
               </Book>
           </BookInventory>


The output should look like this.

 


      isbin: ACCT_1209-01      price: 44.0
      isbin: ACCT_2209-01      price: 4.4
      isbin:ACCT_9932-3423     price:0.44000000000000006


A zip file containing the sources for this example can be found here

The provided ant file requires you set 3 properties in order to resolve several Jar file references.

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

rsearls

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today