Maven 3: Assembling a Jar file containing binary resources - maven

I have setup a Maven project consisting of two child modules, one Java Jar module and one creating a Windows Executable using NPanday. My build is working great.
The problem I am having, is that I would like to create a Jar file containing my Java lib and have the Exe file embedded so I can load it as a resource from the code inside the lib.
It seems the assembly plugin would be the path to go, but I am having some trouble configuring this. I don't even know if this is the correct path to go in this case.
Could someone here please guide me to the right path or give me a hint as to how such an assembly descriptor should look like?
Chris

Well I have a Java Project, that only contains a test Class for now, as I am still in the stage of setting up my build:
Module de.cware.utils:lib-psexec-client:
/de/cware/utils/psexec/client/Test.java
Module de.cware.utils:lib-psexec-service:
outputs a file called "service.exe"
I want the output to look like the client jar, but to also contain the "service.exe" so I can load it from the code in the Client jar.
Module de.cware.utis:lib-psexec-assembly:
/de/cware/utils/psexec/client/Test.java
/service.exe

Ok ... so it seems I sorted out a solution on my own. I know this question was relatively special again ... as all of my questions seem to be :-)
The solution was to create a maven module containing a custom implementation of a PlexusIoResourceCollection and to reference this from a components.xml file in the "META-INF/plexus" directory.
After adding this as a dependency to my assembly plugin, I was able to embed the exe files into my jar :-)
Here comes the code of the component:
package npanday.plugin.archiver;
import org.codehaus.plexus.components.io.resources.PlexusIoCompressedFileResourceCollection;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created with IntelliJ IDEA.
* User: cdutz
* Date: 02.03.12
* Time: 12:04
*/
public class PlexusIoExeResourceCollection extends PlexusIoCompressedFileResourceCollection {
#Override
protected String getDefaultExtension() {
return ".exe";
}
#Override
protected InputStream getInputStream(File file) throws IOException {
// Simply return an InputStream to the resource file.
// This will make it embed the source as a whole.
return new FileInputStream(file);
}
#Override
public String getPath() {
// Without overriding this, the exe would be included with its full path.
// This way it is included directly in the root of the result archive.
return super.getFile().getName();
}
}
Here the config xml in META-INF/plexus/components.xml
<component-set>
<components>
<component>
<role>org.codehaus.plexus.components.io.resources.PlexusIoResourceCollection</role>
<role-hint>exe</role-hint>
<implementation>npanday.plugin.archiver.PlexusIoExeResourceCollection</implementation>
<instantiation-strategy>per-lookup</instantiation-strategy>
</component>
</components>
</component-set>
And finally the usage in my assembly plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.npanday.plugins</groupId>
<artifactId>maven-exe-archiver-plugin</artifactId>
<version>${npanday.version}</version>
</dependency>
</dependencies>
</plugin>
Hopefully it will do the trick for me.

Related

"Unit testing" a Maven build

I have a complex Maven build, and I would like to have some automated check that the JAR files and POM files produced by the build actually contain what I expect them to contain.
For instance, I would like to check for the presence of an Automatic-Module-Name entry in the manifest file. It's a multi-release JAR, so I would like to check that the proper class files exist inside of META-INF/versions. I'm going to publish to Maven Central, so I'd also like to check that the produced pom file contains the dependencies the project needs, but that the produced pom file for the fat jar that I also publish, doesn't contain these dependencies.
Basically, I'd like to unit test my build :).
Unfortunately, it's hard to google for this, because of the words I would use to describe this ("test", "verify") already have very specific different meanings in Maven.
Is there a nice way to do this? I would prefer a Maven plugin, since I'm obviously already using that, but I'm open to other things too.
I ended up, as #khmarbaise suggested in the comments, creating a new Maven submodule. In its pom I used the copy-rename-maven-plugin to copy over the files I actually want to check, like this:
<plugin>
<groupId>com.coderplus.maven.plugins</groupId>
<artifactId>copy-rename-maven-plugin</artifactId>
<version>${version.copy-rename-maven-plugin}</version>
<executions>
<execution>
<id>copy-artifacts</id>
<phase>compile</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<fileSets>
<fileSet>
<sourceFile>${project.basedir}../core/target/.flattened-pom.xml</sourceFile>
<destinationFile>${project.basedir}/src/test/resources/flattened.pom</destinationFile>
</fileSet>
<fileSet>
<sourceFile>${project.basedir}../core/target/myArtifactId-${project.version}.jar</sourceFile>
<destinationFile>${project.basedir}/src/test/resources/myArtifactId.jar</destinationFile>
</fileSet>
<!-- more fileSets here -->
</fileSets>
</configuration>
</execution>
</executions>
</plugin>
Then I was able to read the pom file and do assertions on it. I ended up using Java's built-in XPath API, but you can use whatever.
I was also able to read the JAR file by turning it into a NIO FileSystem:
var filename = "myArtifactId.jar"; // or "flattened.pom"
var file = getClass().getClassLoader().getResource(filename);
var uri = URI.create("jar:" + file.toURI().toString());
FileSystem fs = FileSystems.newFileSystem(uri, Map.of());
You can get a list of files:
var path = fs.getPath("/");
Set<String> filenames = StreamSupport
.stream(walk.spliterator(), false)
.map(Path::toString)
.collect(Collectors.toSet());
Or read the content of a file:
var path = fs.getPath("/META-INF/MANIFEST.MF");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
String content = out.toString();
assertTrue(content.contains("Multi-Release: true"));
var path = fs.getPath("/com/example/MyClass.class");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
byte[] content = out.toByteArray();
var actualVersion = content[7]; // the major version of the class file is at this location
assertEquals(52, actualVersion); // 52 = Java 8
(Note that for this answer, I didn't bother to handle exception or close resources; you'll have to do that yourself.)

