How to handle forward references of XML IDREF with JAXB XmlAdapter during unmarshal?

holic87 picture holic87 · Oct 1, 2011 · Viewed 7.9k times · Source

Is it possible to handle forward references of XML IDREF elements in JAXB XmlAdapter during the unmarshal process? For example, I have the following XML complexType:

<xs:complexType name="person">
    <xs:complexContent>
        <xs:sequence>
            <xs:element name="dateOfBirth" type="xs:dateTime" minOccurs="0"/>
            <xs:element name="firstName" type="xs:string" minOccurs="0"/>
            <xs:element name="gender" type="xs:string" minOccurs="0"/>
            <xs:element name="guardian" type="xs:IDREF" minOccurs="0"/>
            <xs:element name="homePhone" type="xs:string" minOccurs="0"/>
            <xs:element name="lastName" type="xs:string" minOccurs="0"/>
        </xs:sequence>
    </xs:complexContent>
</xs:complexType>

where the guardian field could reference another Person-type element elsewhere in the document. I am currently using an XmlAdapter when marshalling so that the first time an object is marshalled, it is marshalled by containment, and any subsequent occurances of this object are marshalled by reference. See a previous question of mine. However, due to how my XML instance documents are created, the first occurrence of a Person element could happen after an IDREF to it occurs.

Is this something that is possible? Or do I need to approach this differently? Thanks!

Answer

bdoughan picture bdoughan · Oct 3, 2011

I have an answer to your related question I outlined how an XmlAdapter could be used to implement the use case where the first occurrence of an object was marshalled via containment/nesting and all other occurrences were marshalled by reference:

Option #1 - @XmlID/@XmlIDREF

If all of your Person objects are all represented through nesting and you want to introduce some key based relationships then you are best of using @XmlID to mark a field/property as the key, and @XmlID to map a field/property as a foreign key. Your Person class would look something like:

@XmlAccessorType(XmlAccessType.FIELD)
public class Person {

    @XmlID
    private String id;

    @XmlIDREF
    private Person guardian;
}

For More Information

Option #2 - Using XmlAdapter

If you updated the XmlAdapter from my previous answer to be:

package forum7587095;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class PhoneNumberAdapter extends XmlAdapter<PhoneNumberAdapter.AdaptedPhoneNumber, PhoneNumber>{

    private List<PhoneNumber> phoneNumberList = new ArrayList<PhoneNumber>();
    private Map<String, PhoneNumber> phoneNumberMap = new HashMap<String, PhoneNumber>();

    @XmlSeeAlso(AdaptedWorkPhoneNumber.class)
    @XmlType(name="phone-number")
    public static class AdaptedPhoneNumber {
        @XmlAttribute public String id;
        public String number;

        public AdaptedPhoneNumber() {
        }

        public AdaptedPhoneNumber(PhoneNumber phoneNumber) {
            id = phoneNumber.getId();
            number = phoneNumber.getNumber();
        }

        public PhoneNumber getPhoneNumber() {
            PhoneNumber phoneNumber = new PhoneNumber();
            phoneNumber.setId(id);
            phoneNumber.setNumber(number);
            return phoneNumber;
        }

    }

    @XmlType(name="work-phone-number")
    public static class AdaptedWorkPhoneNumber extends AdaptedPhoneNumber {

        public String extension;

        public AdaptedWorkPhoneNumber() {
        }

        public AdaptedWorkPhoneNumber(WorkPhoneNumber workPhoneNumber) {
            super(workPhoneNumber);
            extension = workPhoneNumber.getExtension();
        }

        @Override
        public WorkPhoneNumber getPhoneNumber() {
            WorkPhoneNumber phoneNumber = new WorkPhoneNumber();
            phoneNumber.setId(id);
            phoneNumber.setNumber(number);
            phoneNumber.setExtension(extension);
            return phoneNumber;
        }
}

    @Override
    public AdaptedPhoneNumber marshal(PhoneNumber phoneNumber) throws Exception {
        AdaptedPhoneNumber adaptedPhoneNumber;
        if(phoneNumberList.contains(phoneNumber)) {
            if(phoneNumber instanceof WorkPhoneNumber) {
                adaptedPhoneNumber = new AdaptedWorkPhoneNumber();
            } else {
                adaptedPhoneNumber = new AdaptedPhoneNumber();
            }
            adaptedPhoneNumber.id = phoneNumber.getId();
        } else {
            if(phoneNumber instanceof WorkPhoneNumber) {
                adaptedPhoneNumber = new AdaptedWorkPhoneNumber((WorkPhoneNumber)phoneNumber);
            } else {
                adaptedPhoneNumber = new AdaptedPhoneNumber(phoneNumber);
            }
            phoneNumberList.add(phoneNumber);
        }
        return adaptedPhoneNumber;
    }

    @Override
    public PhoneNumber unmarshal(AdaptedPhoneNumber adaptedPhoneNumber) throws Exception {
        PhoneNumber phoneNumber = phoneNumberMap.get(adaptedPhoneNumber.id);
        if(null != phoneNumber) {
            if(adaptedPhoneNumber.number != null) {
                phoneNumber.setNumber(adaptedPhoneNumber.number);
            }
            return phoneNumber;
        }
        phoneNumber = adaptedPhoneNumber.getPhoneNumber();
        phoneNumberMap.put(phoneNumber.getId(), phoneNumber);
        return phoneNumber;
    }

}

Then you will be able to unmarshal XML documents that look like the following where the reference happens first:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <phone-number id="A"/>
    <phone-number id="B">
        <number>555-BBBB</number>
    </phone-number>
    <phone-number id="A">
        <number>555-AAAA</number>
    </phone-number>
    <phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W">
        <number>555-WORK</number>
        <extension>1234</extension>
    </phone-number>
    <phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W"/>
</customer>