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!
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:
- Can JAXB marshal by containment at first then marshal by @XmlIDREF for subsequent references?
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
- http://blog.bdoughan.com/2010/10/jaxb-and-shared-references-xmlid-and.html
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>