javajaxbogc

JAXB substitution group marshals but will not unmarshal correctly


I am implementing the OGC Web Feature Service and part of that is creating a feature schema that will inherit from the OGC schema. My service marshals the XML fine, but the client is unable to unmarshal the XML. I wrote a tester that illustrates the problem:

...
ObjectFactory wfsfactory = new ObjectFactory();
net.opengis.gml.v_3_1_1.ObjectFactory gmlfactory = new net.opengis.gml.v_3_1_1.ObjectFactory();
com.example.ObjectFactory exampleFactory = new com.example.ObjectFactory();
OgcJaxbManager manager = OgcJaxbManager.getInstance();
FeatureCollectionType featureCollection = wfsfactory
        .createFeatureCollectionType();
FeaturePropertyType prop = gmlfactory.createFeaturePropertyType();
prop.setFeature(exampleFactory.createFoo(exampleFactory.createFoo()));
featureCollection.setFeatureMember(Arrays.asList(prop));
//marshal to XML
String xml = manager.marshal(wfsfactory
        .createFeatureCollection(featureCollection));
log.info(xml);
//unmarshal back to object
FeatureCollectionType afterMarshal = (FeatureCollectionType) manager
        .unmarshal(xml);
JAXBElement<? extends AbstractFeatureType> feature = afterMarshal
        .getFeatureMember().get(0).getFeature();
if (feature == null) {
    log.info("null");
} else {
    log.info("not null");
}
...

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns4:FeatureCollection xmlns:ns2="http://www.w3.org/1999/xlink" xmlns:ns1="http://www.opengis.net/gml" xmlns:ns4="http://www.opengis.net/wfs" xmlns:ns3="http://www.w3.org/2001/SMIL20/" xmlns:ns9="http://www.opengis.net/wms" xmlns:ns5="http://www.opengis.net/ows/1.1" xmlns:ns6="http://www.opengis.net/ogc" xmlns:ns10="http://example.com" xmlns:ns7="http://www.opengis.net/ows" xmlns:ns11="http://www.w3.org/2001/SMIL20/Language" xmlns:ns8="http://www.opengis.net/wcs/1.1.1">
    <ns1:featureMember>
        <ns10:foo>
            <ns10:bar>0</ns10:bar>
        </ns10:foo>
    </ns1:featureMember>
</ns4:FeatureCollection>

null

Here is the OGC schema I am extending:

...
    <element name="FeatureCollection" type="gml:FeatureCollectionType" substitutionGroup="gml:_Feature"/>
    <!-- =========================================================== -->
    <complexType name="FeatureCollectionType">
        <annotation>
            <documentation>Concrete generic feature collection.</documentation>
        </annotation>
        <complexContent>
            <extension base="gml:AbstractFeatureCollectionType"/>
        </complexContent>
    </complexType>
    <!-- ===========================================================   -->
    <complexType name="AbstractFeatureCollectionType" abstract="true">
        <annotation>
            <documentation>A feature collection contains zero or more features.</documentation>
        </annotation>
        <complexContent>
            <extension base="gml:AbstractFeatureType">
                <sequence>
                    <element ref="gml:featureMember" minOccurs="0" maxOccurs="unbounded"/>
                    <element ref="gml:featureMembers" minOccurs="0"/>
                </sequence>
            </extension>
        </complexContent>
    </complexType>
    <!-- ===== property for feature association ==== -->
    <element name="featureMember" type="gml:FeaturePropertyType"/>
    <!-- ============================================================== -->
    <complexType name="FeaturePropertyType">
        <annotation>
            <documentation>Container for a feature - follow gml:AssociationType pattern.</documentation>
        </annotation>
        <sequence minOccurs="0">
            <element ref="gml:_Feature"/>
        </sequence>
        <attributeGroup ref="gml:AssociationAttributeGroup"/>
    </complexType>
    <!-- ============================================================== -->
    <element name="_Feature" type="gml:AbstractFeatureType" abstract="true" substitutionGroup="gml:_GML"/>
    <!-- =========================================================== -->
    <complexType name="AbstractFeatureType" abstract="true">
        <annotation>
            <documentation>An abstract feature provides a set of common properties, including id, metaDataProperty, name and description inherited from AbstractGMLType, plus boundedBy.    A concrete feature type must derive from this type and specify additional  properties in an application schema. A feature must possess an identifying attribute ('id' - 'fid' has been deprecated).</documentation>
        </annotation>
        <complexContent>
            <extension base="gml:AbstractGMLType">
                <sequence>
                    <element ref="gml:boundedBy" minOccurs="0"/>
                    <element ref="gml:location" minOccurs="0">
                        <annotation>
                            <appinfo>deprecated</appinfo>
                            <documentation>deprecated in GML version 3.1</documentation>
                        </annotation>
                    </element>
                    <!-- additional properties must be specified in an application schema -->
                </sequence>
            </extension>
        </complexContent>
    </complexType>
...

