Skip to content
Laurent Schoelens edited this page May 6, 2024 · 8 revisions

Using Episodes

Also known as Separate schema compilation. If you're compiling large sets of schemas (like the OGC Schemas) you may probably want to compile the schemas separately. For instance, if you have two schemas A and B (where B imports A), you may want to compile them into two artifacts A.jar and B.jar such that :

  • Classes relevant to A reside in the A.jar artifact.
  • Classes relevant to B (and only those classes) reside in the B.jar artifact.
  • The A.jar artifact is the dependency of the B.jar artifact.

This task is called the separate or episodic compilation. Kohsuke described it in his blog [newlink] (webarchive link).

JAXB Maven plugin supports episodic compilation via the following configuration parameters :

  • episode - If true, the episode file (describing mapping of elements and types to classes for the compiled schema) will be generated.
  • episodeFile - Target location of the episode file. By default it is target/generated-sources/xjc/META-INF/sun-jaxb.episode so that the episode file will appear as META-INF/sun-jaxb.episode in the JAR - just as XJC wants it.
  • addIfExistsToEpisodeSchemaBindings - If true (default), adds if-exists="true" attributes to the bindings elements associated with schemas (via scd="x-schema::...") in the generated episode files. This is necessary to avoid the annoying SCD "x-schema::tns" didn't match any schema component errors.
  • episodes/episode - If you want to use existing artifacts as episodes for separate compilation, configure them as episodes/episode elements. It is assumed that episode artifacts contain an appropriate META-INF/sun-jaxb.episode resource.
    • groupId - Group id of the episode artifact, required.
    • artifactId - Id of the episode artifact, required.
    • version - Version of the episode artifact. May be omitted. The plugin will then try to find the version using the dependencyManagement and dependencies of the project.
    • type - Type of the episode artifact, optional. Defaults to jar.
    • classifier - Classifier of the episode artifact, optional. Defaults to none.
  • useDependenciesAsEpisodes - Use all of the compile-scope project dependencies as episode artifacts. It is assumed that episode artifacts contain an appropriate META-INF/sun-jaxb.episode resource. Default is false.

For example, consider that we've built the A schema as com.acme.foo:a-schema:jar:1.0 artifact and want to use it as an episode when we compile the B schema. Here's how we configure it :

<project ...>
...
  <dependencies>
  ...
    <dependency>
      <groupId>com.acme.foo</groupId>
      <artifactId>a-schema</artifactId>
      <version>1.0</version>
    </dependency>
  ...
  </dependencies>
  <build>
    <defaultGoal>test</defaultGoal>
    <plugins>
      <plugin>
        <groupId>org.jvnet.jaxb</groupId>
        <artifactId>jaxb-maven-plugin</artifactId>
        <configuration>  
          <extension>true</extension>
          <useDependenciesAsEpisodes>true</useDependenciesAsEpisodes>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Alternatively you can specify episode artifacts explicitly :

<project ...>
...
  <dependencies>
  ...
    <dependency>
      <groupId>com.acme.foo</groupId>
      <artifactId>a-schema</artifactId>
      <version>1.0</version>
    </dependency>
  ...
  </dependencies>
  <build>
    <defaultGoal>test</defaultGoal>
    <plugins>
      <plugin>
        <groupId>org.jvnet.jaxb</groupId>
        <artifactId>jaxb-maven-plugin</artifactId>
        <configuration>  
          <extension>true</extension>
          <episodes>
            <episode>
              <groupId>com.acme.foo</groupId>
              <artifactId>a-schema</artifactId>
              <!-- Version is not required if the artifact is configured as dependency -->
            </episode>
          </episodes>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

In this case JAXB will not generate classes for the imported A schema. The B.jar artifact will only contain classes relevant to the B schema. Note that JAXB still needs to access BOTH A and B schemas during the compilation. You may use catalogs to provide alternative locations of the imported schemas.

See the sample episode project for example.

Note that JAXB still needs to access BOTH A and B schemas during the compilation. And you probably don't want to manually copy or duplicate schemas.

