Deploy a writable properties file in jpackaged Java application - java

I have a Java client application that I am going to package as a stand-alone application using jpackage.
But the application has a Properties file which must be both read and written to by the application. My initial attempts to do this run in the IDE, but when I package the application it fails because the location of the properties file is actually located inside the packaged .jar.
Is there a canonical / "right" way to maintain a writable properties file with a jpackaged application?

Package default settings in your application. At runtime, store a writable copy of them in a known file location, typically in a directory that is a descendant of the user’s home directory:
String applicationName = /* ... */;
Properties configuration;
String os = System.getProperty("os.name");
String home = System.getProperty("user.home");
Path localSettingsDir;
if (os.contains("Mac")) {
localSettingsDir = Path.of(home, "Library", "Application Support");
} else if (os.contains("Windows")) {
String appData = System.getenv("APPDATA");
if (appData != null) {
localSettingsDir = Path.of(appData);
} else {
localSettingsDir = Path.of(home, "AppData", "Roaming");
}
} else {
String configHome = System.getenv("XDG_CONFIG_HOME");
if (configHome != null) {
localSettingsDir = Path.of(configHome);
if (!localSettingsDir.isAbsolute()) {
localSettingsDir = Path.of(home, ".config");
}
} else {
localSettingsDir = Path.of(home, ".config");
}
}
Path localSettings = localSettingsDir.resolve(
Path.of(applicationName, "config.properties"));
configuration = new Properties();
if (!Files.exists(localSettings)) {
Files.createDirectories(localSettings.getParent());
try (InputStream defaultSettings =
MyApplication.class.getResourceAsStream("config.properties")) {
Files.copy(defaultSettings, localSettings);
}
}
try (Reader settingsSource = Files.newBufferedReader(localSettings)) {
configuration.load(settingsSource);
}
For more details on determining a system’s application configuration directory, see Find place for dedicated application folder.

Related

IllegalStateException: Failed to load Application Context when using File functions in Java

I'm using a function like this to get the latest file present based on timestamp:
File file = getFile(path);
Where getFile () is like this:
private File getFile (String path) {
File directory = new File(path);
File[] files = directory.listFiles(File::isFile);
long latestVersion = Long.MIN_VALUE;
File latestFile = null;
if (files!= null) {
for (File file : files) {
if (file.lastModified() > latestVersion) {
latestFile = file;
latestVersion = file.lastModified();
}
}
}
return latestFile;
}
On running mvn clean install I get this error:
IllegalStateException: Failed to load Application Context in a totally different test class of the same module.
If I remove the file method, the build runs fine. Definitely something is wrong here in this code then?
Can anyone help here?
Thanks.

How to load external properties file with Java without rebuilding the Jar?

I use gradle which structures projects in maven style so I have the following
src/main/java/Hello.java and src/main/resources/test.properties
My Hello.java look like this
public class Hello {
public static void main(String[] args) {
Properties configProperties = new Properties();
ClassLoader classLoader = Hello.class.getClassLoader();
try {
configProperties.load(classLoader.getResourceAsStream("test.properties"));
System.out.println(configProperties.getProperty("first") + " " + configProperties.getProperty("last"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
This works fine. however I want to be able to point to .properties file outside of my project and I want to it to be flexible enough that I can point to any location without rebuilding the jar every time. Is there a way to this without using a File API and passing file path as an argument to the main method?
You can try this one, which will first try to load properties file from project home directory so that you don't have to rebuild jar, if not found then will load from classpath
public class Hello {
public static void main(String[] args) {
String configPath = "test.properties";
if (args.length > 0) {
configPath = args[0];
} else if (System.getenv("CONFIG_TEST") != null) {
configPath = System.getenv("CONFIG_TEST");
}
File file = new File(configPath);
try (InputStream input = file.exists() ? new FileInputStream(file) : Hello.class.getClassLoader().getResourceAsStream(configPath)) {
Properties configProperties = new Properties();
configProperties.load(input);
System.out.println(configProperties.getProperty("first") + " " + configProperties.getProperty("last"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
You can send the properties file path as argument or set the path to an environment variable name CONFIG_TEST
Archaius may be complete overkill for such a simple problem, but it is a great way to manage external properties. It is a library for handling configuration: hierarchies of configuration, configuration from property files, configuration from databases, configuration from user defined sources. It may seem complicated, but you will never have to worry about hand-rolling a half-broken solution to configuration again. The Getting Started page has a section on using a local file as the configuration source.

Why I cannot read properties file using getResourceAsStream from WEB-INF/classes in WildFly? [duplicate]

This question already has answers here:
How to read properties file in web application? [duplicate]
(3 answers)
Closed 7 years ago.
My PC's operating system is Windows 7 64-bit.
I created a very simple Dynamic Web Project app in Eclipse:
I have a app.properties file in WEB-INF/classes directory with these properties:
DefaultMaximumBatchSize=1000
DAOFactory=MSSQLSERVER
I have a class AppProperties which reads the above file into a Properties object at startup using getResourceAsStream:
public class AppProperties {
private static final Properties APP_PROPERTIES;
static {
InputStream inputStream = null;
APP_PROPERTIES = new Properties();
try {
inputStream = AppProperties.class.getResourceAsStream("/WEB-INF/classes/app.properties");
System.out.println("AppProperties: inputStream=" + inputStream);
if (inputStream != null) {
APP_PROPERTIES.load(inputStream);
}
} catch (Exception e) {
System.out.println("AppProperties: Exception occured; e=" + e);
}
}
public static String getValue(String propertyName) {
if (propertyName == null || propertyName.equalsIgnoreCase(""))
return null;
else
return APP_PROPERTIES.getProperty(propertyName);
}
}
I have a listener class AppContextListener:
public class AppContextListener implements ServletContextListener {
public AppContextListener() {
}
public void contextInitialized(ServletContextEvent arg0) {
String defaultMaxBatchSize = AppProperties.getValue("DefaultMaximumBatchSize");
System.out.println("AppContextListener: contextInitialized(ServletContextEvent): defaultMaxBatchSize=" + defaultMaxBatchSize);
}
public void contextDestroyed(ServletContextEvent arg0) {
}
}
I deployed the app to JBoss 4.2.3, run the JBoss 4.2.3 and I get this output in server.log:
AppProperties: inputStream=java.io.FileInputStream#1adde645
AppContextListener: contextInitialized(ServletContextEvent): defaultMaxBatchSize=1000
Perfect.
I then deployed the same app to WildFly 8.2.1, run the WildFly 8.2.1 and I get this output in server.log:
AppProperties: inputStream=null
AppContextListener: contextInitialized(ServletContextEvent): defaultMaxBatchSize=null
What happened? What is the correct way to read properties file in WildFly from WEB-INF/classes directory?
Class.getResourceAsStream() looks for a resource in all of the directories and jars that constitute the classpath of the application.
So, if you start a java program with
java -cp foo;bar.jar com.baz.Main
And you use SomeClass.class.getResourceAsStream("/blabla/app.properties"), The classloader will look for the app.properties file under foo/blabla, and in the blabla directory of bar.jar.
Now, in a webapp, what constitutes the classpath of the webapp is
the directory WEB-INF/classes
all the jar files under WEB-INF/lib
So, if you call
AppProperties.class.getResourceAsStream("/WEB-INF/classes/app.properties")
the classloader will look for app.properties in
/WEB-INF/classes/WEB-INF/classes
<all the jar files of WEB-INF/lib>/WEB-INF/classes
The conclusion is that, to load an app.properties file located in WEB-INF/classes, what you need is
AppProperties.class.getResourceAsStream("app.properties")
JBoss shouldn't have worked.
Class.getResourceAsStream retrieves the resource from the classpath and the webapp root folder is not in the classpath.
The WEB-INF/classes folder is. Use getResourceAsStream("/app.properties"), and remember to close the stream:
private static final Properties APP_PROPERTIES = new Properties();
static {
try (InputStream inputStream = AppProperties.class.getResourceAsStream("/app.properties")) {
System.out.println("AppProperties: inputStream=" + inputStream);
if (inputStream != null)
APP_PROPERTIES.load(inputStream);
} catch (Exception e) {
System.out.println("AppProperties: Exception occured; e=" + e);
}
}
Now, if app.properties is always next to AppProperties.class, instead of at the root, make the name unqualified (remove the /). This will work even when your class is in a package (and it is in a package, right?).
Try
InputStream inputStream =
this.getClass().getClassLoader().getResourceAsStream("/my.properties");`

How do you access .properties files inside an AppEngine app?

I've got an AppEngine app with two different instances, one for prod and one for staging. Accordingly, I'd like to configure the staging instance slightly differently, since it'll be used for testing. Disabling emails, talking to a different test backend for data, that kind of thing.
My first intuition was to use a .properties file, but I can't seem to get it to work. I'm using Gradle as a build system, so the file is saved in src/main/webapp/WEB-INF/staging.properties (and a matching production.properties next to it). I'm trying to access it like so:
public class Config {
private static Config sInstance = null;
private Properties mProperties;
public static Config getInstance() {
if (sInstance == null) {
sInstance = new Config();
}
return sInstance;
}
private Config() {
// Select properties filename.
String filename;
if (!STAGING) { // PRODUCTION SETTINGS
filename = "/WEB-INF/production.properties";
} else { // DEBUG SETTINGS
filename = "/WEB-INF/staging.properties";
}
// Get handle to file.
InputStream stream = this.getClass().getClassLoader().getResourceAsStream(filename);
if (stream == null) {
// --> Crashes here. <--
throw new ExceptionInInitializerError("Unable to open settings file: " + filename);
}
// Parse.
mProperties = new Properties();
try {
mProperties.load(stream);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
The problem is that getResourceAsStream() is always returning null. I checked the build/exploded-app directory, and the .properties file shows up there. I also checked the .war file, and found the .properties file there as well.
I've also tried moving the file into /WEB-INF/classes, but that didn't make a difference either.
What am I missing here?
Try
BufferedReader reader = new BufferedReader(new FileReader(filename));
or
InputStream stream = this.getClass().getResourceAsStream(filename);

Read from file system via FileSystem object

In order to list file contents of a specific directory on classpath I'm using the new FileSystem and Path features of Java 7. In one deployment the directory is stored on file system, directly. In another deployment it is stored into a JAR file.
My approach works fine with JAR files: I create a FileSystem object which refers to the JAR file and access the content via Path object.
...
URI dir = ...
String[] array = dir.toString().split("!");
try (final FileSystem fs = FileSystems.newFileSystem(URI.create(array[0]), new HashMap<String, Object>()))
{
final Path directory = fs.getPath(array[1]);
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory))
{
...
Due to the dir object has following value, it works:
jar:file:/C:/Users/pax/.../Detector-1.0.jar!/org/.../destinationdir
But in the other environment the destination directory is stored on file system, directly. dir object contains the value:
file:/C:/Users/pax/.../destinationdir
FileSystems.newFileSystem(...) always throws following exception for / and file:/C:/Users/pax/.../destinationdir as URI:
java.lang.IllegalArgumentException: Path component should be '/'
at sun.nio.fs.WindowsFileSystemProvider.checkUri(WindowsFileSystemProvider.java:68)
How do you use FileSystem.newFileSystem for destinations on file system?
Is there a better approach in order to list the directories content independently from its specific kind of storage (file system or JAR file)?
Following question's resolution tackles the issue ("destination on file system" versus "destination in JAR file") by try-catch approach: NIO2: how to generically map a URI to a Path?
This utility method tries to obtain a correct Path instance. But there may occur a further problem: If this destination resource is contained by a JAR file (instead of file system) then you can only access the resource via its associated FileSystem instance which must not be closed! So, your helper method needs to return the Path object as well as the FileSystem instance (only required if it's not on file system directly). The invoker has to close the FileSystem object, manually:
public static PathReference getPath(final URI resPath) throws IOException
{
try
{
// first try getting a path via existing file systems
return new PathReference(Paths.get(resPath), null);
}
catch (final FileSystemNotFoundException e)
{
/*
* not directly on file system, so then it's somewhere else (e.g.:
* JAR)
*/
final Map<String, ?> env = Collections.emptyMap();
final FileSystem fs = FileSystems.newFileSystem(resPath, env);
return new PathReference(fs.provider().getPath(resPath), fs);
}
}
The wrapper class PathReference should implement AutoClosable so that it can be used in try block:
public class PathReference implements AutoCloseable
{
...
#Override
public void close() throws Exception
{
if (this.fileSystem != null)
this.fileSystem.close();
}
public Path getPath()
{
return this.path;
}
public FileSystem getFileSystem()
{
return this.fileSystem;
}
}
This makes the release of the FileSystem instance a bit more transparent:
...
try (final PathReference fileObj = SignatureUtils.getPath(file))
{
...
try (InputStream fileStream = Files.newInputStream(fileObj.getPath()))
{
...

Categories