Here is my schema:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsd:schema version="1.0" targetNamespace="http://example.com"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:example="http://example.com"
    xmlns:gml="http://www.opengis.net/gml"
    elementFormDefault="qualified">

    <xsd:import namespace="http://www.opengis.net/gml"
        schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd"/>

    <xsd:element name="foo" type="example:foo"
        substitutionGroup="gml:_Feature"/>

    <xsd:complexType name="foo">
        <xsd:complexContent>
            <xsd:extension base="gml:AbstractFeatureType">
                <xsd:sequence>
                    <xsd:element name="bar" type="xsd:string" minOccurs="0"/>
                </xsd:sequence>
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>

</xsd:schema>

Here is the POJO that xjc produces:

package com.example;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "foo", propOrder = { "bar" })
public class Foo extends AbstractFeatureType {

    protected String bar;

    ...

Any help would be greatly appreciated.


Solution

  • I made this work, with two caveats.

    You didn't post the ObjectContext for com.example, so I used a hand-coded ObjectContext. It is critical to include the

    substitutionHeadNamespace="http://www.opengis.net/gml" and substitutionHeadName="_Feature"

    values in the @XmlElementDecl for the factory method, otherwise I see the same symptoms, i.e. marshalling OK, unmarshalling is empty, but no exceptions.

    com.example.ObjectContext looks like this:

    package com.example;
    
    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.annotation.XmlElementDecl;
    import javax.xml.bind.annotation.XmlRegistry;
    import javax.xml.namespace.QName;
    
    @XmlRegistry
    public class ObjectFactory {
    
        public ObjectFactory() { }
    
        public Foo createFoo() { return new Foo(); }
    
        @XmlElementDecl(namespace="http://example.com",
                name="foo",
                substitutionHeadNamespace="http://www.opengis.net/gml",
                substitutionHeadName="_Feature")
        public JAXBElement<Foo> createFoo(Foo foo) {
            return new JAXBElement<Foo>(new QName("http://example.com", "foo"), Foo.class, foo);   
        }
    }
    

    com.example.Foo looks like this, including main:

    package com.example;
    
    import java.io.StringReader;
    import java.io.StringWriter;
    import java.util.Arrays;
    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Marshaller;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlType;
    import javax.xml.bind.annotation.XmlAccessType;
    
    import net.opengis.gml.v_3_1_1.AbstractFeatureType;
    import net.opengis.gml.v_3_1_1.FeaturePropertyType;
    import net.opengis.wfs.v_1_1_0.FeatureCollectionType;
    import net.opengis.wfs.v_1_1_0.ObjectFactory;
    
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "foo", propOrder = { "bar" })
    public class Foo extends AbstractFeatureType {
    
        @XmlElement
        protected String bar = "0";
    
        @Override
        public Object createNewInstance() {
            return new Foo();
        }
    
        public static void main(String[] args) throws JAXBException {
            ObjectFactory wfsfactory = new ObjectFactory();
            net.opengis.gml.v_3_1_1.ObjectFactory gmlfactory = new net.opengis.gml.v_3_1_1.ObjectFactory();
            com.example.ObjectFactory exampleFactory = new com.example.ObjectFactory();
            FeatureCollectionType featureCollection = wfsfactory
                    .createFeatureCollectionType();
            FeaturePropertyType prop = gmlfactory.createFeaturePropertyType();
            prop.setFeature(exampleFactory.createFoo(exampleFactory.createFoo()));
            featureCollection.setFeatureMember(Arrays.asList(prop));
            //marshal to XML
            JAXBContext ctx = JAXBContext.newInstance(ObjectFactory.class, net.opengis.gml.v_3_1_1.ObjectFactory.class, com.example.ObjectFactory.class);
    
            StringWriter sw =new StringWriter();
            Marshaller marshaller = ctx.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.marshal(wfsfactory.createFeatureCollection(featureCollection), sw);
            System.out.println(sw.toString());
    
            //unmarshal back to object
            JAXBElement<FeatureCollectionType> afterMarshal = (JAXBElement<FeatureCollectionType>)
                ctx.createUnmarshaller().unmarshal(new StringReader(sw.toString()));
    
            JAXBElement<? extends AbstractFeatureType> feature = afterMarshal
                    .getValue().getFeatureMember().get(0).getFeature();
            if (feature == null) {
                System.out.println("null");
            } else {
                System.out.println("not null");
            }
        }
    }
    

    And this is the output I get:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <ns3:FeatureCollection xmlns:ns2="http://www.w3.org/1999/xlink" xmlns:ns1="http://www.opengis.net/gml" xmlns:ns4="http://example.com" xmlns:ns3="http://www.opengis.net/wfs" xmlns:ns5="http://www.w3.org/2001/SMIL20/" xmlns:ns6="http://www.opengis.net/ogc" xmlns:ns7="http://www.opengis.net/ows" xmlns:ns8="http://www.w3.org/2001/SMIL20/Language">
        <ns1:featureMember>
            <ns4:foo>
                <ns4:bar>0</ns4:bar>
            </ns4:foo>
        </ns1:featureMember>
    </ns3:FeatureCollection>
    
    not null
    

    Good luck going forward!