One way to solve this is to use the maven-dependency-plugin to unpack the required schema from schema-a.jar.

A much better way is to use a catalog to resolve schema-a.xsd into a resource inside the Maven artifact schema-a:

REWRITE_SYSTEM "http://www.acme.com/foo/a/" "maven:com.acme.foo:a-schema!/"
<configuration>
	<catalog>src/main/resources/catalog.cat</catalog>
	<!-- ... -->
</configuration>

The import of http://www.acme.com/foo/a/a.xsd will be then resolved to the a.xsd resource inside the a-schema.jar.

A combination of episodes, catalogs and referencing resources in Maven artifacts allows fully modular schema compilation. Please see the Modular Schema Compilation guide.

Internals of an episode file

It is quite helpful to understand the mechanics of the episode files.

Episode is actually a JAR file which contains a META-INF/sun-jaxb.episode resource. This resource is nothing else but a bindings file, like those you'd use to customize package or property names. Below is an example of the sun-jaxb.episode for the XLink 1.0 Schema:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
  <bindings scd="x-schema::tns" xmlns:tns="http://www.w3.org/1999/xlink">
    <schemaBindings map="false">
      <package name="org.hisrc.w3c.xlink.v_1_0"/>
    </schemaBindings>
    <bindings scd="~tns:arcType">
      <class ref="org.hisrc.w3c.xlink.v_1_0.ArcType"/>
    </bindings>
    <bindings scd="~tns:resourceType">
      <class ref="org.hisrc.w3c.xlink.v_1_0.ResourceType"/>
    </bindings>
    <!-- ... -->
    <bindings scd="~tns:showType">
      <typesafeEnumClass ref="org.hisrc.w3c.xlink.v_1_0.ShowType"/>
    </bindings>
  </bindings>
</bindings>

Note the following:

  • The binding uses SCD (schema component designator) notation instead of the usual XPath to address schema components.
  • <bindings scd="x-schema::tns" xmlns:tns="http://www.w3.org/1999/xlink" .../> addresses the schema with the target namespace http://www.w3.org/1999/xlink.
  • schemaBindings/@map="false" instructs XJC to suppress generation of classes for this schema.
  • class/@ref binds schema components to existing classes.

In some cases, XJC still generates classes in packages which are actually suppressed by episodes. This is probably a bug in XJC. You can use the delete task from the maven-antrun-plugin to remove these leftovers:

<plugin>
	<artifactId>maven-antrun-plugin</artifactId>
	<executions>
		<execution>
			<phase>process-sources</phase>
			<configuration>
				<tasks>
					<delete dir="${basedir}/target/generated-sources/xjc/net/opengis/filter"/>
					<delete dir="${basedir}/target/generated-sources/xjc/net/opengis/ows"/>
				</tasks>
			</configuration>
			<goals>
				<goal>run</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Adding if-exists="true"

If you compile several schemas at once and generate an episode file, it will contain bindings for all of these schemas. You can recognize them by the x-schema::... SCD-binding. For example, assume we've compiled two schemas with namespaces urn:a and urn:b.

  <bindings xmlns:tns="urn:a" scd="x-schema::tns">
    <!-- ... -->
  </bindings>
  <bindings xmlns:tns="urn:b" scd="x-schema::tns">
    <!-- ... -->
  </bindings>

If later on you'll have some schema urn:c which depends on urn:a (but not on urn:b) and you'll want to use the generated episode, you'll an error like:

"SCD "x-schema::tns" didnt match any schema component"

The problem is that your episode file contains bindings for urn:b but it is not used in your compilation. And, XJC is too strict in this case it produces an error which breaks the build.

It is possible to "soften" this behavour by adding if-exists="true" attribute to the appropriate bindings element. But the problem is that with episodes these elements are generated by the built-in XJC plugin, which does not generate if-exists="true".

To fix this, this plugin implements the addIfExistsToEpisodeSchemaBindings feature. If it is turned on (by default), the plugin will transform the generated episode files adding the if-exists="true" attribute to all the bindings element having scd which start with x-schema:: - i.e. bound to schemas using SCDs.

Clone this wiki locally