JAXB: Represent nested Model Group Schema Components with a java object hierarchy

心不动则不痛 提交于 2019-12-25 06:07:49

问题


Schema allows a designer to specify optional sequences of child elements as follows;

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

    <complexType name="RootType">
        <sequence>
            <choice>
                <sequence minOccurs="1" maxOccurs="1">
                    <element name="A" type="tns:A"></element>
                    <element name="B" type="tns:B"></element>
                    <element name="C" type="tns:C"></element>
                </sequence>
                <element name="P" type="tns:P" minOccurs="1" maxOccurs="1"></element>
                <element name="Q" type="tns:Q" minOccurs="1" maxOccurs="1"></element>
            </choice>
        </sequence>
    </complexType>

    <complexType name="A">
        <sequence>
            <element name="one" type="string"/>
            <element name="two" type="string"/>
            <element name="three" type="string"/>
        </sequence>
    </complexType>

    <complexType name="B">
        <sequence>
            <element name="one" type="string"/>
            <element name="two" type="string"/>
            <element name="three" type="string"/>
        </sequence>
    </complexType>

    <complexType name="C">
        <sequence>
            <element name="one" type="string"/>
            <element name="two" type="string"/>
            <element name="three" type="string"/>
        </sequence>
    </complexType>

    <complexType name="P">
        <sequence>
            <element name="one" type="string"/>
            <element name="two" type="string"/>
            <element name="three" type="string"/>
        </sequence>
    </complexType>

    <complexType name="Q">
        <sequence>
            <element name="one" type="string"/>
            <element name="two" type="string"/>
            <element name="three" type="string"/>
        </sequence>
    </complexType>

    <element name="TopLevel" type="tns:RootType"></element>
</schema>

This validates

<?xml version="1.0" encoding="UTF-8"?>
<tns:TopLevel xmlns:tns="http://www.example.org/Schema1">
    <tns:A>
        <tns:one>tns:one</tns:one>
        <tns:two>tns:two</tns:two>
        <tns:three>tns:three</tns:three>
    </tns:A>
    <tns:B>
        <tns:one>tns:one</tns:one>
        <tns:two>tns:two</tns:two>
        <tns:three>tns:three</tns:three>
    </tns:B>
    <tns:C>
        <tns:one>tns:one</tns:one>
        <tns:two>tns:two</tns:two>
        <tns:three>tns:three</tns:three>
    </tns:C>
</tns:TopLevel>

and rejects

<?xml version="1.0" encoding="UTF-8"?>
<tns:TopLevel xmlns:tns="http://www.example.org/Schema1">
    <tns:A>
        <tns:one>tns:one</tns:one>
        <tns:two>tns:two</tns:two>
        <tns:three>tns:three</tns:three>
    </tns:A>
    <tns:B>
        <tns:one>tns:one</tns:one>
        <tns:two>tns:two</tns:two>
        <tns:three>tns:three</tns:three>
    </tns:B>
</tns:TopLevel>

or

   <?xml version="1.0" encoding="UTF-8"?>
    <tns:TopLevel xmlns:tns="http://www.example.org/Schema1">
        <tns:A>
            <tns:one>tns:one</tns:one>
            <tns:two>tns:two</tns:two>
            <tns:three>tns:three</tns:three>
        </tns:A>
        <tns:B>
            <tns:one>tns:one</tns:one>
            <tns:two>tns:two</tns:two>
            <tns:three>tns:three</tns:three>
        </tns:B>
        <tns:C>
            <tns:one>tns:one</tns:one>
            <tns:two>tns:two</tns:two>
            <tns:three>tns:three</tns:three>
        </tns:C>
        <tns:P>
            <tns:one>tns:one</tns:one>
            <tns:two>tns:two</tns:two>
            <tns:three>tns:three</tns:three>
        </tns:P>
    </tns:TopLevel>

Ofcourse if I generate a JAXB class from this I get

...
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "RootType", propOrder = {
    "a",
    "b",
    "c",
    "p",
    "q"
})
//Manually added for testing 
@XmlRootElement(name="TopLevel")
public class RootType {

    @XmlElement(name = "A")
    protected A a;
    @XmlElement(name = "B")
    protected B b;
    @XmlElement(name = "C")
    protected C c;
    @XmlElement(name = "P")
    protected P p;
    @XmlElement(name = "Q")
    protected Q q;

    /**
     * Gets the value of the a property.
     * 
     * @return
     *     possible object is
     *     {@link A }
     *     
     */
    public A getA() {
        return a;
    }