Maven Enforcer: How to access maven properties from beanshell rule

I successfully created a evaluateBeanshell rule with the maven-enforcer-plugin that scans files in the workspace for common mistakes.
With a hardcoded path the rule works fine. When I want to use the ${project.basedir} variable from the surrounding pom, the script breaks on my Windows machine.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
<executions>
<execution>
<id>enforce-banned-dependencies</id>
<inherited>true</inherited>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<evaluateBeanshell>
<condition>
import scanner.MyScanner;
scanner = new MyScanner();
//hack to read root dir
//project.basedir crashes beanshell with its backslashes
rootPath = new File("");
root = new File(rootPath.getAbsolutePath());
print("Scanning in: " + root);
print("${project.artifactId}"); // works fine
print("${project.basedir}"); // breaks the code
scanner.loopThroughProjects(root);
return everythingIsFine;
</condition>
</evaluateBeanshell>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
In the debug output the line:
print("${project.basedir}");
was replaced by:
print("D:\code\my-maven-project");
Is there another maven property with sanitized slashes or is there another way to access ${project.basedir}?
The hack outlined in the code example kind of works, but I don't like hacks that force me to leave comments.
You could try ${project.baseUri}.
See https://maven.apache.org/ref/3.8.5/maven-model-builder/#Model_Interpolation
On my Windows 10 machine with Java 8 and Maven 3 the following test properties in pom.xml:
<test>${project.baseUri}</test>
<test2>${project.basedir}</test2>
become the following in the 'effective-pom' (via Intellij IDEA maven plugin)
<test>file:/D:/test/path/</test>
<test2>D:\test\path</test2>
This is just as a proof of concept to show the path separators change, and become valid as a Java String.
You could then transform the URI to a file for your needs in the beanshell script as follows:
uri = java.net.URI.create("${project.baseUri}");
root = new java.io.File(uri);
Via https://docs.oracle.com/javase/8/docs/api/java/io/File.html#File-java.net.URI-

Can I start a Spring Boot WAR with PropertiesLauncher?

I have a Spring Boot 1.2 app packaged as a WAR because I need to be able to deploy the app in an app server.
I also want to configure an external path which will contain jars to be added to the classpath. After reading the Launcher documentation, I configured the build to use PropertiesLauncher to this end :
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
...
<layout>ZIP</layout>
</configuration>
</plugin>
I tried to start the app with various combinations of this additional system property : -Dloader.path=lib/,lib-provided/,WEB-INF/classes,<my additional path>
But I always end up with this error :
java.lang.IllegalArgumentException: Invalid source folder C:\<path to my war>\<my war>.war
at org.springframework.boot.loader.archive.ExplodedArchive.<init> ExplodedArchive.java:78)
at org.springframework.boot.loader.archive.ExplodedArchive.<init>(ExplodedArchive.java:66)
at org.springframework.boot.loader.PropertiesLauncher.addParentClassLoaderEntries(PropertiesLauncher.java:530)
at org.springframework.boot.loader.PropertiesLauncher.getClassPathArchives(PropertiesLauncher.java:451)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:60)
at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:609)
I looked at the source code and it seems that PropertiesLauncher can only handle jar archives (ending with ".jar" or ".zip") and "exploded archives" (not ending with the former)
Is it possible to do achieve what I want ? Am I doing it wrong ?
If it's not possible, which alternative is there ?
If somebody end up here this might be useful:
java -cp yourSpringBootWebApp.war -Dloader.path=yourSpringBootWebApp.war!/WEB-INF/classes/,yourSpringBootWebApp.war!/WEB-INF/,externalLib.jar org.springframework.boot.loader.PropertiesLauncher
(Spring-Boot 1.5.9)
https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/executable-jar.html#executable-jar-launching
In Spring Boot 1.2, PropertiesLauncher handles .jar and .zip files as "jar archives" and everything else as "exploded archives" (unzipped jars). It does not properly handles .war
Here's the alternative I found :
I eventually switched back to the regular war launcher and I managed to configure a folder which jar contents are added to the classpath using a SpringApplicationRunListener such as this (pseudo-code for concision) :
public class ClasspathExtender implements SpringApplicationRunListener {
public void contextPrepared(ConfigurableApplicationContext context) {
// read jars folder path from environment
String path = context.getEnvironment().getProperty("my.jars-folder");
// enumerate jars in the folder
File[] files = new File(path).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) { return name.endsWith(".jar"); }
});
URL[] urls = // convert files array to urls array
// create a new classloader which contains the jars...
ClassLoader extendedClassloader = new URLClassLoader(urls, context.getClassLoader());
// and replace the context's classloader
((DefaultResourceLoader) context).setClassLoader(extendedClassloader);
}
// other methods are empty
}
This listener is instanciated by declaring it in a META-INF/spring.factories file :
org.springframework.boot.SpringApplicationRunListener=my.ClasspathExtender
This worked for me (Spring Boot 1.3.2)
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
...
<layout>WAR</layout>
</configuration>
</plugin>

