Maven Jaxb Generate Fails When Compiling A Module That Depends On Multiple Modules

8.1k views Asked by At

I have an Eclipse Maven project consisting of multiple modules, some of which contain Xml schemas that I want to generate classes for (using Jaxb). My project layout is as follows:

schemas\core (pom)
schemas\core\types (jar)
schemas\vehicle (pom)
schemas\vehicle\automobile (jar)
schemas\vehicle\civic (jar)

The projects that contain schemas are:

schemas\core\types (xsd\types.xsd)
schemas\vehicle\automobile (xsd\automobile.xsd)
schemas\vehicle\civic (xsd\civic.xsd)

Some of the modules contain schemas that import schemas from other modules:

automobile.xsd imports types.xsd
civic.xsd imports types.xsd, automobile.xsd

Since the schemas are located in different projects I use a classpath catalog resolver along with catalog files to resolve the location of the schemas.

The automobile project depends on schemas in the types project. Here is the entry in its catalog file (catalog.xml):

<rewriteSystem systemIdStartString="http://schemas/core/types/" rewritePrefix="classpath:xsd/" />

Note the use of classpath:xsd/ to tell the catalog resolver to find the schemas on the classpath.

I also use episodes to prevent the classes in types from being re-generated inside the automobile project. Here is a snippit from my pom.xml:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.8.3</version>
    <configuration>
        <episodes>
            <episode>
                <groupId>schemas.core</groupId>
                <artifactId>types</artifactId>
                <version>1.0-SNAPSHOT</version>
            </episode>
        <episodes>
        <catalog>src/main/resources/catalog.xml</catalog>
        <catalogResolver>org.jvnet.jaxb2.maven2.resolver.tools.ClasspathCatalogResolver</catalogResolver>
        <extension>true</extension>
        ....

When I run mvn clean install on automobile project everything works file. The schema types.xsd is resolved on the classpath and the classes are ultimately generated.

Where I run into problems is trying to compile the project civic.

The civic project depends on both types.xsd and automobile.xsd. I use a catalog file (catalog.xml) to define the location of the schemas:

<rewriteSystem systemIdStartString="http://schemas/core/types/" rewritePrefix="classpath:xsd/" />
<rewriteSystem systemIdStartString="http://schemas/vehicle/automobile/" rewritePrefix="classpath:xsd/" />

I use episodes to prevent re-generation of the classes. Here is a snippit from the pom.xml for civic:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.8.3</version>
    <configuration>
        <episodes>
            <episode>
                <groupId>schemas.core</groupId>
                <artifactId>types</artifactId>
                <version>1.0-SNAPSHOT</version>
            </episode>
            <episode>
                <groupId>schemas.vehicle</groupId>
                <artifactId>automobile</artifactId>
                <version>1.0-SNAPSHOT</version>
            </episode>
        </episodes>
        <catalog>src/main/resources/catalog.xml</catalog>
        <catalogResolver>org.jvnet.jaxb2.maven2.resolver.tools.ClasspathCatalogResolver</catalogResolver>
        <extension>true</extension>
        ...

When I try to run mvn clean install on the civic project I run into problems. It complains about not being able to resolve the public/system ids. Here are some of the error messages I get:

Could not resolve publicId [null], systemId [jar:file:/_m2repository/schemas/vehicle/automobile/1.0-SNAPSHOT/automobile-1.0-SNAPSHOT.jar!http://schemas/core/types/types.xsd]
[ERROR] Error while parsing schema(s).Location [].
com.sun.istack.SAXParseException2; 
IOException thrown when processing "jar:file:/_m2repository/schemas/vehicle/automobile/1.0-SNAPSHOT/automobile-1.0-SNAPSHOT.jar!http://schemas/core/types/types.xsd".
Exception: java.net.MalformedURLException: no !/ in spec.
....

For some reason it cannot find types.xsd when trying to parse the jar file from the automobile project.

Does anyone know why this might be happening?

Thank you.

Note - I was experimenting around with tying to get things to work and I did find one way. If I remove the episodes from the pom.xml file I no longer get the error, however, the project civic ends up with all the types from the dependent modules (which is something I am tying to avoid by using the episodes).

civic project generated java classes

If you want to see the full catalog.xml and pom.xml files for each project please see the following links:

types: http://pastebin.com/Uym3DY6X

automobile: http://pastebin.com/VQM4MPuW

civic: http://pastebin.com/eGSVGwmE

3

There are 3 answers

2
RogierE On BEST ANSWER

I have the same problem. Schema C imports B and A, B imports A. Generating sources for A, works, B is also fine and for C a MalformedUrlException pops up.

I'm still investigating the error but a workaround is to use the systemIdSuffix (Oasis spec 1.1) to match the systemId and rewrite it. You need to do the following:

Remove the 'catalogResolver' element from the plugin configuration in the poms.

Replace the content of the catalog file for the 'automobile' project with the following:

<systemSuffix systemIdSuffix="types.xsd" uri="maven:schemas.core:types!/types.xsd"/>

Replace the content of the catalog file for the 'civic' project with the following:

<systemSuffix systemIdSuffix="types.xsd" uri="maven:schemas.core:types!/types.xsd"/>
<systemSuffix systemIdSuffix="automobile.xsd" uri="maven:schemas.vehicle:automobile!/automobile.xsd"/>

Let me know if this works for you.

1
lexicore On

Author of the maven-jaxb2-plugin here.

I have just released the 0.10.0 version of the maven-jaxb2-plugin. This release fixes the MAVEN_JAXB2_PLUGIN-82 issue which is related to the reported problems.

This was actually NOT a bug in the maven-jaxb2-plugin, but an issue (or, better to say a few issues) in the XJC itself:

These issues cause problems when catalog and binding files are used together. This was also the reason why the Maven artifact resoltion did not work correctly in certain cases.

In the 0.10.0 release, I have implemented workarounds for JAXB-1044 and JAXB-1045. I will try to get my patches to the XJC via pull requests, but you know, I'm not sure, when/if Oracle guys will accept my PRs.

In the maven-jaxb2-plugin I've now implemented quite reliable workarounds. See this test project here:

https://github.com/highsource/maven-jaxb2-plugin/tree/master/tests/MAVEN_JAXB2_PLUGIN-82

This does exactly what you want: resolves schema via catalog AND Maven resolver to the resource from another artifact. Basically, this rewriting:

REWRITE_SYSTEM "http://www.ab.org" "maven:org.jvnet.jaxb2.maven2:maven-jaxb2-plugin-tests-MAVEN_JAXB2_PLUGIN-82-a:jar::!"

now works fine.

In case of problems do mvn -X and check the output, you'll also see the statements of the catalog resolver in the log. This might give you hints, what does not work.

Here's another project which uses schemas, bindings and the catalog itself from one central artifact:

https://github.com/highsource/w3c-schemas

Snippets from the POM:

                    <schemas>
                        <schema>
                            <url>http://www.w3.org/1999/xlink.xsd</url>
                        </schema>
                    </schemas>

                    <schemaIncludes/>
                    <bindings>
                        <binding>
                            <dependencyResource>
                                <groupId>${project.groupId}</groupId>
                                <artifactId>w3c-schemas</artifactId>
                                <resource>globalBindings.xjb</resource>
                                <version>${project.version}</version>
                            </dependencyResource>
                        </binding>
                    </bindings>
                    <catalogs>
                        <catalog>
                            <dependencyResource>
                                <groupId>${project.groupId}</groupId>
                                <artifactId>w3c-schemas</artifactId>
                                <resource>catalog.cat</resource>
                                <version>${project.version}</version>
                            </dependencyResource>
                        </catalog>
                    </catalogs>

Catalog:

REWRITE_SYSTEM "http://www.w3.org" "maven:org.hisrc.w3c:w3c-schemas:jar::!/w3c"

Binding:

<jaxb:bindings schemaLocation="http://www.w3.org/1999/xlink.xsd" node="/xs:schema">
    <jaxb:schemaBindings>
        <jaxb:package name="org.hisrc.w3c.xlink.v_1_0"/>
    </jaxb:schemaBindings>
</jaxb:bindings>

So how all of this works:

  • Schemas as well as the catalog and global bindings are stored in the central artifact w3c-schemas.
  • The project wants to compile the URL http://www.w3.org/1999/xlink.xsd.
  • The catalog rewrites this URL into the systemId maven:org.hisrc.w3c:w3c-schemas:jar::!/w3c/1999/xlink.xsd. (There's a /w3c/1999/xlink.xsd resource in the w3c-schemas jar).
  • This systemId is then resolved my the Maven catalog resolver (delivered my the maven-jaxb2-plugin) into the "real" URL which will be some jar:... URL pointing to the resource within the w3c-schemas artifact JAR in the local repository.
  • So the schema is not downloaded from the Internet but taken from the local resource.
  • The workaround keep the "original" systemIds, therefor you can customize the schema using its original URL. (The resolved systemId won't be convenient.)
  • The catalog file and the global bindings file will be the same for all the individual projects, so they're also put into the central artifact and referenced there using the dependencyResource.
1
Sjaak On

I faced similar problems. I used the sample projects found here.

I modified these projects in 2 ways:

1) Have an A project with 2 namespaces and a local catalog file. Have a project B depending on this, using the episode of A in B.

2) Have an A project, a B project and a C project. B relies on A and C relies on B.

In both cases I got the same exception as you. But I started to realize in situation 2 what is happening.

This is the exception: com.sun.istack.SAXParseException2; IOException thrown when processing "jar:file:/Users/sjaak/.m2/repository/org/tst/b-working/1.0/b-working-1.0.jar!http://www.a1.org/a1/a1.xsd". Exception: java.net.MalformedURLException: no !/ in spec.

So, it tries to resolve namespace http://www.a1.org/a1/a1.xsd relative to project B when building project C. I traced the problem back to com.sun.tools.xjc.reader.internalizerAbstractReferenceFinderImpl, method startElement.

The solution I use is adapting the org.jvnet.jaxb2.maven2:maven-jaxb2-plugin. I used their MavenCatalogResolver (the default one as pointed out above) and made a small change, simply not offering the whole systemId: jar:file:/Users/sjaak/.m2/repository/org/tst/b-working/1.0/b-working-1.0.jar!http://www.a1.org/a1/a1.xsd, but in stead use a pattern that only offers the part after the exclamation mark for resolving.

Here's the code:

package org.jvnet.jaxb2.maven2.resolver.tools;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;

import org.jvnet.jaxb2.maven2.DependencyResource;
import org.jvnet.jaxb2.maven2.DependencyResourceResolver;

import com.sun.org.apache.xml.internal.resolver.CatalogManager;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MavenCatalogResolver extends
        com.sun.org.apache.xml.internal.resolver.tools.CatalogResolver {

    private final static Pattern PTRN = Pattern.compile("^jar:file:(.*).jar!(.*)$");

    public static final String URI_SCHEME_MAVEN = "maven";
    private final DependencyResourceResolver dependencyResourceResolver;
    private final CatalogManager catalogManager;

    public MavenCatalogResolver(CatalogManager catalogManager,
            DependencyResourceResolver dependencyResourceResolver) {
        super(catalogManager);
        this.catalogManager = catalogManager;
        if (dependencyResourceResolver == null) {
            throw new IllegalArgumentException(
                    "Dependency resource resolver must not be null.");
        }
        this.dependencyResourceResolver = dependencyResourceResolver;
    }

    @Override
    public String getResolvedEntity(String publicId, String systemId) 
    {
        String result;
         Matcher matcher = PTRN.matcher(systemId);
         if (matcher.matches())
         {      
              result = super.getResolvedEntity(publicId, matcher.group(2));
         }
         else
         {
              result = super.getResolvedEntity(publicId, systemId);          
         }
        if (result == null) {
            return null;
        }

        try {
            final URI uri = new URI(result);
            if (URI_SCHEME_MAVEN.equals(uri.getScheme())) {
                final String schemeSpecificPart = uri.getSchemeSpecificPart();
                try {
                    final DependencyResource dependencyResource = DependencyResource
                            .valueOf(schemeSpecificPart);
                    try {
                        final URL url = dependencyResourceResolver
                                .resolveDependencyResource(dependencyResource);
                        String resolved = url.toString();
                        return resolved;
                    } catch (Exception ex) {
                        catalogManager.debug.message(1, MessageFormat.format(
                                "Error resolving dependency resource [{0}].",
                                dependencyResource));
                    }

                } catch (IllegalArgumentException iaex) {
                    catalogManager.debug.message(1, MessageFormat.format(
                            "Error parsing dependency descriptor [{0}].",
                            schemeSpecificPart));

                }
                return null;
            } else {
                return result;
            }
        } catch (URISyntaxException urisex) {

            return result;
        }
    }

}

This actually fixed my problem. I'll investigate a bit more. I've got the feeling there might be some XJC arg that I could use, or perhaps the catalog XML format offers more possibilities.

Hope it helps.