7

I have a project which has a schema A and B, both within the same namespace. Both import schema C which also uses the same namespace. How can I generate JAXB classes for A and B to separate packages, while reusing the JAXB classes from C generated to a commons package?

I already know I should probably be using episodes and use the episode generated for schema C as bindings file for the separate executions of schema's A and B. Problem is I don't know how to refer to this generated episode file.

Here's an example:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.12.3</version>
    <executions>
        <execution>
            <id>generate-sources-C</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <generatePackage>com.mymodel.commons</generatePackage>
                <generateDirectory>${project.build.directory}/generated-sources/xjc-commons</generateDirectory>
                <schemas>
                    <schema><url>src/main/resources/xsd/mymodel/c.xsd</url></schema>
                </schemas>
            </configuration>
        </execution>
        <execution>
            <id>generate-sources-A</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <generatePackage>com.mymodel.a</generatePackage>
                <schemas>
                    <schema><url>src/main/resources/xsd/mymodel/a.xsd</url></schema>
                </schemas>
            </configuration>
        </execution>
        <execution>
            <id>generate-sources-B</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <generatePackage>com.mymodel.b</generatePackage>
                <schemas>
                    <schema><url>src/main/resources/xsd/mymodel/b.xsd</url></schema>
                </schemas>
            </configuration>
        </execution>
    </executions>
</plugin>

This causes an episode file to be created under:

target/generated-sources/xjc-commons/META-INF/sun-jaxb.episode

