3

I've created small util to wrap MavenCli, which generates a new Maven project, using the quickstart archetype.

When executing the Util as a unit test, it is working quite well (just generating an empty Maven project).

Now I want to integrate this small wrapper into a Maven plugin. But when I execute the mojo (within a third Maven project), the invocation of MavenCli fails with exception:

[ERROR] Error executing Maven.
[ERROR] java.util.NoSuchElementException
  role: org.apache.maven.eventspy.internal.EventSpyDispatcher
  roleHint: 
[ERROR] Caused by: null

The util looks like:

public void createProject() {
    final MavenCli cli = new MavenCli();
    System.setProperty("maven.multiModuleProjectDirectory", "/usr/share/maven");
    cli.doMain(new String[] { "archetype:generate", "-DgroupId=com.my.company",
            "-DartifactId=hello-world", "-DarchetypeArtifactId=maven-archetype-quickstart",
            "-DinteractiveMode=false" }, "/tmp", System.out, System.out);
}

relevant dependency of the util:

<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-embedder</artifactId>
    <version>3.3.9</version>
</dependency>
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-core</artifactId>
    <version>3.3.9</version>
</dependency>

The mojo code looks like:

@Mojo(name = "custommojo", requiresProject = false)
public class CustomMojo extends AbstractMojo {

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        Util.createProject();
    }

}

The POM of the mojo just includes dependencies to relevant Maven artifacts (plugin-api, plugin-annotation, etc.) and the util.

The third project I mentioned, is an empty "maven-quickstart" project which have dependency to the mojo-project and a configuration for the mojo to execute in compile phase.

I have no idea why it works in context of unit test, but not in context of a mojo.

Can anybody help?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Martin Ackermann
  • 884
  • 6
  • 15

3 Answers3

5

This is a class loading issue.

MavenCli will try to load classes from the context classloader of the current thread. Inside of a Maven plugin, there is a special, restricted, classloader, which has access to:

  • its own classes;
  • the classes used in its dependencies block;
  • exported classes as part of a possible build extension of the project;
  • exported classes from the Maven core and core extensions;
  • and has the bootstrap classloader as parent.

However, the specific class org.apache.maven.eventspy.internal.EventSpyDispatcher is part of Maven core, but it is not part of the exported APIs (the package org.apache.maven.eventspy is not listed as an exportedPackage). So the plugin cannot load that class. This is also why it works in your tests: you're not inside of a plugin, so the classloader is different and has access to that class.

You won't even be able to get away with adding explictly a dependency on maven-core for the plugin: it will be disgarded since it is supposedly already provided.

There are 2 solutions here:

  • Don't use the Maven Embedder API from within a plugin, but the Invoker API. The difference between the two is that the Invoker API will launch Maven in a clean environment, completely distinct with regard to the current one. Since it starts everything anew, you won't have any classloading issue.
  • Use the mojo-executor library, that provides an easy way to invoke other Mojos from within a Maven plugin. You could use it here to invoke the archetype:generate Mojo.
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Thank you a lot - I switched to Invoker API as suggested - and it works pretty well. Note: Before I used Embedder API, I tried already to use mojo-executor, but without success, since it is difficult to execute a whole Lifecycle (e.g. "install") - mojo-executor is working for "archetype:generate", but is also difficult to configure when trying to invoke a mojo for another project (especially when this other project is completely out of scope of the current build) – Martin Ackermann Nov 16 '16 at 07:45
  • It is in fact possible to handle the class loading issue and using `MavenCli`, see my answer. – Alexander Klimetschek Dec 07 '17 at 08:22
1

This works for me inside a custom maven plugin (using Maven 3.5.0):

ClassRealm classRealm = (ClassRealm) Thread.currentThread().getContextClassLoader();
MavenCli cli = new MavenCli(classRealm.getWorld());
cli.doMain( ... );

The plexus Launcher sets the context class loader to its ClassRealm, which has access to the "global" ClassWorld.

Not sure how stable that solution is, but so far looking good.

Used imports:

import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.cli.MavenCli;
Alexander Klimetschek
  • 3,585
  • 31
  • 36
0

@Alexander:

You answer put me in the right direction, I was getting the mentioned error when trying to run to doMain maven commands subsequently (one command would succeed).

However your code gives me a ClassCast exception :

Exception in thread "main" java.lang.ClassCastException: sun.misc.Launcher$AppClassLoader cannot be cast to org.codehaus.plexus.classworlds.realm.ClassRealm
    at com.misys.tools.integration.codegen.cli.cmd.InstallApi.mavenInstallMchApi(InstallApi.java:58)
    at com.misys.tools.integration.codegen.cli.cmd.InstallApi.run(InstallApi.java:50)
    at com.misys.tools.integration.codegen.cli.OpenApiGen.main(OpenApiGen.java:26)

I managed to rewrite the code to:

MavenCli cli = new MavenCli(new ClassWorld("maven",Thread.currentThread().getContextClassLoader()));

And then 2 subsequent doMain invocations of embedded maven succeed!

consultantleon
  • 111
  • 1
  • 3
  • Maybe your code is for some reason not running inside a plexus Launcher, which sets the thread context class loader to the `ClassRealm` (at least back when I worked on this). Maybe something else between the launcher and your plugin is adding the different `sun.misc.Launcher$AppClassLoader` as context class loader. – Alexander Klimetschek Dec 22 '18 at 01:07