Standard/Uniform way to locate files inside and outside JAR file? - java

What is the uniform or standard way to locate and then access files inside and outside JAR files.
I have InJarClass() defined in JAR file which contains configuration text files.
Then I extend this class in my host project which have more configurations.
Let say I have inside the JAR :
config/default.conf
Then in the host project I do :
class MyClass extends InJarClass { .... }
and also :
config/test.conf
then I run it something like this :
XENV=test java myclass
The whole configuration process happens inside InJarClass(), based on environment XENV.
As you see InJarClass() have to access both :
<jar>/config/default.conf
<host_app_dir>/config/test.conf
So to repeat my question is there uniform way to access both, if the directory structure mirror each other.

If you place both the current directory and the .jar file in your classpath:
XENV=test java -classpath .:myjarfile.jar MyClass
you can do this:
String configFile =
"/config/" +
System.getenv().getOrDefault("XENV", "default") +
".conf";
InputStream config = MyClass.class.getResourceAsStream(configFile);
if (config == null) {
throw new FileNotFoundException(
"Cannot locate " + configFile + " in classpath");
}
This works because an application resource is a resource which Java searches for within each location in the classpath. So getResourceAsStream will first search for the requested path relative to the first classpath location, .. If it does not find a file with that name, it will look at the second classpath location, myjarfile.jar, and seeing that it is a .jar file, will search for the requested file inside that .jar.

Related

How to use SpringBoot PropertySource to read from an external configuration file

I've created a configuration class that will store configuration values. These configuration values should be read in from a configuration file "config.properties". Below is the config class:
#Configuration
#ComponentScan(basePackages = {"com.boot.Training.*"})
#PropertySource("file:/src/main/java/config.properties")
public class AppConfig {
#Value("${myFirstName}")
private static String myFirstName;
#Value("${myLastName}")
private static String myLastName;
public static void showVariables() {
System.out.println("firstName: " + myFirstName);
System.out.println("lastName: " + myLastName);
}
}
And below are the contents of the config.properties file:
myFirstName=John
myLastName=Doe
Running my program should simply print the values of these variables. But instead, Eclipse tells me it cannot find the config.properties file, even though I specified that it is located in /src/main/java/config.properties .
I'm likely specifying the file location without taking something else into account. What am I missing here?
The location you are using (file:/src/main/java/config.properties) refers to an absolute path rather than one relative to your project home.
A more common way to do this is to ship config.properties as part of your project in a resources directory, and refer to it via the classpath. If you move it to /src/main/resources/config.properties, you can load it this way:
#PropertySource("classpath:config.properties")
I believe in theory you could just leave it in /src/main/java and change your #PropertySource location to what I have above, but moving it to /resources is the more idiomatic way of doing this.
The basic way to specify a file: location to appoint a properties file that is located elsewhere on your host environment is:
#PropertySource("file:/path/to/application.properties")
Note that /path/to/application.properties should be absolute path pointing to your .properties file (in your example you are mixing file: usage and relative path which is not correct )
it is also possible to specify a system property or an environment variable that will be resolved to its actual value when your application starts. For example, ${CONF_DIR} below will be replaced with its associated value when the Spring application starts:
Open /etc/environment in any text editor like nano or gedit and add the following line:
CONF_DIR=/path/to/directory/with/app/config/files
Check it system variable has been set:
echo $CONF_DIR
/path/to/directory/with/app/config/files
use PropertySource like:
#PropertySource("file:${CONF_DIR}/application.properties")

How to specify the path for getResourceAsStream() method in java

