get the execution location from within a library jar - java

I have a class named Utils with a static method that should determine the execution location.
public class Utils {
public static Path getExecutionLocation() throws URISyntaxException {
return Paths.get(Utils.class.getProtectionDomain().getCodeSource().getLocation().toURI());
}
}
Within eclipse this gives me: C:\Users\USERNAME\workspace\PROJECT\bin\main\
Run as a jar this gives me: C:\PATH\TO\JAR\thatJar.jar
Both is correct and expected.
Now I have that Utils class inside a library called someLib.jar.
When I use that library in another project it works if I build a jar of that project with someLib.jar inside.
But in eclipse it returns the path to someLib.jar.
I want it to return the path to the execution directory of the project:
C:\Users\USERNAME\workspace\A_PROJECT_USING_SOMELIB\bin\main\
I tried
return new File(ClassLoader.getSystemClassLoader().getResource(".").getPath()).toPath();
But that failed inside a jar because getResource(".") results in null.
I could give getExecutionLocation a class from inside the project as a parameter and excute getProtectionDomain() on that. But I want to ask here if someone knows a better solution.

Try this:
public class Utils {
public static Path getExecutionLocation(Class c) throws URISyntaxException {
return Paths.get(c.getProtectionDomain().getCodeSource().getLocation().toURI());
}
}
So you pass the class as an argument to the function. For example:
public class Main
{
public static void main(String[] args)
{
Utils.getExecutionLocation(Main.class);
}
}
Note that there may be more straightforward solutions,
But this is the first one that came into my mind, and I thought, why not :)

I've found a solution based on ClassLoader.getSystemClassLoader() approach that gives me the class loader responsible for the main entry point of the project that is using someLib.jar.
Within an IDE I can use getResource(".") to get URL of the root path of all resources (and sources).
From jar file this does not work. So I use getResource("META-INF") to get URL of the META-INF folder (that has the manifest inside) and should always exist in jar files.
Maybe still not optimal. But so far I can work with it.
public static Path getExecutionLocation() throws URISyntaxException, IOException {
// System ClassLoader is on the highest level and responsible for the main entry point
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// get the resources root path (works within IDEs)
URL executionLocation = systemClassLoader.getResource(".");
// fallback for jars
if(executionLocation == null) {
// look for META-INF folder
URL metaInfLocation = systemClassLoader.getResource("META-INF");
// URL looks like "jar:file:/C:/path/to/jar/jarfile.jar!/META-INF"
// openConnection on URL - does not really establish connection but checks if URL would be valid
JarURLConnection connection = (JarURLConnection) metaInfLocation.openConnection();
// extracts URL to jar file
executionLocation = connection.getJarFileURL();
}
// impossible to determine
if(executionLocation == null) throw new RuntimeException("Impossible to determine exeution location");
return Paths.get(executionLocation.toURI());
}

Related

Unable to access directory classes after setting classpath

