Text field missing with XJC jaxb2-maven-plugin while generating HL7 CDA2 mixed complex types

109 views Asked by At

I'm facing a bad error when generating JAXB classes for parsing HL7 CDA2 documents (here is the complete xsd definition): the complex type ANY is generated without a String field annotaded as @XmlValue that contains the effective tag text, this results in having empty object when unmarshalling and truncated XML when re-marshalling. As an example this fragment of XML containing an address:

        <addr use="H">
            <country>123</country>
            <streetAddressLine>Streen Name, 1</streetAddressLine>
            <postalCode>12345</postalCode>
            <city>City Name</city>
        </addr>

will result in:

        <addr use="H">
            <country/>
            <streetAddressLine/>
            <postalCode/>
            <city/>
        </addr>

This is the XSD definition of the ANY complex type:

    <xs:complexType name="ANY" abstract="true">
        <xs:attribute name="nullFlavor" type="NullFlavor" use="optional">
        </xs:attribute>
    </xs:complexType>

This is the complete pom of the project with the plugin for generating classes with maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>xml.model.cda2</groupId>
    <artifactId>cda-jaxb-model</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
    </parent>
    
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jaxb2-maven-plugin.version>3.1.0</jaxb2-maven-plugin.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.name}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
    <profiles>
        <!-- Jaxb model generation profile
            https://github.com/HL7/CDA-core-2.1/tree/master/schemas/normative
        -->
        <profile>
            <id>jakarta-xml-bind-cda2</id>
            <build>
                <finalName>fse20-xml-cda2</finalName>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>jaxb2-maven-plugin</artifactId>
                        <version>${jaxb2-maven-plugin.version}</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>xjc</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <extension>true</extension>
                            <xjbSources>
                                <xjbSource>src/main/resources/jaxb.binding-cda2.xjb</xjbSource>
                            </xjbSources>
                            <sources>
                                <source>src/main/resources/xsd/CDA.xsd</source>
                            </sources>
                            <outputDirectory>${project.basedir}/target/generated-sources/xml-bind</outputDirectory>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Here follows my binding file, considering that CDA.xsd is under resources/xsd folder, all other xsd files are in resource/xsd/coreschemas, I also added the generateMixedExtensions option that is not working for me:

<?xml version="1.0" encoding="UTF-8"?>
<jxb:bindings version="3.0"
    xmlns:jxb="https://jakarta.ee/xml/ns/jaxb"
    xmlns:xjc="https://jakarta.ee/xml/ns/jaxb/xjc"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <jxb:bindings schemaLocation="xsd/CDA.xsd">
        <jxb:schemaBindings>
            <jxb:package name="xml.model.cda2" />
        </jxb:schemaBindings>
    </jxb:bindings>
    <jxb:bindings
        schemaLocation="xsd/coreschemas/POCD_MT000040UV02.xsd">
        <jxb:bindings
            node="//xs:complexType[@name='POCD_MT000040UV02.ObservationMedia']/xs:attribute[@name='ID']">
            <jxb:property name="id2" />
        </jxb:bindings>
        <jxb:bindings
            node="//xs:complexType[@name='POCD_MT000040UV02.RegionOfInterest']/xs:attribute[@name='ID']">
            <jxb:property name="id2" />
        </jxb:bindings>
        <jxb:bindings
            node="//xs:complexType[@name='POCD_MT000040UV02.Section']/xs:attribute[@name='ID']">
            <jxb:property name="id2" />
        </jxb:bindings>
    </jxb:bindings>
    <jaxb:bindings
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
        xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
        jaxb:extensionBindingPrefixes="xjc">
        <jaxb:globalBindings
            generateMixedExtensions="true" />
    </jaxb:bindings>
</jxb:bindings>

At the moment I've solved with a manual patch adding to the class ANY a string field annotated with @XmlValue after xjc generation.

EDIT/HINT: starting from a suggestion of ChatGPT I discovered that the problem is a limit of JAXB XJC tool that is not able to process complex elements having mixed="true" property, the problem with this xsd deifning CDA documents was already known in the far 2010 and was treated in this topic, but unfortunately the solution provided in this thread doesen't work for me, probably due to the fact that I'm using Glassfish

1

