0

I'm on a java/maven project, using the jaxb2 inheritance plugin in order to define parent classes for xjc-generated java classes.

I'm having this xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance"
    jaxb:extensionBindingPrefixes="inheritance"
    elementFormDefault="qualified" 
    jaxb:version="2.1">

    <xs:element name="input">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="test.thing"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:element name="test.thing">    
        <xs:annotation>
            <xs:appinfo>
                <inheritance:implements>
input.generation.common.IXmlHasListedElementsWithStartTime
                </inheritance:implements>  
                <inheritance:extends>
input.generation.common.XmlAbstractTestThingWithListedElementWithStartTime
                </inheritance:extends>  
            </xs:appinfo>
        </xs:annotation>
        <xs:complexType>
            <xs:sequence>
                <xs:element name="start.timed.thing" type="start.timed.thing" maxOccurs="unbounded" minOccurs="0">
                    <xs:annotation>
                        <xs:appinfo>
                            <jaxb:property name="startTimedThings"/>
                        </xs:appinfo>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:complexType name="start.timed.thing">
        <xs:annotation>
            <xs:appinfo>
                <inheritance:implements>
                    input.generation.IHasStartTime
                </inheritance:implements>  
            </xs:appinfo>
        </xs:annotation>
        <xs:attribute name="start" use="optional" type="xs:date"/>
    </xs:complexType>
    
</xs:schema>

It is generating a class XmlTestThing extends XmlAbstractTestThingWithListedElementWithStartTime implements IXmlHasListedElementsWithStartTime.

The ultimate goal is that the interface defines an easy method that gives the right version of XmlTestThing, given the date.

Here's the problem: when I do a mvn clean install on my project, the process fails on compile errors in my IDE (eclipse). The problem is that the generated class XmlTestThing cannot find its abstract parent XmlAbstractTestThingWithListedElementWithStartTime. It has no problems finding the interface.

However, doing a refresh on the project, just shows that in eclipse there is no compile problem reported for XmlTestThing: maven cannot find the import, while eclipse can.

The generated class is in target/generated-test-sources. The interface to be implemented is in src/main/java The class to be extended is in src/test/java.

I've tested this a bit, and found the following:

  • if the class to be extended is in src/main/java, then it all functions
  • if the class to be extended is in src/test/java, eclipse can find it, but maven reports a compile error because it is not able to find it from target/generated-test-sources.

Apparently maven can find the interface in /src/main/java, but not the parent class in /src/test/java. Or does target/generated-test-sources not have access to src/test/java?

Some more info:

This is the part of pom.xml arranging the test-resources:

<plugin>
    <!--  
    The standard location for generated sources is under target, so that
    it falls outside the scope of subversion/git. However, the standard maven
    setup doesn't support java sources not under src/main/java or src/test/java.  
    This plugin lets maven know that the generated sources under target
    should also be considered as a source folder for java classes.
    In this case it is used for test sources.  -->
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>3.4.0</version>
    <executions>
        <execution>
            <id>add-test-source</id>
            <phase>generate-test-sources</phase>
            <goals>
                <goal>add-test-source</goal>
            </goals>
            <configuration>
                <sources>
                    <!-- This tells the plugin that generated-sources under target is a source folder. -->
                    <source>${project.build.directory}/generated-test-sources/java/</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

and this is the pom.xml configuration of class generation:

<plugin>  
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.15.3</version>
    <configuration>
        <schemaDirectory>src/test/resources</schemaDirectory>
        <generateDirectory>${project.build.directory}/generated-test-sources/java</generateDirectory>
        <extension>true</extension>
        <strict>false</strict>
        <forceRegenerate>true</forceRegenerate>
        <args>
            <arg>-Xinheritance</arg>
        </args>
        <plugins>
            <plugin>
                <groupId>org.jvnet.jaxb2_commons</groupId>
                <artifactId>jaxb2-basics</artifactId>
                <version>0.11.1</version>
            </plugin>
        </plugins>
    </configuration>
    <executions>
        <execution>  
            <id>input</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <schemaIncludes>
                    <include>testInput.xsd</include>
                </schemaIncludes>
                <bindingIncludes>
                    <include>testInput.xjb</include>
                </bindingIncludes>  
            </configuration>
        </execution>  
    </executions>
</plugin>

This xjb does some prefixing of class names:

<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
               xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0" schemaLocation="testInput.xsd">
    <jaxb:schemaBindings>
        <jaxb:package name="nl.erasmusmc.mgz.core.input.generated.test"/>
        <jaxb:nameXmlTransform>
            <!-- Prefix all generated classes with Xml -->
            <jaxb:typeName prefix="Xml"/>
            <jaxb:elementName prefix="Xml"/>
            <jaxb:modelGroupName prefix="Xml"/>
            <jaxb:anonymousTypeName prefix="Xml"/>
        </jaxb:nameXmlTransform>
    </jaxb:schemaBindings>

</jaxb:bindings>

And this would be an xml input file under test:

