Reading resources inside dependency JAR gives NullPointerException - java

I have the following situation:
JAR A has JAR B as dependency
JAR B is packed with some resources that are loaded when JAR A calls specific methods of JAR B (loaded once and for all the lifecycle of JAR B calls)
I am using Java SE 11 with IntelliJ 2021.1.3
JAR B resources tree is something like the following:
- resources
- data
- file.txt
- tariffs
- folder1
- file.xslx
Resources are loaded through the following method:
private InputStream getPath(String nomeFile) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return classLoader.getResourceAsStream(DATA_FOLDER_NAME.concat(File.separator).concat(nomeFile));
}
And then managed through a BufferedReader.
Everything works fine when running mvn test (or application tests) withing JAR B project or when consuming JAR B from JAR A in a Unix environment.
When consuming JAR B from JAR A in a Windows 10 environment the getPath method returns a null InpuStream object thus a NullPointerException from the BufferedReader:
java.lang.NullPointerException: null
at java.base/java.io.Reader.<init>(Reader.java:167)
at java.base/java.io.InputStreamReader.<init>(InputStreamReader.java:72)
...
I tried to change the File.separator to hardcoded "/" in the method and seems like everything works also on Windows, but is failing in other places (where resources are managed) since I suppose Paths need to be hand-fixed.
I tried to change the loader to: this.getClass().getResourcesAsStream(...) and other workaround with no luck.
My question is: is there a way to make the program work as expected also on Windows without changing the above code?
Are there any settings I am missing?
Thank you,
Alberto

Just came over this issue.
The fact was (and is) the usage of File.separator in a Windows environment to access resources inside a JAR.
This is because (as pointed here) inside a JAR file paths are resolved UNIX-style.
The only way, then, to consume resources that are packed within a JAR file used as dependency is to specify resources paths UNIX-style.
In my case, this mean replacing the File.separator (and all occurrences) with a "/".
The other issues that were arising by this replacement were due to an incomplete replace-all of the File.separator directive across the code.

java -classpath A.jar;B.jar x.y.z.Main
Would be what your command to run would look like on Windows. Possibly better to use the absolute path to those jars for fail-proof ops

Related

Reach External Resource With ClassLoader.getResourceFromStream on WebSphere

I'm working with a local WebSphere server configured in IntelliJ Idea, and the application I'm working on is using a third-party library that loads a properties file with:
ThirdPartyClass.class.getClassLoader().getResourceAsStream(fileNameParameter);
It uses the default bootstrapClassLoader.
I've been instructed to make sure the properties file is in a config directory so that it can be edited without deploying a code change. My project looks something like this:
ProjectName
Configs
my.properties
src
java (sources root)
packages, .java files, etc
main (resources root)
schemas, web docs, etc
I have tried several of paths to make it work but it always returns null. Since I initially thought it was reaching from within the third party library package, I tried adding several ..\'s to the file path, but then I learned that this method loads from the classpath, so I pulled a
String test = System.getProperty("java.class.path");
and upon inspection, my classpath is all made up of websphere directories and jars within them:
C:\Users\me\Programs\IBM\AppServer\profiles\AppSrv01/properties
C:\Users\me\Programs\IBM\AppServer\AppSrv01/properties
and several jar files in C:\Users\me\Programs\IBM\AppServer/lib/
So just as a test I stuck the file in C:\Users\me\Programs\IBM\AppServer\AppSrv01/properties, then tried to grab it with just its file name (my.properties), but still couldn't reach it. I've also tried moving the file into the src directory and the main directory, but no matter what I do it just can't seem to find the file.
I'm aware that this method is typically used to grab resources from within a jar file, but from my understanding it seems like it should be possible to reach my file from outside of one as long as it's in a directory in the classpath... but apparently not since that didn't work.
I have the absolute path on my hard drive and will have said path on the server; is there a way to derive the path that ClassLoader.getResourceFromStream() wants with that info? Failing that, is there some obvious mistake I'm making with the resource url?
I think your fileNameParameter simply needs to start with / to indicate that it is in the root level of the classpath. Otherwise it will be searched relative to the class it is loaded from, i.e. the package of ThirdPartyClass in your example.

Getting resource file from inside jar

