5

I wrote a doclet that collects some data and passes it to a reporter. I want this reporter to be exchangeable. I tried to add a reporter implementation to the doclet classpath using an additionalDependency and/or a pluginDependency. I can't load the reporter implementation with the Java 6 service loader and it also doesn't work to get the class using the doclets class loader or the threads context class loader.

How can I get the test.TestReporterImpl into the test-doclet classpath?

In the doclet:

apiReporterServiceLoader = ServiceLoader.load(TestReporter.class); // test.TestReporter

apiReporterServiceLoader.iterator().hasNext(); // false

Thread.currentThread().getContextClassLoader().loadClass("test.TestReporterImpl"); // ClassNotFoundException

getClass().getClassLoader().loadClass("test.TestReporterImpl"); // ClassNotFoundException

in the pom executing the doclet

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.10.3</version>
    <executions>
        <execution>
            <id>run-my-doclet</id>
            <goals>
                <goal>javadoc</goal>
            </goals>
            <phase>generate-resources</phase>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>test</groupId>
            <artifactId>test-doclet-test-reporter</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
    <configuration>
        <doclet>test.TestDoclet</doclet>
        <docletArtifact>
            <groupId>test</groupId>
            <artifactId>test-doclet</artifactId>
            <version>${project.version}</version>
        </docletArtifact>
        <additionalDependencies>
            <additionalDependency>
                <groupId>test</groupId>
                <artifactId>test-doclet-test-reporter</artifactId>
                <version>${project.version}</version>
            </additionalDependency>
        </additionalDependencies>
        <useStandardDocletOptions>false</useStandardDocletOptions>
    </configuration>
</plugin>

test-doclet-test-reporter/src/main/resources/META-INF/services/test.TestReporter

test.TestReporterImpl
messivanio
  • 2,263
  • 18
  • 24
Sven Tschui
  • 1,377
  • 8
  • 20

3 Answers3

0

You'd have to specify the directory to be included in your class path by adding the following to your POM anywhere under build. This is true for your classes under the meta-inf folder under resource, in cases of bugs to pick up from the default implicit resource folder.

 <project>
     ...
     <build>
       ...
       <resources>
         <resource>
           <directory>[your folder containing the class or to be in the classpath here]</directory>
         </resource>
       </resources>
       ...
     </build>
     ...
    </project>
messivanio
  • 2,263
  • 18
  • 24
Milind J
  • 517
  • 5
  • 10
  • Which resources are you referring to? All resources are on classpath. The problem is the plugin depenency / additionalDependency is not on the doclet's classpath – Sven Tschui May 21 '15 at 06:18
  • Even though the resource dir should be ideally part of the class path and be included in final generated jars, I had a similar problem where I had to mention a properties file explicitly in resources under sub directory to be included in my final jar (using the shade plugin). Finally I tried to include it by explicitly specifying resource folder under the pom.xml resources tag and it worked. Conversely if I removed the explicit declaration, the properties file was not part of the final jar. – Milind J May 21 '15 at 23:45
  • my use case is that I want the second jar exchangeable. My resources are on classpath but the second jar doesn't get added to the cp by the doclet plugin – Sven Tschui May 22 '15 at 06:18
  • Ok, If I were you I would try adding it as a dependency. Force it to be in the classpath using poms other options like of resources / dependancies. – Milind J May 22 '15 at 06:47
  • 1
    I can't get the jar on the classpath of the doclet. Whether as regular dependency, plugin dependency nor as additionalDependency – Sven Tschui May 25 '15 at 10:07
0

I had a similar problem with a multi-release jar providing Doclet implementations for Java 8 and Java9+. I used the javadoc plugin option docletPath:

                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <id>javadoc9-test1</id>
                        <goals>
                            <goal>javadoc-no-fork</goal>
                        </goals>
                        <phase>test</phase>
                        <configuration>
                            <doclet>de.ohmesoftware.javadoctoproperties.Converter9</doclet>
                            <additionalOptions>-prefix rest.description -output mydocs.properties</additionalOptions>
                            <debug>true</debug>
                            <docletPath>${project.build.outputDirectory}/META-INF/versions/9</docletPath>
                            <sourcepath>${project.basedir}/src/test/java/de/ohmesoftware/javadoctoproperties/model
                            </sourcepath>
                        </configuration>
                    </execution>
             <configuration>
                    <useStandardDocletOptions>false</useStandardDocletOptions>
                    <javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
                </configuration>
            </plugin>
k_o_
  • 5,143
  • 1
  • 34
  • 43
  • javadoc plugin option `docletPath` is ok BUT using it directly bypasses the fundamental dependency resolution mechanism provided by maven—see my answer for further details. – Stefano Chizzolini Feb 11 '23 at 15:00
0

My scenario was exactly the same as yours (using ServiceLoader to dynamically provide my doclet with an implementation from a decoupled artifact). Unfortunately, maven plugins documentation was bare & sketchy as usual, so I had to investigate a little bit to wrap my head around this issue...


Our goal is to load service providers implementing our interface. To do so, we need to honor the ServiceLoader protocol and to make our providers available to the correct class loader.

Assuming that you honored the ServiceLoader protocol, two combined problems related to class loading appear in your attempts:

  1. wrong configuration: don't place your service provider in additionalDependency or pluginDependency, the right way is to assign another docletArtifact in order to make both the doclet artifact and the service-provider artifact visible to the same docletpath-based class loader (URLCLassLoader, as defined by jdk.javadoc.internal.tool.Start.preprocess(List<String>) in openjdk 17) — under the hood, maven-javadoc-plugin translates docletArtifact elements into the -docletpath javadoc option; explicitly using the latter (as suggested by k_o_) is extremely inconvenient, as you would lose the fundamental dependency resolution provided by maven! Here we go:

    <doclet>test.TestDoclet</doclet>
    <docletArtifacts>
      <docletArtifact>
        <groupId>test</groupId>
        <artifactId>test-doclet</artifactId>
        <version>${project.version}</version>
      </docletArtifact>
      <docletArtifact>
        <groupId>test</groupId>
        <artifactId>test-doclet-test-reporter</artifactId>
        <version>${project.version}</version>
      </docletArtifact>
    </docletArtifacts>
    
  2. wrong class loader: a correct configuration is not enough if we don't pay attention also to the proper class loader to use for service loading; DON'T invoke

    ServiceLoader.load(TestReporter.class)
    

    as it uses the current thread's context class loader — take a look to my class loader hierarchy as seen from my doclet constructor:

    jdk.internal.loader.ClassLoaders$PlatformClassLoader@3d82c5f3
      jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7 // context class loader
        java.net.URLClassLoader@61e4705b // docletpath class loader
    

    As you can see, the context class loader is the parent of our docletpath class loader: because of the visibility principle, our service provider (which is placed inside the docletpath) is NOT visible to the context class loader and therefore, in turn, the ServiceLoader cannot succeed using the default (context) class loader. Use this overload instead:

    ServiceLoader.load(TestReporter.class, getClass().getClassLoader())