    /**
     * Sets the value of the a property.
     * 
     * @param value
     *     allowed object is
     *     {@link A }
     *     
     */
    public void setA(A value) {
        this.a = value;
    }

    ....

    etc.

which will load all 3 instances without complaining provided if I don't set up the Schema validation on the Unmarshaller.

The java instance is then contrary to the Schema, and the following will fail. Even if it is valid I must do extra work to determine what state the RootType instance is in.

private static void validate(uk.co.his.test.complexSequenceAndChoice.generated.nact1moxy.RootType root)
{
    if(root.getA()!=null)
    {
        Assert.assertTrue("RootType should either be a sequence of A, B, C or a sinle P or a single Q", root.getB()!=null&&root.getC()!=null&&root.getP()==null&&root.getQ()==null);
    }
    else
    {
        Assert.assertTrue("RootType should either be a sequence of A, B, C or a sinle P or a single Q", root.getB()==null&&root.getC()==null
                &&((root.getP()!=null&&root.getQ()==null)
                || (root.getP()==null&&root.getQ()!=null)));
    }
}

The question is;

  • Is there anyway to represent this more accurately in a Java class hierachy? Generally Model Group Schema Components specify the ordering of child elements, but Java can only capture the 'bucket of all children' in cases where more complex orderings are specified by nested Model Group Schema components?

The difficulty is we would like to represent these groupings by java classes that do not bind to Element parsing events in a one to one way;

Imagine a @XmlElementSequence annotation existed that allowed a sequence of children to be specified;

package uk.co.his.test.complexSequenceAndChoice.manual5;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "RootType")
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
public class RootType {

    @XmlElements({
        @XmlElementSequence(Sequencepart.class), 
        @XmlElement(name="P", type=P.class), 
        @XmlElement(name="Q", type=Q.class)
        })
    public Object partA;

}


package uk.co.his.test.complexSequenceAndChoice.manual5;

import javax.xml.bind.annotation.XmlElement;

import uk.co.his.test.complexSequenceAndChoice.generated.nact1.A;
import uk.co.his.test.complexSequenceAndChoice.generated.nact1.B;

@XmlElementSequenceDef(propOrder = {
    "a",
    "b",
    "c"
})
public class Sequencepart {

    @XmlElement(name = "A")
    protected A a;
    @XmlElement(name = "B")
    protected B b;
    @XmlElement(name = "C")

}

The marshaller would be relatively simple. The Unmarshaller would have to know to create a Sequencepart when it saw the start of an "A" element tag, and to insert the A values inside that Sequencepart instance.

I have asked this on the MOXy site, and included a sample Maven project.


回答1:


This can be done using MOXy specific annotations.

See the section "Creating "Self" Mappings" here for more detail.

If I annotate my JAXB class with the MOXy @XmlPath annotation as follows;

import java.util.Optional;

import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import org.eclipse.persistence.oxm.annotations.XmlPath;



@XmlAccessorType(XmlAccessType.NONE)
@XmlType(name = "RootType", propOrder = {
    "seq1",
    "p",
    "q"
})
@XmlRootElement(name="TopLevel")
public class RootType {


    @XmlPath(".")
    private InnerSequence seq1;
    @XmlElement(name = "P")
    private P p;
    @XmlElement(name = "Q")
    private Q q;

    public static class InnerSequence 
    {
        @XmlElement(name = "A")
        public A a;
        @XmlElement(name = "B")
        public B b;
        @XmlElement(name = "C")
        public C c;

        public boolean isEmpty() {
            return c==null&&b==null&&a==null;
        }
        ...
    }
...

I can now create XML that validates against the schema;

<?xml version="1.0" encoding="UTF-8"?>
<TopLevel xmlns="http://www.example.org/Schema1">
   <A>
      <one>Hello One A</one>
   </A>
   <B>
      <one>Hello One B</one>
   </B>
   <C>
      <one>Hello One C</one>
   </C>
</TopLevel>

Note that MOXy will populate seq1 with an instance of InnerSequence during Unmarshalling regardless of whether the file contains any A, B or Cs.

The file;

<?xml version="1.0" encoding="UTF-8"?>
<TopLevel xmlns="http://www.example.org/Schema1">
   <P>
      <one>Hello One A</one>
   </P>
</TopLevel>

still populates seq1 with an InnerSequence.

The answer I posted to the Eclipselink forum has an attached Maven project which gives more (working details)



来源:https://stackoverflow.com/questions/33105486/jaxb-represent-nested-model-group-schema-components-with-a-java-object-hierarch

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!