Classpath variable in java - 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

Related

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.

How do I use a classloader to get inputstream on a model.zip file where the zip file is wrapped in a .jar file in classpath

The issue:
We have a jetty web-app, and in the application-code, I am trying to access a zip within a jar in classpath. Here's the jar in the libs folder:
/path/to/app/x.x.0-SNAPSHOT/apps/libs/my-model.jar
where my-model.jar is really just an empty folder with model.zip file inside it. If I extract this jar, I get johnsnow/mymodel.zip
My application code tries to access this zip as:
getClass().getResourceAsStream("johnsnow/mymodel.zip")
but of course, I don't get a proper handle to this resource and wind up getting a nullpointer exception. What am I doing wrong? Shouldn't I be able to access a file within a jar file in classpath using the getClass().getResourceAsStream() method?
Footnote:
Because model.zip was too large, we decided against shipping it with the code base. Thus we pushed it into a nexus repository, and reference the jar via a gradle compile dependency as follows:
compile "com.company.group.nlp:my-model:1.0#jar"
The fact that building the distribution pulls this jar, and puts it in apps/libs tells me that gradle does its part (of downloading the dependency to a classpath). The issue remains that I can't seem to find a way to access mymodel.zip inside my-model.jar
Try adding a slash to the file path:
getClass().getResourceAsStream("/johnsnow/mymodel.zip");
It will tell java to start looking for the class from the root folder, not from the current class package.

Using relative path in a maven project

I have a maven project with these standard directory structures:
src/main/java
src/main/java/pdf/Pdf.java
src/test/resources
src/test/resources/files/x.pdf
In my Pdf.java,
File file = new File("../../../test/resources/files/x.pdf");
Why does it report "No such file or dirctory"? The relative path should work. Right?
Relative paths work relative to the current working directory. Maven does not set it, so it is inherited from whatever value it had in the Java process your code is executing in.
The only reliable way is to figure it out in your own code. Depending on how you do things, there are several ways to do so. See How to get the real path of Java application at runtime? for suggestions. You are most likely looking at this.getClass().getProtectionDomain().getCodeSource().getLocation() and then you know where the class file is and can navigate relative to that.
Why does it report "No such file or dirctory"? The relative path should work. Right?
wrong.
Your classes are compiled to $PROJECT_ROOT/target/classes
and your resources are copied to the same folder keeping their relative paths below src/main/resources.
The file will be located relative to the classpath of which the root is $PROJECT_ROOT/target/classes. Therefore you have to write in your Pdf.java:
File file = new File("/files/x.pdf");
Your relative path will be evaluated from the projects current working directory which is $PROJECT_ROOT (AFAIR).
But it does not matter because you want that to work in your final application and not only in your build environment. Therefore you should access the file with getClass().getResource("/path/to/file/within/classpath") which searches the file in the class path of which the root is $PROJECT_ROOT/target/classes.
No the way you are referencing the files is according to your file system. Java knows about the classpath not the file system if you want to reference something like that you have to use the fully qualified name of the file.
Also I do not know if File constructor works with the classpath since it's an abstraction to manage the file system it will depend where the application is run from. Say it is run from the target directory at the same level as source in that case you have to go one directory up and then on src then test the resources the files and finally in x.pdf.
Since you are using a resources folder I think you want the file to be on the classpath and then you can load a resource with:
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("<path in classpath>");
Then you can create a FileInputStream or something to wrap around the file. Otherwise use the fully qualiefied name and put it somewere like /home/{user}/files/x.pdf.

Maven configuration: pass file inside a classpath jar as an argument

Several maven plugins need/support passing a java.io.File as a configuration parameter, wherein we specify the relative/absolute location of the file for the plugin to locate and use.
Is there a way I can specify a property file in the plugin configuration where the file has to be found from inside a jar in the classpath? I'm particularly wanting this to know and use with the aspectj-maven-plugin, where I can specify the Xlintfile value to be the custom XlinkDefault.properties file location. The file, in my case, will be found inside a classpath jar.
I use maven-2.2.1 by the way.
No, not in general; there's no magic that will turn something that's not a file on disk into a java.io.File. Many Maven plugins (e.g., maven-checkstyle-plugin's configLocation are designed to allow more flexible input for just these cases:
This parameter is resolved as resource, URL, then file. If successfully resolved, the contents of the configuration is copied into the ${project.build.directory}/checkstyle-configuration.xml file before being passed to Checkstyle as a configuration.
As a workaround, if the plugin cannot be changed, dependency:unpack may be a way to get a classpath resource into a local file (see Maven: extract files from jar).

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