I'm trying to migrate a large codebase from maven to bazel and I've found that some of the tests write to target/classes and target/test-classes and the production code reads it as resources on the classpath. This is because maven surefire/failsafe run by default from the module directory and add target/classes and target/test-classes to the classpath.
For me to migrate this large codebase the only reasonable solution is to create target, target/classes and target/test-classes folders and add the last two to the classpath of the tests.
Any ideas on how this can be achieved?
Thanks
Another line of approach. Instead of generating a test suite, create a custom javaagent and a custom class loader. Use jvm_flags to setup and configure it.
The javaagent has a premain method. This sounds like a natural place to do things that happen before the regular main method, even if they don't have anything to do with class instrumentation, debugging, coverage gathering, or any other usual uses of javaagents.
The custom javaagent reads system property extra.dirs and creates directories specified there. It then reads property extra.link.path and creates the symbolic links as specified there, so I can place resources where the tests expect them, without having to copy them.
Classloader is needed so that we can amend the classpath at runtime without hacks. Great advantage is that this solution works on Java 10.
The custom classloader reads system property extra.class.path and (in effect) prepends it before what is in java.class.path.
Doing things this way means that standard bazel rules can be used.
BUILD
runtime_classgen_dirs = ":".join([
"target/classes",
"target/test-classes",
])
java_test(
...,
jvm_flags = [
# agent
"-javaagent:$(location //tools:test-agent_deploy.jar)",
"-Dextra.dirs=" + runtime_classgen_dirs,
# classloader
"-Djava.system.class.loader=ResourceJavaAgent",
"-Dextra.class.path=" + runtime_classgen_dirs,
],
,,,,
deps = [
# not runtime_deps, cause https://github.com/bazelbuild/bazel/issues/1566
"//tools:test-agent_deploy.jartest-agent_deploy.jar"
],
...,
)
tools/BUILD
java_binary(
name = "test-agent",
testonly = True,
srcs = ["ResourceJavaAgent.java"],
deploy_manifest_lines = ["Premain-Class: ResourceJavaAgent"],
main_class = "ResourceJavaAgent",
visibility = ["//visibility:public"],
)
tools/ResourceJavaAgent.java
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
// https://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime
public class ResourceJavaAgent extends URLClassLoader {
private final ClassLoader parent;
public ResourceJavaAgent(ClassLoader parent) throws MalformedURLException {
super(buildClassPath(), null);
this.parent = parent; // I need the parent as backup for SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
System.out.println("initializing url classloader");
}
private static URL[] buildClassPath() throws MalformedURLException {
final String JAVA_CLASS_PATH = "java.class.path";
final String EXTRA_CLASS_PATH = "extra.class.path";
List<String> paths = new LinkedList<>();
paths.addAll(Arrays.asList(System.getProperty(EXTRA_CLASS_PATH, "").split(File.pathSeparator)));
paths.addAll(Arrays.asList(System.getProperty(JAVA_CLASS_PATH, "").split(File.pathSeparator)));
URL[] urls = new URL[paths.size()];
for (int i = 0; i < paths.size(); i++) {
urls[i] = Paths.get(paths.get(i)).toUri().toURL(); // important only for resource url, really: this url must be absolute, to pass getClass().getResource("/users.properties").toURI()) with uri that isOpaque == false.
// System.out.println(urls[i]);
}
// this is for spawnVM functionality in tests
System.setProperty(JAVA_CLASS_PATH, System.getProperty(EXTRA_CLASS_PATH, "") + File.pathSeparator + System.getProperty(JAVA_CLASS_PATH));
return urls;
}
#Override
public Class<?> loadClass(String s) throws ClassNotFoundException {
try {
return super.loadClass(s);
} catch (ClassNotFoundException e) {
return parent.loadClass(s); // we search parent second, not first, as the default URLClassLoader would
}
}
private static void createRequestedDirs() {
for (String path : System.getProperty("extra.dirs", "").split(File.pathSeparator)) {
new File(path).mkdirs();
}
}
private static void createRequestedLinks() {
String linkPaths = System.getProperty("extra.link.path", null);
if (linkPaths == null) {
return;
}
for (String linkPath : linkPaths.split(",")) {
String[] fromTo = linkPath.split(":");
Path from = Paths.get(fromTo[0]);
Path to = Paths.get(fromTo[1]);
try {
Files.createSymbolicLink(from.toAbsolutePath(), to.toAbsolutePath());
} catch (IOException e) {
throw new IllegalArgumentException("Unable to create link " + linkPath, e);
}
}
}
public static void premain(String args, Instrumentation instrumentation) throws Exception {
createRequestedDirs();
createRequestedLinks();
}
}
If you could tell the tests where to write these files (in case target/classes and target/test-classes are hardcoded), and then turn the test run into a genrule, then you can specify the genrule's outputs as data for the production binary's *_binary rule.
I solved the first part, creating the directories. I still don't know how to add the latter two to classpath.
Starting from https://gerrit.googlesource.com/bazlets/+/master/tools/junit.bzl, I modified it to read
_OUTPUT = """import org.junit.runners.Suite;
import org.junit.runner.RunWith;
import org.junit.BeforeClass;
import java.io.File;
#RunWith(Suite.class)
#Suite.SuiteClasses({%s})
public class %s {
#BeforeClass
public static void setUp() throws Exception {
new File("./target").mkdir();
}
}
"""
_PREFIXES = ("org", "com", "edu")
# ...
I added the #BeforeClass setUp method.
I stored this as junit.bzl into third_party directory in my project.
Then in a BUILD file,
load("//third_party:junit.bzl", "junit_tests")
junit_tests(
name = "my_bundled_test",
srcs = glob(["src/test/java/**/*.java"]),
data = glob(["src/test/resources/**"]),
resources = glob(["src/test/resources/**"]),
tags = [
# ...
],
runtime_deps = [
# ...
],
],
deps = [
# ...
],
)
Now the test itself is wrapped with a setUp method which will create a directory for me. I am not deleting them afterwards, which is probably a sound idea to do.
The reason I need test resources in a directory (as opposed to in a jar file, which bazel gives by default) is that my test passes the URI to new FileInputStream(new File(uri)). If the file resides in a JAR, the URI will be file:/path/to/my.jar!/my.file and the rest of the test cannot work with such URI.
Related
I can't seem to find any info on whether scanning all available classes (for interfaces, annotations etc) is still possible in runtime, the way Spring, Reflections and many other frameworks and libraries currently do, in the face of Jigsaw related changes to the way classes are loaded.
EDIT:
This question is about scanning the real physical file paths looking for classes. The other question is about dynamically loading classes and resources. It's related but very much not a duplicate.
UPDATE: Jetty project has made a JEP proposal for a standardized API for this. If you have a way to help make this reality, please do. Otherwise, wait and hope.
UPDATE 2: Found this relevant sounding post. Quoting the code snippet for posterity:
If you are really just looking to get at the contents of the modules in
the boot layer (the modules that are resolved at startup) then you'll do
something like this:
ModuleLayer.boot().configuration().modules().stream()
.map(ResolvedModule::reference)
.forEach(mref -> {
System.out.println(mref.descriptor().name());
try (ModuleReader reader = mref.open()) {
reader.list().forEach(System.out::println);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
});
The following code achieves module path scanning in Java 9+ (Jigsaw / JPMS). It finds all classes on the callstack, then for each class reference, calls classRef.getModule().getLayer().getConfiguration().modules(), which returns a a List<ResolvedModule>, rather than just a List<Module>. (ResolvedModule gives you access to the module resources, whereas Module does not.) Given a ResolvedModule reference for each module, you can call the .reference() method to get the ModuleReference for a module. ModuleReference#open() gives you a ModuleReader, which allows you to list the resources in a module, using ModuleReader#list(), or to open a resource using Optional<InputStream> ModuleReader#open(resourcePath) or Optional<ByteBuffer> ModuleReader#read(resourcePath). You then close the ModuleReader when you're done with the module. This is not documented anywhere that I have seen. It was very difficult to figure all this out. But here is the code, in the hope that someone else will benefit from this.
Note that even in JDK9+, you can still utilize traditional classpath elements along with module path elements, so for a complete module path + classpath scan, you should probably use a proper classpath scanning solution, such as ClassGraph, which supports module scanning using the below mechanism (disclaimer, I am the author). You can find a reflection-based version of the following code here.
Also note that there was a bug in StackWalker in several JDK releases after JDK 9 that has to be worked around, see the above reflection-based code for details.
package main;
import java.lang.StackWalker;
import java.lang.StackWalker.Option;
import java.lang.StackWalker.StackFrame;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
public class Java9Scanner {
/** Recursively find the topological sort order of ancestral layers. */
private static void findLayerOrder(ModuleLayer layer,
Set<ModuleLayer> visited, Deque<ModuleLayer> layersOut) {
if (visited.add(layer)) {
List<ModuleLayer> parents = layer.parents();
for (int i = 0; i < parents.size(); i++) {
findLayerOrder(parents.get(i), visited, layersOut);
}
layersOut.push(layer);
}
}
/** Get ModuleReferences from a Class reference. */
private static List<Entry<ModuleReference, ModuleLayer>> findModuleRefs(
Class<?>[] callStack) {
Deque<ModuleLayer> layerOrder = new ArrayDeque<>();
Set<ModuleLayer> visited = new HashSet<>();
for (int i = 0; i < callStack.length; i++) {
ModuleLayer layer = callStack[i].getModule().getLayer();
findLayerOrder(layer, visited, layerOrder);
}
Set<ModuleReference> addedModules = new HashSet<>();
List<Entry<ModuleReference, ModuleLayer>> moduleRefs = new ArrayList<>();
for (ModuleLayer layer : layerOrder) {
Set<ResolvedModule> modulesInLayerSet = layer.configuration()
.modules();
final List<Entry<ModuleReference, ModuleLayer>> modulesInLayer =
new ArrayList<>();
for (ResolvedModule module : modulesInLayerSet) {
modulesInLayer
.add(new SimpleEntry<>(module.reference(), layer));
}
// Sort modules in layer by name for consistency
Collections.sort(modulesInLayer,
(e1, e2) -> e1.getKey().descriptor().name()
.compareTo(e2.getKey().descriptor().name()));
// To be safe, dedup ModuleReferences, in case a module occurs in multiple
// layers and reuses its ModuleReference (no idea if this can happen)
for (Entry<ModuleReference, ModuleLayer> m : modulesInLayer) {
if (addedModules.add(m.getKey())) {
moduleRefs.add(m);
}
}
}
return moduleRefs;
}
/** Get the classes in the call stack. */
private static Class<?>[] getCallStack() {
// Try StackWalker (JDK 9+)
PrivilegedAction<Class<?>[]> stackWalkerAction =
(PrivilegedAction<Class<?>[]>) () ->
StackWalker.getInstance(
Option.RETAIN_CLASS_REFERENCE)
.walk(s -> s.map(
StackFrame::getDeclaringClass)
.toArray(Class[]::new));
try {
// Try with doPrivileged()
return AccessController
.doPrivileged(stackWalkerAction);
} catch (Exception e) {
}
try {
// Try without doPrivileged()
return stackWalkerAction.run();
} catch (Exception e) {
}
// Try SecurityManager
PrivilegedAction<Class<?>[]> callerResolverAction =
(PrivilegedAction<Class<?>[]>) () ->
new SecurityManager() {
#Override
public Class<?>[] getClassContext() {
return super.getClassContext();
}
}.getClassContext();
try {
// Try with doPrivileged()
return AccessController
.doPrivileged(callerResolverAction);
} catch (Exception e) {
}
try {
// Try without doPrivileged()
return callerResolverAction.run();
} catch (Exception e) {
}
// As a fallback, use getStackTrace() to try to get the call stack
try {
throw new Exception();
} catch (final Exception e) {
final List<Class<?>> classes = new ArrayList<>();
for (final StackTraceElement elt : e.getStackTrace()) {
try {
classes.add(Class.forName(elt.getClassName()));
} catch (final Throwable e2) {
// Ignore
}
}
if (classes.size() > 0) {
return classes.toArray(new Class<?>[0]);
} else {
// Last-ditch effort -- include just this class
return new Class<?>[] { Java9Scanner.class };
}
}
}
/**
* Return true if the given module name is a system module.
* There can be system modules in layers above the boot layer.
*/
private static boolean isSystemModule(
final ModuleReference moduleReference) {
String name = moduleReference.descriptor().name();
if (name == null) {
return false;
}
return name.startsWith("java.") || name.startsWith("jdk.")
|| name.startsWith("javafx.") || name.startsWith("oracle.");
}
public static void main(String[] args) throws Exception {
// Get ModuleReferences for modules of all classes in call stack,
List<Entry<ModuleReference, ModuleLayer>> systemModuleRefs = new ArrayList<>();
List<Entry<ModuleReference, ModuleLayer>> nonSystemModuleRefs = new ArrayList<>();
Class<?>[] callStack = getCallStack();
List<Entry<ModuleReference, ModuleLayer>> moduleRefs = findModuleRefs(
callStack);
// Split module refs into system and non-system modules based on module name
for (Entry<ModuleReference, ModuleLayer> m : moduleRefs) {
(isSystemModule(m.getKey()) ? systemModuleRefs
: nonSystemModuleRefs).add(m);
}
// List system modules
System.out.println("\nSYSTEM MODULES:\n");
for (Entry<ModuleReference, ModuleLayer> e : systemModuleRefs) {
ModuleReference ref = e.getKey();
System.out.println(" " + ref.descriptor().name());
}
// Show info for non-system modules
System.out.println("\nNON-SYSTEM MODULES:");
for (Entry<ModuleReference, ModuleLayer> e : nonSystemModuleRefs) {
ModuleReference ref = e.getKey();
ModuleLayer layer = e.getValue();
System.out.println("\n " + ref.descriptor().name());
System.out.println(
" Version: " + ref.descriptor().toNameAndVersion());
System.out.println(
" Packages: " + ref.descriptor().packages());
System.out.println(" ClassLoader: "
+ layer.findLoader(ref.descriptor().name()));
Optional<URI> location = ref.location();
if (location.isPresent()) {
System.out.println(" Location: " + location.get());
}
try (ModuleReader moduleReader = ref.open()) {
Stream<String> stream = moduleReader.list();
stream.forEach(s -> System.out.println(" File: " + s));
}
}
}
}
The actual issue here is to find the paths to all jars and folders on the classpath. Once when you have them, you can scan.
What I did is the following:
get the current module descriptor for current class
get all requires modules
for each such module open resource of MANIFEST.MF
remove the MANIFEST.MF path from the resource url
what remains is the classpath of the module, i.e. to it's jar or folder.
I do the same for current module, to get the classpath for current code.
This way I collect classpath of a currently working module and all its required modules (1 step away). That was working for me - and my Java8 scanner was still being able to do the job. This approach does not require any additional VM flag etc.
I could extend this approach to get all required modules easily (not only the first level), but for now, I don't need that.
Code.
Is there a way to remove a folder from the classpath similar to adding a folder at runtime (Can a directory be added to the class path at runtime?)
Please find below a snippet as technical example to demonstrate adding / removing a path.
create following source files in any directory
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Stack;
import sun.misc.URLClassPath;
public class EvilPathDemo {
public static void addPath(String path) throws Exception {
URL u = new File(path).toURI().toURL();
URLClassLoader urlClassLoader = (URLClassLoader)
ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL",
new Class[]{URL.class}
);
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
}
public static void removePath(String path) throws Exception {
URL url = new File(path).toURI().toURL();
URLClassLoader urlClassLoader = (URLClassLoader)
ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Field ucpField = urlClass.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);
Class<?> ucpClass = URLClassPath.class;
Field urlsField = ucpClass.getDeclaredField("urls");
urlsField.setAccessible(true);
Stack urls = (Stack) urlsField.get(ucp);
urls.remove(url);
}
public static void main(String[] args) throws Exception {
String parm = args.length == 1 ? args[0] : "";
String evilPath = "/tmp";
String classpath = System.getProperty("java.class.path");
boolean isEvilPathSet = false;
for (String path : classpath.split(File.pathSeparator)) {
if (path.equalsIgnoreCase(evilPath)) {
System.out.printf("evil path '%s' in classpath%n", evilPath);
isEvilPathSet = true;
break;
}
}
if (isEvilPathSet && parm.equalsIgnoreCase("REMOVE")) {
System.out.printf("evil path '%s' will be removed%n", evilPath);
removePath(evilPath);
}
tryToLoad("Foo");
if (parm.equalsIgnoreCase("ADD")) {
System.out.printf("evil path '%s' will be added%n", evilPath);
addPath(evilPath);
}
tryToLoad("Bar");
}
private static void tryToLoad(String className) {
try {
Class<?> foo = Class.forName(className);
System.out.printf("class loaded: %s%n", foo.getName());
} catch (ClassNotFoundException ex) {
System.out.println(ex);
}
}
}
.
public class Foo {
static {
System.out.println("I'm foo...");
}
}
.
public class Bar {
static {
System.out.println("I'm bar...");
}
}
compile them as follow
javac EvilPathDemo.java
javac -d /tmp Foo.java Bar.java
During the test we will try to load the classes Foo and Bar.
without /tmp in the classpath
java -cp . EvilPathDemo
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar
adding /tmp to the classpath
java -cp . EvilPathDemo add
java.lang.ClassNotFoundException: Foo
evil path '/tmp' will be added
I'm bar...
class loaded: Bar
with /tmp in the classpath
java -cp .:/tmp EvilPathDemo
evil path '/tmp' in the classpath
I'm foo...
class loaded: Foo
I'm bar...
class loaded: Bar
remove /tmp from the classpath
java -cp .:/tmp EvilPathDemo remove
evil path '/tmp' in the classpath
evil path '/tmp' will be removed
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar
During the testing I found out that following cases are not working.
addPath(evilPath);
tryToLoad("Foo");
removePath(evilPath); // had not effect
tryToLoad("Bar");
removePath(evilPath);
tryToLoad("Foo");
addPath(evilPath); // had no effect
tryToLoad("Bar");
tryToLoad("Foo");
removePath(evilPath); // had no effect
tryToLoad("Bar");
I did not spent time to find out why. Because I don't see any practical use in it. If you really need/wish to play with the classpaths have a look how classloaders are working.
The removePath method from above did not work for me and my Weld Container, the url stack was always emtpy.
The following ugly smugly method worked:
public static void removeLastClasspathEntry() throws Exception {
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Field ucpField = urlClass.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);
Field loadersField = URLClassPath.class.getDeclaredField("loaders");
loadersField.setAccessible(true);
List jarEntries = (List) loadersField.get(ucp);
jarEntries.remove(jarEntries.size() - 1);
Field pathField = URLClassPath.class.getDeclaredField("path");
pathField.setAccessible(true);
List pathList = (List) pathField.get(ucp);
URL jarUrl = (URL) pathList.get(pathList.size() - 1);
String jarName = jarUrl.toString();
pathList.remove(pathList.size() - 1);
Field lmapField = URLClassPath.class.getDeclaredField("lmap");
lmapField.setAccessible(true);
Map lmapMap = (Map) lmapField.get(ucp);
lmapMap.remove(jarName.replaceFirst("file:/", "file:///"));
}
Class loaders can be nested so instead of modifying the system class loader which is the root of the tree of class loaders, it is better to simply create a nested classloader and use that to load classes.
The system classloader itself is immutable (for good reasons) but you can do whatever you want in nested class loaders, including destroying them to unload classes and resources. This is commonly used in e.g. osgi and application servers to load/unload e.g. plugins, applications, etc.
For nested class loaders you can completely customize how to load classes. The URLClassloader is probably a good starting point for what you want.
I dont think there is a straight forward way to do it. You can follow :
Get class path variables using : System.getenv("CLASSPATH"). It will return semi colon separated values.
String classPath = System.getenv("CLASSPATH")
Take the folder path as input and replace it with "" like :
String remainigPath = classPath.replace(inputpath,"");
Put the remaining paths in an array using split method.
String[] paths = remainigPath .split(";");
For adding classPath, You already have the code.
I had the same issue, so I tackled it by creating a library that works on every ClassLoader that uses a URLClassPath (so, currently, URLClassLoader).
The library has methods for:
Adding new entries in front
Appending new entries
Remove existing entries
Please note that, since this library accesses internal and proprietary APIs, it is no guaranteed to work in future versions of the JDK. It currently does for Java 7 and Java 8 (Oracle and OpenJDK).
Here is the GitHub page (contribution is appreciated), and here is the Maven Central artifact
I have a simple parent project with modules/applications within it. My build tool of choice is gradle. The parent build.gradle is defined below.
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
allprojects {
repositories {
mavenCentral()
}
version "0.1.0-SNAPSHOT"
}
What I would like to do is utilize the version attribute (0.1.0-SNAPSHOT) within my swing application. Specifically, I'd like it to display in the titlebar of the main JFrame. I expect to be able to do something like this.setTitle("My Application - v." + ???.version);
The application is a plain java project, but I'm not opposed to adding groovy support it it will help.
I like creating a properties file during the build. Here's a way to do that from Gradle directly:
task createProperties(dependsOn: processResources) {
doLast {
new File("$buildDir/resources/main/version.properties").withWriter { w ->
Properties p = new Properties()
p['version'] = project.version.toString()
p.store w, null
}
}
}
classes {
dependsOn createProperties
}
You can always use brute force as somebody suggested and generate properties file during build. More elegant answer, which works only partially would be to use
getClass().getPackage().getImplementationVersion()
Problem is that this will work only if you run your application from generated jar - if you run it directly from IDE/expanded classes, getPackage above will return null. It is good enough for many cases - just display 'DEVELOPMENT' if you run from IDE(geting null package) and will work for actual client deployments.
Better idea is to keep the project version in gradle.properties file. All the properties from this file will be automatically loaded and can be used in build.gradle script.
Then if you need the version in your swing application, add a version.properties file under src/main/resources folder and filter this file during application build, here is a post that shows how it should be done.
version.properties will be included in the final jar, hence can be read and via ClassLoader and properties from this file can be displayed in application.
Simpler and updated solution of #Craig Trader (ready for Gradle 4.0/5.0)
task createProperties {
doLast {
def version = project.version.toString()
def file = new File("$buildDir/resources/main/version.txt")
file.write(version)
}
}
war {
dependsOn createProperties
}
I used #Craig Trader's answer, but had to add quite some changes to make it work (it also adds git-details):
task createProperties() {
doLast {
def details = versionDetails()
new File("$buildDir/resources/main/version.properties").withWriter { w ->
Properties p = new Properties()
p['version'] = project.version.toString()
p['gitLastTag'] = details.lastTag
p['gitCommitDistance'] = details.commitDistance.toString()
p['gitHash'] = details.gitHash.toString()
p['gitHashFull'] = details.gitHashFull.toString() // full 40-character Git commit hash
p['gitBranchName'] = details.branchName // is null if the repository in detached HEAD mode
p['gitIsCleanTag'] = details.isCleanTag.toString()
p.store w, null
}
// copy needed, otherwise the bean VersionController can't load the file at startup when running complete-app tests.
copy {
from "$buildDir/resources/main/version.properties"
into "bin/main/"
}
}
}
classes {
dependsOn createProperties
}
And load it from the constructor of class: VersionController
import static net.logstash.logback.argument.StructuredArguments.v;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
#RestController
public class VersionController {
final static Logger log = LoggerFactory.getLogger(AppInfoController.class);
private Properties versionProperties = new Properties();
private String gitLastTag;
private String gitHash;
private String gitBranchName;
private String gitIsCleanTag;
VersionController()
{
String AllGitVersionProperties = "";
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("classpath:/version.properties");
if(inputStream == null)
{
// When running unit tests, no jar is built, so we load a copy of the file that we saved during build.gradle.
// Possibly this also is the case during debugging, therefore we save in bin/main instead of bin/test.
try {
inputStream = new FileInputStream("bin/main/version.properties");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
versionProperties.load(inputStream);
} catch (IOException e) {
AllGitVersionProperties += e.getMessage()+":";
log.error("Could not load classpath:/version.properties",e);
}
gitLastTag = versionProperties.getProperty("gitLastTag","last-tag-not-found");
gitHash = versionProperties.getProperty("gitHash","git-hash-not-found");
gitBranchName = versionProperties.getProperty("gitBranchName","git-branch-name-not-found");
gitIsCleanTag = versionProperties.getProperty("gitIsCleanTag","git-isCleanTag-not-found");
Set<Map.Entry<Object, Object>> mainPropertiesSet = versionProperties.entrySet();
for(Map.Entry oneEntry : mainPropertiesSet){
AllGitVersionProperties += "+" + oneEntry.getKey()+":"+oneEntry.getValue();
}
log.info("All Git Version-Properties:",v("GitVersionProperties", AllGitVersionProperties));
}
}
Using #Craig Trader's solution to save the properties in a version.properties file. Add to build.gradle:
task createProperties() {
doLast {
def details = versionDetails()
new File("$buildDir/resources/main/version.properties").withWriter { w ->
Properties p = new Properties()
p['version'] = project.version.toString()
p['gitLastTag'] = details.lastTag
p['gitCommitDistance'] = details.commitDistance.toString()
p['gitHash'] = details.gitHash.toString()
p['gitHashFull'] = details.gitHashFull.toString() // full 40-character Git commit hash
p['gitBranchName'] = details.branchName // is null if the repository in detached HEAD mode
p['gitIsCleanTag'] = details.isCleanTag.toString()
p.store w, null
}
// copy needed, otherwise the bean VersionController can't load the file at startup when running complete-app tests.
copy {
from "$buildDir/resources/main/version.properties"
into "bin/main/"
}
}
}
classes {
dependsOn createProperties
}
To load the properties runtime in version.properties you need to annotate your class with #PropertySource({"classpath:version.properties"})
Then you can assign a property to a private variable with annotation like:
#Value("${gitLastTag}")
private String gitLastTag;
Full example:
package com.versioncontroller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import javax.annotation.PostConstruct;
import java.util.Properties;
#PropertySource({"classpath:version.properties"})
public class VersionController {
#Value("${gitLastTag}")
private String gitLastTag;
#Value("${gitHash}")
private String gitHash;
#Value("${gitBranchName}")
private String gitBranchName;
#Value("${gitIsCleanTag}")
private String gitIsCleanTag;
#PostConstruct // properties are only set after the constructor has run
private void logVersion(){
// when called during the constructor, all values are null.
System.out.println("All Git Version-Properties:");
System.out.println("gitLastTag: " + gitLastTag),
System.out.println("gitHash: " + gitHash),
System.out.println("gitBranchName: " + gitBranchName),
System.out.println("gitIsCleanTag: " + gitIsCleanTag));
}
}
I'm trying to achieve a way to obtain the base path of the current classloader when runnning from within a jar.
I mean programatically, I already know it should have the shape of "jarPath+jarFile.jar!/"
Unlike file system's call, getResource(".") or .getResource("/") do not work from inside the jar.
Ideally it should be an abstract solution for any file provider, so something like:
Path BASE_PATH = Paths.get(...getResource("").toURI())
which could return the correct root path for both jars and file system so I can use relative urls to my resources without having to do any conditional statements and url manual string parsing/build.
You should be able to find out the path of the jar and or target folder containing you class or any resource by using this code:
package com.stackoverflow.test;
import java.net.URL;
import java.nio.file.Paths;
public class ClassPathUtils {
public static String getBasePath(String jarPath) {
String path = getJarPathFromClass(jarPath);
if (path == null) {
return null;
}
if (path.startsWith("jar:")) {
path = path.substring("jar:".length());
}
if (path.startsWith("file:")) {
path = path.substring("file:".length());
}
if (path.endsWith(jarPath)) {
path = path.substring(0, path.length()-jarPath.length());
}
return path;
}
public static String getBasePath(Class clazz) {
return getBasePath(classNameDotClass(clazz));
}
private static String classNameDotClass(Class clazz) {
return clazz.getName().replaceAll("\\.", "/") + ".class";
}
private static String getJarPathFromClass(String resource) {
final URL url = ClassPathUtils.class.getClassLoader().getResource(resource);
return url == null ? null : url.toString();
}
public static void main(String[] args) {
//System.out.println(Paths.get(ClassPathUtils.getBasePath("."))); // doesn't work in a jar
System.out.println(Paths.get(ClassPathUtils.getBasePath(ClassPathUtils.class)));
System.out.println(Paths.get(ClassPathUtils.getBasePath("fonts/atcitadelscript.ttf"))); // any classpath resource
System.out.println(Paths.get(ClassPathUtils.getBasePath(String.class))); // actually finds rt.jar
}
}
If you run this code from your IDE, or from maven, it will give you the paths to target/classes for your own resources, or the path to a jar for other resources (E.g. String.class).
If you call it from a jar, it will always tell you the path of the jar file.
run from IDE:
/home/alexander/projects/stackoverflow/stuff/target/classes
/home/alexander/projects/stackoverflow/stuff/target/classes
/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar!`
run from JAR:
/home/alexander/projects/stackoverflow/stuff/target/test-stuff-0.1-SNAPSHOT.jar!
/home/alexander/projects/stackoverflow/stuff/target/test-stuff-0.1-SNAPSHOT.jar!
/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar!
Is that what you're looking for?
How can I dynamically load a jar file and list classes which is in it?
Here is code for listing classes in jar:
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class JarList {
public static void main(String args[]) throws IOException {
if (args.length > 0) {
JarFile jarFile = new JarFile(args[0]);
Enumeration allEntries = jarFile.entries();
while (allEntries.hasMoreElements()) {
JarEntry entry = (JarEntry) allEntries.nextElement();
String name = entry.getName();
System.out.println(name);
}
}
}
}
You can view the contents of a JAR file from command prompt by using the following command:
jar tf jar-file
For example:
jar tf TicTacToe.jar
META-INF/MANIFEST.MF
TicTacToe.class
audio/
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au
images/
images/cross.gif
images/not.gif
Have a look at the classes in the package java.util.jar. You can find examples of how to list the files inside the JAR on the web, here's an example. (Also note the links at the bottom of that page, there are many more examples that show you how to work with JAR files).
Fast way: just open the .jar as .zip e.g. in 7-Zip and look for the directory-names.
Here is a version that scans a given jar for all non-abstract classes extending a particular class:
try (JarFile jf = new JarFile("/path/to/file.jar")) {
for (Enumeration<JarEntry> en = jf.entries(); en.hasMoreElements(); ) {
JarEntry e = en.nextElement();
String name = e.getName();
// Check for package or sub-package (you can change the test for *exact* package here)
if (name.startsWith("my/specific/package/") && name.endsWith(".class")) {
// Strip out ".class" and reformat path to package name
String javaName = name.substring(0, name.lastIndexOf('.')).replace('/', '.');
System.out.print("Checking "+javaName+" ... ");
Class<?> cls;
try {
cls = Class.forName(javaName);
} catch (ClassNotFoundException ex) { // E.g. internal classes, ...
continue;
}
if ((cls.getModifiers() & Modifier.ABSTRACT) != 0) { // Only instanciable classes
System.out.println("(abstract)");
continue;
}
if (!TheSuper.class.isAssignableFrom(cls)) { // Only subclasses of "TheSuper" class
System.out.println("(not TheSuper)");
continue;
}
// Found!
System.out.println("OK");
}
}
} catch (IOException e) {
e.printStackTrace();
}
You can use that code directly when you know where are your jars. To get that information, refer to this other question, as going through classpath has changed since Java 9 and the introduction of modules.