<?xml version="1.0" encoding="UTF-8"?>
<input 
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="testInput.xsd" >
   
  <test.thing>
      <start.timed.thing start="1800-01-01"/>  
      <start.timed.thing start="1970-01-01"/>  
      <start.timed.thing start="2000-01-01"/>  
  </test.thing> 

</input>

2
  • Can you please create a complete example xsd, so it's easier to reproduce? Thanks! Commented Oct 27 at 7:47
  • 1
    @nineninesevenfour: yes, I've updated it with the complete xsd, a test xml, and more information. Thanks for looking at it!. Commented Oct 27 at 12:53

2 Answers 2

1

I think you overlooked, that there isn't just "compile" in Maven, but two compile steps: default-compile (for regular/production sources) and test-compile (for test sources). And naturally, since test code shouldn't be packed with the regular code, test sources aren't visible for the default-compile execution.

And saying that, it's actually irrelevant, where you put the sources generated with the maven-jaxb2-plugin, these are just directories and their naming can be configured, the relevant point is when they are generated and compiled.

Executing the maven-buildplan-plugin reveals it:

mvn fr.jcgay.maven.plugins:buildplan-maven-plugin:list -Dbuildplan.tasks=clean,package
--------------------------------------------------------------------------------------------
PLUGIN                    | PHASE                  | ID                    | GOAL           
--------------------------------------------------------------------------------------------
maven-clean-plugin        | clean                  | default-clean         | clean          
maven-jaxb2-plugin        | generate-sources       | input                 | generate       
maven-resources-plugin    | process-resources      | default-resources     | resources      
maven-compiler-plugin     | compile                | default-compile       | compile        
build-helper-maven-plugin | generate-test-sources  | add-test-source       | add-test-source
maven-resources-plugin    | process-test-resources | default-testResources | testResources  
maven-compiler-plugin     | test-compile           | default-testCompile   | testCompile    
maven-surefire-plugin     | test                   | default-test          | test           
maven-jar-plugin          | package                | default-jar           | jar    

What happens here?

  1. Since you didn't configure a lifecycle phase for the maven-jaxb2-plugin, it will use the default lifecycle for that plugin, which is generate-sources.
  2. In the default-compile execution the maven-compiler-plugin "sees"1 that there are new generated sources and tries to compile them, but since they are referencing some sources from src/test/java (which haven't been compiled yet), the compilation fails.

Solution?

Depends on what you want to achieve, which you didn't really say, but reading between the lines, I assume you are using jaxb only for testing purposes, not for production code. Otherwise you wouldn't reference classes from src/test/java in your testInput.xsd schema, that wouldn't make sense at all.

To do that, you need to bind maven-jaxb2-plugin to the generate-test-sources phase, which is possible:

            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>0.15.3</version>
                <executions>
                    <execution>
                        <id>input</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <!-- addition: phase specified -->
                        <phase>generate-test-sources</phase>
                        <!-- configuration follows -->

And see compilation happily succeeding! 🎉

For completeness the updated table of the maven-buildplan-plugin after that change:

--------------------------------------------------------------------------------------------
PLUGIN                    | PHASE                  | ID                    | GOAL           
--------------------------------------------------------------------------------------------
maven-clean-plugin        | clean                  | default-clean         | clean          
maven-resources-plugin    | process-resources      | default-resources     | resources      
maven-compiler-plugin     | compile                | default-compile       | compile        
maven-jaxb2-plugin        | generate-test-sources  | input                 | generate       
build-helper-maven-plugin | generate-test-sources  | add-test-source       | add-test-source
maven-resources-plugin    | process-test-resources | default-testResources | testResources  
maven-compiler-plugin     | test-compile           | default-testCompile   | testCompile    
maven-surefire-plugin     | test                   | default-test          | test           
maven-jar-plugin          | package                | default-jar           | jar            

1How can the maven-compiler-plugin "see" the sources generated by the maven-jaxb2-plugin implicitly?

The reason is, that the maven-jaxb2-plugin has two configuration switches for it: addCompileSourceRoot with a default value true and addTestCompileSourceRoot with no default, meaning false. They seem to be correctly bound to the execution phase (not the directory naming) and essentially do the same as the build-helper-maven-plugin add-source and add-test-source goals, with the difference, that the directory from generateDirectory will be automatically used. So you can also remove build-helper-maven-plugin and simply add

<addTestCompileSourceRoot>true</addTestCompileSourceRoot>

to the configuration of the maven-jaxb2-plugin.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the very clear explanation. That worked like a charm
You are welcome! Glad it works for you! :)
0

the problem was that the generated code is placed under:

${project.build.directory}/generated-test-sources/java

Eclipse doesn’t care because it builds everything in one go, but Maven’s build lifecycle separates phases strictly.

That means Maven treats it as test source code is only compiled in the test-compile phase and does not have access to src/test/java during generation. So when the JAXB plugin runs, it cannot find your abstract parent class.

Try to change in:

<generateDirectory>${project.build.directory}/generated-sources/java</generateDirectory>

In this case Maven will treat the generated classes as main soirces and are automatically visible to test code.

So if you want the generated JAXB classes to be usable both in main and in test code put them in main classpath.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.