Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB converts @XmlElementRefs and @XmlElements to xs:choice

Tags:

java

xml

jaxb

I have 4 classes. Person class, and abstract ContactInformation with Phone and Address class extending it.

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Person {
    @XmlElement(required = true, nillable = false)
    private String first;
    @XmlElement(required = true, nillable = false)
    private String last;
    @XmlElementWrapper(name = "contacts")
    @XmlElementRefs({
        @XmlElementRef(name = "phone", type = Phone.class, required = true),
        @XmlElementRef(name = "address", type = Address.class, required = true)
    })
    private final List<ContactInfomation> contacts = new ArrayList<>();
}

ContactInformation is used only as container:

public abstract class ContactInfomation { /* empty class */ }

Phone class:

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Phone extends ContactInfomation {
    @XmlElement(required = true, nillable = false)
    private String number;
}

And Address class:

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Address extends ContactInfomation {
    @XmlElement(required = true, nillable = false)
    private String country;
    @XmlElement(required = true, nillable = false)
    private String city;
}

The problem is when I change @XmlElementrefs to @XmlElements in Person class, nothing happens. JAXB maps them to xs:choice. The XML output is same as before and schemagen generates the same schema as before. Here is a sample output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
    <first>majid</first>
    <last>azimi</last>
    <contacts>
        <address>
            <country>US</country>
            <city>Los Angles</city>
        </address>
        <address>
            <country>US</country>
            <city>New York</city>
        </address>
        <phone>
            <number>5551037</number>
        </phone>
    </contacts>
</person>

And here is the schema:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="address" type="address"/>

  <xs:element name="person" type="person"/>

  <xs:element name="phone" type="phone"/>

  <xs:complexType name="person">
    <xs:sequence>
      <xs:element name="first" type="xs:string"/>
      <xs:element name="last" type="xs:string"/>
      <xs:element name="contacts" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
              <xs:element ref="phone"/>
              <xs:element ref="address"/>
            </xs:choice>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="phone">
    <xs:complexContent>
      <xs:extension base="contactInfomation">
        <xs:sequence>
          <xs:element name="number" type="xs:string"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="contactInfomation" abstract="true">
    <xs:sequence/>
  </xs:complexType>

  <xs:complexType name="address">
    <xs:complexContent>
      <xs:extension base="contactInfomation">
        <xs:sequence>
          <xs:element name="country" type="xs:string"/>
          <xs:element name="city" type="xs:string"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
</xs:schema>

Documentation of @XmlElementRef uses it for Substitution group and XML choice. I'm completely confused what is the difference of @XmlElementRefs/@XmlElementRef and @XmlElements/@XmlElement. Can anyone help?

like image 686
Majid Azimi Avatar asked Jan 01 '14 21:01

Majid Azimi


People also ask

What is XmlElementRef?

Annotation Type XmlElementRef. @Retention(value=RUNTIME) @Target(value={FIELD,METHOD}) public @interface XmlElementRef. Maps a JavaBean property to a XML element derived from property's type. Usage. @XmlElementRef annotation can be used with a JavaBean property or from within XmlElementRefs.

Which tag represents the root element for the XML document in JAXB?

@XmlRootElement annotation can be used to map a class or enum type to XML type. When a top level class or an enum type is annotated with the @XmlRootElement annotation, then its value is represented as XML element in an XML document.

What is @XmlRootElement?

Annotation Type XmlRootElementMaps a class or an enum type to an XML element. Usage. The @XmlRootElement annotation can be used with the following program elements: a top level class. an enum type.

Which annotation is used to map a Java class object to XML element?

The JAXB annotations defined in the javax. xml. bind. annotations package can be used to customize Java program elements to XML schema mapping.


1 Answers

TL;DR

Basic Differenece Between @XmlElement and @XmlElementRef

The difference between @XmlElement and @XmlElementRef is if the corresponding generated element will contain a local element definition or a reference to a global element definition.

Choice and Substitution Groups

Choice in XML Schema is really a superset of what can be done with substitution groups. So to simplifiy the mapping JAXB leverages one mapping for both.

JAXB and Schema Generation

JAXB can generated a Java model from any XML Schema, on the flip side JAXB does not preserve all metadata about the XML schema. Therefore JAXB can't generate every XML schema.


@XmlElementRefs/@XmlElementRef and @XmlRootElement

Below is what you have in your model.

Person

I have modified the mapping on the contacts field to make the mapping to the Address class more distinct.

@XmlElementWrapper(name = "contacts")
@XmlElementRefs({
    @XmlElementRef(name = "phone", type = Phone.class, required = true),
    @XmlElementRef(name = "ADDRESS", type = Address.class, required = true)
})
private final List<ContactInfomation> contacts = new ArrayList<>();

Phone

When you map with @XmlElementRef the information you specify needs to correspond to a global element definition supplied by @XmlRootElement or @XmlElementDecl (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html). By default the root element for the Person class with be person.

@XmlRootElement
public class Person {

Address

I have used the @XmlRootElement annotation to override the default name for the Address class.

@XmlRootElement(name="ADDRESS")
public class Address extends ContactInfomation {

Generated XML Schema

Here is the generated schema. We see that the element definitions within the choice structure leverage ref to reference an existing element instead of defining a local element.

  <xs:element name="contacts" minOccurs="0">
    <xs:complexType>
      <xs:sequence>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element ref="phone"/>
          <xs:element ref="ADDRESS"/>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

@XmlElements/@XmlElement

I have changed the mapping on your contacts field to use @XmlElements.

Person

@XmlElementWrapper(name = "contacts")
@XmlElements({
    @XmlElement(name = "phone-number", type = Phone.class, required = true),
    @XmlElement(name = "home-address", type = Address.class, required = true)
})
private final List<ContactInfomation> contacts = new ArrayList<>();

Phone

None of the referenced classes are required to be annotated with @XmlRootElement (or have a corresponding @XmlElementDecl annotation.

public class Phone extends ContactInfomation {

Address

If he class does have an @XmlRootElement annotation it is not required to match the @XmlElement annotation within the @XmlElements.

@XmlRootElement(name="ADDRESS")
public class Address extends ContactInfomation {

Generated XML Schema

Here is the generated schema. We see that the element definitions within the choice structure are now defined as local elements.

  <xs:element name="contacts" minOccurs="0">
    <xs:complexType>
      <xs:sequence>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="phone-number" type="phone"/>
          <xs:element name="home-address" type="address"/>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
like image 173
bdoughan Avatar answered Sep 17 '22 15:09

bdoughan