Pass parameters to a maven mojo invoked programmatically - java

Here I have my Maven Mojo:
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.shared.invoker.*;
import java.util.Collections;
#Mojo(name = "run")
public class RunMojo extends AbstractMojo {
#Override
public void execute() throws MojoExecutionException {
InvocationRequest request = new DefaultInvocationRequest();
request.setGoals(Collections.singletonList("myplugin:mygoal"));
// need to set parameters to pass to the goal
Invoker invoker = new DefaultInvoker();
try {
invoker.execute(request);
} catch (MavenInvocationException e) {
e.printStackTrace();
}
}
}
And I need to invoke a second Mojo passing some parameters as I do when defining the plugin inside the pom.xml, as follow.
<build>
<plugins>
<plugin>
<artifactId>myPlugin</artifactId>
<groupId>myGroupId</groupId>
<version>myVersion</version>
<configuration>
<param1>value1</param1>
<param2>value2</param2>
<param3>value3</param3>
</configuration>
</plugin>
</plugins>
</build>
Any solution?

It might be an older question, but found myself in this situation recently and taught I'd show how I created a new plugin where which wraps another plugin.
I've ended up using the mojo-executor plugin (https://github.com/TimMoore/mojo-executor) . Even though it is not actively maintained, it does the job quite well. Here is an example of how I executed the html2pdf-maven-plugin (https://bitbucket.org/prunge/html2pdf-maven-plugin/wiki/Home) from my own maven plugin.
The generatePdf(...) method is called from the execute() method of your Mojo class. The getMvnPlugin(...) method is where I specified the plugin groupId and ArtifactId and the execution details. The getPluginConfiguration(...) method is where the plugin configuration is made, specifying the parameters of the html2pdf-maven-plugin.
private void generatePdf(File inputFilePath, String outputFile) {
String inputDirectory = inputFilePath.getParent();
String inputFileName = inputFilePath.getName();
try {
Plugin html2pdfPlugin = getMvnPlugin(outputFile, inputFile, inputFileName);
MojoExecutor.ExecutionEnvironment executionEnvironment = MojoExecutor.executionEnvironment(mavenProject, mavenSession, pluginManager);
MojoExecutor.executeMojo(html2pdfPlugin, "html2pdf", getPluginConfiguration(outputFile, inputDirectory, inputFileName), executionEnvironment);
} catch (Exception ex) {
String errorMessage = String.format("Failed to generate PDF file [%s] from html file [%s].", outputFile, inputFile);
getLog().error(errorMessage, ex);
}
}
private Plugin getMvnPlugin(String outputFile, String inputDirectory, String inputFileName) {
Plugin plugin = new Plugin();
plugin.setGroupId("au.net.causal.maven.plugins");
plugin.setArtifactId("html2pdf-maven-plugin");
plugin.setVersion("2.0");
PluginExecution pluginExecution = new PluginExecution();
pluginExecution.setGoals(Collections.singletonList("html2pdf"));
pluginExecution.setId("generate-pdf");
pluginExecution.setPhase("generate-resources");
plugin.setExecutions(Collections.singletonList(pluginExecution));
plugin.setConfiguration(getPluginConfiguration(outputFile, inputDirectory, inputFileName));
return plugin;
}
private Xpp3Dom getPluginConfiguration(String outputFile, String inputDirectory, String inputFileName) {
MojoExecutor.Element outputFileElement = new MojoExecutor.Element("outputFile", outputFile);
MojoExecutor.Element includeElement = new MojoExecutor.Element("include", "**/" + inputFileName);
MojoExecutor.Element includesElement = new MojoExecutor.Element("includes", includeElement);
MojoExecutor.Element directoryElement = new MojoExecutor.Element("directory", inputDirectory);
MojoExecutor.Element htmlFileSetElement = new MojoExecutor.Element("htmlFileSet", directoryElement, includesElement);
MojoExecutor.Element htmlFileSetsElement = new MojoExecutor.Element("htmlFileSets", htmlFileSetElement);
return MojoExecutor.configuration(outputFileElement, htmlFileSetsElement);
}
The mavenProject, mavenSession and pluginManager just have to be injected in the Mojo class using the org.apache.maven.plugins.annotations.Component.
#Component
private MavenProject mavenProject;
#Component
private MavenSession mavenSession;
#Component
private BuildPluginManager pluginManager;

