scanning java classpath in the maven plugin - java

What I'm trying to do is to write some Maven plugin which scans application classes looking for implementation of a particular interface (it might be classes with some annotation as well) and basis on the result generating some code. I've successfully implemented plugin running in the generate-sources phase and writing source code to the generated-sources directory.
The problem is with scanning classpath for the particular interface implementations/classes with some annotation.
I am using the Reflections library to scan classes in the following way:
private Set<Class< ? extends MyInterface >> scan(final String packageName) {
final Reflections reflections = new Reflections(packageName);
return reflections.getSubTypesOf(MyInterface.class);
}
Unfortunately, this method returns empty set. When I print my classpath in the class extending org.apache.maven.plugin.AbstractMojo (the same within which I'm using Reflections) I get the following result:
/home/pd5108/apache-maven-2.2.1/boot/classworlds-1.1.jar
The classes I want to find using Reflections exists in dependend JARs as well as in the module within which plugin is configured. Looking at the classpath printed out it seems that at this point (generate-sources phase) dependencies defined in maven all not available on classpath yet - probably they are added in the next phases. Is that true? Is there any other approach I can use?
Here is the way how classpath is printed out:
URL[] urls = ((URLClassLoader)sysClassLoader).getURLs();
for(int i=0; i< urls.length; i++) {
System.out.println(urls[i].getFile());
}

Required MOJO class fields:
/**
* The project currently being built.
*
* #parameter expression="${project}"
* #readonly
* #required
*/
private MavenProject project;
/** #parameter expression="${localRepository}" */
protected ArtifactRepository m_localRepository;
/**#parameter default-value="${localRepository}" */
private org.apache.maven.artifact.repository.ArtifactRepository
localRepository;
/** #parameter default-value="${project.remoteArtifactRepositories}" */
private java.util.List remoteRepositories;
/** #component */
private org.apache.maven.artifact.factory.ArtifactFactory artifactFactory;
/** #component */
private org.apache.maven.artifact.resolver.ArtifactResolver resolver;
Resolution of all dependencies JARs:
final List<Dependency> dependencies = project.getDependencies();
for (Dependency d : dependencies) {
final Artifact artifact =
artifactFactory.createArtifactWithClassifier(d.getGroupId(),
d.getArtifactId(), d.getVersion(), d.getType(),
d.getClassifier());
try {
resolver.resolve(artifact, remoteRepositories,
localRepository );
} catch (ArtifactResolutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ArtifactNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
File artifactFile = artifact.getFile();
System.out.println(artifactFile.getAbsolutePath());
}
And now we need to scan these JARs using reflection API looking for the appropriate classes.
At this point I think that there's no other way, since I work in generate-sources phase and artifact values for the next phases are not computed yet.

There are artifact dependencies defined in <dependencies> section and plugin dependencies defined under <plugin><dependencies>.
Plugin dependencies are added to the classpath while I am not sure about the artifact dependencies. Did you try to add your plugin dependencies under the <plugin><dependencies>?

Might be quicker/safer/easier to just have the plugin config list the classes you want to generate code based on. Then you just add it to the pom and you're done. No reflection needed, and it'd certainly speed up the running of the plugin.

Related

Getting the resource path of the java project from a custom gradle plugin

Trying to create a custom gradle plugin in java, how do i get the resources path from inside the task class?
public class MyCustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getTasks().register("doStuff", CustomTask.class);
}
}
public class CustomTask extends DefaultTask {
// How do I get java project resources dir from here?
#Inject
public CustomTask(ProjectLayout projectLayout) {
directoryProperty = projectLayout.getBuildDirectory();
}
#TaskAction
public void execute() {
...
}
}
I would recommend to not get the directory inside the task, because the plugin that provides it might not be applied. Instead I would do it from within your plugin that registers the task, this way you can also ensure that the necessary plugin is actually applied. Gradle will display an error if the task is used without a value being assigned to the input that explains that nothing was assigned.
With the kotlin-dsl:
#CacheableTask
abstract class CustomTask : DefaultTask() {
#get:InputFiles
abstract val resources: FileCollection
//...
}
I cannot answer if #InputFiles is the right annotation for your use case, because I don't know what you want to do with the resource. Refer to the Gradle documentation for more information on the available annotations, and what they do.
plugins {
java
}
tasks.register<CustomTask>("customTask") {
resources.set(sourceSets.main.map { it.resources })
}
Notice the map {} which ensures that our task has a dependency on the processResources task, this is done automatically for us because we stick to the provider API of Gradle for everything.
Note that the resources are by default in one directory, but they don't have to be. This is why the resources are defined as SourceDirectorySet and not as Provider<Directory>. The same is true for anything that originates from the SourceSetContainer. It is easier to explain with Java source code: imagine you have Java and Kotlin, then you will have src/main/java and src/main/kotlin, hence, 2 directories. The former will have a **/*.java include filter, whereas the latter has a **/*.kt includes filter. If we just want to get all sources then we use sourceSets.main.map { it.java.sourceDirectories }, and if we want to get one of both it gets complicated. 😝
First, you'd have to ensure this is a Java project: either applying the "java" plugin from your plugin (project.getPluginManager().apply("java")), or only registering the task when the "java" plugin has been applied by the user (project.getPluginManager().withPlugin("java", ignored -> { project.getTasks().register(…); });).
You could then get the resources from the main source set:
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
// Use named() instead of get() if you prefer/need to use providers
SourceSet mainSourceSet = sourceSets.get(SourceSet.MAIN_SOURCE_SET_NAME);
SourceDirectorySet resources = mainSourceSet.getResources();
BTW, the best practice is to have tasks only declare their inputs and outputs (e.g. I need a set of directories, or files, as inputs, and my outputs will be one single file, or in one single directory) and have the actual wiring with default values be done by the plugin.
You could have the plugin unconditionally register the task, then conditionally when the "java" plugin is applied configure its inputs to the project resources; or conditionally register the task or unconditionally apply the "java" plugin, as I showed above.
You can access the sources through the project.sourceSets.
#Inject
public CustomTask(Project project) {
directoryProperty = project.projectLayout.getBuildDirectory();
sourceSet = project.sourceSets.main
}
See also the reference documentation here: https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_project_layout

