Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

I have a list in an object. I need to output this list in XML. Normally this would be accomplished by the following:

@XmlElementWrapper(name = "licenseList")
@XmlElement(name = "license")
public List<WatchLicense> getLicenses() {
    return licenses;
}

This produces the following output

<licenseList>
    <license>data</license>
    <license>data</license>
    <license>data</license>
</licenseList>

This is great, until I have the need to add an attribute to licenseList. You can’t add an attribute to a generated wrapper via annotations only. Enter the xml type adapter. There are three pieces. The defined type that is marshallable (LicenseListType), the annotation of the unmarshallable element with the xml type adapter and then the adapter itself (LicenseListAdapter).

First off is the new type. There are several things here. First off, it is a normal wrapper of the type that used to have to be used before @XmlElementWrapper. You used to have to add this to your class directly as a property. I didn’t want to because the names of the elements and their location had meaning to my views. Second is the addition of the getHashCode() method. This is the attribute that I needed to add. It uses the generated hashCode method.

package com.appriss.jxp.watchapi.jaxb;

import com.appriss.jxp.watchapi.WatchLicense;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.springframework.util.AutoPopulatingList;

import javax.xml.bind.annotation.XmlAttribute;

import java.util.List;

/**
 * Wrapper for list of license.  Normally, this would be handled by adding @XmlElementWrapper(name = "licenseList") to
 * the getter.  In this case, we also need to add a hashCode attribute the value and JAXB doesn't support an element
 * wrapper with an attribute.
 * @author nshelton
 */
public class LicenseListType {

    @XmlAttribute(name = "hashCode")
    public int getHashCode() {
        return hashCode();
    }

    private List<WatchLicense> license = new AutoPopulatingList<WatchLicense>(WatchLicense.class);

    public List<WatchLicense> getLicense() {
        return license;
    }

    public void setLicense(List<WatchLicense> license) {
        this.license = license;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(license).toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof LicenseListType) {
            final LicenseListType other = (LicenseListType) obj;
            return new EqualsBuilder().append(license, other.license).isEquals();
        } else {
            return false;
        }
    }
}

Next up is the adapter class which will directly use this new type. The adapter’s job is to translate from one unmarshallable object to a marshallable object.

package com.appriss.jxp.watchapi.jaxb;

import com.appriss.jxp.watchapi.WatchLicense;

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

import java.util.LinkedList;
import java.util.List;

/**
 * License list type adapter to convert between a list of watch licenses and a license list type.  This would normally
 * be handled by @XmlElementWrapper, but we also need to specify an attribute and JAXB doesn't support attributes on an
 * annotation generated wrapper.
 * @author nshelton
 */
public class LicenseListAdapter extends XmlAdapter<LicenseListType, List<WatchLicense>> {

    /**
     * Convert a value type to a bound type.
     * @param licenseListType The value to be converted. Can be null.
     * @throws Exception if there's an error during the conversion. The caller is responsible for reporting the error to
     *                   the user through {@link javax.xml.bind.ValidationEventHandler}.
     */
    @Override
    public List<WatchLicense> unmarshal(LicenseListType licenseListType) throws Exception {
        List<WatchLicense> watchLicenses = new LinkedList<WatchLicense>();
        watchLicenses.addAll(licenseListType.getLicense());
        return watchLicenses;
    }

    /**
     * Convert a bound type to a value type.
     * @param watchLicenses The value to be converted. Can be null.
     * @throws Exception if there's an error during the conversion. The caller is responsible for reporting the error to
     *                   the user through {@link javax.xml.bind.ValidationEventHandler}.
     */
    @Override
    public LicenseListType marshal(List<WatchLicense> watchLicenses) throws Exception {
        LicenseListType licenseListType = new LicenseListType();
        licenseListType.setLicense(watchLicenses);
        return licenseListType;
    }
}

The magic happens in the marshall and unmarshall methods. In the marshall method, it accepts the unmarshallable class and returns a marshallable class. In the unmarshaller, it accepts the marshallable class and returns the unmarshallable class.

Now all that is left to do is to define the adapter class for a property. The @XmlJavaTypeAdapter annotation is used to identify the class that will be used to marshall/unmarshall this element.

@XmlJavaTypeAdapter(LicenseListAdapter.class)
public List<WatchLicense> getLicenses() {
    return licenses;
}

August 22nd, 2012

Posted In: Java, JAXB, json

Tags: , , ,

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

LinkedIn Auto Publish Powered By : XYZScripts.com