Related

How to identify dependency file location for a dependency in a build plugin

I am trying to write a custom maven plugin. Part of what the plugin will do is examine the JAR files in the m2 folder of other dependencies listed along side it in a <plugin> tag. I will need the exact JAR file/location.
Example:
<build>
<plugins>
<plugin>
<groupId>myPluginGroup</groupId>
<artifactId>myPluginArtifact</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<!-- find me -->
<dependency>
<groupId>example</groupId>
<artifactId>example</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
....
</plugin>
</plugins>
</build>
So i want to be able to scan and find "example.example" and point it to "C:\User\username\.m2\repository\example\example\1.0.0\example-1.0.0.jar".
I can find the plugin dependencies with:
final List<Plugin> plugins = project.getBuild().getPlugins();
for (Plugin plugin : plugins) {
final List<Dependency> dependencyArtifacts = plugin.getDependencies();
for (Dependency dependency : dependencyArtifacts) {
...
}
}
This doesn't give me the location of the JARs, but it does provide the list of build plugin dependencies which is part of what I need.
I can get some locations with:
DependencyNode rootNode = treeBuilder.buildDependencyTree(project, localRepository,
artifactFactory, artifactMetadataSource, artifactFilter, artifactCollector);
CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
rootNode.accept(visitor);
List<DependencyNode> nodes = visitor.getNodes();
for (DependencyNode dependencyNode : nodes) {
...
}
But it doesn't provide plugins and even for what it does provide, it doesn't provide all m2 locations.
Is there a way to identify dependency file locations for dependencies in a build plugin?
You can use Maven Resolver Api to resolve artifacts, in your case code can look like:
(example without loops - only final usage of resolver)
public class MyMojo {
#Component
private RepositorySystem repoSystem;
#Parameter( defaultValue = "${repositorySystemSession}", readonly = true )
private RepositorySystemSession repoSession;
#Parameter(defaultValue = "${project}", readonly = true)
protected MavenProject project;
void resolveOneArtifact() throws ArtifactResolutionException {
Dependency dependency = project.getBuild().getPlugins().get(0).getDependencies().get(0);
ArtifactRequest request = new ArtifactRequest();
request.setArtifact(RepositoryUtils.toDependency(dependency, repoSession.getArtifactTypeRegistry()).getArtifact());
request.setRepositories(project.getRemotePluginRepositories());
ArtifactResult artifactResult = repoSystem.resolveArtifact(repoSession, request);
System.out.println(artifactResult.getArtifact().getFile());
}
void resolveArtifactWithTransitiveDependencies() throws DependencyResolutionException {
Dependency dependency = project.getBuild().getPlugins().get(0).getDependencies().get(0);
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(RepositoryUtils.toDependency(dependency, repoSession.getArtifactTypeRegistry()));
collectRequest.setRepositories(project.getRemotePluginRepositories());
DependencyRequest request = new DependencyRequest();
request.setCollectRequest(collectRequest);
DependencyResult dependencyResult = repoSystem.resolveDependencies(repoSession, request);
ArtifactResult artifactResult = dependencyResult.getArtifactResults().get(0);
System.out.println(artifactResult.getArtifact().getFile());
}
}
More examples please investigate code: https://github.com/apache/maven-resolver/tree/master/maven-resolver-demos

Get a List of Files from a Classpath Resource Folder?