I'm having a hard time setting the classpath for a directory to a package of classes. I'm trying to run a jar file that takes a directory as a command line argument. The program uses the directory to access class files in a folder and uses reflection to explore the class fields and methods.
final File folder = new File(args[0]);
classList = dirParse.listFilesForFolder(folder);
I then go through the classList, get the name of each class, and use the Class.forName() method to access the classes.
Class c = Class.forName(className);
For the line above to work, I have to set the classpath to the address of the directory containing the classes.
I can get the program to run just fine when I'm using a directory of classes that do not belong to a package like below:
java -cp "Explorer.jar:/Users/john/Desktop/TestClass/" explorer.ExplorerDemo /Users/john/Desktop/TestClass/
However, for the following line, monopoly is a package and the program throws a ClassNotFoundException after calling Class.forName(className)
java -cp "Explorer.jar:/Users/john/Desktop/Programming\ Project/Monopoly/build/classes/monopoly/" explorer.ExplorerDemo /Users/john/Desktop/Programming\ Project/Monopoly/build/classes/monopoly/
For testing purposes, I tried adjusting `Class.forName() call to include the package name like below:
Class c = Class.forName("monopoly."+className);
However, this also throws ClassNotFoundException.
Class.forName is a shortcut to obtaining class information within the context of ClassLoader of the current class. Javadoc states that this is equivalent to
Class.forName("Foo", true, this.getClass().getClassLoader())
Provided that you class directory is supplied as runtime parameter and is not part of the original classpath, I would suggest you instantiating custom URLClassLoader instance that will be pointing to your directory.
Sample code:
public class ReflectionClassAnalysis {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
// URLClassLoader supports both directories and jar files
Path directory = Paths.get("/some/directory/");
Path jar = Paths.get("/some/binary.jar");
// You may be interested in providing parent ClassLoader for your new instance
// You can either use current class ClassLoader like
ClassLoader contextClassLoader = ReflectionClassAnalysis.class.getClassLoader();
// or current thread ClassLoader
// ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader myClassLoader = new URLClassLoader(
new URL[]{
directory.toUri().toURL(),
jar.toUri().toURL()
},
contextClassLoader
);
// You may use ClassLoader directly to load class meta
Class<?> externalClass = myClassLoader.loadClass("your.class.name");
// or supply ClassLoader to forName method
// Class.forName("your.class.name", true, myClassLoader);
// Do your class analysis here
}
}
For JAR with classpath instructions please refer to: Run a JAR file from the command line and specify classpath

Where is getResourceAsStream("file") searching when running from a test?

I'm struggling to create a test to verify a ServletListener that loads a properties file. I've tested when running the application that it works fine and it finds the file in the classpath. But I don't know how to create a test for it. My idea is to create a temp file with a test property and then verify the property is put into the System properties. But I always fail to create the file in the right place.
I've tried creating the file in /target/test-classes or directly in the root of the application but it never finds it. Any idea?
This is the code I'm trying to test:
public class PropertyReadingListener implements ServletContextListener {
public static final String PROFILES_PROPERTIES = "profiles.properties";
#Override
public void contextDestroyed(ServletContextEvent event) {
}
#Override
public void contextInitialized(ServletContextEvent event) {
Properties propsFromFile = new Properties();
try {
propsFromFile.load(getClass().getResourceAsStream(PROFILES_PROPERTIES));
} catch (final Exception e) {
log.warn("Unable to find {}, using default profile", PROFILES_PROPERTIES);
}
propsFromFile.stringPropertyNames().stream()
.filter(prop -> System.getProperty(prop) == null)
.forEach(prop -> System.setProperty(prop, propsFromFile.getProperty(prop)));
}
}
Thanks.
Assuming that you are using maven, put your properties file here:
src/test/resources/foo.properties
The maven resources plugin will place the (possibly filtered) copy of the file in
target/test-classes/foo.properties
The test-classes directory is on the classpath, so you reference the file like this (note the slash in front of the file name):
... getResourceAsStream("/foo.properties");
Where is getResourceAsStream("file") searching when running from a test?
Assuming that you are talking about JUnit ....
Unless you have done something funky, your unit tests will be loaded by the default classloader, and that means that the normal JVM classpath with be searched.
(Junit 4 allows you to use a different classloader: see https://stackoverflow.com/a/9192126/139985)
But I always fail to create the file in the right place.
It seems that your real problem is understanding how the Java classpath and classpath searching works. If you understand that (and you know what JUnit runner's actual classpath is) then it should be obvious where to put the properties file so that the classloader can find it.
See Different ways of loading a file as an InputStream
Basically when you do a getClass().getResourceAsStream it looks in the package of that class for the file.. so if your PropertyReadingListener is in com.company.listeners.PropertyReadingListener then it will look in com/company/listeners for the property file.
For testability, I would pass in an InputStream into the listener that way the test can create the input stream in a convienent way and the actual user of the class in code can pass in the InputStream returned from getResourceAsStream

Java class.getResource() returns null

I am trying to use files in resource directory that was marked as "resource root" in IntelliJ, but the below code fails to find the file.
Could you tell me what was wrong? thanks.
public class ResourceTest {
public void testResource() {
URL url = this.getClass().getResource("resources/table.1gram");
System.out.println(url);
}
public static void main(String[] args) {
ResourceTest rt = new ResourceTest();
rt.testResource();
}
}
Files in the resources folder will be packaged to the root of the .jar file, meaning that during development, the resources folder itself is in the classpath, so you need this.getClass().getResource("/table.1gram"), or without the / since your class is in the unnamed package, aka also in the root of the .jar file.

Pentaho Data Integration User Defined Java Class

I create simple java class and export it to jar:
package test;
public class Test {
public Test() {
// TODO Auto-generated constructor stub
}
}
Jar file add to lib folder in Pentaho (there are many jar files)
Next step I want to use my class in Pentaho Data Integration so I created User Defined Java Class:
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException
{
test.Test t = new test.Test();
return true;
}
When I click Test class I get the following information:
Line 3, Column 12: Class "test.Test" not found
So I have a question: Where is the mistake and why is the class not found?
Try checking the launcher.property file inside the /design-tools/data-integration/launcher folder. Make sure that the classpath and the libraries are having the path of the jar defined. Since you have placed your JAR file inside the lib folder, look for that.
Restart the Spoon after editing and it would work ideally.
I have placed the code inside the libext folder, so i have added :../libext to the classpath and libraries. And below is the code snip:
In case it still throws an error, try checking the Java code again. I assume something might have gone wrong there.
Also documented the above in here.
Hope it helps :)

Load libs from a different classloader in a web application

When a web application is loaded in Tomcat it is loaded by a specific classloader, right?
I assume that all libraries (under WEB-INF\lib) used by this web application are all loaded by this same classloader?
In this case, is there a way to load a library under a different classloader without any issues?
The reason I want to do this is because Axis uses some configuration properties that are bound to the classloader and would like to do requests with different properties thereby use a different classloader.
Is this possible?
If you want to load classes programmatically at run time, you can use URLClassLoader, but it can be quite tricky to really get it right. You would do something like this:
URL[] urls = new URL[] {
/* URL to your axis jar */,
/* other URLs you need */
};
URLClassLoader classLoader = new URLClassLoader(urls, getClass().getClassLoader());
Class<...> axisClass = classLoader.findClass(/* fully qualified name */);
Then you should be able to create a new instance of this class and use it.
Edit: Here is a more concrete example, albeit not using Axis because it would be too difficult to set up. I have create a JAR file that contains the following class:
public class Hello {
public Hello(String config) {
}
public String getMessage() {
return "Hello World";
}
}
I have copied this jar file to the source folder of my test project, so I can find it using UrlClassloaderTest.class.getResource("hello.jar"). In a web app, you should probably put it into WebContent/WEB-INF (or something similar) and use the method javax.servlet.ServletContext.getRealPath("WEB-INF/hello.jar") to find it. I can then access the Hello class using the URLClassLoader and reflection:
public class UrlClassloaderTest {
public static void main(String[] args) throws Exception {
URL jarUrl = UrlClassloaderTest.class.getResource("hello.jar");
URLClassLoader cl = new URLClassLoader(new URL[] { jarUrl }, UrlClassloaderTest.class.getClassLoader());
Class helloClass = cl.loadClass("test.Hello");
Constructor constructor = helloClass.getConstructor(String.class);
Object helloObject = constructor.newInstance("some configuration");
Method messageMethod = helloClass.getMethod("getMessage");
String message = (String) messageMethod.invoke(helloObject);
System.out.println(message);
}
}
Note that I can not use Hello as a type here because it is not on the class path of the application, and so it is not known to the class loader of the class UrlClassLoaderTest!

Categories