Team lead for the TopLink/EclipseLink JAXB & SDO implementations, and the Oracle representative on those specifications. Blaise is a DZone MVB and is not an employee of DZone and has posted 44 posts at DZone. You can read more from them at their website. View Full User Profile

Mapping Bad XML - Enumerated Collection Elements

06.24.2013
| 2557 views |
  • submit to reddit
In a previous post I introduced EclipseLink JAXB (MOXy)'s @XmlVariableNode extension.  In this post I'll demonstrate how @XmlVariableNode could be leveraged to handle an interesting question I came across on Stack Overflow.  In that question instead of a collection being represented with an element that appeared multiple times, the element name contained the index.  While I would never recommend structuring your XML document this way sometimes you encounter it and need to be able to map it.

XML Input (input.xml)/Output 

Below is what the problematic XML looks like.  In this example the number of different elements prefixed with phone-number is not known.
<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <phone-number1 type="work">555-1111</phone-number1>
    <phone-number2 type="home">555-2222</phone-number2>
    <phone-number3 type="cell">555-3333</phone-number3>
</customer>

Java Model

Below is the Java model that we will use for this example.

Customer

Ultimately we wish to represent the XML in Java as a Customer that has a collection of PhoneNumber instances.  For this use case we will leverage MOXy's @XmlVariableNode extension.  The referenced object does not have a property that we can use to store the node name, so we will use an XmlAdapter to convert PhoneNumber into an object that does.

package blog.variablenode.enumeratedlist;
 
import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
    @XmlVariableNode("nodeName")
    @XmlJavaTypeAdapter(PhoneNumberAdapter.class)
    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
 
}
view sourceprint?PhoneNumber

Note how PhoneNumber does not have a field to store the node name

package blog.variablenode.enumeratedlist;
 
import javax.xml.bind.annotation.*;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
 
    @XmlAttribute
    private String type;
 
    @XmlValue
    private String number;
 
}
XMLAdapter (PhoneNumberAdapter)

We will use an XmlAdapter to convert the PhoneNumber into an AdaptedPhoneNumberAdaptedPhoneNumber has thenodeName field (line 14) that we referenced in the @XmlVariableNode annotation that we used on the phoneNumbersfield in the Customer class.  We will annotate this field with @XmlTransient to prevent it from being marshalled/unmarshalled (see: JAXB and Unmapped Properties).  We will leverage MOXy's @XmlPath extension to avoid having to copy the contents of PhoneNumber into the AdaptedPhoneNumber (see: XPath Based Mapping).

package blog.variablenode.enumeratedlist;
 
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
public class PhoneNumberAdapter extends XmlAdapter<PhoneNumberAdapter.AdaptedPhoneNumber, PhoneNumber>{
 
    private int counter = 1;
 
    public static class AdaptedPhoneNumber {
 
        @XmlTransient
        public String nodeName;
 
        @XmlPath(".")
        public PhoneNumber phoneNumber;
 
    }
 
    @Override
    public AdaptedPhoneNumber marshal(PhoneNumber phoneNumber) throws Exception {
        AdaptedPhoneNumber adaptedPhoneNumber = new AdaptedPhoneNumber();
        adaptedPhoneNumber.nodeName = "phone-number" + counter++;
        adaptedPhoneNumber.phoneNumber = phoneNumber;
        return adaptedPhoneNumber;
    }
 
    @Override
    public PhoneNumber unmarshal(AdaptedPhoneNumber adaptedPhoneNumber) throws Exception {
        return adaptedPhoneNumber.phoneNumber;
    }
 
}
Demo

The following demo code can be used to prove that everything works.  Since the PhoneNumberAdapter is stateful we need to set and instance of it on the Marshaller (line 17)

package blog.variablenode.enumeratedlist;
 
import java.io.File;
import javax.xml.bind.*;
 
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/variablenode/enumeratedlist/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);
         
        Marshaller marshaller = jc.createMarshaller();
        PhoneNumberAdapter phoneNumberAdapter = new PhoneNumberAdapter();
        marshaller.setAdapter(phoneNumberAdapter);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }
 
}


Published at DZone with permission of Blaise Doughan, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Jakub Stransky replied on Fri, 2013/11/15 - 10:59am

Sorry wrong article

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.