I need to get a resource from inside the root of the application when its packed into jar. My project is like this:
ProjectRoot
resource.txt //want to access from here
src
main
java
package1
package2
package3
Main.java
target
app.jar
classes
resource.txt //works when here
package1
package2
package3
Main.class
I use this code:
Path path = Paths.get("resource.txt");
When run before packaging into a jar, it finds the file just fine (inside ProjectRoot). When running the jar, it can't find it, and transforms this path to target/resource.txt.
This code:
BufferedReader br = new BufferedReader(new InputStreamReader(new Main().getClass().getClassLoader().getResourceAsStream(
"resource.txt")));
when run before packaging looks for the resource inside target/classes. After packaging it claims to taking the resource from .../target/app.jar!/resource.txt.
This code:
BufferedReader br = new BufferedReader(new InputStreamReader(new Main().getClass().getClassLoader().getResourceAsStream(
"/resource.txt")));
I can't understand where's looking for the resource, but it doesn't seem to be ProjectRoot.
All I want to do is to place the resource inside ProjectRoot and be able to access it from both outside jar (when running the class files from IDE) and inside (after having packaged the files into a jar file using Maven).
EDIT: I NEED THE CODE TO WORK BOTH FOR PRE- AND POST- packaging. MEANING: If I run a Main.java FROM INSIDE IDE IT WOULD GET THE RESOURCE; IF I PACKAGE EVERYTHING INTO JAR AND RUN JAR IT WOULD GET THE RESOURCE - ALL WITH THE SAME CODE.
Use: Main.class.getResource("/resource.txt").
Note that your attempt using any call to getClassLoader is strictly worse (it's more text, and will fail more often, because that class loader can in exotic cases be null (specifically, when you're part of the bootstrap loader), whereas calling getResource directly on the class always works.
The reason your snippet does not work is because when invoking getResource on the classloader, you must NOT start the resource with a slash. When invoking on a class directly, you can (if you don't, it'll be relative to the package of the class you're calling it on, if you do, it'll be relative to the root).
TL;DR: Of the forms SomeClass.class.getClassLoader().getResource, getClass().getResource and MyClass.class.getResource, only the last one is correct, the rest are strictly inferior and therefore should not be used at all.
Maven uses something called the Standard Directory Layout. If you don't follow this layout then the plugins can't do their job correctly. Technically, you can configure Maven to use different directories but 99.999% of the time this is not necessary.
One of the features of this layout is that production files go in:
<project-dir>/src/main/java
All *.java files
<project-dir>/src/main/resources
All non-*.java files (that are meant to be resources)
When you build your project the Java source files are compiled and the *.class files are put into the target/classes directory; this is done by the maven-compiler-plugin. Meanwhile, the resource files are copied from src/main/resources into target/classes as well; the maven-resources-plugin is responsible for this.
Note: See Introduction to the Build Lifecycle for more information about phases and which plugins are executed by which phase. This Stack Overflow question may also be useful.
When you launch your application from the IDE (possibly via the exec-maven-plugin) the target/classes directory is put on the classpath. This means all the compiled classes from src/main/java and all the copied resources from src/main/resources are available to use via the classpath.
Then, when you package your application in a JAR file, all the files in target/classes are added to the resulting JAR file (handled by the maven-jar-plugin). This includes the resources copied from src/main/resources. When you launch the application using this JAR file the resources are still available to use via the classpath, because they're embedded in the JAR file.
To make resource.txt available on the classpath, just move:
<project-dir>/resource.txt
To:
<project-dir>/src/main/resources/resource.txt.
Then you can use Class#getResource with /resource.txt as the path and everything should work out for you. The URL returned by getResource will be different depending on if you're executing against target/classes or against the JAR file.
When executing against target/classes you'll get something like:
file:///.../<project-dir>/target/classes/resource.txt
When executing against the JAR file you'll get something like:
jar:file:///.../<project-dir>/target/projectname-version.jar!/resource.txt
Note: This all assumes resource.txt is actually supposed to be a resource and not an external file. Resources are typically read-only once deployed in a JAR file; if you need a writable file then it's up to you to use a designated location for the file (e.g. a folder in the user's home directory). One typically accesses external files via either java.io.File or java.nio.file.*. Remember, resources are not the same thing as normal files.
Now, if you were to put resource.txt directly under <project-dir> that would mean nothing to Maven. It would not be copied to target/classes or end up in the JAR file which means the resource is never available on the classpath. So just to reiterate, all resources go under src/main/resources.
Check out the Javadoc of java.lang.Class#getResource(String) for more information about the path, such as when to use a leading / and when not to. The link points to the Javadoc for Java 12 which includes information about resources and modules (JPMS/Jigsaw modules, not Maven modules); if you aren't using modules you can ignore that part of the documentation.

