`
xinklabi
  • 浏览: 1560274 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

Simple XML用法(以面向对象的方式解析和持久化XML,类似ORM)

 
阅读更多
Serializing a simple object
<!-- padding -->

In order to serialize an object to XML a series of annotations must be placed within that object. These annotations tell the persister how the object should be serialized. For example take the class shown below. Here there are three different annotations, one used to describe the name of the root element, one that describes an XML message element, and a final annotation for an id attribute.

@Root
public class Example {

   @Element
   private String text;

   @Attribute
   private int index;

   public Example() {
      super();
   }  

   public Example(String text, int index) {
      this.text = text;
      this.index = index;
   }

   public String getMessage() {
      return text;
   }

   public int getId() {
      return index;
   }
}

To serialize an instance of the above object a Persister is required. The persister object is then given an instance of the annotated object and an output result, which is a file in this example. Other output formats are possible with the persister object.

Serializer serializer = new Persister();
Example example = new Example("Example message", 123);
File result = new File("example.xml");

serializer.write(example, result);

Once the above code is executed the object instance will have been transferred as an XML document to the specified file. The resulting XML file will contain the contents shown below.

<example index="123">
   <text>Example message</text>
</example>

As well as the capability of using the field an object name to acquire the XML element and attribute names explicit naming is possible. Each annotation contains a name attribute, which can be given a string providing the name of the XML attribute or element. This ensures that should the object have unusable field or method names they can be overridden, also if your code is obfuscated explicit naming is the only reliable way to serialize and deserialize objects consistently. An example of the previous object with explicit naming is shown below.

@Root(name="root")
public class Example {

   @Element(name="message")
   private String text;

   @Attribute(name="id")
   private int index;

   public Example() {
      super();
   }  

   public Example(String text, int index) {
      this.text = text;
      this.index = index;
   }

   public String getMessage() {
      return text;
   }

   public int getId() {
      return index;
   }
}

For the above object the XML document constructed from an instance of the object results in a different format. Here the XML element and attribute names have been overridden with the annotation names. The resulting output is shown below.

<root id="123">
   <message>Example message</message>
</root>
<!-- archive/deserializing_a_simple_object.html -->
Deserializing a simple object
<!-- padding -->

Taking the above example object the XML deserialization process is described in the code snippet shown below. As can be seen the deserialization process is just as simple. The persister is given the class representing the serialized object and the source of the XML document. To deserialize the object the read method is used, which produces an instance of the annotated object. Also, note that there is no need to cast the return value from the read method as the method is generic.

Serializer serializer = new Persister();
File source = new File("example.xml");

Example example = serializer.read(Example.class, source);
<!-- archive/nested_object_serialization.html -->
Nested object serialization
<!-- padding -->

As well as simple object serialization, nested object serialization is possible. This is where a serializable object can contain any number of serializable objects, to any depth. Take the example shown in the code snippet below. This shows several objects that are linked together to form a single serializable entity. Here the root configuration object contains a server object, which in turn contains a security information object.

@Root
public class Configuration {

   @Element
   private Server server;

   @Attribute
   private int id;

   public int getIdentity() {
      return id;
   }

   public Server getServer() {
      return server;           
   }
}

public class Server {

   @Attribute
   private int port;

   @Element
   private String host;

   @Element
   private Security security;

   public int getPort() {
      return port;           
   }

   public String getHost() {
      return host;           
   }

   public Security getSecurity() {
      return security;           
   }
}

public class Security {

   @Attribute
   private boolean ssl;

   @Element
   private String keyStore;

   public boolean isSSL() {
      return ssl;           
   }

   public String getKeyStore() {
      return keyStore;           
   }
}

In order to create an initialized configuration object an XML document can be used. This XML document needs to match the XML annotations for the object graph. So taking the above class schema the XML document would look like the following example.

<configuration id="1234">
   <server port="80">
      <host>www.domain.com</host>
      <security ssl="true">
         <keyStore>example keystore</keyStore>
      </security>
   </server>
</configuration>

How the mapping is done can be seen by examining the XML document elements and attributes and comparing these to the annotations within the schema classes. The mapping is quite simple and can be picked up and understood in several minutes.

<!-- archive/optional_elements_and_attributes.html -->
Optional elements and attributes
<!-- padding -->

At times it may be required to have an optional XML element or attribute as the source XML may not contain the attribute or element. Also, it may be that an object field is null and so cannot be serialized. In such scenarios the element or attribute can be set as not required. The following code example demonstrates an optional element and attribute.

@Root
public class OptionalExample {

   @Attribute(required=false)
   private int version;

   @Attribute
   private String id;

   @Element(required=false)
   private String name;   

   @Element
   private String address;

   public int getId() {
      return id;
   }

   public int getVersion() {
      return version;
   }

   public String getName() {
      return name;
   }

   public String getAddress() {
      return address;
   }
}

For the above object the version and name are not required. So, and XML source document may not contain either of these details and the object can still be serialized safely. For example take the following XML document, which is a valid representation of the above object.

<optionalExample id="10">
   <address>Some example address</address>
</optionalExample>

Even without the name and version XML nodes this document can be deserialized in to an object. This feature is useful when your XML contains optional details and allows more flexible parsing. To further clarify the implementation of optional fields take the example shown below. This shows how the entry object is deserialized from the above document, which is contained within a file. Once deserialized the object values can be examined.

Serializer serializer = new Persister();
File source = new File("example.xml");
OptionalExample example = serializer.read(OptionalExample.class, source);

assert example.getVersion() == 0;
assert example.getName() == null;
assert example.getId() == 10;
<!-- archive/reading_a_list_of_elements.html -->
Reading a list of elements
<!-- padding -->

In XML configuration and in Java objects there is often a one to many relationship from a parent to a child object. In order to support this common relationship an ElementList annotation has been provided. This allows an annotated schema class to be used as an entry to a Java collection object. Take the example shown below.

@Root
public class PropertyList {

   @ElementList
   private List<Entry> list;

   @Attribute
   private String name;

   public String getName() {
      return name;
   }

   public List getProperties() {
      return list;
   }
}

@Root
public class Entry {

   @Attribute
   private String key;

   @Element
   private String value;

   public String getName() {
      return name;
   }

   public String getValue() {
      return value;
   }
}

From the above code snippet the element list annotation can be seen. The field type is reflectively instantiated as a matching concrete object from the Java collections framework, typically it is an array list, but can be any collection object if the field type declaration provides a concrete implementation type rather than the abstract list type shown in the above example.

Below an XML document is shown that matches the schema class. Here each entry element will be deserialized using the declared entry class and inserted into the collection instance created. Once all entry objects have been deserialized the object instance contains a collection containing individual property objects.

<propertyList name="example">
   <list>
      <entry key="one">
         <value>first value</value>
      </entry>
      <entry key="two">
         <value>first value</value>
      </entry>
      <entry key="three">
         <value>first value</value>
      </entry>
      <entry key="four">
         <value>first value</value>
      </entry>
   </list>
</propertyList>

From the above example it can be seen that the entry details are taken from the generic type of the collection. It declares a list with the entry class as its generic parameter. This type of declaration is often not possible, for example if a specialized list contains more than one generic type which one is the correct type to use for deserialization or serialization. In such scenarios the type must be provided explicitly. Take the following example.

@Root
public class ExampleList {

   @ElementList(type=C.class)
   private SpecialList<A, B, C> list;

   public SpecialList<A, B, C> getSpecialList() {
      return list;
   }
}

In the above example the special list takes three generic parameters, however only one is used as the generic parameter for the collection. As can be seen an explicit declaration of which type to use is required. This can be done with the type attribute of the ElementList annotation.

<!-- archive/overriding_an_annotated_type.html -->
Overriding an annotated type
<!-- padding -->

In order to accommodate dynamic types within the deserialization process a class attribute can be added to an XML element, which will ensure that that element can be instantiated as the declared type. This ensures that field and method types can reference abstract classes and interfaces, it also allows multiple types to be added into an annotated collection.

package example.demo;

public interface Task {

   public double execute();
}

@Root
public class Example implements Task {

   @Element
   private Task task;

   public double execute() {
      return task.execute();
   }  
}

public class DivideTask implements Task {

   @Element(name="left")
   private float text;

   @Element(name="right")
   private float right;

   public double execute() {
      return left / right;
   }
}

public class MultiplyTask implements Task {

   @Element(name="first")
   private int first;

   @Element(name="second")
   private int second;

   public double execute() {
      return first * second;
   }
}

The class attribute must be a fully qualified class name so that the context class loader can load it. Also, the type can contain its own unique annotations and types which makes the deserialization and serialization process truly dynamic. Below is an example XML document declaring the class type for the task object.

<example>
   <task class="example.demo.DivideTask">
      <left>16.5</left>
      <right>4.1</right>
   </task>
</example>  

In order to execute the task described in the XML document the following code can be used. Here it is assumed the XML source is contained within a file. Once the example object has been deserialized the task can be executed and the result acquired.

Serializer serializer = new Persister();
File example = new File("example.xml");
Example example = serializer.read(Example.class, example)

double value = example.execute();
<!-- archive/dealining_with_inline_lists.html -->
Dealing with an inline list of elements
<!-- padding -->

When dealing with third party XML or with XML that contains a grouping of related elements a common format involves the elements to exist in a sequence with no wrapping parent element. In order to accomodate such structures the element list annotation can be configured to ignore the parent element for the list. For example take the following XML document.

<propertyList>
   <name>example</name>
   <entry key="one">
      <value>first value</value>
   </entry>
   <entry key="two">
      <value>second value</value>
   </entry>
   <entry key="three">
      <value>third value</value>
   </entry>
</propertyList>

In the above XML document there is a sequence of entry elements, however unlike the previous example these are not enclosed within a parent element. In order to achieve this the inline attribute of the ElementList annotation can be set to true. The following code snippet demonstrates how to use the inline attribute to process the above XML document.

@Root
public class PropertyList {

   @ElementList(inline=true)
   private List<Entry> list;

   @Element
   private String name;

   public String getName() {
      return name;
   }

   public List getProperties() {
      return list;
   }
}

There are a number of conditions for the use of the inline element list. Firstly, each element within the inline list must be placed one after another. They cannot be dispersed in between other elements. Also, each entry type within the list must have the same root name, to clarify take the following example.

package example.demo;

@Root
public class Entry {

    @Attribute
    protected String key;

    @Element
    protected String value;

    public String getKey() {
       return key;
    }
}

public class ValidEntry extends Entry {

   public String getValue() {
      return value;
   }
}

@Root
public class InvalidEntry extends Entry {

   public String getValue() {
      return value;
   }
}

@Root(name="entry")
public class FixedEntry extends InvalidEntry {
}

All of the above types extend the same base type, and so all are candidates for use with the PropertyList described earlier. However, although all types could be successfully deserialized and serialized using a list which is not inline, only some can be serialized with an inline list. For instance the type InvalidEntry could not be serialized as it will be serialized with a different name from all the other entrie implementations. The InvalidEntry object has a Root annotation which means that its XML element name will be "invalidEntry". In order to be used with the inline list all objects must have the same XML element name of "entry". By extending the InvalidEntry type and explicitly specifying the name to be "entry" the FixedEntry subclass can be used without any issues. For example take the following XML document, which could represent a mixture of entry types.

<propertyList>
   <name>example</name>
   <entry key="one" class="example.demo.ValidEntry">
      <value>first value</value>
   </entry>
   <entry key="two" class="example.demo.FixedEntry">
      <value>second value</value>
   </entry>
   <entry key="three" class="example.demo.Entry">
      <value>third value</value>
   </entry>
</propertyList>

All of the above entry elements within the inline list contain the same XML element name. Also each type is specified as a subclass implementation of the root Entry object.

<!-- archive/constructor_injection.html -->
Constructor injection
<!-- padding -->

All but the simplest of programs will have some form of immutable objects. These are objects that do not have setters and so will acquire data by using constructor injection. In this manner the object sets its internal state from the data provided to the constructor. This can also be achieved with serialization, if you would like to serialize and deserialize objects but do not want to provide setter methods this can be done, as illustrated in the example below.

@Root
public class OrderManager {

    private final List<Order> orders;

    public OrderManager(@ElementList(name="orders") List<Order> orders) {
        this.orders = orders;
    }

    @ElementList(name="orders")
    public List<Order> getOrders() {
        return orders;
    }
}

@Root
public class Order {

    @Attribute(name="name")
    private final String name;

    @Element(name="product")
    private final String product;

    public Order(@Attribute(name="name") String name, 
                 @Element(name="product") String product) 
    {
        this.product = product;
        this.name = name;
    }

    public String getProduct() {
        return product;
    }
}

The above code illustrates an order manager that contains a list of immutable order objects. On deserialization the values are taken from the XML document and injected in to the constructor to instantiate the object. This is a very useful feature that is not often found in serialization frameworks. One restriction on the constructor injection is that it must be used with an annotated get method or field. This is required so that on serialization the persister knows where to get the data to write. Taking the above example if the getOrders method was not annotated then there would be no way to determine how to write the order manager object. Below is some example XML resulting from serialization of the order manager.

<orderManager>
    <order name="AX101">
        <product>Product A</product>
    </order>
    <order name="AX102">
        <product>Product B</product>
    </order>
    <order name="AX103">
        <product>Product C</product>
    </order>
</orderManager>
<!-- archive/reading_an_array_of_elements.html -->
Reading an array of elements
<!-- padding -->

As well as being able to deserialize elements in to a collection arrays can also be serialized and deserialized. However, unlike the @ElementList annotation the ElementArray annotation can also deserialize primitive values such as int arrays, char arrays, and so on. Below is an example object with an array of integer values and a parallel array of string values.

@Root
public class AddressBook {

   @ElementArray
   private Address[] addresses;   

   @ElementArray
   private String[] names;        

   @ElementArray
   private int[] ages;   

   public Address[] getAddresses() {
      return addresses;           
   }

   public String[] getNames() {
      return names;           
   }

   public int[] getAges() {
      return ages;           
   }
}

@Root
public class Address {

   @Element(required=false)
   private String house;        

   @Element
   private String street;  

   @Element
   private String city;

   public String getHouse() {
      return house;           
   }

   public String getStreet() {
      return street;           
   }

   public String getCity() {
      return city;           
   }     
}

For the above object both primitive arrays require an entry attribute, this is because primitives can not be annotated with the Root annotation. The entry attribute tells the persister than an extra XML element is required to wrap the entry. This entry element can also be applied to serializable objects that have the Root annotation, however it is typically only used for primitive arrays. The following XML is an example of what is produced by the above objects.

<addressBook>
   <addresses length="3">
      <address>
         <house>House 33</house>
         <street>Sesame Street</street>
         <city>City</city>
      </address>
      <address>
         <street>Some Street</street>
         <city>The City</city>
      </address>
      <address>
         <house>Another House</house>
         <street>My Street</street>
         <city>Same City</city>
      </address>
   </addresses>
   <names length="3">
      <string>Jonny Walker</string>
      <string>Jack Daniels</string>
      <string>Jim Beam</string>
   </names>
   <ages length="3">
      <int>30</int>
      <int>42</int>
      <int>31</int>
   </ages>
</properties>

Looking at the above XML it can be seen that each entity within an array index is named the same as its type. So a string is wrapped in a 'string' element and an int is wrapped in an 'int' element. This is done because the default name for the ElementArray annotation is its type name, unless the Root annotation is used with a name. This can be overridden by providing an explicit entry name for the array. For example take the simple object below which contains an array of names as string objects.

@Root
public class NameList {

   @ElementArray(entry="name")
   private String[] names;        

   public String[] getNames() {
      return names;           
   }
}

For the above XML the following document is a valid representation. Notice how each of the names within the XML document is wrapped in a 'name' element. This element name is taken from the annotation provided.

<nameList>
   <names length="3">
      <name>Jonny Walker</name>
      <name>Jack Daniels</name>
      <name>Jim Beam</name>
   </names>
</nameList>
<!-- archive/adding_text_and_attributes_to_elements.html -->
Adding text and attributes to elements
<!-- padding -->

As can be seen from the previous example annotating a primitive such as a String with the Element annotation will result in text been added to a names XML element. However it is also possible to add text to an element that contains attributes. An example of such a class schema is shown below.

@Root
public class Entry {

   @Attribute
   private String name;

   @Attribute
   private int version;     

   @Text
   private String value;

   public int getVersion() {
      return version;           
   }

   public String getName() {
      return name;
   }

   public String getValue() {
      return value;              
   }
}

Here the class is annotated in such a way that an element contains two attributes named version and name. It also contains a text annotation which specifies text to add to the generated element. Below is an example XML document that can be generated using the specified class schema.

<entry version='1' name='name'>
   Some example text within an element
</entry>  

The rules that govern the use of the Text annotation are that there can only be one per schema class. Also, this annotation cannot be used with the Element annotation. Only the Attribute annotation can be used with it as this annotation does not add any content within the owning element.

<!-- archive/serialize_a_map_object.html -->
Dealing with map objects
<!-- padding -->

Although it is possible to deal with most repetitive XML elements within documents using element lists it is often more convenient to use a Map object. In order to deal with maps the ElementMap annotation can be used. The element map annotation can be used with both primitive and composite objects. For example take the following XML document.

<properties>
   <property key="one">first value</property>
   <property key="two">second value</property>
   <property key="three">third value</property>
   <name>example name</name>
</properties>

In the above XML document the sequence of properties elements can be used to describe a map of strings, where the key attribute acts as the key for the value within the property element. The following code snipped demonstrates how to use the ElementMap annotation to process the above XML document.

@Root(name="properties")
public class PropertyMap {

   @ElementMap(entry="property", key="key", attribute=true, inline=true)
   private Map<String, String> map;

   @Element
   private String name;  

   public String getName() {
      return name;
   }

   public Map<String, Entry> getMap() {
      return map;
   }
}
<!-- archive/scattering_a_list_of_elements.html -->
Scattering inline element entries
<!-- padding -->

Elements that are scattered throughout an XML document can be collected by inline lists and inline maps. Simply provide an entry name for the XML element name the list or map is to collect and they will be extracted and placed in to the collection object. For example take the following XML element. It contains include and exclude XML elements which are in no specific order. Even though they are not in any order the deserialization process is able to gather the XML elements as thet are encountered.

<fileSet path="/user/niall">
   <include pattern=".*.jar"/>
   <exclude pattern=".*.bak"/>
   <exclude pattern="~.*"/>
   <include pattern=".*.class"/>
   <exclude pattern="images/.*"/>
</fileSet>

In order to achieve this the following object can be used. This declares two inline collections which specify the name of the entry objects that they are collecting. If the entry attribute is not specified then the name of the object will be used instead.

@Root
public class FileSet {

   @ElementList(entry="include", inline=true)
   private List<Match> include;

   @ElementList(entry="exclude", inline=true)
   private List<Match> exclude;

   @Attribute
   private File path;

   private List<File> files;

   public FileSet() {
      this.files = new ArrayList<File>();
   }

   @Commit
   public void commit() {
      scan(path);
   }

   private void scan(File path) { 
      File[] list = path.listFiles();

      for(File file : list) {
         if(file.isDirectory()) {
            scan(path);
         } else {            
            if(matches(file)) {
               files.add(file);
            }
         }
      }
   }

   private boolean matches(File file) {
      for(Match match : exclude) {
         if(match.matches(file)) {
            return false;
         }
      }
      for(Match match : include) {
         if(match.matches(file)) {
            return true;
         }
      }
      return false;
   }

   public List<File> getFiles() {
      return files;
   }

   @Root
   private static class Match {

      @Attribute            
      private String pattern;            

      public boolean matches(File file) {
         Stirng path = file.getPath();

         if(!file.isFile()) {
            return false;
         }
         return path.matches(pattern);         
      }         
   }
}
<!-- archive/loose_object_mapping.html -->
Loose object mapping
<!-- padding -->

An important feature for any XML tool is the ability to sift through the source XML to find particular XML attributes an elements of interest. It would not be very convinient if you had to write an object that accurately mapped every attribute an element in an XML document if all you are interested in is perhaps an element and several attributes. Take the following XML document.

<contact id="71" version="1.0">
   <name>
      <first>Niall</first>
      <surname>Gallagher</surname>
   </name>
   <address>
      <house>House 33</house>
      <street>Sesame Street</street>
      <city>City</city>
   </address>
   <phone>
      <mobile>123456789</mobile>
      <home>987654321</home>
   </phone>
</example> 

If my object only required the some of the details of the specified contact, for example the phone contacts and the name then it needs to be able to ignore the address details safely. The following code shows how this can be done by setting strict to false within the Root annotation.

@Root(strict=false)
public class Contact {

   @Element
   private Name name;

   @Element
   private Phone phone;

   public String getName() {
      return name.first;
   }

   public String getSurname() {
      return name.surname;
   }

   public String getMobilePhone() {
      return phone.mobile;
   }

   public String getHomePhone() {
      return phone.home;
   }

   @Root
   private static class Name {
    
      @Element
      private String first;

      @Element
      private String surname;
   }

   @Root
   private static class Phone {

      @Element(required=false)
      private String mobile;

      @Element
      private String home;
   }
}

The above object can be used to parse the contact XML source. This simple ignores any XML elements or attributes that do not appear in the class schema. To further clarify the implementation of loose mappings take the example shown below. This shows how the entry object is deserialized from the above document, which is contained within a file. Once deserialized the object values can be examined.

Serializer serializer = new Persister();
File source = new File("contact.xml");
Contact contact = serializer.read(Contact.class, source);

assert contact.getName().equals("Niall");
assert contact.getSurname().equals("Gallagher");
assert contact.getMobilePhone().equals("123456789");
assert contact.getHomePhone().equals("987654321");

Should there be more than a single object that requires loose mapping then using the Root annotation might not be the ideal solution. In such a scenario the persister itself can be asked to perform loose mapping. Simply pass a boolean to the read method indicating the type of mapping required. By default the persister uses strict mapping, which can be overridden on an object by object basis using the Root annotation, as shown in the above example. However, this default can be overridden as can be seen in the code snippet below.

Contact contact = serializer.read(Contact.class, source, false);

Here the boolean passed to the overridden read method tells the serializer to perform a loose mapping. There is no need to specify anything in the annotations, the serializer will simply map every object loosely. This can be a much more convenient way to perform loose mapping, as you only need to annotate your objects with the elements or attributes you are interested in, all other elements and attributes will be ignored during deserialization. Such a solution is best suited to external XML documents where your annotated objects do not define the schema.

<!-- archive/java_bean_serialization.html -->
Java Bean serialization
<!-- padding -->

Although field based serialization offers a simple and efficient means for serializing and deserializing an object it can often be benificial to use Java Bean getters and setters to read and write values. In particular annotating Java Bean setter and getter methods will allow for a cleaner means to override the serialization behaviour than using fields. It also allows for processing and validation to be performed as the object is being deserialized. Below is an example of how to annotate an objects methods for use in the serialization process, this example mixes annotated fields with annotated methods.

@Root
public class Message {

   private Collection<Entry> list;
         
   @Attribute
   private float version;        

   @ElementList
   public void setList(Collection<Entry> entry) {
      if(entry.isEmpty()) {
         throw new IllegalArgumentException("Empty collection");              
      }
      this.entry = entry;           
   }        

   @ElementList
   public Collection<Entry> getList() {
      return entry;           
   }
}

@Root
public class Entry {

   @Attribute
   public String name;    

   public String text;   

   @Text
   public String getText() {
      return text;           
   }

   @Text
   public void setText(String text){
      this.text = text;           
   }
   
   public String getName() {
      return name;           
   }
}

In the above code the message class will have its methods invoked when a list of entry objects is encountered. Here the method can perform some form of validation when the list of entry objects is deserialized. Such validation can also be peformed using the persister callback methods, which is described in a later section. The requirements for Java Bean method serialization are that both the setter and getter must be annotated with the same annotation, and both annotations must contain identical attributes. The object class schema could produce the following XML document.

<message version="1.2">
   <list>
      <entry name="a">Example text one</entry>
      <entry name="b">Example text two</entry>
   </list>
</message>
<!-- archive/default_serialization.html -->
Default object serialization
<!-- padding -->

If an object grows large it often becomes tedious to annotate each field or method that needs to be serialized. In such scenarios the Default annotation can be used. This will apply default annotations to either the fields or methods of an object that is to be serialized. To specify whether it is the fields or methods that will have default annotations, the DefaultType enumeration can be used. Take the code snippet below, this shows two objects with default annotations, one that will apply defaults to the object fields, and one that will apply defaults to the Java Bean methods of the object.

@Root
@Default(DefaultType.FIELD)
public class OrderItem {

   private Customer customer;

   private String name;

   @Attribute
   private double price;

   @Transient
   private String category;

   public String getName() {
      return name;
   }   

   public Customer getCustomer() {
      return customer;
   }
}

@Root
@Default(DefaultType.PROPERTY)
private class Customer {

   private String name;

   private String address;

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}

In the above object the Transient annotation is used to specify that even though default annotations should be applied to the objects fields, the field annotated as transient should not be serialized. Below is the XML that could be produced using the above classes, notice that if defaults apply to Java Bean methods, the fields will not be defaulted, instead they will be ignored.

<orderItem price="10.99">
    <customer>
        <name>Elvis Presley</name>
    </customer>
    <name>IRT452</name>
</orderItem>

From the above XML it is obvious, that defaults apply to only those fields or methods requested. If a field or method already has an annotation, that is the annotation that is used. If a field or method is to be omitted from serialization then it can be marked as transient. Applying defaults to an object, can often lead to a cleaner object structure, and makes it much easier to make objects serializable.

<!-- archive/example_using_template_filters.html -->
Example using template filters
<!-- padding -->

Another very powerful feature with this XML serialization framework is the ability to use templating when deserializing an XML document. This allows values within elements and attributes to use template variables that can be replaced using a Filter object. The simplest filter object is the map filter, which allows the user to place a Java map within the filter object exposing the key value pairs to the templating system. The template system can now use the filter to find replacement values for template variables within the XML document. To clarify take the following example.

@Root
public class Layout {

   @Element
   private String path;

   @Element
   private String user;

   @Attribute
   private int id;

   public String getPath() {
      return path;
   }

   public String getUser() {
      return user;
   }

   public int getId() {
      return id;
   }
}   

The above object has declared two elements and an attribute to be deserialized from an XML document. These values are typically static values within the XML source. However using a template variable syntax the deserialization process will attempt to substitute the keys with values from the filter. Take the XML document below with two template variables declared ${home.path} and ${user.name}.

<layout id="123">
   <path>${home.path}</path>
   <user>${user.name}</user>
</layout> 
To ensure that these values can be replaced with user specified mappings a map filter can be used. Below is an example of how to create a persister that can be given user specified key value pairs. Here the above XML source is deserialized from a file and the annotated fields are given filter mappings if there is a mapping specified.
Map map = new HashMap();

map.put("home.path", "/home/john.doe");
map.put("user.name", "john.doe");

Filter filter = new MapFilter(map);
Serializer serializer = new Persister(filter);
File source = new File("layout.xml");
Layout layout = serializer.read(Layout.class, source);

assert layout.getPath().equals("/home/john.doe");
assert layout.getUser().equals("john.doe");

As well as the map filter there are several stock filters which can be used to substitute template variables with OS environment variables and JVM system properties. Also several template variables can exist within the values. For example take the following XML document, which could be used in the above example given that the mappings for ${first.name} and ${second.name} were added to the map filter.

<layout id="123">
   <path>/home/${first.name}.${second.name}</path>
   <user>${first.name}.${second.name}</user>
</layout> 
<!-- archive/receiving_persister_callbacks.html -->
Receiving persister callbacks
<!-- padding -->

Of critical importance to the serialization and deserialization process is that the objects have some control or participation in the process. It is no good to have the persister deserialize the object tree from an XML document only to see that the data is not valid or that further data structures need to be created in many of the deserialized objects. To allow objects to participate in the deserialization process two annotations can be used, these are the Validate and Commit annotations.

Both are involved in the deserialization process (not the serialization process) and are called immediately after an object has been deserialized. Validation is performed first, and if the deserialized object contains a method annotated with the validate annotation it is invoked. This allows the object to perform validation of its fields, if the object requirements are met the method returns quietly, if they are not met the object can throw an exception to terminate the deserialization process. The commit method is invoked in much the same way, the persister looks for a method marked with the commit annotation, if one exists it is invoked. However, unlike the validate method the commit method is typically used to build further data structures, for example hash tables or trees. Below is an example of an object making use of these annotations.

@Root
public class PropertyMap {

   private Map<String, Property> map;

   @ElementList
   private List<Property> list;

   public PropertyMap() {
      this.map = new HashMap<String, Entry>();
   }

   @Validate
   public void validate() {
      List<String> keys = new ArrayList<String>();

      for(Property entry : list) {
         String key = entry.getKey();

         if(keys.contains(key)) {
            throw new PersistenceException("Duplicate key %s", key);
         }
         keys.put(key);         
      }      
   }

   @Commit
   public void build() {
      for(Property entry : list) {
         insert(entry);
      }     
   }

   public void insert(Property entry) {
      map.put(entry.getName(), entry);      
   }  

   public String getProperty(String name) {
      return map.get(name).getValue();
   }
}
The above object deserializes a list of property objects into a list. Once the property objects have been deserialized they are validated by checking that an entry with a specific key exists only once. After the validation process has completed the commit method is invoked by the persister, here the object uses the deserialized property object to build a hash table containing the property values keyed via the property key. Below is how the above object would be represented as an XML document.
<properties>
   <list>
      <entry key="one">
         <value>first value</value>
      </entry>
      <entry key="two">
         <value>first value</value>
      </entry>
      <entry key="three">
         <value>first value</value>
      </entry>
   </list>
</properties>
As well as annotations involved in the deserialization process there are annotations that can be used to receive persister callbacks for the serialization process. Two annotations can be used, they are the Persist and Complete methods. To receive persister callbacks the methods must be no argument methods marked with the appropriate annotations.

 

The persist method is invoked before the serialization of the object. This allows the object to prepare in some implementation specific way for the serialization process. This method may throw an exception to terminate the serialization process. Once serialization has completed the complete method is invoked. This allows the object to revert to its previous state, that is, to undo what the persist method has done. Below is an example of how these annotations can be used.

@Root
public class MailMessage {
   
   @Attribute
   private Stirng format;

   @Element
   private String encoded;

   private byte[] content;

   private Encoder encoder;

   public MailMessage() {
      this.encoder = new Encoder();
   }

   public void setEncoding(String format) {
      this.format = format;
   }

   public String getEncoding() {
      return format;
   }

   public void setMessage(byte[] content) {
      this.content = content;
   }

   public byte[] getMessage() {
      return content;
   }

   @Commit
   public void commit() {
      decoded = encoder.decode(encoded, format);
      encoded = null;
   }

   @Persist
   public void prepare() {
      encoded = encoder.encode(decoded, format);      
   }

   @Complete
   public void release() {
      encoded = null;
   }
}

The above example illustrates how the persist and complete methods can be used in a scenario where the serialization process needs to encode a byte array into a specific encoding format. Before the object is persisted the persistable field is set to an encoded string. When serialization has completed the encoded value is nulled to free the memory it holds. This example is somewhat contrived however it effectively demonstrates how the annotations can be used. Below is an example of what the XML document should look like.

<mailMessage format="base64">
    U2ltcGxlIGlzIGFuIFhNTCBzZXJpYWxpemF0aW9uIGZyYW1ld29yayBmb3IgSmF2YS4gSXRzIGdv
    YWwgaXMgdG8gcHJvdmlkZSBhbiBYTUwgZnJhbWV3b3JrIHRoYXQgZW5hYmxlcyByYXBpZCBkZXZl
    bG9wbWVudCBvZiBYTUwgY29uZmlndXJhdGlvbiBhbmQgY29tbXVuaWNhdGlvbiBzeXN0ZW1zLiBU
    aGlzIGZyYW1ld29yayBhaWRzIHRoZSBkZXZlbG9wbWVudCBvZiBYTUwgc3lzdGVtcyB3aXRoIG1p
    bmltYWwgZWZmb3J0IGFuZCByZWR1Y2VkIGVycm9ycy4gVGhlIGZyYW1ld29yayBib3Jyb3dzIGlk
    ZWFzIGFuZCBjb25jZXB0cyBmcm9tIGV4aXN0aW5nIFhNTCB0b29scyBzdWNoIGFzIEMjIFhNTCBz
    ZXJpYWxpemF0aW9uIGFuZCBvdGhlciBwcm9wcmlldGFyeSBmcmFtZXdvcmtzIGFuZCBjb21iaW5l
    cyB0aG9zZSBpZGVhcyByZXN1bHRpbmcgaW4gYSBzaW1wbGUgeWV0IGV4dHJlbWVseSBwb3dlcmZ1
    bCB0b29sIGZvciB1c2luZyBhbmQgbWFuaXB1bGF0aW5nIFhNTC4gQmVsb3cgaXMgYSBsaXN0IG9m
    IHNvbWUgb2YgdGhlIGNhcGFiaWxpdGllcyBvZiB0aGUgZnJhbWV3b3JrLiA=    
</mailMessage>

For the above XML message the contents can be serialized and deserialized safely using persister callbacks. The object can prepare itself before serialization by encoding the contents of the message to the encoding format specified. Once it has been encoded and serialized any resources created for serialization can be released.

<!-- archive/maintaining_state_betwen_persister_callbacks.html -->
Maintaining state between persister callbacks
<!-- padding -->

When serializing and deserializing objects there is often a need to share information between callbacks without affecting the object implementation. In order to achieve this the persister can provide a session map to the methods annotated for persister callbacks. Below is an example of a serializable object that can receive a persister session object.

@Root
public class Person {

   @ElementList
   private List<Variable> details;

   @Element
   private Address address;

   private List names;
   
   @Validate
   public void validate(Map session) throws PersistenceException {
      if(session.isEmpty()) {
         throw new PersistenceException("Map must not be empty")
      }
   }

   @Commit
   public void commit(Map session) {
      Set keys = session.keySet();

      for(Object item : keys) {
         names.add(item);
      }
   }
}

@Address
public class Address {

   @Element
   private String street;

   @Element
   private String city;

   @Element
   private String state;

   public String getStreet() {
      return street;
   }

   public String getCity() {
      return city;
   }

   public String getState() {
      return state;
   }
}

@Root
public class Variable {

   @Attribute
   private String name;

   @Attribute
   private String value;

   @Commit
   public void commit(Map session) {
      session.put(name, value);
   }

   public String getName() {
      return name;
   }

   public String getValue() {
      return value;
   }
}

The above example shows how entry objects can pass there names to its parent during the deserialization process. To clarify, deserialization is performed in a depth first manner so for this example the entry objects will be initialized and have their callback methods invoked before the root example class.

 

Although this may not seem like a very powerful feature, it offers great capabilities when paired with the templating system described earlier. The templating engine has access to all details placed into the session map object. So other values within the XML document can reference each other. For example take the XML document below for the above objects.

<person>
   <details>
      <var name="name" value="John Doe"/>
      <var name="street" value="Sesame Street"/>
      <var name="city" value="Metropolis"/>
      <var name="state" value="Some State"/>
   </details>
   <address>
      <street>${street}</street>
      <city>${city}</city>
      <state>${state}</state>
   </address>   
</person>

The above XML document illustrates how the variable objects values are accessible to the elements declared in the address element. The street, city, and state needed to be defined only once to be shared throughout the document.

 

<!-- archive/serializing_with_cdata_blocks.html -->
Serializing with CDATA blocks
<!-- padding -->

At times it is nessecary to serialize large text and element data values. Such values may also contain formatting that you wish to preserve. In such situations it is often best to wrap the values within XML CDATA blocks. The CDATA block can contain XML characters and formatting information which will not be modified by other XML parsers. For example take the following XML source.

<query type="scrape" name="title">
   <data><![CDATA[

         <news>
         {
            for $text in .//B
            return $text
         }
         </news>

    ]]></data>
</query>

The above XML there is an embedded XQuery expression which is encapsulated within a CDATA block. Such a configuration allows the XQuery expression to exist within the XML document without any need to escape the XML characters. Also, if the XQuery expression was very large then this form of encoding would provide better performance. In order to ensure that the data is maintained within the CDATA block the following could be used.

@Root
public class Query {

   @Attribute
   private String scrape;

   @Attribute
   private String title;

   @Element(data=true)
   private String data;  

   public String getData() {
      return data;
   }

   public String getTitle() {
      return title;
   }

   public String getScrape() {
      return scrape;
   }
}

Here the Element annotation has the data attribute set to true. This tells the serialization process that any value stored within the data field must be written to the resulting XML document within a CDATA block. The data attribute can be used with the Text, ElementArray, and ElementList annotations also.

<!-- archive/using_namespaces.html -->
Using XML namespaces
<!-- padding -->

Namespaces are used to qualify an element or an attribute in an XML document. In order to use namespaces the Namespace annotation can be used. This allows the declaration of the namespace prefix and reference, often called the namespace URI. Namespace annotations can be used on fields, methods, and even classes. For example take the XML snippet below.

<parent xmlns="/parent">
   <pre:child xmlns:pre="/child">
      <name>John Doe</name>
      <address xmlns="">
          <street>Sin City</street>
      </address>
   </pre:child>
</parent>

In the above XML document, the root element is qualified with a default namespace. A default namespace is a namespace that is inherited by all child elements, for further details see Section 6.2 of the namespaces in XML 1.0 specification. In order to annotate a field, method, or class with a default namespace the Namespace annotation can be declared using only the reference attribute. For example, see the annotated class below that produces the above XML.

@Root
@Namespace(reference="/parent")
public class Parent {

   @Element
   @Namespace(reference="/child", prefix="pre")
   private Child child;
    
   public Child getChild() {
      return child;
   }
}

@Root
public class Child {

   @Element
   private String name;

   @Element
   @Namespace
   private Address address;

   public Address getAddress() {
      return address;
   }
}

@Root
public class Address {
   
   @Element
   private String street;

   public String getStreet() {
      return street;
   }
}

The above code snippet also shows an annotation with both the namespace reference and prefix attributes declared. Such an annotation declaration will result in a namespace qualified with a prefix. As can be seen in the XML example a prefixed namespace qualifies the XML element with a string prefix followed by a colon.

Should your document require more than one namespace declared in a single element the NamespaceList annotation can be used. This allows multiple namespaces to be declared in a single element. Declaring multiple namespaces in a single element can produce a cleaner more readable XML document. Take the XML snippet below from the namespaces in XML 1.0 specification, which shows an element with multiple namespaces.

<?xml version="1.0"?>
<book xmlns="urn:loc.gov:books" xmlns:isbn="urn:ISBN:0-395-36341-6">
    <title>Cheaper by the Dozen</title>
    <isbn:number>1568491379</isbn:number>
</book>

This XML snippet shows two namespaces declared in the root element. Here the root element will be qualified with the default namespace, and child elements can if desired be qualified by the prefixed namespace. To illustrate how such namespace declarations can be done, see the annotated class below.

@Root
@NamespaceList({
@Namespace(reference="urn:loc.gov:books")
@Namespace(reference="urn:ISBN:0-395-36341-6", prefix="isbn")
})
public class Book {

   @Element
   @Namespace(reference="urn:ISBN:0-395-36341-6")
   private String number;

   @Element
   private String title;

   public String getTitle() {
      return title;
   }
}

As can be seen above, there is no need to redeclare the prefix attribute once it has already been declared. This allows the annotation declarations to be less verbose and ensures a consistent use of a prefix for a given namespace reference. Also, once a namespace has been declared and is in scope then it will not be declared a second time in the resulting XML, this ensures the resulting XML document does not contain redundant namespace declarations.

<!-- archive/resolving_object_reference_cycles.html -->
Resolving object reference cycles
<!-- padding -->

When there are cycles in your object graph this can lead to recursive serialization. However it is possible to resolve these references using a stock strategy. The CycleStrategy maintains the object graph during serialization and deserialization such that cyclical references can be traced and resolved. For example take the following object relationships.

@Root
public class Parent {

   private Collection<Child> children;

   private String name;

   @Attribute
   public String getName() {
      return name;                   
   }

   @Attribute
   public void setName(String name) {
      this.name = name;           
   }

   @Element
   public void setChildren(Collection<Child> children) {
      this.children = children;           
   }
    
   @Element   
   public Collection<Child> getChildren() {
      return children;           
   }        

   public void addChild(Child child) {
      children.add(child);           
   }
}

@Root
public class Child {

   private Parent parent;

   private String name;

   public Child() {
      super();           
   }

   public Child(Parent parent) {
      this.parent = parent;           
   }

   @Attribute
   public String getName() {
      return name;           
   }

   @Attribute
   public void setName(String name) {
      this.name = name;           
   }

   @Element
   public Parent getParent() {
      return parent;
   }

   @Element
   public void setParent(Parent parent) {
      this.parent = parent;
   }
}

In the above code snippet the cyclic relation ship between the parent and child can be seen. A parent can have multiple children and a child can have a reference to its parent. This can cause problems for some XML binding and serialization frameworks. However this form of object relationship can be handled seamlessly using the CycleStrategy object. Below is an example of what a resulting XML document might look like.

<parent name="john" id="1">
   <children>
      <child id="2" name="tom">
         <parent ref="1"/>
      </child>
      <child id="3" name="dick">
         <parent ref="1"/>
      </child>
      <child id="4" name="harry">
         <parent ref="1"/>
      </child>
   </children>
</parent>

As can be seen there are two extra attributes present, the id attribute and the ref attribute. These references are inserted into the serialized XML document when the object is persisted. They allow object relationships and references to be recreated during deserialization. To further clarify take the following code snippet which shows how to create a persister that can handle such references.

Strategy strategy = new CycleStrategy("id", "ref");
Serializer serializer = new Persister(strategy);
File source = new File("example.xml");
Parent parent = serializer.read(Parent.class, source);

The strategy is created by specifying the identity attribute as id and the refering attribute as ref. For convinience these attributes have reasonable defaults and the no argument constructor can be used to create the strategy. Although the example shown here is very simple the cycle strategy is capable of serializing and deserializing large and complex relationships.

<!-- archive/reusing_xml_elements.html -->
Reusing XML elements
<!-- padding -->

As can be seen from using the CycleStrategy in the previous section object references can easily be maintained regardless of complexity. Another benifit of using the cycle strategy is that you can conviniently reuse elements when creating configuration. For example take the following example of a task framework.

@Root
public class Workspace {

   @Attribute
   private File path;

   @Attribute
   private String name

   private File getPath() {
      return path;           
   }

   private String getName() {
      return name;           
   }
}

@Root
public abstract Task {

   @Element        
   private Workspace workspace;         

   public abstract void execute() throws Exception;
}

public class DeleteTask extends Task {

   @ElementList(inline=true, entry="resource")        
   private Collection<String> list;        

   public void execute() {
      File root = getPath();

      for(String path : list) {
         new File(root, path).delete();              
      }
   }  
}

public class MoveTask extends Task {

   @ElementList(inline=true, entry="resource")
   private Collection<String> list;

   @Attribute
   private File from;

   public void execute() {
      File root = getPath();

      for(String path : list) {
         File create = new File(root, path);
         File copy = new File(from, path);

         copy.renameTo(create);
      }
   }
}

The above code snippet shows a very simple task framework that is used to perform actions on a workspace. Each task must contain details for the workspace it will perform its specific task on. So, making use of the cycle strategy it is possible to declare a specific object once, using a know identifier and referencing that object throughout a single XML document. This eases the configuration burden and ensures that less errors can creap in to large complex documents where may objects are declared.

<job>
   <workspace id="default">
      <path>c:\workspace\task</path>
   </workspace>
   <task class="example.DeleteTask">
      <workspace ref="default"/>
      <resource>output.dat</resource>
      <resource>result.log</resource>
   </task>
   <task class="example.MoveTask">
      <workspace ref="default"/>
      <from>c:\workspace\data</from>
      <resource>input.xml</resource>
   </task>
</job>
<!-- archive/using_utility_collections.html -->
Using utility collections
<!-- padding -->

For convinience there are several convinience collections which can be used. These collections only need to be annotated with the ElementList annotation to be used. The first stock collection resembles a map in that it will accept values that have a known key or name object, it is the Dictionary collection. This collection requires objects of type Entry to be inserted on deserialization as this object contains a known key value. To illustrate how to use this collection take the following example.

@Root
public class TextMap {
   
   @ElementList(inline=true)
   private Dictionary<Text> list;   

   public Text get(String name) {
      return list.get(name);           
   }
}

@Root
public class Text extends Entry {

   @Text          
   public String text;

   public String getText() {
      return text;           
   }
}

The above objects show how the dictionary collection is annotated with the element list annotation. The containing object can not serialize and deserialize entry objects which can be retrieve by name. For example take the following XML which shows the serialized representation of the text map object.

<textMap>
   <text name="name">Niall Gallagher</text>
   <text name="street">Seasme Street</text>
   <text name="city">Atlantis</text>
</textMap>

Each text entry deserialized in to the dictionary can now be acquired by name. Although this offers a convinient map like structure of acquring objects based on a name there is often a need to match objects. For such a requirement the Resolver collection can be used. This offers a fast pattern matching collection that matches names or keys to patterns. Patterns are deserialized within Match objects, which are inserted in to the resolver on deserialization. An example of the resolver is shown below.

@Root
private static class ContentType extends Match {

   @Attribute
   private String value;        

   public ContentType() {
      super();                  
   }

   public ContentType(String pattern, String value) {
      this.pattern = pattern;
      this.value = value;        
   }
}
   
@Root
private static class ContentResolver implements Iterable {

   @ElementList
   private Resolver<ContentType> list;           

   @Attribute
   private String name;

   public Iterator<ContentType> iterator() {
      return list.iterator();
   }

   public ContentType resolve(String name) {
      return list.resolve(name);              
   }
}   

The above content resolver will match a string with a content type. Such an arrangement could be used to resolve paths to content types. For example the following XML document illustrates how the resolver could be used to match URL paths to content types for a web application.

<contentResolver name="example">
   <contentType pattern="*.html" value="text/html"/>
   <contentType pattern="*.jpg" value="image/jpeg"/>
   <contentType pattern="/images/*" value="image/jpeg"/>
   <contentType pattern="/log/**" value="text/plain"/>
   <contentType pattern="*.exe" value="application/octetstream"/>
   <contentType pattern="**.txt" value="text/plain"/>
   <contentType pattern="/html/*" value="text/html"/>
</contentResolver>

Although the resolver collection can only deal with wild card characters such as * and ? it is much faster than resolutions performed using Java regular expressions. Typically it is several orders of magnitude faster that regular expressions, particularly when it is used to match reoccuring values, such as URI paths.

<!-- archive/object_substitution.html -->
Object substitution
<!-- padding -->

Often there is a need to substitute an object into the XML stream either during serialization or deserialization. For example it may be more convinient to use several XML documents to represent a configuration that can be deserialized in to a single object graph transparently. For example take the following XML.

<registry>
    <import name="external.xml" class="example.ExternalDefinition"/>
    <define name="blah" class="example.DefaultDefinition">
       <property key="a">Some value</property>
       <property key="b">Some other value</property>
    </define>
 </registry>

In the above XML document there is an import XML element, which references a file external.xml. Given that this external file contains further definitions it would be nice to be able to replace the import with the definition from the file. In such cases the Resolve annotation can be used. Below is an example of how to annotate your class to substitute the objects.

@Root
private class Registry {

   @ElementList(inline=true)
   private Dictionary<Definition> import;

   @ElementList(inline=true)
   private Dictionary<Definition> define;

   public Definition getDefinition(String name) {
      Definition value = define.get(name);
 
      if(value == null) {
         value = import.get(name);
      }
      return value;
   }
}
 
public interface Definition {

   public String getProperty(String key);
}

@Root(name="define")
public class DefaultDefinition implements Definition {

   @ElementList(inline=true)
   private Dictionary<Property> list;

   public String getProperty(String key) { 
      return list.get(key);
   }
}

@Root(name="import")
public class ExternalDefinition implements Definition {

   @Element
   private File name;

   public String getProperty(String key) {
      throw new IllegalStateException("Method not supported");
   }    

   @Resolve
   public Definition substitute() throws Exception {
      return new Persister().read(Definition.class, name);
   }
}

Using this form of substitution objects can be replaced in such a way that deserialized objects can be used as factories for other object instances. This is similar to the Java serialization concept of readResolve and writeReplace methods.

<!-- archive/serializing_java_langiage_types.html -->
Serializing Java language types
<!-- padding -->

A common requirement of any serialization framework is to be able to serialize and deserialize existing types without modification. In particular types from the Java class libraries, like dates, locales, and files. For many of the Java class library types there is a corrosponding Transform implementation, which enables the serialization and deserialization of that type. For example the java.util.Date type has a transform that accepts a date instance and transforms that into a string, which can be embedded in to the generated XML document during serialization. For deserialization the same transform is used, however this time it converts the string value back in to a date instance. The code snippet below demonstrates how a such transformations make it possible to use such a type when implementing your class XML schema.

@Root
public class DateList {

   @Attribute
   private Date created;

   @ElementList
   private List<Date> list;

   public Date getCreationDate() {
      retrun created;
   }

   public List<Date> getDates() {
      return list;
   }
}

Here the date object is used like any other Java primitive, it can be used with any of the XML annotations. Such objects can also be used with the CycleStrategy so that references to a single instance within your object graph can be maintained throughout serialization and deserialization operations. Below is an example of the XML document generated.

<dateList created="2007-01-03 18:05:11.234 GMT">
    <list>
        <date>2007-01-03 18:05:11.234 GMT</date>
        <date>2007-01-03 18:05:11.234 GMT</date>
    </list>
</dateList>
Using standard Java types, such as the Date type, can be used with any of the XML annotations. The set of supported types is shown below. Of particular note are the primitive array types, which when used with the ElementArray annotation enable support for multidimentional arrays.
char
char[]
java.lang.Character
java.lang.Character[]
int
int[]
java.lang.Integer 
java.lang.Integer[]
short
short[]
java.lang.Short
java.lang.Short[]
long        
long[]
java.lang.Long
java.lang.Long[]
double
double[]
java.lang.Double
java.lang.Double[]
byte
byte[]
java.lang.Byte
java.lang.Byte[]
float       
float[]        
java.lang.Float        
java.lang.Float[]        
boolean
boolean[]
java.lang.Boolean
java.lang.Boolean[]
java.lang.String
java.lang.String[]
java.util.Date
java.util.Locale
java.util.Currency
java.util.TimeZone
java.util.GregorianCalendar
java.net.URL
java.io.File
java.math.BigInteger
java.math.BigDecimal
java.sql.Date
java.sql.Time
java.sql.Timestamp

For example take the following code snippet, here points on a graph are represented as a multidimentional array of integers. The array is annotated in such a way that it can be serialized and deserialized seamlessly. Each index of the array holds an array of type int, which is transformed using the Transformer in to a comma separated list of integer values. Obviously this is not of much use in a real world situation, however it does illustrate how the transformable types can be integrated seamlessly with existing XML annotations.

@Root
public class Graph {

   @ElementArray(entry="point")
   private int[][] points;

   public Graph() {
      super();
   }

   @Validate
   private void validate() throws Exception {
      for(int[] array : points) {
         if(array.length != 2) {
            throw new InvalidPointException("Point can not have %s values", array.length);
         }
      }
   }

   public int[][] getPoints() {
      return points;      
   }
}

For the above code example the resulting XML generated would look like the XML document below. Here each index of the element array represents an array of integers within the comma separated list. Such structures also work well with the cycle strategy in maintaining references.

<graph>
   <points length="4">
      <point>3, 5</point>
      <point>5, 6</point>
      <point>5, 1</point>
      <point>3, 2</point>
   </points>
</graph>
<!-- archive/styling_serialized_xml.html -->
Styling serialized XML
<!-- padding -->

In order to serialize objects in a consistent format a Style implementation can be used to format the elements and attributes written to the XML document. Styling of XML allows both serialization and deserialization to be performed. So once serialized in a styled XML format you can deserialize the same document back in to an object.

@Root
public class PersonProfile {

   @Attribute
   private String firstName;

   @Attribute
   private String lastName;

   @Element
   private PersonAddress personAddress;

   @Element
   private Date personDOB;

   public Date getDateOfBirth() {
      return personDOB;
   }

   public String getFirstName() {
      return firstName;
   }

   public String getLastName() {
      return lastName;
   }

   public PersonAddress getAddress() {
      return personAddress;
   }
}

@Root
public class PersonAddress {

   @Element
   private String houseNumber;

   @Element
   private String streetName;

   @Element
   private String city;

   public String getHouseNumber() {
      return houseNumber;
   }

   public String getStreetName() {
      return streetName;
   }

   public String getCity() {
      return city;
   }
}

For example, taking the above annotated objects. An instance of the person profile can be serialized in to an XML document that is styled with a hyphenated format. This produces a consistently formated result which is just as deserializable as a serialization that is not styled.

<person-profile first-name="Niall" last-name="Gallagher">
   <person-DOB>10/10/2008</person-DOB>
   <person-address>
      <house-number>10</house-number>
      <street-name>Sesame Street</street-name>
      <city>Disney Land</city>
   </person-address>
</person-profile>

In order to serialize an object in a styled format either the HyphenStyle or CamelCaseStyle can be used. If neither suits one can always be implemented. Also, for convenience any of the elements or attributes can be overridden with a specific string by setting it to the style instance. The code snippet below shows how to serialize the object in the hyphenated style above.

Style style=new HyphenStyle();
Format format = new Format(style);
Serializer serializer = new Persister(format);

serializer.write(personDetail, file);
<!-- archive/version_tolerant_serialization.html -->
Version tolerant serialization
<!-- padding -->

In order to serialize objects in a version tolerant format a Version annotation can be introduced to the class. This will allow a later, modified class to be read from XML generated by the original class. For example take the following code snippet showing an annotated class.

@Root
@Namespace(prefix="p", reference="/person")
public class Person {

  @Attribute
  private String name;

  @Element
  private String height;

  @Element
  private String weight;

  public String getName() {
     return name;
  }
  
  public String getHeight() {
     return height;
  }

  public String getWeight() {
     return weight;
  }
}

The above annotated class schema will generate XML in a format compatible with that class. For example, a serialization of the class could result in the following XML snippet. This shows the height and weight elements as well as the name attribute.

<p:person name="John Doe" xmlns:p="/person">
    <height>185</height>
    <weight>84</height>
</p:person>

Having used this class schema to serialize instances of the Person class, It could later be extended or modified as follows and still read and write in a format compatible with the old class schema like so, even though the resulting XML has changed.

@Root
@Namespace(prefix="p", reference="/person")
public class Person {

  @Version(revision=1.1)
  private double version;

  @Attribute
  private String name;

  @Attribute
  private int age;

  @Element
  private int height;

  public String getName() {
     return name; 
  }
  
  public int getHeight() {
     return height;
  }

  public int getAge() {
     return age;
  }
}

Here the version attribute is annotated with the special Version annotation. This will read the previously generated XML and compare the version attribute of the person element and compare it to the revision attribute of the annotation. If the version annotation does not exist the initial 2.0 version is assumed. So when using the new modified class, which is revision 1.1, with the old serialized XML the serializer will determine that the two have differing versions. So when deserializing it will ignore the excess weight element and ignore the fact that the age attribute does not exist. It will do this for all attributes and elements that do not match.

This is quite similar to the C# XML serialization version capability. Where the initial version of each class is 1.0 (implicitly) and subsequent versions increase. This tells the serializer how it should approach deserialization of different versions. The later version of the class when serialized will explicitly write the version as follows.

<p:person version="1.1" name="John Doe" age="60" xmlns:p="/person">
    <height>185</height>
</p:person>
<!-- archive/annotating_static_fields.html -->
Serializing static final fields
<!-- padding -->

Often there is a need to add elements and attributes to an XML document that do not change. In such an event it is often attractive to declare these fields as static final fields. When annotating static final fields they form part of the XML schema and contribute to the validation of the document but do not get set when deserializing the XML in to a object instance. So should a required static final field not exist in the source XML then an exception is thrown when deserializing, much like what would happen if the field was mutable. For example take the code snippet below, which shows an immutable static final field used with mutable fields.

@Root
public class Citation {

   @Element(name="author")
   private static final String AUTHOR = "Niall Gallagher";

   @Element
   private String title;
   
   @Element   
   private String date;

   public Date getDate() {
      return date;
   }
}

The above annotated object describes a simple citation. The citation object itself contains a static final field that will be written to the resulting XML document when serialized. However, on deserialization the values read from the XML will not change the annotated final static field. Instead the deserialization process will simply validate the presence of the elements and attributes within the document. This results in an object that will always write the same value for the annotated final static elements or attributes.

<citation>
   <author>Niall Gallagher</author>
   <title>Simple XML Serialization</author>
   <date>12 January 2010</date>
</citation>
<!-- archive/overriding_serialization.html -->
Overriding serialization with converters
<!-- padding -->

Often times there is a need to serialize an object that can not be annotated. Such objects may exist in third party dependencies or packages. To serialize these objects a Converter object can be implemented. A converter can be used to intercept the normal serialization process. Interception of the normal serialization process can be done in several different ways, for example take the code snippet below.

@Root
public class Example {

   @Element
   @Convert(ExternalConverter.class)
   private External external;
    
   public External getExternal() {
      return external;
   }
}

public class ExternalConverter implements Converter<External> {

   public External read(InputNode node) {
      String name = node.getAttribute("name");
      String value = node.getAttribute("value");

      return new External(name, value);
   }

   public void write(OutputNode node, External external) {
      String name = external.getName();
      String value = external.getValue();

      node.setAttribute("name", name);
      node.setAttribute("value", value);
   }
}

The above code snippet also shows a field annotated with the Convert annotation. The converter specified by the annotation will be used to intercept the normal serialization process and produce a customized XML element. Take the XML below, this is produced when the example object is serialized.

<example>
    <external name="book" value="Shantaram"/>
</example>

Interception of the normal serialization process is performed using a Strategy implementation and so does not form part of the core serialization process. Instead a specific strategy needs to be provided to the persister as is shown in the code snippet below.

Strategy strategy = new AnnotationStrategy();
Serializer serializer = new Persister(strategy);

serializer.read(Example.class, file);

Without the specification of the AnnotationStrategy the interception could not be performed, as the core serialization process will not acknowledge the Convert annotation. So in effect this strategy extends the core serialization process in an independent and transparent manner. Another even more transparent way to intercept the normal serialization process is to use another strategy implementation. The RegistryStrategy allows bindings to be registered between classes and converters, there is no need for additional annotations as was required for the previous example. Below is an example of how to establish bindings between a class and a converter.

Registry registry = new Registry();
Strategy strategy = new RegistryStrategy(registry);
Serializer serializer = new Persister(strategy);

registry.bind(External.class, ExternalConverter.class);

As many bindings as is required can be established with a registry. Also, if more complex converters are required a converter instance can be registered. Such a converter could have a reference to the Persister object so that nested serialization can be performed. This registry strategy also ensures that objects within Java collection objects can be serialized with registered converters. To illustrate how a complex converter could be registered take a look at the snippet below.

Registry registry = new Registry();
Strategy strategy = new RegistryStrategy(registry);
Serializer serializer = new Persister(strategy);
ComplexConverter converter = new ComplexConverter(serializer);

registry.bind(ComplexObject.class, converter);
<!-- archive/intercepting_serialization.html -->
Intercepting the serialization process
<!-- padding -->

Interception of the serialization process can be useful in several scenarios, for example if attributes are to be added or removed from an XML element then that element can be intercepted and modified during the serialization process. One useful application of interception is to change attribute names or values. For example, the "class" annotations added by the TreeStrategy could be intercepted and changed to a language neutral format that does not contain the Java class name. Below is an example of how to use a Visitor to add debug comments to an obect which is to be serialized.

@Root
@Default
public class CommentExample {

    private String name;

    private BigDecimal number;

    private Date date;     
} 

public class CommentVisitor implements Visitor {

   public void write(Type type, NodeMap<OutputNode> node) {
      OutputNode element = node.getNode();
      Class type = type.getType();
      String comment = type.getName();

      if(!element.isRoot()) {
         element.setComment(comment);
      }
   }
} 

The above visitor implementation will get the OutputNode that represents the XML element for the provided map of attributes. If the element does not represent the root element in the XML document then every element will have an associated comment, which descrives the class it represents. Such a visitor can be useful when serializing large document structures. The XML snippet below provides an example of what would be written.

<commentExample>
    <!-- java.lang.String -->
    <name>John Doe</name>
    <!-- java.math.BigDecimal -->
    <number>100.0</number>
    <!-- java.lang.Integer -->
    <value>18</value>
</commentExample>

To add a visitor to serialization the VisitorStrategy must be used. This strategy takes a visitor implementation and if required a strategy to delegate to. As usual, this strategy implementation can then be used to construct a persister, which can then serialize and deserialize objects.

<!-- archive/path_expressions.html -->
Mapping with XPath expressions
<!-- padding -->

At times it is useful to have your object model map to complex XML documents, without having to write an annotated class to map to the required elements and attributes. For such scenarios the Path annotation can be used. This requires the user to specify an XPath expression for a field or method. For example take annotated fields below.

@Root
public class ServerDeployment {

   @Attribute
   @Path("group")
   private ServerType type;

   @Element
   @Path("group/server[1]/details")
   private Server primary;

   @Element
   @Path("group/server[2]/details")
   private Server secondary;
    
   public Server getPrimary() {
      return primary;
   }

   public Server getSecondary() {
      return secondary;
   }
}

@Root
public class Server {

   @Attribute
   private String host;

   @Attribute
   private int port;
}

public enum ServerType {
   WINDOWS,
   LINUX,
   SOLARIS
}

The above code shows annotations applied to two objects. One contains XPath expressions that tell the serialization process how to read and write the details to an from the document. Here the expression defines a server within wrapper elements. When serializing such objects, the following XML results.

<serverDeployment>
   <group type="LINUX">
      <server>
         <details>
            <primary host="host1.domain.com" port="4567"/> 
         </details>
      </server>
      <server>
         <details>
            <secondary host="host2.domain.com" port="4567"/> 
         </details>
      </server>
   </group>
</serverDeployment>

As can be seen the XPath expressions defined have been used to determine the structure of the XML document. Such expressions allow a complex XML format to be serialized in to two simple objects. This can greatly reduce the number of types required to map an object to an XML structure. Both attributes and elements can be mapped in this manner.

When ordering elements with the Order annotation these wrapper elements can be sorted. To order wrapper elements an XPath expression can be used to identify the wrapper. Simply place the expression in the order annotation along with any element or attribute names and it is ordered as required. For example, take the following code snippet.

 

@Default
@Order(elements={"name[1]/first", "name[1]/surname", "age/date", "name[2]/nickname"})
public class Person {
   
   @Path("name[1]")
   private String first;

   @Path("name[1]")
   private String surname;

   @Path("name[2]")
   private String nickname;

   @Path("age")
   private String date;

   public String getName() {
      return first;
   }

   public String getSurname() {
      return surname;
   }
}

Just like ordering of elements or attributes without XPath expressions, a reference is all that is needed to ensure order. For the above code the serialization of the object will result in the following XML.

<person>
   <name>
      <first>Jack</first>
      <surname>Daniels</surname>
   </name>
   <age>
       <birth>19/10/1912</birth>
   </age>
   <name>
      <nickname>JD</nickname>
   </name>
</person>

In the above XML snippet we have serialized a single object in to multiple elements and ensured the order of the elements is as we required. Ordering can be applied to elements and attributes with Path annotations as easily as it can to those without, and both can be mixed within the same annotation. Using this type of ordering it is possible to generate very predictible results. One thing to note when using such annotations, is that only a subset of the XPath expression syntax is supported. For example, element and attribute references can not be taken from the root of the document, only references within the current context are allowed.

<!-- archive/dynamic_serialization_with_unions.html -->
Dynamic serialization with unions
<!-- padding -->

In order to perform dynamic serialization where element names bind to specific types the ElementUnion annotation can be used. This allows different XML schema classes to be associated with a single annotated field or method. Serialization of the associated instance determines the XML element name using the instance type. On deserialization the XML element name is then used to determine the schema class to use. For example, take the following set of annotated classes.

public interface Shape {

   public double area();
}

@Root
public class Circle implements Shape {

   @Element
   private double radius;

   public Circle(@Element(name="radius") double radius) {
      this.radius = radius;
   }

   public double area() {
      return Math.PI * Math.pow(radius, 2.0);
   }
}

@Root
public class Rectangle implements Shape {

   @Element
   private double width;

   @Element
   private double height;

   public Rectangle(
      @Element(name="width") double width, 
      @Element(name="height") double height) 
   {
      this.height = height;
      this.width = width;
   }

   public double area() {
      return width * height;
   }
}

@Root
public class Diagram {
      
   @ElementUnion({
      @Element(name="circle", type=Circle.class),
      @Element(name="rectangle", type=Rectangle.class)
   })
   private Shape shape;
      
   public Diagram() {
      super();
   }
      
   public void setShape(Shape shape){
      this.shape = shape;
   }
      
   public Shape getShape() {
      return shape;
   }
}

The above set of classes can now be used to dynamically deserialize different XML documents using a single schema class. For example, take the XML snippet below, this shows what is generated when the shape is assigned an instance of the circle type.

<diagram>
   <circle>
      <radius>3.0</radius>
   </circle>
</diagram>

However, if the shape field is assigned an instance of the square type then serialization of the diagram results in a different XML document. See the XML snippet below.

<diagram>
   <rectangle>
      <width>5.0</width>
      <height>11.0</heigth>
   </rectangle>
</diagram>

Providing dynamic serialization capabilities via the ElementUnion annotation ensures that more complex XML documents can be handled with ease. Typically, such unions will be required for a list of similar types. To tackle lists the ElementListUnion annotation can be used. This can be used as a union of inline lists to collect similar XML declarations in to a single list. For example, take the annotated classes below.

public interface Operation {
   
   public void execute();
}

@Default
public class Delete implements Operation {

   private File file;
   
   public Delete(@Element(name="file") File file) {
      this.file = file;
   }
   
   public void execute() {
      file.delete();
   }
}

@Default
public class MakeDirectory implements Operation {

   private File path;
   
   private MakeDirectory(@Element(name="path") File path) {
      this.path = path;
   }
   
   public void execute() {
      path.mkdirs();
   }
}

@Default
public class Move implements Operation {
   
   private File source;
   private File destination;
   
   public Move(
         @Element(name="source") File source,
         @Element(name="destination") File destination)
   {
      this.source = source;
      this.destination = destination;
   }
   
   public void execute() {
      source.renameTo(destination);
   }
}

@Root
public class Task {
   
   @ElementListUnion({
      @ElementList(entry="delete", inline=true, type=Delete.class),
      @ElementList(entry="mkdir", inline=true, type=MakeDirectory.class),
      @ElementList(entry="move", inline=true, type=Move.class)
   })
   private List<Operation> operations;
   
   @Attribute
   private String name;
   
   public Task(@Attribute(name="name") String name) {
      this.operations = new LinkedList<Operation>();
      this.name = name;
   }
   
   public void add(Operation operation) {
      operations.add(operation);
   }
   
   public void execute() {
      for(Operation operation : operations) {
         operation.execute();
      }
   }
}

For the above set of annotated classes a list of operations can be defined in an XML document. Each type inserted in to the list can be resolved using the XML element name. Below is an example XML document generated from the annotated classes.

<task name="setup">
   <delete>
      <file>C:\workspace\classes</file>
   </delete>
   <mkdir>
      <path>C:\workspace\classes</path>
   </mkdir>
   <move>
      <source>C:\worksace\classes</source>
      <destination>C:\workspace\build</destination>
   </move>
</task>
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics