What exactly is the best way to generate config files for my Java application? Right now I have a runnable jar which when opened will open a file selector gui for the user to select where the config files along with saved data should be stored at. I have my default config file saved in my resource folder and I am wanting to save that file to the location specified. Anyways the problem I am having is that I am not sure how I will be able to refence back to those files in the future because I only want that file selector to pop up once. As soon as the application is closed all references to that file pathway would be lost. The only thing I can think of is that I could replace my config.yml file inside the resource folder with the newly generated file and include a file location parameter (if that is even possible). But to be honest I am not sure how programs actually handle this and would love any insight into this topic.
Perhaps place the selected path into a small text file located within the Startup directory of your JAR file. Encrypt it if you like or whatever. The method below should provide you with the Directory (folder) path of where your JAR file was started:
/**
* Returns the path from where the JAR file was started. In other words, the
* installed JAR file's home directory (folder).<br><br>
*
* <b>Example Usage:</b><pre>
* <code>String applicationPath = appPath(MyStartupClassName.class);</code></pre>
*
* #param mainStartupClassName (Class) The main startup class for your
* particular Java application. To be supplied
* as: <b>MyMainClass.class</b><br>
*
* #return (String) The full path to where the JAR file resides (its Home
* Directory).
*/
public static String appPath(Class<?> mainStartupClassName) {
try {
String path = mainStartupClassName.getProtectionDomain().getCodeSource().getLocation().getPath();
String pathDecoded = java.net.URLDecoder.decode(path, "UTF-8");
pathDecoded = pathDecoded.trim().replace("/", File.separator);
if (pathDecoded.startsWith(File.separator)) {
pathDecoded = pathDecoded.substring(1);
}
return pathDecoded.substring(0, pathDecoded.lastIndexOf(File.separator));
}
catch (java.io.UnsupportedEncodingException ex) {
java.util.logging.Logger.getLogger("appPath()")
.log(java.util.logging.Level.SEVERE, null, ex);
}
return null;
}
Related
I'm trying to use a file in my code but I don't want to have specify the absolute file path, only the file name, for example "fileName.txt".
I want to do this so I have the ability to use this code on different laptops where the file may be stored in different folders.
The code below is what I'm using at the moment but I receive a NoSuchFileException when I ran it.
FileSystem fs FileSystems.getDefault();
Path fileIn = Paths.get("fileName.txt");
Any ideas how to overcome this problem so I can find the file without knowing its absolute path?
Ideas on how to find the file without knowing its absolute path:
Instruct the user of the app to place the file in the working directory.
Instruct the user of the app to give the path to the file as a program argument, then change the program to use the argument.
Have the program read a configuration file, found using options 1 or 2, then instruct the user of the app to give the path to the file in the configuration file.
Prompt the user for the file name.
(Not recommended) Scan the entire file system for the file, making sure there is only one file with the given name. Optional: If more than one file is found, prompt the user for which file to use.
if you don't ask the user for the complete path, and you don't have a specific folder that it must be in, then your only choice is to search for it.
Start with a rootmost path. Learn to use the File class. Then search all the children. This implementation only returned the first file found with that name.
public File findFile(File folder, String fileName) {
File fullPath = new File(folder,fileName);
if (fullPath.exists()) {
return fullPath;
}
for (File child : folder.listFiles()) {
if (child.isDirectory()) {
File possible = findFile(child,fileName);
if (possible!=null) {
return possible;
}
}
}
return null;
}
Then start this by calling either the root of the file system, or the configured rootmost path that you want to search
File userFile = findFile( new File("/"), fileName );
the best option, however, is to make the user input the entire path. There are nice file system browsing tools for most environments that will do this for the user.
private void copyFile() throws IOException {
Path destination;
String currentWorkingDir = System.getProperty("user.dir");
File fileToCopy = component.getArchiveServerFile();
if (path.contains(File.separator)) {
destination = Paths.get(path);
} else {
destination = Paths.get(currentWorkingDir + File.separator + path);
}
if (!Files.exists(destination)) {
try {
Files.createDirectories(destination);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
FileUtils.copyFileToDirectory(fileToCopy, new File(destination.toString()));
}
}
Basically what I'm trying to do here is copying a file in some location using the path provided in the class's constructor. The logic is like this:
If the path has file separator, I consider it a full path and copy the file at the end.
If the path doesn't have file separator, I copy the file in the working directory from which the .exe file was launched.
So far, only the first option works (the full path). For some reason, the working directory option is not working and I can't figure out why.
UPDATE: If I just change the following line:
String currentWorkingDir = System.getProperty("user.dir");
to
String currentWorkingDir = System.getProperty("user.home");
It works. So I'm guessing the problem is coming from user.dir? Maybe at runtime, the folder is already being used and as a result, it can't copy the file into it?
The weird thing is, I don't have any exceptions or error, but nothing happens as well.
UPDATE 2: I think the problem here is that I'm trying to copy a file which is embedded in the application (.exe file) that I'm executing during runtime, and java can't copy it while the current working directory is being used by the application.
UPDATE 3:
Since this copy method is used in an external library, I had to come up with another way (other than logs) to see the content of system property user.dir. So I wrote I little program to create a file and write in it the value return by the property.
To my surprise, the path is not where my application was launched. It was in:
C:\Users\jj\AppData\Local\Temp\2\e4j1263.tmp_dir1602852411
Which is weird because I launched the program from :
C:\Users\jj\workspace\installer\product\target\
Any idea why I'm getting this unexpected value for user.dir?
I want to create a function to download a remote directory (Ex: "https://server.net/production/current/") via HTTP to a local folder. I don't have control over the remote directory so I can't just create a convenient tar ball. I was able to find lots of questions related to retrieving individual files, but I couldn't find one that matched my use case.
To give you an idea of what I am referring to, here is a sample of what the directory looks like in browser.
In other words I want to create a function equivalent to this wget where Y is the local destination folder and X is the remote directory to retrieve. I would call wget directly, but I want a cross-platform solution that will work on windows without additional setup.
wget -r -np -R "index.html*" -P Y X
The end goal is a java function like the one shown below.
/**
* Recursively downloads all of the files in a remote HTTPS directory to the local destination
* folder.
* #param remoteFolder a folder URL (Ex: "https://server.net/production/current/")
* #param destination a local folder (Ex: "C:\Users\Home\project\production")
*/
public static void downloadDirectory(String remoteFolder, String destination) {}
It can assume there are no circular dependencies in the remote directory and that the destination folder exists and is empty.
I was hoping there was some magic function or best practice in java.io or maybe Apache commons-io to do this, but since it sounds like none exists I wrote my own version that manually goes through the html page and follows links.
I'm just going to leave this answer here in case someone else has the same question or someone knows a way to improve my version.
import org.apache.commons.io.FileUtils;
private static final Pattern HREF_PATTERN = Pattern.compile("href=\"(.*?)\"");
/**
* Recursively downloads all of the files in a remote HTTPS directory to a local
* destination folder. This implementation requires that the destination string
* ends in a file delimiter. If you don't know if it does, append "/" to the end
* just to be safe.
*
* #param src remote folder URL (Ex: "https://server.net/production/current/")
* #param dst local folder to copy into (Ex: "C:\Users\Home\project\production\")
*/
public static void downloadDirectory(String src, String dst) throws IOException {
Scanner out = new Scanner(new URL(src).openStream(), "UTF-8").useDelimiter("\n");
List<String> hrefs = new ArrayList<>(8);
while (out.hasNext()) {
Matcher match = HREF_PATTERN.matcher(out.next());
if (match.find())
hrefs.add(match.group(1));
}
out.close();
for (String next : hrefs) {
if (next.equals("../"))
continue;
if (next.endsWith("/"))
copyURLToDirectory(src + next, dst + next);
else
FileUtils.copyURLToFile(new URL(src + next), new File(dst + next));
}
}
I have created an application which does some job and it has a configuration folder inside it(named /xml/Configuration.xml).Now, I am trying to use the same jar in multiple projects and the only change which I want to make it is by changing the configuration file contained within the jar file.
String xmlConfigPath = System.getProperty(user.dir) + File.separator
+ "xml" + File.separator + "Configuration.xml";
As you can see here, I access the XML file by using the "user.dir" followed by the complete file name.This(user directory) obviously will change when the jar added in a project is run from a different user.dir which hence does not find the Configuration.xml file placed inside the jar file added to my project.
Is there a way to make this jar access its own configuration folder irrespective of which project or location its placed in?
Please see the following code
Inside my jar file I have the following function which loads the configuration
/**
* This method is responsible for reading the EMail Configuration
* #return <MailSender>
* #throws Exception
*/
private static MailSender getConfig(String configPackagePath, String fileName) throws Exception {
JAXBContext jaxbContext;
Unmarshaller unmarshaller;
StreamSource source;
JAXBElement<MailSender> mailSenderElement;
MailSender mailSenderConfig = null;
String xmlConfigPath = getUserDirectoryPath() + File.separator
+ ApplicationConstants.XML + File.separator + fileName;
try {
jaxbContext = JAXBContext.newInstance(configPackagePath);
File file = new File(xmlConfigPath);
unmarshaller = jaxbContext.createUnmarshaller();
source = new StreamSource(file);
mailSenderElement = unmarshaller.unmarshal(source, MailSender.class);
mailSenderConfig = mailSenderElement.getValue();
} catch (JAXBException e) {
String errorMessage = "Error encountered while reading the EMail XML Configuration file at " + xmlConfigPath;
throw new Exception(errorMessage, e);
}
return mailSenderConfig;
}
/**
* This method is responsible for fetching the User Directory Path
* #return <String> userDirectoryPath
*/
private static String getUserDirectoryPath() {
return System.getProperty(ApplicationConstants.USER_DIR);
}
Now, while running this as a standalone project, it finds the correct path and works fine.Now, when I package this as a jar, this fails because it cannot fetch the file from the current user directory.The intention here is I want the jar file to use absolute path to access the file already placed inside it without having anything to do with the location of where it(jar file) might itself be placed.
You should instead put the configuration file on the classpath of your application and load it using ClassLoader.getResourceAsStream
This means for example using xml/ as source folder inside Eclipse, then you can get the configuration file from anywhere using :
getClass().getResourceAsStream("/xml/Configuration.xml");
Some suggestions:
use Classpath for accessing your config file. See more info here How to really read text file from classpath in Java
use absolute path
use path traversal with ../.. (up to one level)
If the file is within the JAR file then you should use something like:
File conf=new File(getClass().getResource("/xml/Configuration.xml").toURI());
// The "absolute path" here points at the root of the JAR
I have a use case where I need to export this specific piece of code as a java library (which will be a JAR eventually) but the problem is that it needs to use some piece of information stored in physical files on the file system.
I have 2 questions here:
1) Where should I put these files on the filesystem (One option that I could think of was in the resources directory of the Java module containing the library: Have a doubt though that the resources directory also gets compiled into the jar?)
2) When I am using this library from an external Java application, how would the library be able to locate the files? Would they still be in the classpath?
You have two options, first one is to place the files inside the package structure, so that they will be packed inside the jar. You would get them from the code like this:
getClass().getResourceAsStream("/path/to/your/resource/resource.ext");
If you would call it from a static method of class named A then you should write like this:
A.class.getResourceAsStream("/path/to/your/resource/resource.ext");
The "/path" part of the path is the topmost package, and the resource.ext is your file name.
The other option is to put them outside the jar package, but then the jar needs to know their location:
provide it as an argument to the program (java -jar program.jar system/path/to/file)
hardcode the location from which you would read the file with paths
The way I undestood your queastion and answered it, it has nothing to do with classpath:
The CLASSPATH variable is one way to tell applications, including the JDK tools, where to look for user classes. (Classes that are part of the JRE, JDK platform, and extensions should be defined through other means, such as the bootstrap class path or the extensions directory.)
EDIT:
but you can nevertheless, put it there and get it from code like this:
System.getProperty("java.class.path");
It would however require some logic to parse it out.
You can pass the location of the files in a property file or some technique like this.
Where should I put these files on the filesystem
That is up to you to decide, though it would be a good idea to make this configurable. It would also be a good idea to try to fit into the conventions of the host operating system / distro, though these vary ... and depend on the nature of your application.
When I am using this library from an external Java application, how would the library be able to locate the files?
You would typically use a configuration property or initialization parameter to hold/pass the location. If you were writing an application rather that a library, you could use the Java Preferences APIs, though this probably a poor choice for a library.
Would they still be in the classpath?
Only if you put the location on the classpath ... and that is going to make configuration more tricky. Given that these files are required to be stored in the file system, I'd recommend using FileInputStream or similar.
Using Eclipse, I always create a package 'resources' where I put the files the jar needs. I access the files (from pretty much anywhere) through
this.getClass().getClassLoader().getResources("/resources/file.ext");
With export->runnable jar all those files are included in the .jar. I'm not sure this is the correct way of doing it though. Also, I'm not 100% sure about the "/" before resources, maybe it should be omitted.
I found a relevant answer as a part of another question : How to load a folder from a .jar?
I am able to successfully retrieve the files using the following code:
/**
* List directory contents for a resource folder. Not recursive.
* This is basically a brute-force implementation.
* Works for regular files and also JARs.
*
* #author Greg Briggs
* #param clazz Any java class that lives in the same place as the resources you want.
* #param path Should end with "/", but not start with one.
* #return Just the name of each member item, not the full paths.
* #throws URISyntaxException
* #throws IOException
*/
String[] getResourceListing(Class clazz, String path) throws URISyntaxException, IOException {
URL dirURL = clazz.getClassLoader().getResource(path);
if (dirURL != null && dirURL.getProtocol().equals("file")) {
/* A file path: easy enough */
return new File(dirURL.toURI()).list();
}
if (dirURL == null) {
/*
* In case of a jar file, we can't actually find a directory.
* Have to assume the same jar as clazz.
*/
String me = clazz.getName().replace(".", "/")+".class";
dirURL = clazz.getClassLoader().getResource(me);
}
if (dirURL.getProtocol().equals("jar")) {
/* A JAR path */
String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory
while(entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.startsWith(path)) { //filter according to the path
String entry = name.substring(path.length());
int checkSubdir = entry.indexOf("/");
if (checkSubdir >= 0) {
// if it is a subdirectory, we just return the directory name
entry = entry.substring(0, checkSubdir);
}
result.add(entry);
}
}
return result.toArray(new String[result.size()]);
}
throw new UnsupportedOperationException("Cannot list files for URL "+dirURL);
}