Thread.currentThread().getContextClassLoader().getResourceAsStream() returns null

I have following code block in my application;
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(FilePath);
Here 'FilePath' is an absolute path of the file.
Above code works fine in linux and in windows when i run the application in normal mode.(ie: in command prompt)
But this is NOT working, when I run the application as a windows service. I get input stream as 'null'.
Anyone encountered such issue before? I could not find any information regarding this other than java classloaders . Here we use "ContextClassLoader", which is the right classloader to be used..
Any clue on this?
I think this happens because you have "." (the current folder) on the classpath. That is a) a bad idea and b) makes your app break in odd ways.
What you need to understand is the difference between a file and a resource. A file is something outside of the classpath.
You should use File and FileReader to access them.
A resource is something on the classpath. Paths for resources always use / as file separator and not File.separator.
Another way to fix this is to add $HOME/repository/ (Linux) or %HOME%/repository/ to the classpath and load the resource using "resources/api_templates/api.xml". for this to work, resources must be a folder in $HOME/repository/.
If you don't do this, then all files in your home directory (or whatever directory you happen to start the application in) are added as resources to the classpath.

Classpath variable in java

I'm reading a few files in my application and referring to them as new File("src/main/resource/filename") and it works. But when I package the jar with the Maven assembly plugin and run java - jar I get an error, naturally:
Error occured: src\main\resources\UPDATE.txt (The system cannot find the path specified)
Because there is no src/main/resources in the jar, how can I refer to src/main/resources as some kind of classpath variable, so that the application works both in standalone java and in an assembled jar?
You will need to load the file using the Class.getResourceAsStream() method
E.g.
InputStream str = getClass().getResourceAsStream("/UPDATE.txt");
Or if you are in a static method, then specify the class explicitly
InputStream str = MyApp.class.getResourceAsStream("/UPDATE.txt");
EDIT:
With a StreamSource, just pass the input stream into the stream source, e.g.
new StreamSource(getClass().getResourceAsStream("/UPDATE.txt"));
But watch out, getResourceAsStream returns null if the resource doesn't exist, so you might want to explicitly check for that and throw an exception.
The src/main/resources is a development time convention followed by maven projects to place artifacts other than source code. Once the jar has been build they are added to the classpath. So in your e.g. scenario the UPDATE.TXT is at the root of the classpath.
So you should be referring to the resources from the classpath and not from the file-system. http://mindprod.com/jgloss/getresourceasstream.html

getResourceAsStream not loading resource

The project that I am currently working on utilizes an old application contained within a .jar file. One of the responsibilities of this application is that it updates the database when changes to the configuration files are made. Every time I try to run this file (which is a simple Ant Task extension), I get an exception thrown early in the process. I decompiled the Java file responsible, and found that the exception being thrown happens here. I do not know what the issue is, as "hibernate.cfg.xml" is contained within the same .jar file as the .class throwing the exception.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream in = loader.getResourceAsStream("hibernate.cfg.xml");
if (in == null) {
throw new RuntimeException("Couldn't find built in hibernate config");
}
If anyone has any ideas, even pointing me in the right direction, I would be grateful.
Of course, any solution will have to be external, as the client already has this build of the program in production use.
Are you loading it from the root dir and you need "/hibernate.cfg.xml" instead of just hibernate.cfg.xml?
getResourceAsStream() expects the file to be in the same package as the Class that was the origin of the ClassLoader. Is your file in the right package?
Doh. Didn't read the question fully. Please ignore this.
try
InputStream in = YourClass.class..getResourceAsStream("hibernate.cfg.xml");
this will work if the class is in the same jar as the cfg file.
edit:
if you can't rebuild the application, you will need to add the jar to the bootstrap classloader. this is very ugly.
you can do it by running the jvm with (play with the exact arguments, you may need to add rt.jar from your jre to it as well).
-Xbootclasspath your.jar
your problem is that the code is using the classloader that loaded the Thread class, which is most likely the bootstrap classloader. and that you are now in a more complex environment (app server?) that loads your jar using a different classloader. so the bootstrap classloader can't find your resource.
It is possible that the classloader cannot open files contained in the jar file. Try one of two things 1) try extracting the jar file and running it from the file system, or 2) if the jar manifest has a ClassPath entry, try extracting the hibernate.cfg.xml into a directory in the classpath and see if it runs.
Apparently the hibernate.cfg.xml file isn't located in the source root of the JAR file, but it is instead placed inside a package. You'll need to specify the package path in the resource name as well.

Categories

Resources