Load project class within maven mojo

I am trying to load a projects class during the execution of a maven mojo.
Unfortunately this operation fails, since the class loader is missing a referenced class.
Looking around I found already the approaches Maven mojo plugin to load class from hosting project and Maven plugin can't load class
I combined the two approaches, ending up with the following code:
private ClassLoader getClassLoader(final MavenProject project) {
try {
final List<URL> classPathUrls = new ArrayList<>();
// adding the projects dependency jars
final Set<Artifact> artifacts = getProject().getDependencyArtifacts();
for (final Artifact artifact : artifacts) {
classPathUrls.add(artifact.getFile().toURI().toURL());
}
// adding the projects classes itself
final List<String> classpathElements = project.getCompileClasspathElements();
classpathElements.add(project.getBuild().getOutputDirectory());
classpathElements.add(project.getBuild().getTestOutputDirectory());
for (final String classpathElement : classpathElements) {
classPathUrls.add(new File(classpathElement).toURI().toURL());
}
// creating a class loader
final URL urls[] = new URL[classPathUrls.size()];
for (int i = 0; i < classPathUrls.size(); ++i) {
urls[i] = classPathUrls.get(i);
}
return new URLClassLoader(urls, this.getClass().getClassLoader());
} catch (final Exception e) {
getLog().debug("Couldn't get the classloader.");
return this.getClass().getClassLoader();
}
}
The class which fails to load is an implementation of the interface "org.bson.codecs.Codec", which is contained in "org.mongodb:bson", which is implicit imported via the dependency:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.1.1</version>
<scope>provided</scope>
</dependency>
This dependency has a dependency to another jar (scope: provided), containing the mentioned interface, which can be seen in the maven dependency tree:
$> mvn dependency:tree
[INFO] net.my.project:my-project:jar:1.0-SNAPSHOT
[INFO] +- org.mongodb:mongodb-driver-sync:jar:4.1.1:provided
[INFO] | +- org.mongodb:bson:jar:4.1.1:provided
[INFO] | \- org.mongodb:mongodb-driver-core:jar:4.1.1:provided
When I look to the class path elements of the created class loader, I see that mongodb-driver-sync.jar is included, but since it declares the "org.mongodb:bson" dependency with scope provided it the interface is still not in class path.
So, the final question is: How can I load a class which references a class from an "indirect" dependency?
I've noticed, that
project.getArtifacts()
was empty, even though the javadoc says it is supposed to contain all dependencies (lazily populated).
So, with additional research I found Custom Maven Plugin development - getArtifacts is empty though dependencies are included which suggests to adjust the #Mojo annotation:
#Mojo(name = "mojoName", requiresDependencyResolution = ResolutionScope.COMPILE)
after adjusting the annotation, it is even enough to use the "project.getCompileClasspathElements();", it is not necessary anymore to iterate through the artifacts at all.

