Question

My model element may contain several child elements of different types. For each type, only 0 or 1 child elements may be present. The order of these elements is defined, i.e. all elements of type A come before B etc. My model class, generated by JAXB, should therefore have a separate getter/setter property for each type of child element.

What complicates things is the one exception to the above rule: At the beginning and near the end of the list of child elements there may each appear 0 or 1 elements of type X, and these elements both have the same name.

Here's a slightly simplified version of my current real life XSD type:

<xs:complexType name="Activity">
    <xs:sequence>
        <xs:element name="log" type="DiagnosticLogMessage" minOccurs="0" maxOccurs="1" />
        <xs:element name="inputs" type="Mappings" minOccurs="0" maxOccurs="1" />
        <xs:element name="outputs" type="Mappings" minOccurs="0" maxOccurs="1" />
        <xs:element name="log" type="DiagnosticLogMessage" minOccurs="0" maxOccurs="1" />
        <xs:element name="to" type="DirectConnection" minOccurs="0" maxOccurs="1" />
    </xs:sequence>
</xs:complexType>

"log" is the problematic element. With the above type definition, "to" correctly gets its own property, but the remaining elements go into a list named "logsAndInputsAndOutputs". If I rename the two "log" elements to "logBefore" and "logAfter", each elements gets its own property, so clearly the problem is that JAXB cannot differentiate between the two "log"s.

I understand that the XSD itself is not really exact: It allows a single child element of type "log", in which case it's undefined if this is to be considered the "before" or the "after" element. In my current (manually written) reader class I have explicit code to detect this case and interpret a single "log" element as the "before" element.

I'm looking for a way to tell JAXB to treat the two "log" elements differently, either via xjb mappings or possibly with a more specific XSD.

Was it helpful?

Solution

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

Below is how you could handle this use case by leveraging MOXy's @XmlPath extension.

XML Schema (schema.xsd)

Here is an XML schema based on the fragment you have provided in your question.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="urn:forum17408865" xmlns="urn:forum17408865"
    elementFormDefault="qualified">

    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" type="Activity"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="Activity">
        <xs:sequence>
            <xs:element name="log" type="DiagnosticLogMessage"
                minOccurs="0" maxOccurs="1" />
            <xs:element name="inputs" type="Mappings" minOccurs="0"
                maxOccurs="1" />
            <xs:element name="outputs" type="Mappings" minOccurs="0"
                maxOccurs="1" />
            <xs:element name="log" type="DiagnosticLogMessage"
                minOccurs="0" maxOccurs="1" />
            <xs:element name="to" type="DirectConnection" minOccurs="0"
                maxOccurs="1" />
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="DiagnosticLogMessage" />

    <xs:complexType name="Mappings" />

    <xs:complexType name="DirectConnection" />

</xs:schema>

Activity

MOXy has an @XmlPath extension that allows you to map to an XML element based on its position. This class can't be generated from the XML schema so we will create it ourselves.

package forum17408865;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder={"log1", "inputs", "outputs", "log2", "to"})
public class Activity {

    @XmlPath("log[1]")
    private DiagnosticLogMessage log1;

    private Mappings inputs;

    private Mappings outputs;

    @XmlPath("log[2]")
    private DiagnosticLogMessage log2;

    private DirectConnection to;

}

binding.xml

To have JAXB use the class we created manually we will leverage a JAXB bindings file.

<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1">

    <jxb:bindings schemaLocation="schema.xsd">
        <jxb:bindings node="//xs:complexType[@name='Activity']">
            <jxb:class ref="forum17408865.Activity" />
        </jxb:bindings>
    </jxb:bindings>

</jxb:bindings>

XJC Call

Below is the XJC call that leverages the bindings file. Note how we also needed to use the -nv flag to disable schema validation.

xjc -nv -b binding.xml schema.xs

For More Information

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top