I'm trying to set an JFX ImageView image from a resource folder, but can't seem to get an appropriate URL/String filepath that won't throw an exception.
var x = getRandomImageFromPackage("pictures").toString();
var y = getClass().getClassLoader().getResource("pictures/mindwave/active/Super Saiyan.gif").toString();
this.iconImageView.setImage(new Image(x));
x returns
/home/sarah/Desktop/Dropbox/School/Current/MAX MSP Utilities/MindWaveMobileDataServer/target/classes/pictures/0515e3b7cb30ac92ebfe729440870a5c.jpg
whereas y returns something that looks like:
file:/home/sarah/Desktop/Dropbox/School/Current/MAX%20MSP%20Utilities/MindWaveMobileDataServer/target/classes/pictures/mindwave/active/Super%20Saiyan.gif
In theory either of these would be acceptable, however, only x will throw an exception if it is placed in the below setImage(String) line.
Is there any way to get a list of images in the package so that I can select a random one and set the ImageView?
I know that there was a custom scanner option, but it appears rather dated (being over 11 years old and wasn't really supported at the time):
Get a list of resources from classpath directory
Routine:
/**
* Gets a picture from the classpath pictures folder.
*
* #param packageName The string path (in package format) to the classpath
* folder
* #return The random picture
*/
private Path getRandomImageFromPackage(String packageName) {
try {
var list = Arrays.asList(new File(Thread.currentThread().getContextClassLoader().getResource(packageName)
.toURI()).listFiles());
var x = list.get(new Random().nextInt(list.size())).toString();
return list.get(new Random().nextInt(list.size())).toPath();
} catch (URISyntaxException ex) {
throw new IllegalStateException("Encountered an error while trying to get a picture from the classpath"
+ "filesystem", ex);
}
}
For reference, this is the resource folder:
Issues with your approach
You don't have a well-formed url
new Image(String url) takes a url as a parameter.
A space is not a valid character for a URL:
Which characters make a URL invalid?
which is why your x string is not a valid URL and cannot be used to construct an image.
You need to provide an input recognized by the Image constructor
Note, that it is slightly more complex because, from the Image javadoc, the url parameter can be somethings other than a straight url, but even still, none of them match what you are trying to lookup.
If a URL string is passed to a constructor, it be any of the
following:
the name of a resource that can be resolved by the context ClassLoader
for this thread
a file path that can be resolved by File
a URL that
can be resolved by URL and for which a protocol handler exists
The RFC
2397 "data" scheme for URLs is supported in addition to the protocol
handlers that are registered for the application. If a URL uses the
"data" scheme, the data must be base64-encoded and the MIME type must
either be empty or a subtype of the image type.
You are assuming the resources are in a file system, but that won't always work
If you pack your resources into a jar, then this will not work:
Arrays.asList(
new File(
Thread.currentThread()
.getContextClassLoader()
.getResource(packageName)
.toURI()
).listFiles()
);
This doesn't work because files in the jar are located using the jar: protocol rather than the file: protocol. So, you will be unable to create File objects from the jar: protocol URIs that will be returned by getResource.
Recommended Approach: Use Spring
Getting a list of resources from a jar is actually a pretty tricky thing. From the question you linked, the easiest solution is the one which uses
Spring's PathMatchingResourcePatternResolver
Unfortunately, that means requiring a dependency on the Spring framework to use it, which is total overkill for this task . . . however I don't know of any other simple robust solution. But at least you can just call the Spring utility class, you don't need to start up a whole spring dependency injection container to use it, so you don't really need to know any Spring at all or suffer any Spring overhead to do it this way.
So, you could write something like this (ResourceLister is a class I created, as well as the toURL method, see the example app):
public List<String> getResourceUrls(String locationPattern) throws IOException {
ClassLoader classLoader = ResourceLister.class.getClassLoader();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);
Resource[] resources = resolver.getResources(locationPattern);
return Arrays.stream(resources)
.map(this::toURL)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
Executable Example
ResourceLister.java
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class ResourceLister {
// currently, only gets pngs, if needed, can add
// other patterns and union the results to get
// multiple image types.
private static final String IMAGE_PATTERN =
"classpath:/img/*.png";
public List<String> getImageUrls() throws IOException {
return getResourceUrls(IMAGE_PATTERN);
}
public List<String> getResourceUrls(String locationPattern) throws IOException {
ClassLoader classLoader = ResourceLister.class.getClassLoader();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);
Resource[] resources = resolver.getResources(locationPattern);
return Arrays.stream(resources)
.map(this::toURL)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private String toURL(Resource r) {
try {
if (r == null) {
return null;
}
return r.getURL().toExternalForm();
} catch (IOException e) {
return null;
}
}
public static void main(String[] args) throws IOException {
ResourceLister lister = new ResourceLister();
System.out.println(lister.getImageUrls());
}
}
AnimalApp.java
import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class AnimalApp extends Application {
private static final double ANIMAL_SIZE = 512;
// remove the magic seed if you want a different random sequence all the time.
private final Random random = new Random(42);
private final ResourceLister resourceLister = new ResourceLister();
private List<Image> images;
#Override
public void init() {
List<String> imageUrls = findImageUrls();
images = imageUrls.stream()
.map(Image::new)
.collect(Collectors.toList());
}
#Override
public void start(Stage stage) {
ImageView animalView = new ImageView();
animalView.setFitWidth(ANIMAL_SIZE);
animalView.setFitHeight(ANIMAL_SIZE);
animalView.setPreserveRatio(true);
Button findAnimalButton = new Button("Find animal");
findAnimalButton.setOnAction(e ->
animalView.setImage(randomImage())
);
VBox layout = new VBox(10,
findAnimalButton,
animalView
);
layout.setPadding(new Insets(10));
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
private List<String> findImageUrls() {
try {
return resourceLister.getImageUrls();
} catch (IOException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
/**
* Chooses a random image.
*
* Allows the next random image chosen to be the same as the previous image.
*
* #return a random image or null if no images were found.
*/
private Image randomImage() {
if (images == null || images.isEmpty()) {
return null;
}
return images.get(random.nextInt(images.size()));
}
public static void main(String[] args) {
launch(args);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>resource-lister</artifactId>
<version>1.0-SNAPSHOT</version>
<name>resource-lister</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.7.1</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>17.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
images
Place in src/main/resources/img.
chicken.png
cow.png
pig.png
sheep.png
execution command
Set the VM arguments for your JavaFX SDK installation:
-p C:\dev\javafx-sdk-17.0.2\lib --add-modules javafx.controls
There is no easy and reliable way to do that. Therefore I create and put an inventory file into my resources folder. So at runtime I can read that in and then have all the file names awailable that I need.
Here is a little test that shows how I create that file:
public class ListAppDefaultsInventory {
#Test
public void test() throws IOException {
List<String> inventory = listFilteredFiles("src/main/resources/app-defaults", Integer.MAX_VALUE);
assertFalse("Directory 'app-defaults' is empty.", inventory.isEmpty());
System.out.println("# src/main/resources/app-defaults-inventory.txt");
inventory.forEach(s -> System.out.println(s));
}
public List<String> listFilteredFiles(String dir, int depth) throws IOException {
try (Stream<Path> stream = Files.walk(Paths.get(dir), depth)) {
return stream
.filter(file -> !Files.isDirectory(file))
.filter(file -> !file.getFileName().toString().startsWith("."))
.map(Path::toString)
.map(s -> s.replaceFirst("src/main/resources/app-defaults/", ""))
.collect(Collectors.toList());
}
}
}

Artifactory + Custom Gradle Plugin Programmatically (2022)

I'm trying to create a custom gradle plugin (100% java) which will automatically configure Artifactory, avoiding the need of the following DSL:
artifactory {
contextUrl = "${artifactory_contextUrl}" //The base Artifactory URL if not overridden by the publisher/resolver
publish {
repository {
contextUrl = "${artifactory_contextUrl}"
repoKey = 'android-dev'
username = "${artifactory_user}"
password = "${artifactory_password}"
maven = true
}
}
resolve {
repository {
contextUrl = "${artifactory_contextUrl}"
repoKey = 'android-dev-distributions'
username = "${artifactory_user}"
password = "${artifactory_password}"
maven = true
}
}
}
I'm trying to re-create #agrosner own solution (from https://stackoverflow.com/a/25669431/1880280) but I'm missing "ArtifactoryAction". I can't find it anywhere.
The nonworking version posted by #agrosner is the following code:
// Set up plugins so we never need to add them to a build.gradle
project.getPlugins().apply(MAVEN);
project.getPlugins().apply(ARTIFACTORY);
project.setGroup(GROUP);
// Add Artifactory repo to the repositories
project.getRepositories().maven(new ArtifactoryAction(contextUrl + ARTIFACTORY_REPO_ENDPOINT, user, pass));
// We will define the plugin convention here so all of our libraries do not need to
// declare the artifactory closure manually
ArtifactoryPluginConvention pluginConvention =
ArtifactoryPluginUtil.getArtifactoryConvention(project);
pluginConvention.setContextUrl(contextUrl);
PublisherConfig publisherConfig = new PublisherConfig(pluginConvention);
publisherConfig.setContextUrl(contextUrl);
pluginConvention.setPublisherConfig(publisherConfig);
// Use reflection to access private field
PublisherConfig.Repository repository = null;
Field[] fields = PublisherConfig.class.getDeclaredFields();
for(Field field : fields) {
if(field.getName().equalsIgnoreCase("repository")) {
try {
field.setAccessible(true);
repository = (PublisherConfig.Repository) field.get(publisherConfig);
} catch (Exception e) {
e.printStackTrace();
}
}
}
if(repository != null) {
repository.setPassword(pass);
repository.setUsername(user);
repository.setRepoKey(PUBLISHER_REPO_KEY);
repository.setMavenCompatible(true);
}
GradleArtifactoryClientConfigUpdater.update(pluginConvention.getClientConfig(), project.getRootProject());
Can anyone help with an updated 100% java version of this?
Additionally, how would be for the following DSL?
artifactory {
publish {
repository {
repoKey = 'default-gradle-dev-local' // The Artifactory repository key to publish to
username = "${artifactory_user}" // The publisher user name
password = "${artifactory_password}" // The publisher password
maven = true
}
defaults {
publications('mavenJava')
publishArtifacts = true
publishPom = true
}
}}
Thanks in advance
César
ps. The DSL version that was published at #agrosner question thread is not useful for me. I need a Java version.
For your first question related to ArtifactoryAction: this is neither Gradle API nor Artifactory plugin related api, but most probably a custom class that the response author has implemented himself, as a shortcut to declare his custom Artifactory maven repo.
See this API, used to declare maven repositories :
MavenArtifactRepository maven​(Action<? super MavenArtifactRepository> action)
So you can use:
project.getRepositories().maven( mavenArtifactRepository -> {
mavenArtifactRepository.setUrl(contextUrl + MAVEN_PUBLIC_REPO);
mavenArtifactRepository.getCredentials().setUsername("user");
mavenArtifactRepository.getCredentials().setPassword("password");
});
or wrap the action code into a custom implementation of Action<? super MavenArtifactRepository> :
project.getRepositories().maven( new ArtifactoryAction(contextUrl + MAVEN_PUBLIC_REPO, "user", "password") );
[...]
// custom action class, defined somewhere else
class ArtifactoryAction implements Action<MavenArtifactRepository> {
private final String url, userName, password;
ArtifactoryAction(String url, String userName, String password) {
this.url = url; this.userName = userName; this.password = password;
}
#Override
public void execute(MavenArtifactRepository target) {
target.setUrl(url);
target.getCredentials().setUsername(userName);
target.getCredentials().setPassword(password);
}
}
For the other question with java translation of the artifactory { } DSL : see full example below with some inline comments. ( not tested but translated from my kotlin implementation which works fine)
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin;
import org.jfrog.gradle.plugin.artifactory.ArtifactoryPluginUtil;
import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention;
import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig;
public class CustomArtifactoryPlugin implements Plugin<Project> {
private static String MAVEN_PUBLIC_REPO = "/maven-public";
private String contextUrl = "MY_CUSTOM_REPO_BASE_URL";
#Override
public void apply(Project project) {
// Base gradle publishing plugin
// - maven plugin for pulishing java artefacts
// - ivy plugin if for publishing other type of artefacts: rpms, archives, ..
project.getPluginManager().apply(MavenPublishPlugin.class);
// project.getPluginManager().apply(IvyPublishPlugin.class);
// Apply the Artifactory plugin
project.getPluginManager().apply(ArtifactoryPlugin.class);
// Add Artifactory repo to the repositories
project.getRepositories().maven(new ArtifactoryAction(contextUrl + MAVEN_PUBLIC_REPO, "user", "password"));
// Configure artifactory plugin - using 'withPlugin' ensures that plugin has already been applied
project.getPluginManager().withPlugin("com.jfrog.artifactory", appliedPlugin -> {
// artifactory {
ArtifactoryPluginConvention pluginConvention = ArtifactoryPluginUtil.getArtifactoryConvention(project);
// contextUrl = "${contextUrl}"
pluginConvention.setContextUrl(contextUrl);
// publish {
PublisherConfig publisherConfig = new PublisherConfig(pluginConvention);
pluginConvention.setPublisherConfig(publisherConfig);
// repository {
pluginConvention.getPublisherConfig().repository(repository -> {
// repoKey = 'default-gradle-dev-local' ...
repository.setRepoKey("default-gradle-dev-local"); // The Artifactory repository key to publish to
repository.setUsername("${artifactory_user}"); // The publisher user name
repository.setPassword("${artifactory_password}"); // The publisher password
repository.setMavenCompatible(true);
});
// defaults {
pluginConvention.getPublisherConfig().defaults(artifactoryTask -> {
// publications('mavenJava')
artifactoryTask.publications("mavenJava");
artifactoryTask.setPublishArtifacts("true");
artifactoryTask.setPublishPom("true");
});
});
}
}
EDIT for the publication configuration, you can do as follows:
// create maven publication
project.getExtensions().configure(PublishingExtension.class, publishingExtension -> {
publishingExtension.publications(publications -> {
publications.create("mavenPublication", MavenPublication.class, mavenPublication -> {
mavenPublication.setVersion("1.0.0");
mavenPublication.setGroupId("groupId");
mavenPublication.from(project.getComponents().findByName("java"));
});
});
});

Support for manifest only jars in Java Compiler API

Does the Java Compiler API support manifest-only jar files with Class-Path entries in the classpath arguments?
I am trying to use the Java Compiler API in Maven Surefire tests, but it seems that the Java Compiler API, or more precisely the ToolProvider.getSystemJavaCompiler(), does not handle manifest-only jars of Surefire properly.
Here is a code snippet that shows a failing test
new File("target/out").mkdir();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
URLClassLoader classLoader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
// create classpath
StringBuilder path = new StringBuilder();
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
if (path.length() > 0) {
path.append(File.pathSeparator);
}
String decodedPath = URLDecoder.decode(url.getPath(), "UTF-8");
path.append(new File(decodedPath).getAbsolutePath());
}
System.err.println(path);
// compile
List<String> options = Arrays.asList(
"-classpath", path.toString(),
"-s", "target/out",
"src/test/java/com/mysema/codegen/SimpleCompilerTest.java");
int compilationResult = compiler.run(null, null, null,
options.toArray(new String[options.size()]));
if (compilationResult != 0) {
Assert.fail("Compilation Failed");
}
I ran into a similar issue running embedded jetty with jsp compilation inside of surefire unit tests. The easier solution was to configure the surefire plugin to not use the manifest-only jar
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useManifestOnlyJar>false</useManifestOnlyJar>
</configuration>
</plugin>
The harder solution was to expand the class path to include jar files referenced from manifest class-path fields
static List<String> getClassPathForJspCompiler() throws IOException {
List<String> classPath = Lists.newArrayList(System.getProperty("java.class.path")
.split(File.pathSeparator));
return expandManifestClassPathElements(classPath);
}
private static List<String> expandManifestClassPathElements(List<String> classPath)
throws IOException {
for (int i = 0; i < classPath.size(); i++) {
String element = classPath.get(i);
if (element.endsWith(".jar")) {
for (String manifestElement : getManifestClassPath(element)) {
// add to the end of the class path so it will get processed
if (!classPath.contains(manifestElement)) {
// only add if not already present to prevent cyclic loop
classPath.add(manifestElement);
}
}
}
}
return classPath;
}
private static List<String> getManifestClassPath(String jarFilePath) throws IOException {
File jarFile = new File(jarFilePath);
if (!jarFile.isFile()) {
return ImmutableList.of();
}
Manifest manifest = new JarFile(jarFile).getManifest();
if (manifest == null) {
return ImmutableList.of();
}
String manifestClassPath = manifest.getMainAttributes().getValue(
Attributes.Name.CLASS_PATH);
if (manifestClassPath == null) {
return ImmutableList.of();
}
// split at all spaces that are not preceded by a backslash
return Lists.newArrayList(manifestClassPath.split("(?<!\\\\) "));
}

Get Maven artifact version at runtime

I have noticed that in a Maven artifact's JAR, the project.version attribute is included in two files:
META-INF/maven/${groupId}/${artifactId}/pom.properties
META-INF/maven/${groupId}/${artifactId}/pom.xml
Is there a recommended way to read this version at runtime?
You should not need to access Maven-specific files to get the version information of any given library/class.
You can simply use getClass().getPackage().getImplementationVersion() to get the version information that is stored in a .jar-files MANIFEST.MF. Unfortunately Maven does not write the correct information to the manifest as well by default!
Instead one has to modify the <archive> configuration element of the maven-jar-plugin to set addDefaultImplementationEntries and addDefaultSpecificationEntries to true, like this:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
Ideally this configuration should be put into the company pom or another base-pom.
Detailed documentation of the <archive> element can be found in the Maven Archive documentation.
To follow up the answer above, for a .war artifact, I found I had to apply the equivalent configuration to maven-war-plugin, rather than maven-jar-plugin:
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
This added the version information to MANIFEST.MF in the project's .jar (included in WEB-INF/lib of the .war)
Here's a method for getting the version from the pom.properties, falling back to getting it from the manifest
public synchronized String getVersion() {
String version = null;
// try to load from maven properties first
try {
Properties p = new Properties();
InputStream is = getClass().getResourceAsStream("/META-INF/maven/com.my.group/my-artefact/pom.properties");
if (is != null) {
p.load(is);
version = p.getProperty("version", "");
}
} catch (Exception e) {
// ignore
}
// fallback to using Java API
if (version == null) {
Package aPackage = getClass().getPackage();
if (aPackage != null) {
version = aPackage.getImplementationVersion();
if (version == null) {
version = aPackage.getSpecificationVersion();
}
}
}
if (version == null) {
// we could not compute the version so use a blank
version = "";
}
return version;
}
I am using maven-assembly-plugin for my maven packaging. The usage of Apache Maven Archiver in Joachim Sauer's answer could also work:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
<executions>
<execution .../>
</executions>
</plugin>
Because archiever is one of maven shared components, it could be used by multiple maven building plugins, which could also have conflict if two or more plugins introduced, including archive configuration inside.
If you happen to use Spring Boot you can make use of the BuildProperties class.
Take the following snippet from our OpenAPI configuration class as an example:
#Configuration
#RequiredArgsConstructor // <- lombok
public class OpenApi {
private final BuildProperties buildProperties; // <- you can also autowire it
#Bean
public OpenAPI yourBeautifulAPI() {
return new OpenAPI().info(new Info()
.title(buildProperties.getName())
.description("The description")
.version(buildProperties.getVersion())
.license(new License().name("Your company")));
}
}
I know it's a very late answer but I'd like to share what I did as per this link:
I added the below code to the pom.xml:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
And this Advice Controller in order to get the version as model attribute:
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
#ControllerAdvice
public class CommonControllerAdvice
{
#Autowired
BuildProperties buildProperties;
#ModelAttribute("version")
public String getVersion() throws IOException
{
String version = buildProperties.getVersion();
return version;
}
}
I spent some time on the two main approaches here and they didn't work-out for me. I am using Netbeans for the builds, may be there's more going on there. I had some errors and warnings from Maven 3 with some constructs, but I think those were easy to correct. No biggie.
I did find an answer that looks maintainable and simple to implement in this article on DZone:
Stamping Version Number and Build Time in a Properties File with Maven
I already have a resources/config sub-folder, and I named my file: app.properties, to better reflect the kind of stuff we may keep there (like a support URL, etc.).
The only caveat is that Netbeans gives a warning that the IDE needs filtering off. Not sure where/how. It has no effect at this point. Perhaps there's a work around for that if I need to cross that bridge. Best of luck.
To get this running in Eclipse, as well as in a Maven build, you should add the addDefaultImplementationEntries and addDefaultSpecificationEntries pom entries as described in other replies, then use the following code:
public synchronized static final String getVersion() {
// Try to get version number from pom.xml (available in Eclipse)
try {
String className = getClass().getName();
String classfileName = "/" + className.replace('.', '/') + ".class";
URL classfileResource = getClass().getResource(classfileName);
if (classfileResource != null) {
Path absolutePackagePath = Paths.get(classfileResource.toURI())
.getParent();
int packagePathSegments = className.length()
- className.replace(".", "").length();
// Remove package segments from path, plus two more levels
// for "target/classes", which is the standard location for
// classes in Eclipse.
Path path = absolutePackagePath;
for (int i = 0, segmentsToRemove = packagePathSegments + 2;
i < segmentsToRemove; i++) {
path = path.getParent();
}
Path pom = path.resolve("pom.xml");
try (InputStream is = Files.newInputStream(pom)) {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(is);
doc.getDocumentElement().normalize();
String version = (String) XPathFactory.newInstance()
.newXPath().compile("/project/version")
.evaluate(doc, XPathConstants.STRING);
if (version != null) {
version = version.trim();
if (!version.isEmpty()) {
return version;
}
}
}
}
} catch (Exception e) {
// Ignore
}
// Try to get version number from maven properties in jar's META-INF
try (InputStream is = getClass()
.getResourceAsStream("/META-INF/maven/" + MAVEN_PACKAGE + "/"
+ MAVEN_ARTIFACT + "/pom.properties")) {
if (is != null) {
Properties p = new Properties();
p.load(is);
String version = p.getProperty("version", "").trim();
if (!version.isEmpty()) {
return version;
}
}
} catch (Exception e) {
// Ignore
}
// Fallback to using Java API to get version from MANIFEST.MF
String version = null;
Package pkg = getClass().getPackage();
if (pkg != null) {
version = pkg.getImplementationVersion();
if (version == null) {
version = pkg.getSpecificationVersion();
}
}
version = version == null ? "" : version.trim();
return version.isEmpty() ? "unknown" : version;
}
If your Java build puts target classes somewhere other than "target/classes", then you may need to adjust the value of segmentsToRemove.
On my spring boot application, the solution from the accepted answer worked until I recently updated my jdk to version 12. Tried all the other answers as well and couldn't get that to work.
At that point, I added the below line to the first class of my spring boot application, just after the annotation #SpringBootApplication
#PropertySources({
#PropertySource("/META-INF/maven/com.my.group/my-artefact/pom.properties")
})
Later I use the below to get the value from the properties file in whichever class I want to use its value and appVersion gets the project version to me:
#Value("${version}")
private String appVersion;
Hope that helps someone.
The most graceful solutions I've found is that one from J.Chomel: link
Doesn't require any hacks with properties. To avoid issues with broken link in a future I'll duplicate it here:
YourClass.class.getPackage().getImplementationVersion();
And (if you don't have Manifest file in your jar/war yet, for me Intellij Idea's Maven already included them) you will require also a small change in pom.xml:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
...
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
...
A simple solution which is Maven compatible and works for any (thus also third party) class:
private static Optional<String> getVersionFromManifest(Class<?> clazz) {
try {
File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
if (file.isFile()) {
JarFile jarFile = new JarFile(file);
Manifest manifest = jarFile.getManifest();
Attributes attributes = manifest.getMainAttributes();
final String version = attributes.getValue("Bundle-Version");
return Optional.of(version);
}
} catch (Exception e) {
// ignore
}
return Optional.empty();
}
Here’s a version without Optional<> that just returns null if not present (for quick debugging/dumping):
private static String getVersionFromManifest(Class<?> clazz) {
try {
File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
if (file.isFile()) {
JarFile jarFile = new JarFile(file);
Manifest manifest = jarFile.getManifest();
Attributes attributes = manifest.getMainAttributes();
return attributes.getValue("Bundle-Version");
}
} catch (Exception e) {
// ignore
}
return null;
}
Tried all the answers above but nothing worked for me:
I did not use Spring
Managed to put Version inside of manifest, but someClass.class.getPackage().getImplementationVersion() returned null
However version was appended to the jar file name so I was able to find a jar file using:
new File(ClassLoader.getSystemResource("").toURI()).getParentFile();
and then extract it from the file name.
Java 8 variant for EJB in war file with maven project. Tested on EAP 7.0.
#Log4j // lombok annotation
#Startup
#Singleton
public class ApplicationLogic {
public static final String DEVELOPMENT_APPLICATION_NAME = "application";
public static final String DEVELOPMENT_GROUP_NAME = "com.group";
private static final String POM_PROPERTIES_LOCATION = "/META-INF/maven/" + DEVELOPMENT_GROUP_NAME + "/" + DEVELOPMENT_APPLICATION_NAME + "/pom.properties";
// In case no pom.properties file was generated or wrong location is configured, no pom.properties loading is done; otherwise VERSION will be assigned later
public static String VERSION = "No pom.properties file present in folder " + POM_PROPERTIES_LOCATION;
private static final String VERSION_ERROR = "Version could not be determinated";
{
Optional.ofNullable(getClass().getResourceAsStream(POM_PROPERTIES_LOCATION)).ifPresent(p -> {
Properties properties = new Properties();
try {
properties.load(p);
VERSION = properties.getProperty("version", VERSION_ERROR);
} catch (Exception e) {
VERSION = VERSION_ERROR;
log.fatal("Unexpected error occured during loading process of pom.properties file in META-INF folder!");
}
});
}
}

Categories