ServiceLoader does not load implementation jars

I am trying to implement a simple modular application with a self written plugin loader. For that I created a gradle multiproject build. The main project is going to be the started application. In the subproject spi is a module containing the Plugin interface, while in the subproject simple-plugin I wanted to create a plugin to test the plugin loading.
The Plugin interface is very simple:
package io.github.zeroneca.orgascreen.spi;
public interface Plugin {
String getId();
}
module-info.java:
module io.github.zeroneca.orgascreen.spi {
exports io.github.zeroneca.orgascreen.spi;
}
SimplePlugin.java:
package io.github.zeroneca.orgascreen.plugins.simple;
import io.github.zeroneca.orgascreen.spi.Plugin;
public class SimplePlugin implements Plugin {
private final String id = "SimplePlugin";
#Override
public String getId() { return id; }
}
module-info.java
module io.github.zeroneca.orgascreen.plugins.simple {
requires io.github.zeroneca.orgascreen.spi;
provides io.github.zeroneca.orgascreen.spi.Plugin with io.github.zeroneca.orgascreen.plugins.simple.SimplePlugin;
}
Now in the class PluginLoader I'm loading all jars from a directory and want to add all Plugins to a List:
package io.github.zeroneca.orgascreen;
// several imports
public class PluginLoader {
private static final List<Plugin> PLUGINS = new ArrayList<>();
public static void load() {
URL[] pluginURLs = getPluginURLs(); // returns all URLs of jar files in a directory
URLClassLoader urlClassLoader = URLClassLoader.newInstance(pluginURLs);
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, urlClassLoader);
for (Plugin plugin : serviceLoader) {
PLUGINS.add(plugin);
}
}
// some more methods hidden here
}
module-info.java
module io.github.zeroneca.orgascreen {
requires io.github.zeroneca.orgascreen.spi;
uses io.github.zeroneca.orgascreen.spi.Plugin;
}
Now after building with gradle and copying the resulting jar simple-plugin-0.1.jar into the directory, I can see, that the URL of the file is in pluginJars but the List PLUGINS remains empty. I'm not sure what I'm doing wrong, online searches didn't help, I cannot see a difference to working solution, but the use of gradle.
gradle 6.5.1 is in use and modularity.inferModulePath is set to true.
Help is greatly appreciated!
Note: when adding META-INF/services/io.github.zeroneca.orgascreen.spi.Plugin to the jar of SimplePlugin it works, so something must be wrong with the module declarations. But I just can't see what.

Get list of maven dependencies of org.apache.maven.project.MavenProject

