Sunday, May 19, 2013

JAXB XML Adapter

JAXB provides XMLAdapter to change the format of default JAXB types. For example, formatting decimal places in Decimal or BigDecimal, Date formatting etc.
How to do it?
First look at the following plain vanilla example of JAXB
package com.test;

import java.math.BigDecimal;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.datatype.XMLGregorianCalendar;

@XmlRootElement
public class Loan {
	private String name;
	private Long amount;
	private XMLGregorianCalendar startDate;
	private BigDecimal interestRate;
	public String getName() {
		return name;
	}
	@XmlElement
	public void setName(String name) {
		this.name = name;
	}
	public BigDecimal getInterestRate() {
		return interestRate;
	}
	@XmlElement
	public void setInterestRate(BigDecimal interestRate) {
		this.interestRate = interestRate;
	}
	public XMLGregorianCalendar getStartDate() {
		return startDate;
	}
	@XmlElement
	public void setStartDate(XMLGregorianCalendar startDate) {
		this.startDate = startDate;
	}
	public Long getAmount() {
		return amount;
	}
	@XmlElement
	public void setAmount(Long amount) {
		this.amount = amount;
	}	
}
If we marshall the Loan object, the output is similar to
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<loan>
    <amount>200000</amount>
    <interestRate>15.6</interestRate>
    <name>Name</name>
    <startDate>2013-05-19T16:02:51.080+05:30</startDate>
</loan>
Now, format the XMLGregorianCalendar to YYYY-MM-DD in XML. For this, we need to add a new XMLAdapter which can convert(marshall) from Calendar format to String format.
package com.test;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.datatype.XMLGregorianCalendar;

public class CalendarAdaptor extends XmlAdapter<String, XMLGregorianCalendar> {

	@Override
	public String marshal(XMLGregorianCalendar v) throws Exception {
		return v.getYear()+"-"+v.getMonth()+"-"+v.getDay();
	}

	@Override
	public XMLGregorianCalendar unmarshal(String v) throws Exception {
		return null;
	}
}
and Add the Adapter to the property where we would like to change. So the Loan.java file will be changed at the setStartDate method to following
        @XmlJavaTypeAdapter(CalendarAdaptor.class)
	public void setStartDate(XMLGregorianCalendar startDate) {
		this.startDate = startDate;
	}
When we marshall the same Loan object now, with the above change, the xml string will be
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<loan>
    <amount>200000</amount>
    <interestRate>15.6</interestRate>
    <name>Name</name>
    <startDate>2013-5-19</startDate>
</loan>
But the thing is, we need to change each property for the class definitions to affect this change. Instead of changing the classes, we can provide this adapter change at the package level by defining a class called package-info.java without changing the individual class properties. By doing this, all the XMLGregorianCalendar attributes in the package will follow the format defined by the Adapter. package-info.java
@XmlJavaTypeAdapter(value=CalendarAdaptor.class, type=XMLGregorianCalendar.class)
package com.test;

import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;