There are 1 answers

2
Laurent Schoelens On BEST ANSWER

I'm not sure that this answer will solve your problem since I don't have all your project input but there's a suggestion of update based on your inputs.

The following bindings.xjb file is updated to not mix JAXB3/JAXB2 bindings and add the <jxb:globalBindings generateMixedExtensions="true" /> instruction.

<?xml version="1.0" encoding="UTF-8"?>
<jxb:bindings version="3.0"
    xmlns:jxb="https://jakarta.ee/xml/ns/jaxb"
    xmlns:xjc="https://jakarta.ee/xml/ns/jaxb/xjc"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    jxb:extensionBindingPrefixes="xjc">

    <jxb:globalBindings generateMixedExtensions="true" />

    <jxb:bindings schemaLocation="xsd/CDA.xsd">
        <jxb:schemaBindings>
            <jxb:package name="xml.model.cda2" />
        </jxb:schemaBindings>
    </jxb:bindings>
    <jxb:bindings
        schemaLocation="xsd/POCD_MT000040UV02.xsd">
        <jxb:bindings
            node="//xs:complexType[@name='POCD_MT000040UV02.ObservationMedia']/xs:attribute[@name='ID']">
            <jxb:property name="id2" />
        </jxb:bindings>
        <jxb:bindings
            node="//xs:complexType[@name='POCD_MT000040UV02.RegionOfInterest']/xs:attribute[@name='ID']">
            <jxb:property name="id2" />
        </jxb:bindings>
        <jxb:bindings
            node="//xs:complexType[@name='POCD_MT000040UV02.Section']/xs:attribute[@name='ID']">
            <jxb:property name="id2" />
        </jxb:bindings>
    </jxb:bindings>
</jxb:bindings>

By adding this like this, the generated BIN class evolves from

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BIN")
@XmlSeeAlso({
    ED.class,
    EDR2B.class
})
public abstract class BIN
    extends ANY
{

    @XmlAttribute(name = "representation")
    protected BinaryDataEncoding representation;

    /**
     * Obtient la valeur de la propriété representation.
     * 
     * @return
     *     possible object is
     *     {@link BinaryDataEncoding }
     *     
     */
    public BinaryDataEncoding getRepresentation() {
        if (representation == null) {
            return BinaryDataEncoding.TXT;
        } else {
            return representation;
        }
    }

    /**
     * Définit la valeur de la propriété representation.
     * 
     * @param value
     *     allowed object is
     *     {@link BinaryDataEncoding }
     *     
     */
    public void setRepresentation(BinaryDataEncoding value) {
        this.representation = value;
    }

}

To the following with a new field @XmlMixed protected List<Serializable> content;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BIN", propOrder = {
    "content"
})
@XmlSeeAlso({
    ED.class,
    EDR2B.class
})
public abstract class BIN
    extends ANY
{

    @XmlMixed
    protected List<Serializable> content;
    @XmlAttribute(name = "representation")
    protected BinaryDataEncoding representation;

    /**
     * 
     *             Binary data is a raw block of bits. Binary data is a
     *             protected type that MUST not be used outside the data
     *             type specification.
     *          Gets the value of the content property.
     * 
     * <p>
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the Jakarta XML Binding object.
     * This is why there is not a {@code set} method for the content property.
     * 
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getContent().add(newItem);
     * </pre>
     * 
     * 
     * <p>
     * Objects of the following type(s) are allowed in the list
     * {@link String }
     * 
     * 
     * @return
     *     The value of the content property.
     */
    public List<Serializable> getContent() {
        if (content == null) {
            content = new ArrayList<>();
        }
        return this.content;
    }

    /**
     * Obtient la valeur de la propriété representation.
     * 
     * @return
     *     possible object is
     *     {@link BinaryDataEncoding }
     *     
     */
    public BinaryDataEncoding getRepresentation() {
        if (representation == null) {
            return BinaryDataEncoding.TXT;
        } else {
            return representation;
        }
    }

    /**
     * Définit la valeur de la propriété representation.
     * 
     * @param value
     *     allowed object is
     *     {@link BinaryDataEncoding }
     *     
     */
    public void setRepresentation(BinaryDataEncoding value) {
        this.representation = value;
    }

}