I'm trying to get a list of dependencies of some maven artifacts using org.apache.maven.project.MavenProject.
My code is like this.
public List<Dependencies> loadProject() {
Model mavenModel = new Model();
mavenModel.setModelVersion("4.0.0");
mavenModel.setGroupId("org");
mavenModel.setArtifactId("wso2");
mavenModel.setVersion("1.0.0");
addDependency(mavenModel, "com.google.inject", "guice", "4.2.2");
addDependency(mavenModel, "ch.qos.logback", "logback-classic", "1.2.3");
MavenProject mavenProject = new MavenProject(mavenModel);
//******* Need to resolve dependencies of `mavenProject` and *******
//******* get the list of dependencies of this project. *******
return dependencies;
}
private static void addDependency(Model mavenModel, String groupId, String artifactId, String version) {
Dependency dependency = new Dependency();
dependency.setGroupId(groupId);
dependency.setArtifactId(artifactId);
dependency.setVersion(version);
mavenModel.addDependency(dependency);
}
Basically I'm trying to get the dependency tree results which returns by mvn dependency:tree command as a list by programmatically.
Example
For the artifacts:
com.google.inject:guide:4.2.2
ch.qos.logback:logback-classic:1.2.3
Dependency list:
List = [
com.google.inject:guice:jar:4.2.2:compile,
javax.inject:javax.inject:jar:1:compile,
aopalliance:aopalliance:jar:1.0:compile,
com.google.guava:guava:jar:25.1-android:compile,
com.google.code.findbugs:jsr305:jar:3.0.2:compile,
org.checkerframework:checker-compat-qual:jar:2.0.0:compile,
com.google.errorprone:error_prone_annotations:jar:2.1.3:compile,
com.google.j2objc:j2objc-annotations:jar:1.1:compile,
org.codehaus.mojo:animal-sniffer-annotations:jar:1.14:compile,
ch.qos.logback:logback-classic:jar:1.2.3:compile,
ch.qos.logback:logback-core:jar:1.2.3:compile,
org.slf4j:slf4j-api:jar:1.7.25:compile
]
You can use the method public Set<Artifact> getArtifacts() of your MavenProject class which returns a set of artifacts representing all dependencies that the project has, including transitive ones.
NB: Contents are lazily populated, so depending on what phases have run dependencies in some scopes won't be included. eg. if only compile phase has run, dependencies with scope test won't be included.
All the information are coming from the documentation that you can find here.

AspectJ annotated based pointcuts not being invoked

I am attempting to create an android library that checks if internet is available before executing a method that has a custom annotation I have defined. I'm using AspectJ to accomplish this.
My annotation is as follows :
#Target({METHOD}) #Retention(RUNTIME)
public #interface InternetRequired {
}
Now for my aspect:
#Aspect
public class CilantroAspect
{
private static final String POINTCUT_METHOD = "execution(#com.cilantro.service.InternetRequired * *(..))";
private static final String POINTCUT_METHOD2 ="#annotation(com.cilantro.service.InternetRequired)";
;
#Pointcut(POINTCUT_METHOD2)
public void internetAnnotatedMethod() {
}
#Around("internetAnnotatedMethod()")
public void checkInternetConnectivity(ProceedingJoinPoint joinPoint) throws Throwable {
Log.v("Aspect","advice being triggered");
if (Cilantro.isConnected()) {
joinPoint.proceed();
} else {
Cilantro.Toast("Internet not available");
}
}
}
Snippet of my activity with the annotated method.
....
Cilantro.init(this);
test();
}
#InternetRequired
public void test()
{
Toast.makeText(this,"Test method",Toast.LENGTH_LONG).show();
}
When I run my android app the around advice is not being triggered. I tried using POINTCUT_METHOD and POINTCUT_METHOD2. Still no luck.
My android app is configured to use Aspect J so I know that's not the problem because if I make errors within the pointcut definitions it's detected..but just to be sure let me share.
Main build script
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.+'
}
....
module containing Aspects
apply plugin: 'com.android.library'
apply plugin: 'android-aspectj'
....
The around advice wasn't being triggered because I was using annotations related to the Aspect from the app level while the aspect was contained in the library. For the aspects to be weaved into the app module at compile time I had to simply publish the library to maven (local for testing and maven central for distribution) and then include the library as a project dependency in a gradle plugin which contains the AspectJ weaving tasks.The plugin is then applied to the app's module.
Here's a snippet of my plugin that's written in groovy. I add my library that contains my Aspects and then run the weaving tasks on the app's module.
project.dependencies {
compile 'com.github.jd-alexander:flender-runtime:1.0'
// TODO this should come transitively
compile 'org.aspectj:aspectjrt:1.8.5'
}
variants.all { variant ->
variant.dex.doFirst {
String[] args = [
"-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
For a complete example of how this is done you can check my library out on github https://github.com/jd-alexander/flender

Categories