Spring Boot: Thymeleaf not resolving fragments after packaging

im using fragments like this:
#RequestMapping(value="/fragment/nodeListWithStatus", method= RequestMethod.GET)
public String nodeListWithStatus(Model model) {
// status der nodes
model.addAttribute("nodeList", nodeService.getNodeListWithOnlineStatus());
return "/fragments :: nodeList";
}
The templates are in /src/main/resources/templates. This works fine when starting the application from IntelliJ.
As soon as i create an .jar and start it, above code no longer works. Error:
[2014-10-21 20:37:09.191] log4j - 7941 ERROR [http-nio-666-exec-2] --- TemplateEngine: [THYMELEAF][http-nio-666-exec-2] Exception processing template "/fragments": Error resolving template "/fragments", template might not exist or might not be accessible by any of the configured Template Resolvers
When i open the .jar with winrar, i see /templates/fragments.html - so it seems to be there.
My pom.xml has this part for building the jar (Maven clean, install) :
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>de.filth.Application</mainClass>
<layout>JAR</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Can anyone tell me what im doing wrong here?
Thanks!
You don't need the leading / on the view name, i.e. you should return fragments :: nodeList rather than /fragments :: nodeList. Having made this change Thymeleaf should be able to find the template when run from your IDE or from a jar file.
If you're interested, here's what's happening under the hood:
The view name is used to search for a resource on the classpath. fragments :: nodeList means that the resource name is /templates/fragments.html and /fragments :: nodeList means that the resource name is /templates//fragments.html (note the double slash). When you're running in your IDE the resource is available straight off the filesystem and the double slash doesn't cause a problem. When you're running from a jar file the resource is nested within that jar and the double slash prevents it from being found. I don't fully understand why there's this difference in behaviour and it is rather unfortunate. I've opened an issue so that we (the Spring Boot team) can see if there's anything we can do to make the behaviour consistent.
It's an old topic, but I stumbled upon it while having problem with similar symptoms and different root cause. Wanted to share solution which helped me in case it could help somebody else...
Apparently name of the messages.properties file is case sensitive, but not everywhere. I had mine called "Messages.properties" (with capital M) and it worked just fine from inside IDE (IntelliJ), but once I tried to run app from jar, all messages were replaced with ??parameter.name??. Replacing M with lowercase m resolved the problem.

How can I ban a Maven dependency where the version contains a certain part?

I'm tryin to use Maven Enforcer's banned dependencies where I want to ban that there are compile and runtime dependencies to any artifact that contains -redhat-. The background of this: The JEE API and other stuff already exists in the JBoss AS and should never be included in the EAR.
This is what I'm trying, but it doesn't work:
<execution>
<id>banned-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<searchTransitive>false</searchTransitive>
<excludes>
<exclude>*:*:*-redhat-*:*:compile</exclude>
<exclude>*:*:*-redhat-*:*:runtime</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
As you have discovered this wont work the way you are wanting. (I presume you are finding that the enforcer plugin is matching all dependencies listed in your pom?)
The problem is that Maven expects the version given to either be a single * or to conform to maven's Version Spec. (i.e. 1.0, [1.0,) etc) It can't handle the multiple wildcards that you are using.
Unfortunately I don't really have a solution for you. You could potentially
Write Your Own Rule and extend the BannedDependencies rule and have it work the way you would like.
What follows is a dive into the code that is causing your issue
In the BannedDependencies class there is the following check for the version given in the exclude string:
if (pattern[2].equals("*") || artifact.getVersion().equals(pattern[2]) ) {
result = true;
} else {
try {
result = AbstractVersionEnforcer.containsVersion(
VersionRange.createFromVersionSpec(pattern[2]),
new DefaultArtifactVersion(artifact.getBaseVersion()));
} catch ( InvalidVersionSpecificationException e ) {
throw new EnforcerRuleException("Invalid Version Range: ", e);
}
}
The specific problem for you is
AbstractVersionEnforcer.containsVersion(
VersionRange.createFromVersionSpec(pattern[2]),
new DefaultArtifactVersion(artifact.getBaseVersion()))
You can see that it is expecting a VersionRange due to VersionRange.createFromVersionSpec(). The code for that can be seen here:
VersionRange source code

Resources