How do I refer to this episode/bindings file in executions for A and B? Using Episodes only mentions how to refer to an episode file from other jar dependencies (or I simply didn't understand it properly, which is more likely).

I've seen an older answer suggest to pass it as a parameter -b to XJC, but that didn't seem to do anything for me. I still end up with the same class from C generated three times.

lexicore
  • 42,748
  • 17
  • 132
  • 221
Benny Bottema
  • 11,111
  • 10
  • 71
  • 96

1 Answers1

9

Disclaimer: I am the author of .

TL;DR here's a test project which demonstrates how to do this.

This is possible, but is a bit hairy, so please bear with me.

If a.xsd, b.xsd and c.xsd are in the same namespace, a.xsd and b.xsd cannot import c.xsd, they can only include it. We want to generate each of the XSDs into its own package, say test.a, test.b and test.c and do it within the same single Maven project.

To do this we will need three separate executions of the maven-jaxb2-plugin, each configured with its own schema and target package. For example:

        <plugin>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <executions>
                <execution>
                    <id>xjc-a</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <configuration>
                        <generatePackage>test.a</generatePackage>
                        <generateDirectory>${project.build.directory}/xjc-a</generateDirectory>
                        <schemaIncludes>
                            <includes>a.xsd</includes>
                        </schemaIncludes>
                    </configuration>
                </execution>
                <!-- xjc-b and xjc-c follow -->
            </executions>
        </plugin>

It is is important to use different target directories for separate executions.

OK, this would create three target directories with three target packages. Next problem is that classes from c.xsd will generated in test.a and test.b which we want to avoid.

To achieve this, we have to tell XJC to use classes from test.c for types from c.xsd. This is actually what episode file is for. This file is normally generated under META-INF\sun-jaxb.episode and it contains bindings for all types in the processed schema. Here's an example generated for c.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" if-exists="true" version="2.1">
  <bindings xmlns:tns="urn:test" if-exists="true" scd="x-schema::tns">
    <schemaBindings map="false">
      <package name="test.c"/>
    </schemaBindings>
    <bindings if-exists="true" scd="~tns:CType">
      <class ref="test.c.CType"/>
    </bindings>
  </bindings>
</bindings>

Episode file is actually a normal bindings file. So you can directly use it in compilation:

                <execution>
                    <id>xjc-a</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <configuration>
                        <generatePackage>test.a</generatePackage>
                        <generateDirectory>${project.build.directory}/xjc-a</generateDirectory>
                        <schemaIncludes>
                            <includes>a.xsd</includes>
                        </schemaIncludes>
                        <bindings>
                            <binding>
                                <fileset>
                                    <directory>${project.build.directory}/xjc-c/META-INF</directory>
                                    <includes>
                                        <include>sun-jaxb.episode</include>
                                    </includes>
                                </fileset>
                            </binding>
                        </bindings>
                    </configuration>
                </execution>

There is just one tiny problem left. Episode files generated by XJC also contain this fragment:

    <schemaBindings map="false">
      <!-- ... -->
    </schemaBindings>

It effectively says "do not generate code for schema in the given namespace". This would not be a problem if a.xsd or b.xsd would be in a different namespace. But since they are in the same namespace, this fragment will effectively turn off all code generation for a.xsd or b.xsd.

To work around this we can post-process the sun-jaxb.episode which was generated for c.xsd. This can be done with a simple XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" version="1.0">
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="jaxb:schemaBindings"/>
</xsl:stylesheet>

This XSLT should be run after the code for c.xsd, but before the code for a.xsd and b.xsd is generated. This can be achieved by putting these executions into different phases (generate-sources, process-sources, generate-resources).


Below is the complete pom.xml:

<?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>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>divide</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.2.11</version>
        </dependency>
        <!-- JUnit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.12</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>xml-maven-plugin</artifactId>
                <version>1.0.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>transform</goal>
                        </goals>
                        <phase>process-sources</phase>
                    </execution>
                </executions>
                <configuration>
                    <transformationSets>
                        <transformationSet>
                            <dir>${project.build.directory}/xjc-c/META-INF</dir>
                            <outputDir>${project.build.directory}/xjc-c/META-INF</outputDir>
                            <includes>
                                <include>sun-jaxb.episode</include>
                            </includes>
                            <stylesheet>src/main/xslt/removeJaxbSchemaBindings.xslt</stylesheet>
                        </transformationSet>
                    </transformationSets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>0.13.3</version>
                <executions>
                    <execution>
                        <id>xjc-c</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>generate-sources</phase>
                        <configuration>
                            <generatePackage>test.c</generatePackage>
                            <generateDirectory>${project.build.directory}/xjc-c</generateDirectory>
                            <schemaIncludes>
                                <includes>c.xsd</includes>
                            </schemaIncludes>
                        </configuration>
                    </execution>
                    <execution>
                        <id>xjc-a</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <generatePackage>test.a</generatePackage>
                            <generateDirectory>${project.build.directory}/xjc-a</generateDirectory>
                            <schemaIncludes>
                                <includes>a.xsd</includes>
                            </schemaIncludes>
                            <bindings>
                                <binding>
                                    <fileset>
                                        <directory>${project.build.directory}/xjc-c/META-INF</directory>
                                        <includes>
                                            <include>sun-jaxb.episode</include>
                                        </includes>
                                    </fileset>
                                </binding>
                            </bindings>
                        </configuration>
                    </execution>
                    <execution>
                        <id>xjc-b</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <generatePackage>test.b</generatePackage>
                            <generateDirectory>${project.build.directory}/xjc-b</generateDirectory>
                            <schemaIncludes>
                                <includes>b.xsd</includes>
                            </schemaIncludes>
                            <bindings>
                                <binding>
                                    <fileset>
                                        <directory>${project.build.directory}/xjc-c/META-INF</directory>
                                        <includes>
                                            <include>sun-jaxb.episode</include>
                                        </includes>
                                    </fileset>
                                </binding>
                            </bindings>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
lexicore
  • 42,748
  • 17
  • 132
  • 221
  • I did not expect this. I guess I'm trying to do something quite unconventional. Thank you so much for looking into this. I guess you had to play around with it a little bit too, considering the XSLT step :D – Benny Bottema May 06 '18 at 10:27
  • 1
    @BennyBottema It *is* unconventional. Conventional is one namespace - one package. You want one namespace - three packages. *If* `c.xsd` would have had its own namespace, it would have been much easier (no XSLT). – lexicore May 06 '18 at 11:33
  • Yeah, thought as much. Unfortunately, I'm working with the constraints of a suite of legacy systems that are locked into this package pattern. Again, thank you so much for clearing this up! – Benny Bottema May 06 '18 at 11:35
  • Apparently this doesn't work if the entry schema is a WSDL which imports the respective XSD's. If I refer to the XSD's directly, this setup works. I'm not sure if I even need the WSDL's here, but is there a way around this? – Benny Bottema May 07 '18 at 08:02
  • 1
    @BennyBottema I don't have much experience with WSDLs. Probably yes, bindings+catalogs have much power, but hard to say without experimenting. – lexicore May 07 '18 at 08:06
  • Just to add I think its awesome lexicore lurks stackoverflow and responds to these jaxb questions.. I'd give more votes but I only have one ;). –  Dec 14 '18 at 10:10
  • Thanks a lot for this answer and for providing the complete pom.xml. I had exactly the same problem (except of 11 (!) schemas instead of 2 including to a "common" schema). One hint: for the generateDirectory I had to use ${project.build.directory}/generated-sources/xjc-b instead of just ${project.build.directory}/xjc-b, so that the generated sources were also compiled. – stefitz Jan 23 '20 at 16:45