June 27, 2011

JAXB and Factory Methods

Everyone knows that by default JAXB uses the no-argument constructor when instantiating objects.  Readers of this blog are also familiar with using XmlAdapters to build objects.  In this post I'll cover how the use of factory classes and methods can be configured through the @XmlType annotation.


Java Model

CustomerFactory

We will use a factory class to create instances of Customer.  The factory class must have a public, static, zero-argument method that returns an instance of the desired domain object.

package blog.factory;

import java.util.Date;

public class CustomerFactory {

    public static Customer createCustomer() {
        return new Customer(new Date());
    }

}

Customer

We configure the Customer class to be instantiated using CustomerFactory via the @XmlType annotation.

package blog.factory;

import java.util.Date;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement
@XmlType(
    propOrder={"name", "address", "phoneNumbers"},
    factoryClass=CustomerFactory.class,
    factoryMethod="createCustomer")
public class Customer {

    private String name;
    private Address address;
    private List<PhoneNumber> phoneNumbers;
    private Date createTime;

    public Customer(Date time) {
        this.createTime = time;
    }

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @XmlElement(name="phone-number")
    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

}

Address

If we use the @XmlType annotation to specify a factoryMethod, without a factoryClass, then the JAXB implementation will look for the method on the domain class.

package blog.factory;

import java.util.Date;

import javax.xml.bind.annotation.XmlType;

@XmlType(factoryMethod="createAddress")
public class Address {

    private String street;
    private Date createTime;
    
    public Address(Date time) {
        this.createTime = time;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public static Address createAddress() {
        return new Address(new Date());
    }

}

PhoneNumber

If a factory method is not specified then JAXB will use the no-argument constructor.  This is the default behaviour.

package blog.factory;

import javax.xml.bind.annotation.XmlValue;

public class PhoneNumber {

    private String value;

    @XmlValue
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

Demo Code

The following code can be used to run this example.  The different instantiation mechanisms will be leveraged during the unmarshal operation.

package blog.factory;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
        
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/blog/factory/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);
        
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }
    
}

XML Input (input.xml)

Below is the XML that will be unmarshalled.

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <name>Jane Doe</name>
    <address>
        <street>123 A Street</street>
    </address>
    <phone-number>555-1111</phone-number>
    <phone-number>555-2222</phone-number>
</customer>

Further Reading

If you enjoyed this post, you may also be interested in:

1 comment:

  1. My goodness, I spent the last several hours looking in vain for clues as to how to get my factory method to work, until I finally read this. Thank you!

    ReplyDelete

Note: Only a member of this blog may post a comment.