Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xjc goal fails when module that uses jaxb2-maven-plugin is a sibling of the main application module #239

Open
yogregg opened this issue Dec 21, 2022 · 3 comments

Comments

@yogregg
Copy link

yogregg commented Dec 21, 2022

I have a maven project where I use jaxb2-maven-plugin 2.5.0. I'm using that version because I need something that works with both Java 8 and Java 11. It has a project structure like this:

application
pom.xml
shared
pom.xml
src/main/resources/xsd/example.xsd

Here, I'm just showing one application module. But in the real situation I have, there are many applications that use the "shared" module, which uses jaxb2-maven-plugin's xjc goal. The key characteristic here is that "shared" isn't a child directory of "application", it is a sibling. application/pom.xml refers to shared with an entry like this: ../shared. I've attached jaxb2-maven-plugin-bug.tar.gz that shows this structure and that can be used to reproduce the problem I'll describe -- just cd into the "application" directory and run "mvn clean verify".

jaxb2-maven-plugin-bug.tar.gz

shared/pom.xml has this invocation of the xjc goal:

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxb2-maven-plugin</artifactId>
        <version>2.5.0</version>
        <executions>
          <execution>
            <id>xjc</id>
            <goals>
              <goal>xjc</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <sources>
            <source>src/main/resources/xsd/example.xsd</source>
          </sources>
          <extension>true</extension>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

When I run maven in the application directory, it fails because it tries to use a mangled path to the example.xsd file. The path it looks for is file:/myprojects/jaxb2-maven-plugin-bug/application/myprojects/jaxb2-maven-plugin-bug/shared/src/main/resources/xsd/example.xsd which incorrectly has /myprojects/jaxb2-maven-plugin-bug/application prepended to it. The correct URL would be file:/myprojects/jaxb2-maven-plugin-bug/shared/src/main/resources/xsd/example.xsd. The exception is

org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'file:/myprojects/jaxb2-maven-plugin-bug/application/myprojects/jaxb2-maven-plugin-bug/shared/src/main/resources/xsd/example.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.

This doesn't happen on Windows, but it fails like this on Linux. My example project uses a relative path to the xsd (<source>src/main/resources/xsd/example.xsd</source>) but the same problem happens if I use an absolute path, because it seems that before the code that introduces the bad path runs, the path has been canonicalized to a (correct) absolute path, but then later gets mangled.

Here's a complete log of running "mvn clean verify" when cd'd into the "application" directory:

jaxb2-maven-plugin-bug.log

I tracked the problem down to an error in src/main/java/org/codehaus/mojo/jaxb2/shared/FileSystemUtilities.java, in the relativize method. Here's the original 2.5.0 code for that:

    /**
     * If the supplied path refers to a file or directory below the supplied basedir, the returned
     * path is identical to the part below the basedir.
     *
     * @param path      The path to strip off basedir path from, and return.
     * @param parentDir The maven project basedir.
     * @param removeInitialFileSep If true, an initial {@code File#separator} is removed before returning.
     * @return The path relative to basedir, if it is situated below the basedir. Otherwise the supplied path.
     */
    public static String relativize(final String path,
                                    final File parentDir,
                                    final boolean removeInitialFileSep) {

        // Check sanity
        Validate.notNull(path, "path");
        Validate.notNull(parentDir, "parentDir");

        final String basedirPath = FileSystemUtilities.getCanonicalPath(parentDir);
        String toReturn = path;

        // Compare case insensitive
        if (path.toLowerCase().startsWith(basedirPath.toLowerCase())) {
            toReturn = path.substring(basedirPath.length());
        }

        // Handle whitespace in the argument.
        return removeInitialFileSep && toReturn.startsWith(File.separator)
            ? toReturn.substring(File.separator.length())
            : toReturn;
    }

