Search code examples
xmlmavenxsdjaxb

Apache CXF XSD scheme to Java, change output class and factory name


I'm using CXF maven plugin to generate Java classes out of XSD scheme definitions. I use two xsdOptions-tags to create Java files of two different schemes into two different packages. The generated Java classes and factories have the same name, e.g. "ObjectFactory.java". How can I change the name of the generated files to a custom name?

Plugin entry in my POM.xml file:

<plugin>
                    <groupId>org.apache.cxf</groupId>
                    <artifactId>cxf-xjc-plugin</artifactId>
                    <version>3.3.0</version>
                    <executions>
                        <execution>
                            <id>xjc</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>xsdtojava</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <sourceRoot>${basedir}/src/main/java/</sourceRoot>
                        <xsdOptions>
                            <xsdOption>
                                <xsd>${basedir}/src/main/resources/schemes/A.xsd</xsd>
                                <packagename>com.exmaple.A</packagename>
                            </xsdOption>
                            <xsdOption>
                                <xsd>${basedir}/src/main/resources/B.xsd</xsd>
                                <packagename>com.example.B</packagename>
                            </xsdOption>
                        </xsdOptions>
                    </configuration>
                </plugin>

I tried adding extensionArgs-tags but that did not run.


Solution

  • This download (zip) is a stand-alone Maven project to demonstrate the configuration of the Maven cxf-xjc-plugin from Apache CXF together with the XJC HiSrc BasicJAXB Plugins. Specifically,

    Note: After extracting the download to your local folder, you can run the tests and/or the demo using:

    mvn -Ptest clean test
    mvn -Pexec compile exec:java
    

    Both XML schemas (A and B) define similar business models. Each models a notebook. The A schema models a paper notebook while the B schema models an electronic notebook. Both models have the same element names:

    Business Model

    notebook
        owner
        pageSpec
    

    By default, XJC generates a JAXB class for each of the above, in separate Java packages:

    Without Customization: A

    org.example.nb.A.Notebook
    org.example.nb.A.Owner
    org.example.nb.A.PageSpec
    org.example.nb.A.ObjectFactory
    

    Without Customization: B

    org.example.nb.B.Notebook
    org.example.nb.B.Owner
    org.example.nb.B.PageSpec
    org.example.nb.B.ObjectFactory
    

    Although the class names are the same, each class is qualified by its own Java package. And on the XML side, each document declares it's own namespace. When unmarshalling, JAXB uses the namespace from the XML documents (nbA.xml and nbB.xml) to determine the correct business model.

    The problem is that references to the two models, in the same Java code block, require use of fully qualified class names.

    To solve that problem, each XML schema is customized using a JAXB binding file invoking jaxb:class declarations to rename the generate Java classes.

    The classes from the A schema are customized to include the suffix: Analog

    Extract from nbA.xjb

    <jaxb:bindings schemaLocation="nbA.xsd" node="/xs:schema">
    
        <jaxb:bindings node="//xs:element[@name='notebook']/xs:complexType">
            <anx:annotate>@jakarta.xml.bind.annotation.XmlRootElement(name = "notebook")</anx:annotate>
            <jaxb:class name="NotebookAnalog" />
        </jaxb:bindings>
    
        <jaxb:bindings node="//xs:complexType[@name='Owner']">
            <jaxb:class name="OwnerAnalog" />
        </jaxb:bindings>
    
        <jaxb:bindings node="//xs:complexType[@name='PageSpec']">
            <jaxb:class name="PageSpecAnalog" />
        </jaxb:bindings>
    
    </jaxb:bindings>
    

    The classes from the B schema are customized to include the suffix: Digital

    Extract from nbB.xjb

    <jaxb:bindings schemaLocation="nbB.xsd" node="/xs:schema">
    
        <jaxb:bindings node="//xs:element[@name='notebook']/xs:complexType">
            <anx:annotate>@jakarta.xml.bind.annotation.XmlRootElement(name = "notebook")</anx:annotate>
            <jaxb:class name="NotebookDigital" />
        </jaxb:bindings>
    
        <jaxb:bindings node="//xs:complexType[@name='Owner']">
            <jaxb:class name="OwnerDigital" />
        </jaxb:bindings>
    
        <jaxb:bindings node="//xs:complexType[@name='PageSpec']">
            <jaxb:class name="PageSpecDigital" />
        </jaxb:bindings>
    
    </jaxb:bindings>
    

    Note: Why is there an <anx:annotate ... > extension on the notebook element? Because, XJC heuristics omit the normally present @XmlRootElement annotation when jaxb:class is used to change the Notebook class name. HiSrc HyperJAXB Annox is used to add the annotation back in.

    The JAXB/XJC naming convention for ObjectFactory is less flexible. The JAXB reference implementation and the API both assume that this is a well known class name, qualified by its Java package name. For example, the JAXB RI registers the name as a reserved class name.

    The good news is that, an application only requires one instance of each ObjectFactory; thus, each can be declared with short names as static instances.

    Example: AbstractNotebookTest

    protected static final org.example.nb.A.ObjectFactory OFA = new org.example.nb.A.ObjectFactory();
    protected static final org.example.nb.B.ObjectFactory OFB = new org.example.nb.B.ObjectFactory();
    

    Then the OFA and OFB instances can be used conveniently, in the same code block, as demonstrated here...

    FluentAPITest

    final Object object = getUnmarshaller().unmarshal(sample);
    if ( object instanceof NotebookAnalog )
    {
        NotebookAnalog nba1 = (NotebookAnalog) object;
        NotebookAnalog nba2 = OFA.createNotebookAnalog()
            .useTitle("PaperBook")
            .useOwner(OFA.createOwnerAnalog()
                .useFirstname("Paul")
                .useLastname("McCartney"))
            .usePageSpec(OFA.createPageSpecAnalog()
                .useLinesPerPage(new BigInteger("66"))
                .usePageCount(new BigInteger("100")));
    
        getLogger().debug("NBA1: {}", nba1);
        getLogger().debug("NBA2: {}", nba2);
    
        assertEquals(nba1, nba2, "Unmarshaled and Fluent NBAs are equal.");
    }
    else if ( object instanceof NotebookDigital )
    {
        NotebookDigital nbb1 = (NotebookDigital) object;
        NotebookDigital nbb2 = OFB.createNotebookDigital()
            .useTitle("EBook")
            .useOwner(OFB.createOwnerDigital()
                .useFirstname("John")
                .useLastname("Lennon"))
            .usePageSpec(OFB.createPageSpecDigital()
                .useKbPerPage(new BigInteger("512"))
                .usePageCount(new BigInteger("1024")));
    
        getLogger().debug("NBB1: {}", nbb1);
        getLogger().debug("NBB2: {}", nbb2);
    
        assertEquals(nbb1, nbb2, "Unmarshaled and Fluent NBBs are equal.");
    }
    

    POM Configuration

    The POM is configured to use two blocks for each schema: A and B.

    Extract from pom.xml

    ...
    <!-- mvn cxf-xjc:help -Ddetail=true -->
    <!-- mvn cxf-xjc::generate -->
    <plugin>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-xjc-plugin</artifactId>
        <version>${cxf-xjc-plugin.version}</version>
        <executions>
            <execution>
                <id>generate-sources</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>xsdtojava</goal>
                </goals>
                <configuration>
                    <fork>false</fork>
                    <sourceRoot>${basedir}/target/generated-sources/xjc/</sourceRoot>
                    <extensions>
                        <extension>org.patrodyne.jvnet:hisrc-basicjaxb-plugins:2.2.1</extension>
                        <extension>org.patrodyne.jvnet:hisrc-hyperjaxb-annox-plugin:2.2.1</extension>
                    </extensions>
                    <xsdOptions>
    
                        <xsdOption>
                            <bindingFiles>
                                <bindingFile>${basedir}/src/main/resources/nb.xjb</bindingFile>
                                <bindingFile>${basedir}/src/main/resources/nbA.xjb</bindingFile>
                            </bindingFiles>
                            <packagename>org.example.nb.A</packagename>
                            <xsd>${basedir}/src/main/resources/nbA.xsd</xsd>
                            <extension>true</extension>
                            <extensionArgs>
                                ...
                                <extensionArg>-XfluentAPI</extensionArg>
                                <extensionArg>-XfluentAPI-fluentMethodPrefix=use</extensionArg>
                            </extensionArgs>
                        </xsdOption>
    
                        <xsdOption>
                            <bindingFiles>
                                <bindingFile>${basedir}/src/main/resources/nb.xjb</bindingFile>
                                <bindingFile>${basedir}/src/main/resources/nbB.xjb</bindingFile>
                            </bindingFiles>
                            <packagename>org.example.nb.B</packagename>
                            <xsd>${basedir}/src/main/resources/nbB.xsd</xsd>
                            <extension>true</extension>
                            <extensionArgs>
                                ...
                                <extensionArg>-XfluentAPI</extensionArg>
                                <extensionArg>-XfluentAPI-fluentMethodPrefix=use</extensionArg>
                            </extensionArgs>
                        </xsdOption>
    
                    </xsdOptions>
                </configuration>
            </execution>
        </executions>
        ... (dependencies)
    </plugin>
    ...
    

    Note: The cxf-xjc-plugin provides extensionArgs to activate XJC plugins from an extension library like HiSrc BasicJAXB Plugins. It is list of additional arguments passed to XJC. (ex: -XfluentAPI).

    JAR Conflicts

    The cxf-xjc-plugin above explicitly declares several dependencies (not shown) to resolve JAR conflicts between the CXF product and the HiSrc product. Because these products are produced by independent teams, some version conflicts are likely. The download (zip) contains a tool, named ReadJarManifest.java to identify and resolve conflicts. The conflicts have been resolved in the download sample.

    Generate build.log

    mvn -Dorg.slf4j.simpleLogger.log.org.apache.cxf.maven_plugin.XSDToJavaMojo=DEBUG -Ptest clean test >build.log
    

    ReadJarManifest.java

    Read JAR Manifest from a JAR file or a LOG file.
    
    Usage:
        javac ReadJarManifest.java
        java ReadJarManifest [jarfile | stdin]
    
    Example:
        java ReadJarManifest <build.log
    

    Note: The ReadJarManifest.java tool catches most issues but can also report false conflicts. In this example, the conflict percentage is falsely high because two non-conflicting jars have the same version number but different yet similar names.

    Conflict%: 89
      file:/home/rick/.m2/repository/org/glassfish/jaxb/txw2/4.0.5/txw2-4.0.5.jar
      file:/home/rick/.m2/repository/org/glassfish/jaxb/xsom/4.0.5/xsom-4.0.5.jar
    

    Disclaimer: I am the maintainer for the HiSrc projects.