I know this question has been asked several times but I still can't get it work by those solutions.
I have a maven project. And one Config.java file located in consumer/src/main/java. Here's the content:
import java.util.Properties;
public class Config {
Properties configFile;
public Config() {
configFile = new Properties();
try {
configFile.load(this.getClass().getClassLoader().
getResourceAsStream("property_table.config.txt"));
} catch(Exception e) {
e.printStackTrace();
}
}
public String getProperty(String key) {
String value = this.configFile.getProperty(key);
return value;
}
public static void main(String[] args) {
Config config = new Config();
System.out.println("URL: " + config.getProperty("URL"));
System.out.println("PASSWORD: " + config.getProperty("PASSWORD"));
}
}
I kept getting nullpointer exception. I know that's because it can't find the file property_table.config.txt.
At first I put the property_table_config.txt file in the same folder(consumer/src/main/java/) as Config.java file. And tried use /property_table_config.txt and 'property_table_config.txt`. Neither of them work.
And then I tried using absolute path, not working. And tried using /main/java/property_table_config, not working either.
Then I saw this solution: https://stackoverflow.com/a/2103625/8159477.
So I make a directory called resources and put it under main folder (i.e. the path of the folder is consumer/src/main/resources, and create a sub-folder config under resources. After putting the property_table_config.txt file there, I changed the code into this:
configFile.load(this.getClass().getClassLoader().getResourceAsStream("/config/property_table.config.txt"));
But this still didn't work. Can anyone give some hint on this? Any suggestions will be appreciated!!
According to Class.getResourceAsStream:
This method delegates to this object's class loader. If this object was loaded by the bootstrap class loader, the method delegates to ClassLoader.getSystemResourceAsStream.
Before delegation, an absolute resource name is constructed from the given resource name using this algorithm:
If the name begins with a '/' ('\u002f'), then the absolute name of the resource is the portion of the name following the '/'.
Otherwise, the absolute name is of the following form:
modified_package_name/name
Where the modified_package_name is the package name of this object with '/' substituted for '.' ('\u002e').
This is how I understand the comments:
If you use ClassLoader.getResourceAsStream, send the absolute path from package root, but omitting the first /.
If you use Class.getResourceAsStream, send either a path relative the the current Class object (and the method will take the package into account), or send the absolute path from package root, starting with a /.
But in addition to this, you need to be cognizant of your build system. With maven, resource files are stored under src/main/resources.
So, in your case, I believe making the following changes should resolve the issue:
Put the file in src/main/resources.
Change the code to
this.getClass()
.getResourceAsStream("/property_table.config.txt")
//or `Config.class.getResource...
Alternatively, use
this.getClass().getClassLoader()
.getResourceAsStream("property_table.config.txt")`
I've tried this with a similar setup, and it works as expected.
ClassLoader().getResourceAsStream() is looking files only in classpath.
What you need is to have your config file in directory which is in classpath.
So, you have options:
when you run your java application from command line you can set path to directory in -cp parameter or CLASSPATH system variable. point there is: directory from where you need to get config file must be in class path - not a file. (e.g. if file location is c:\my_projects\test-project\config\my_config.properties and c:\my_projects\test-project\ is in classpath then getResourceAsStream call will be ClassLoader().getResourceAsStream("config/my_config.properties")
you can package your file into jar file and root of jar file is starting point for getResourceAsStream("config/my_config.properties")
If your Maven project is a jar project you need to use Maven resource plugin to put additional resource(s) into jar.
BTW: Maven does not put anything into jar file from src/main/java/ directory (if you do not explicitly specify it for resource plugin)
If you use IDE like Eclipse with your Maven project src/main/resources is a part of build classpath. Double check is it there and if it is not - do "Update Maven Project" or add it manually.
Still ClassLoader will see your properties file from src/main/resources folder only when you run project in IDE not from standalone Jar file - if you did not package your file or provide location in classpath.
hi can you try this one
String dirBase = new ClassPathResource("property_table.config.txt").getURI().getPath().replace("property_table.config.txt", "");
Can you try following code.
Config.class.getResourceAsStream("property_table.config.txt")
Update:
This is the code I tried.
package test;
import java.util.Properties;
public class Config
{
Properties configFile;
public Config()
{
configFile = new Properties();
try
{
configFile.load(Config.class.getResourceAsStream("property_table.config.txt"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
public String getProperty(String key)
{
String value = this.configFile.getProperty(key);
return value;
}
public static void main(String[] args)
{
Config config = new Config();
System.out.println("URL: " + config.getProperty("URL"));
System.out.println("PASSWORD: " + config.getProperty("PASSWORD"));
}
}
And I placed property_table.config.txt file withn test package and it worked.

right way to make a jar file from javafx application

I have a javafx project, which contains multiple paths for images and text files :
private Image imgMan = new Image(getClass().getResource("../man.gif").toExternalForm());
FileHelper.resetScores("./bin/application/MAP/BestScores.txt");
...
When i launch from eclipse, it work normally, and access to images and files without any problem.
But when i try to export my project to a jar file, it export correctly, but it don't launch !
I try to launch it from cmd, the trace of stack said that he don't know the paths...
Caused by: java.lang.NullPointerException
at application.Client.(Client.java:31)
(line 31 in my code refer to the first line of code given in the question)
I try to create a resource folder and put all files into it, but no result.
So what is the best way to make it ?
where must i create the resource folder ?
and how to access the files into it from the code ?
Thank you
There are several thing you should check:
verify the path in your jar against the class you are looking. Your image must be there.
verify you have successfully loaded a resource because using it, eg: check if getResource returns null.
For the first point, it depends on how you build your jar:
Eclipse will by default copy class file and resources to bin unless you use m2e. If you use the Extract runnable JAR (from File > Export menu), it may ignore some resources.
If you use Maven then your images must be in src/main/resources by default.
For the second point, you should use a method that should check the resource exists before delegating to Image. While it won't change your core problem, you would have a less subtile error:
static javafx.scene.image.Image loadImage(Class<?> source, String path) {
final InputStream is = source.getResourceAsStream(path);
if (null == is) {
throw new IllegalStateException("Could not load image from " + source + " path: " + path);
}
try (is) { // Java 9 -> you may want to use InputStream is2 = is
return new javafx.scene.image.Image(is); // use is2 for Java < 9
}
}
You should also try with an absolute path (from the root of the jar, or your src/main/resources if you use maven):
Image image = loadImage(this.getClass(), "/images/man.gif");

Path of the folder inside the jar file

I have jar file langdetect.jar.
It has a hierarchy shown in image
There is a class LanguageDetection at com/langdetect package.
I need to access the path of the profiles.sm folder from above class while executing the jar file.
Thanks in advance.
Jars are nothing else than Zip files and Java provides support for handling those.
Java 6 (and earlier)
You can open the jar file as a ZipFile and iterate over the entries of it. Each entry has a full path name inside the file, there is no such thing as relative path names. Though you have to take care, that all entries - although being absolute in the zip file - do not start with a '/', if you need this, you have to add it. The following snippet will get you the path of a class file. The className has to end with .class, i.e. LanguageDetection.class
String getPath(String jar, String className) throws IOException {
final ZipFile zf = new ZipFile(jar);
try {
for (ZipEntry ze : Collections.list(zf.entries())) {
final String path = ze.getName();
if (path.endsWith(className)) {
final StringBuilder buf = new StringBuilder(path);
buf.delete(path.lastIndexOf('/'), path.length()); //removes the name of the class to get the path only
if (!path.startsWith("/")) { //you may omit this part if leading / is not required
buf.insert(0, '/');
}
return buf.toString();
}
}
} finally {
zf.close();
}
return null;
}
Java 7/8
You may open the JAR file using the Java7 FileSystem support for JAR files. This allows you to operate on the jar file as if it would be normal FileSystem. So you could walk the fileTree until you have found your file and the get the Path from it. The following example uses Java8 Streams and Lambdas, a version for Java7 could be derived from this but would be a bit larger.
Path jarFile = ...;
Map<String, String> env = new HashMap<String, String>() {{
put("create", "false");
}};
try(FileSystem zipFs = newFileSystem(URI.create("jar:" + jarFileFile.toUri()), env)) {
Optional<Path> path = Files.walk(zipFs.getPath("/"))
.filter(p -> p.getFileName().toString().startsWith("LanguageDetection"))
.map(Path::getParent)
.findFirst();
path.ifPresent(System.out::println);
}
Your particular Problem
The above solutions are for finding the path inside a Jar or Zip, but may possibly not be the solution to your problem.
Im not sure, whether I understand your problem correctly. As far as I see it, you'd like to have access to the path inside the classfolder for any purpose. The problem with that is, that the Class/Resource lookup mechanism doesn't apply to folders, only files. The concept that is close is a package, but that is always bound to a class.
So you always need a concrete file to be accessed via getResource() method. For example MyClass.class.getResource(/path/to/resource.txt).
If the resources are located in a profiles.sm folder relative to a class and its package, i.e. in /com/languagedetect/profile.sm/ you could build the path from the reference class, for example the class LanguageDetection in that package and derive the absolute path from this to the profiles.sm path:
String basePath = "/" + LanguageDetection.class.getPackage().getName().replaceAll("\\.", "/") + "/profiles.sm/";
URL resource = LanguageDetection.class.getResource(basePath + "myResource.txt");
If there is only one profiles.sm in the root of the jar, simply go for
String basePath = "/profiles.sm/";
URL resource = LanguageDetection.class.getResource(basePath + "myResource.txt");
If you have multiple jars with a resource in /profiles.sm, you could gain access to all of those via the classloader and then extract the Jar file from the URL of the class
for(URL u : Collections.list(LanguageDetection.class.getClassLoader().getResources("/profiles.sm/yourResource"))){
System.out.println(u);
}
In any case it's not possible without accessing the zip/jar file to browse the contents of this path or folder because Java does not support browsing for classes or resources inside a package/folder in classpath. You may use the Reflections lib for that or extend the ClassLoader example above by additionally reading the content of the detected jars using the zip example from above.

Why would ClassLoader.getResourceAsStream() return null?

Having the following code broken deliberately to identify the source of a NullPointerException in something that should have been very simple but turns out to drive me nuts:
Properties properties = new Properties();
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader = currentThread.getContextClassLoader();
InputStream propertiesStream = contextClassLoader.getResourceAsStream("resource.properties");
if (propertiesStream != null) {
properties.load(propertiesStream);
// TODO close the stream
} else {
// Properties file not found!
}
I get the "Properties file not found!" error, i.e. contextClassLoader.getResourceAsStream("resource.properties"); returns null.
This is a CXF-based client and I verified that the "resource.properties" file is in the current directory in which the client's jar resides (and runs).
I also verified the absolute path by including the following diagnostic code:
File file = new File("resource.properties");
System.out.println(file.getAbsolutePath());
The absolute path points to where the client's jar is.
I also tried finding out the context of the class loader, using:
System.out.println(Thread.currentThread().getContextClassLoader());
but instead some directory structure as demonstrated here, all I get is:
com.simontuffs.onejar.JarClassLoader#1decdec
Why would ClassLoader.getResourceAsStream() return null?
What am I missing?
I solved the mystery.
The key to solving was embedding some diagnostic logging when propertiesStream is null:
String classpath = System.getProperty("java.class.path");
LOG.info("CLASSPATH: " + classpath);
ClassLoader loader = MyClientMain.class.getClassLoader();
System.out.println("ClassLoader resource path: " + loader.getResource("resource.properties"));
So when I run with the original
contextClassLoader.getResourceAsStream("resource.properties")
I receive the null pointer condition, printing:
INFO: CLASSPATH: myproj.one-jar.jar
ClassLoader resource path: null
.
I then started suspecting something related to the "jar within a jar" as this is what the com.simontuffs.onejar essentially does (i.e. wrapping my project's jar inside a jar that contains all other library jars), so I opened myproj.one-jar.jar with 7-Zip and noted the full (absolute) path of "resource.properties":
myproj.one-jar.jar\main\myproj.jar\webapp\WEB-INF\classes\resource.properties
.
So I modified getResource("resource.properties") to:
getResource("/main/myproj.jar/webapp/WEB-INF/classes/resource.properties")
which didn't fix the problem but printed the following upon the null pointer condition:
INFO: CLASSPATH: myproj.one-jar.jar
ClassLoader resource path: jar:file:/myproj.one-jar.jar!/main/myproj.jar!//main/myproj.jar/webapp/WEB-INF/classes/resource.properties
.
Then... divine intervention fell upon me and I had the insight (not reading any documentation that could even hint this, I swear!) that I should be using this path instead:
getResource("/webapp/WEB-INF/classes/resource.properties")
And Voila! It works.
Whew.
As EJP pointed out, it means that the resource isn't available via the classpath for this particular classloader (different classloaders can have different classpaths).
Since the classloader is a JarClassLoader, it will only be able to load resources that are included inside the jar file. It won't see files that are in the same directory as the jar file.

Categories