The problem is that when the application maven module is a sibling of the module that invokes xjc, the path startsWith test is false, so by the javadoc (@return The path relative to basedir, if it is situated below the basedir. Otherwise the supplied path.) it should return the path argument as-is, which will be an absolute path. But then it removes the initial slash in the return statement, resulting in a relative path that isn't actually correctly relative to the "parentDir" -- that would only be the right relativized path if parentDir was "/", which it isn't.

The fix to make it behave as the javadoc specifies is simple, moving the return statement into the if, and otherwise just returning the supplied path, as the "@return" javadoc specifies. With just that change, my project builds correctly.

Here's my modified code:

    /**
     * If the supplied path refers to a file or directory below the supplied basedir, the returned
     * path is identical to the part below the basedir.
     *
     * @param path      The path to strip off basedir path from, and return.
     * @param parentDir The maven project basedir.
     * @param removeInitialFileSep If true, an initial {@code File#separator} is removed before returning.
     * @return The path relative to basedir, if it is situated below the basedir. Otherwise the supplied path.
     */
    public static String relativize(final String path,
                                    final File parentDir,
                                    final boolean removeInitialFileSep) {

        // Check sanity
        Validate.notNull(path, "path");
        Validate.notNull(parentDir, "parentDir");

        final String basedirPath = FileSystemUtilities.getCanonicalPath(parentDir);
        String toReturn = path;

        // Compare case insensitive
        if (path.toLowerCase().startsWith(basedirPath.toLowerCase())) {
            toReturn = path.substring(basedirPath.length());

            // Handle whitespace in the argument.
            return removeInitialFileSep && toReturn.startsWith(File.separator)
                ? toReturn.substring(File.separator.length())
                : toReturn;
        }

        return toReturn;
    }

I ran all of the tests, and all of them pass except validateRelativizingPaths in FileSystemUtilitiesTest. That test calls relativize with path set to /project/backend/foobar/my-schema.xsd with various values of parentDir. Two of the five verifications there fail, these ones:

parentDir2Expected.put( "", "project/backend/foobar/my-schema.xsd" );
parentDir2Expected.put( "/not/a/path", "project/backend/foobar/my-schema.xsd" );

In both cases, the tests fail because the specified expected result in the test isn't actually a correct relative path from the parentDir to the path. In the first one, it passes in "" as the parentDir, which the test then passes to relativize as new File(""). So the actual parent path passed to the method isn't "" but is the jaxb2-maven-plugin project directory, and "project/backend/foobar/my-schema.xsd" isn't the correct relative path from the plugin project directory. The problem in the /not/a/path case is the same -- the indicated expected relativized path from /not/a/path to /project/backend/foobar/my-schema.xsd isn't project/backend/foobar/my-schema.xsd but would be ../../../project/backend/foobar/my-schema.xsd.

But, the javadoc says that when the path isn't a child of parentDir, just return the path argument as-is rather than returning a relative path, and the code seems to work correctly with my modified code that does that. All of the tests passed including all of the integration tests after I retrained those two cases to be these:

parentDir2Expected.put( "", "/project/backend/foobar/my-schema.xsd" );
parentDir2Expected.put( "/not/a/path", "/project/backend/foobar/my-schema.xsd" );

It would be great if this could be fixed and released as a 2.5.1 update, since I can't yet use the 3.1.0 release that relies on the jakarta classes.

@ratoaq2
Copy link

ratoaq2 commented Feb 25, 2023

Facing exactly the same issue here, but on latest 3.1.0... and this is blocking my migration to jakarta classes.

@ratoaq2
Copy link

ratoaq2 commented Feb 25, 2023

Most likely the PR https://github.com/mojohaus/jaxb2-maven-plugin/pull/143/files would fix the issue

@yogregg
Copy link
Author

yogregg commented Mar 19, 2023

That pull request does look like the equivalent of my proposed fix. But my fix is for the 2.5 version of the relativize method while the pull request is for the 3.1 version of that method.

I would love to see both 2.5 and 3.1 patched because as I mentioned earlier I am not yet in a position where I can use 3.1. What are the prospects for picking up the fix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants