Resource loading in Java not working as it should - java

This is the well known problem of loading resources from a jar file. This is not the first time I've tried to do this, but now it doesn't work the way I expect it to.
Normally I try to load the Resources with this.getClass.getResource("foo.png"), or getResourceAsStream()and it works. Now however it does not. The Resource is always null.
If I let System.out.println(this.getClass.getResource("")) print me the path (from eclipse) it shows /path/to/eclipseproject/package/structure/. Running this from a jar it just shows rsrc:package/structure
If I recall correctly this should print the path to the jar. Furthermore I thought this would print the package structure in both cases. Am I doing something wrong?

Here is the thing...
When Extracting the file from the Jar use:
this.getClass.getResource("/foo.png")
When running from a runnable Jar use, to reference an external file in the Jar folder path:
this.getClass.getResource("foo.png")
// When running this from Eclipse, it would refer to files in project root!
I have a code in the lower level determining where I'm running from to determine the correct path.

Doe this get the path you need?
this.getClass().getClassLoader().getResource("<your class name>.class").getPath();
See also this question for more on this issue.

Unless you prepend the path to the resources with '/', Class.getResource() will search for the resource in class package. E.g.: tld.domain.Foo.class.getResource("Bar.txt") will search for tld/domain/Bar.txt

Check the URLClassLoader for all the gory details, but it really depends on whether you are trying to access a ressource in the jar,
using a class loaded inside the same jar, in this case your file 'root' is the root of the jar
using a class loaded outside the jar (your eclipse case) where the root is your 'working directory'
To access resources inside a jar from outside, you should use something like
URL url = new URL( "jar", "", "file:" + jar.getCanonicalPath( ) + "!/" + localPathResource );
url.openStream(...)

This answer provides an explanation of how to load class resources from JAR files, even when the class is not in the JAR file and not in the Class-Path specified in the JAR file's manifest. There are also links to code.

Related

Why ClassLoader returns a path with extraneous second exclamation point

I'm trying to grab a resource in ClassLoader. A simplified version of the code looks something like this:
String ePath = "rewrite/common/RenameFunctor.groovy"
String fPath = ThClType.class.getClassLoader().getResource(ePath);
the response I get back as fPath is jar:file:/Users/myName/warPath/warName.war!/WEB-INF/classes!/rewrite/common/RenameFunctor.groovy. The actual path to the resource we want it exactly that, except without the second exclamation point. (Unlike warName.war, classes is just a normal directory.)
Does anyone know what might have caused the extra exclamation point and/or what might be done to fix it? This is as part of a process of updating some pretty old code that I didn't write, so if esoteric customization of ClassLoader behavior is possible, it's possible it's been done in this case. if it is possible, then I don't know how to check, and would appreciate any insight.
Warning: this answer is part supposition and guess-work on the mechanics, it may not be 100% correct, but I think it comes close enough. As far as I know actual WAR class loading will vary per servlet container or application server, so this answer may not hold for all of them.
If you look at
jar:file:/Users/myName/warPath/warName.war!/WEB-INF/classes!/rewrite/common/RenameFunctor.groovy
you can split it up in the following parts:
file:/Users/myName/warPath/warName.war
/WEB-INF/classes
/rewrite/common/RenameFunctor.groovy
The actual resource that is on the class path is the last one, /rewrite/common/RenameFunctor.groovy, the other parts are the coordinates used by the war class loader to find the part of the class path that contains that resource: first the location of the war file itself, file:/Users/myName/warPath/warName.war, and then the path within the war, /WEB-INF/classes.
This theory build on the documentation of the JarURLConnection which states:
A URL Connection to a Java ARchive (JAR) file or an entry in a JAR
file.
The syntax of a JAR URL is:
jar:<url>!/{entry}
for example:
jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class
Jar URLs should be used to refer to a JAR file or entries in a JAR
file. The example above is a JAR URL which refers to a JAR entry. If
the entry name is omitted, the URL refers to the whole JAR file:
jar:http://www.foo.com/bar/baz.jar!/
So for a plain jar, the first part of the URL identifies the jar file itself, and the second part identifies the resource within the jar.
Technically war files are jar files, but contrary to jar files, the war file itself is not part of the class path. Instead it contains elements that are added to the class path., for example jar files in WEB-INF/lib and the classes and other files in WEB-INF/classes.
The ! separated parts then define the steps taken by the war class loader to locate a specific resource, in this case /rewrite/common/RenameFunctor.groovy.

Whats the best way to specify a path to read from class-path even if project was a jar

I am trying to read a Properties file in a maven nature project using the Properties.load(); I am specifying a path as a string ex. "./someFolder/file.properties",
but when I try to use my project as dependency in other projects I am forced to copy those files to the other project , simply because the "." means current directory.
Is there a way to specify a path so it will always be valid despite where I am calling it from ? ,
I have tried using the MyClass.class.getClassLoader().getResourceAsStream() but I am having trouble using it , it worked sometimes and failed other times.
There are lots of misconceptions in your question.
"." means classPath
No. When used inside a filesystem path (i.e. a path passed to the constructor of a File, or FileReader, or FileInputStream), "." means the current directory.
When used in a resource path (i.e. passed to Class[Loader].getResource[AsStream]()), it's invalid.
The trick is to carefully read the documentation.
getResourceAsStream() expects a /-separated path.
When using ClassLoader.getResource[AsStream](), this path always starts from the root of the classpath. So you would pass a path looking exactly like a fully qualified class name, except the dots would be replaced by slashes. So, com/foo/bar.properties looks for a resource named bar.properties, in the package com.foo.
When using SomeClass.class.getResource[AsStream](), either the path starts with a /, and the path starts from the root of the classpath, or it doesn't, and it starts from the package of SomeClass. So, if SomeClass is in the package com.foo, using /com/foo/bar.properties is equivalent to using bar.properties.
It's hard to tell what you're doing wrong, since you're not providing any detail. But you really need to understand the difference between opening a file on the file system, and reading a resource loaded by the class loader. Sometimes, the resources just happen to be loaded by the class loader from the filesystem, because the classpath happens to contain directories, and not just jar files.
I noticed that my problem was that I had my properties files in the project path itself, and that the ClassLoader.getResource[AsStream](); looks is the target/classes folder, and that I didn't have the resources folder in my project.
I solved it my adding the resources folder to my build path and adding my files in the src/main/resources as the following src/main/resources/foo/bar.properties and loading it by SomeClass.class.getClassLoader().loadResourceAsStream("foo/bar.properties");.

how to find a local resource from dependent jar?

I have written a code that is packed to 1.jar
with this code:
return isProd? "/etc/waze/automation/devices.json":
DeviceRepositoryFromJsonFile.class.getClassLoader().getResource("devices.json").getPath().toString();
devices.json is here:
I have another project that depends on 1.jar
however the classLoader doesn't find the local devices.json file but rather one packed in the jar
anyhow it shows the file doesn't exist.
How can I fix this? just use a absolute path anyhow?
If as in your screenshot the devices.json locate in the src/main/resources and the package have successfully treat that as the package path and put in the jar file root directory, then you can just find the file via:
DeviceRepositoryFromJsonFile.class.getResource("/devices.json");
Note the "/" slash is important to indicate that to search from the root of the classpath.
It does not answer your question directly, but it may solve your problems faster.
As far as I can see you try to detect the absolute path to json file and pass it to another method so this file could be processed. Instead, it could be done simpler:
public byte[] getDevicesJsonBytes() {
return isProd
? IOUtils.toByteArray(ABSOLUTE_PATH_TO_PROD_FILE)
: IOUtils.toByteArray(DeviceRepositoryFromJsonFile.class.getResourceAsStream(RESOURCE_CLASSPATH);
}
The common way to read classpath resources it to use getResourceAsStream on class or classLoader instance. Also, many frameworks have their own resources abstractions, but I guess you don't need them now.

Java (maven web app), getting full file path for file in resources folder?

I'm working with a project that is setup using the standard Maven directory structure so I have a folder called "resources" and within this I have made a folder called "fonts" and then put a file in it. I need to pass in the full String file path (of a file that is located, within my project structure, at resources/fonts/somefont.ttf) to an object I am using, from a 3rd party library, as below, I have searched on this for a while but have become a bit confused as to the proper way to do this. I have tried as below but it isn't able to find it. I looked at using ResourceBundle but that seemed to involve making an actual File object when I just need the path to pass into a method like the one below (don't have the actual method call in front of me so just giving an example from my memory):
FontFactory.somemethod("resources/fonts/somefont.ttf");
I had thought there was a way, with a project with standard Maven directory structure to get a file from the resource folder without having to use the full relative path from the class / package. Any advice on this is greatly appreciated.
I don't want to use a hard-coded path since different developers who work on the project have different setups and I want to include this as part of the project so that they get it directly when they checkout the project source.
This is for a web application (Struts 1.3 app) and when I look into the exploded WAR file (which I am running the project off of through Tomcat), the file is at:
<Exploded war dir>/resources/fonts/somefont.ttf
Code:
import java.io.File;
import org.springframework.core.io.*;
public String getFontFilePath(String classpathRelativePath) {
Resource rsrc = new ClassPathResource(classpathRelativePath);
return rsrc.getFile().getAbsolutePath();
}
In your case, classpathRelativePath would be something like "/resources/fonts/somefont.ttf".
You can use the below mentioned to get the path of the file:
String fileName = "/filename.extension"; //use forward slash to recognize your file
String path = this.getClass().getResource(fileName).toString();
use/pass the path to your methods.
If your resources directory is in the root of your war, that means resources/fonts/somefont.ttf would be a "virtual path" where that file is available. You can get the "real path"--the absolute file system path--from the ServletContext. Note (in the docs) that this only works if the WAR is exploded. If your container runs the app from the war file without expanding it, this method won't work.
You can look up the answer to the question on similar lines which I had
Loading XML Files during Maven Test run
The answer given by BobG should work. Though you need to keep in mind that path for the resource file is relative to path of the current class. Both resources and java source files are in classpath

Absolute Path of Project's folder in Java

Lots of confusion in this topic. Several Questions have been asked. Things still seem unclear.
ClassLoader, Absolute File Paths etc etc
Suppose I have a project directory structure as,
MyProject--
--dist
--lib
--src
--test
I have a resource say "txtfile.txt" in "lib/txt" directory. I want to access it in a system independent way. I need the absolute path of the project.
So I can code the path as abspath+"/lib/Dictionary/txtfile.txt"
Suppose I do this
java.io.File file = new java.io.File(""); //Dummy file
String abspath=file.getAbsolutePath();
I get the current working directory which is not necessarily project root.
Suppose I execute the final 'prj.jar' from the 'dist' folder which also contains "lib/txt/txtfile.txt" directory structure and resource,It should work here too. I should absolute path of dist folder.
Hope the problem is clear.
You should really be using getResource() or getResourceAsStream() using your class loader for this sort of thing. In particular, these methods use your ClassLoader to determine the search context for resources within your project.
Specify something like getClass().getResource("lib/txtfile.txt") in order to pick up the text file.
To clarify: instead of thinking about how to get the path of the resource you ought to be thinking about getting the resource -- in this case a file in a directory somewhere (possibly inside your JAR). It's not necessary to know some absolute path in this case, only some URL to get at the file, and the ClassLoader will return this URL for you. If you want to open a stream to the file you can do this directly without messing around with a URL using getResourceAsStream.
The resources you're trying to access through the ClassLoader need to be on the Class-Path (configured in the Manifest of your JAR file). This is critical! The ClassLoader uses the Class-Path to find the resources, so if you don't provide enough context in the Class-Path it won't be able to find anything. If you add . the ClassLoader should resolve anything inside or outside of the JAR depending on how you refer to the resource, though you can certainly be more specific.
Referring to the resource prefixed with a . will cause the ClassLoader to also look for files outside of the JAR, while not prefixing the resource path with a period will direct the ClassLoader to look only inside the JAR file.
That means if you have some file inside the JAR in a directory lib with name foo.txt and you want to get the resource then you'd run getResource("lib/foo.txt");
If the same resource were outside the JAR you'd run getResource("./lib/foo.txt");
First, make sure the lib directory is in your classpath. You can do this by adding the command line parameter in your startup script:
$JAVA_HOME/bin/java -classpath .:lib com.example.MyMainClass
save this as MyProject/start.sh or any os dependent script.
Then you can access the textfile.txt (as rightly mentioned by Mark) as:
// if you want this as a File
URL res = getClass().getClassLoader().getResource("text/textfile.txt");
File f = new File(res.getFile());
// As InputStream
InputStream in = getClass().getClassLoader()
.getResourceAsStream("text/textfile.txt");
#Mark is correct. That is by far the simplest and most robust approach.
However, if you really have to have a File, then your best bet is to try the following:
turn the contents of the System property "java.class.path" into a list of pathnames,
identify the JAR pathname in the list based on its filename,
figure out what "../.." is relative to the JAR pathname to give you the "project" directory, and
build your target path relative to the project directory.
Another alternative is to embed the project directory name in a wrapper script and set it as a system property using a -D option. It is also possible to have a wrapper script figure out its own absolute pathname; e.g. using whence.

Categories