It seems I've written this code for a few projects - so I though it's good enough to share.
There is always a need for a Java application to know about its environment. It's easy enough to set properties on the command line, or to encode them in some kind of a constants file, but sometimes you need to change the properties on the fly, without recompiling or re-launching. If it's a production application, you don't have the liberty of bringing it down just to change settings.
So I wrote a convenience implementation of AppProperties object. It reads a properties file, parses it into a Properties object, and makes it available as a singleton to the application. Then all you need to do to change settings is to update the file and wait a minute or two (or whatever you set the interval to be), and presto - properties are updated.
The easiest way to set it up in a web application is to have a separate servlet, which loads on startup, like this:
<servlet>
<servlet-name>AppStartupServlet</servlet-name>
<display-name>Startup Servlet</display-name>
<description>Servlet to perform webapp initialization tasks, such as configuring app properties singleton and initializing logging</description>
<servlet-class>com.example.AppStartupServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
Then, in the Init method of the servlet, initialize the AppProperties object, like this:
public void init() {
context = getServletContext();
String confDirName = context.getRealPath("/" + "WEB-INF" + File.separator + "classes" + File.separator);
String confFileName = AppConstants.CONFIG_FILE_NAME; // the name of the config file
AppProperties.getInstance().init(confDirName, confFileName);
context.log("Application initialization complete");
log.info("Init complete");
}
|
From within the application, you can load properties like this:
String value1 = AppProperties.getInstance().getProperty("Key");
String value = AppProperties.getInstance().getProperty("Key", "Default value");
|
Here is the actual code:
/**
* Title: Application Properties object
* Description: Application Properties object
* @author Ilya
* @version 1.0
*/
import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
import org.apache.log4j.Logger;
/***********************************************************
* Singleton properties object; properties loaded from props file and
* available to calling classes. This class will not know about
* any specific values in the props file, and whether they are mandatory.
* This is done for two reasons:
* 1. This is a utility class and should not contain business logic and
* 2. Business classes should as much as possible have all their rules
* and logic contained in them and should not rely on this class to
* enforce them.
* This class exists to improve performance by only accessing the filesystem
* once per refresh cycle and to isolate system-dependent value (props file location) to one
* place in the code.
* The refresh interval is defined by property "property-file-refresh-time" in the same
* properties file, or will default to 1 minute if one is not defined
***********************************************************/
public class AppProperties {
private static AppProperties _instance;
// transient object, to force publish update to other threads. Because there is only one thread
// writing to this property, 'transient' is sufficient to assure thread safety
private transient static Properties _props;
public final static long serialVersionUID = 01L;
private static Thread tPoll;
private static Logger log = Logger.getLogger(AppProperties.class.getName());
// pass-through to encapsulated class
public String getProperty(String key) {
return _props.getProperty(key);
}
// pass-through to encapsulated class
public String getProperty(String key, String defaultValue) {
return _props.getProperty(key, defaultValue);
}
/**
* Primary way to interact with this object
* @return
*/
public static AppProperties getInstance(){
if (_instance == null){
throw new AssertionError("init() must be called before calling getInstance() for the first time");
}
return _instance;
}
/**
* call this init() method before using the singleton. Technically given this intended use, it should not be
* necessary to make it synchronized. Adding the synchronized block out of abundance of caution for possible
* reuse of this code in a different context. Acquiring a lock is expensive in relative terms but cheap in
* absolute terms; this should be called once (or rarely)
* @param confDirName directory where the config file is located
* @param confFileName name of the config file
*/
public void init(String confDirName, String confFileName) {
synchronized (AppProperties.class) {
try {
if (tPoll != null){ // this must be a subsequent invocation
tPoll.interrupt(); // interrupt old thread
}
AppPollThread pollThread = new AppPollThread(confDirName, confFileName);
tPoll = new Thread(pollThread); // create new thread based on new parameters
tPoll.start();
// sleep until init finished. The thread will not stop on its own so no way to wait() for it
while (pollThread.initComplete == false) {
Thread.sleep(1000); // 1 second
}
} catch (Exception e) {
log.error("error configuring App: ", e);
}
}
}
/**
* The way to stop a running thread is to tell it that it is interrupted. A well-behaved thread should then stop itself
*/
public void stopUpdates() {
tPoll.interrupt();
}
private class AppPollThread implements Runnable {
Logger log = Logger.getLogger(AppPollThread.class.getName());
public transient boolean initComplete = false;
private String confDir;
private String propFileName;
public AppPollThread(String configFileDir, String propFileNameParam) {
super();
confDir = configFileDir;
propFileName = propFileNameParam;
}
public void run() {
long lastPropUpdate = -1;
int propFileRefreshTime = 1; // default; minutes
try {
// iterate...
while (!tPoll.isInterrupted()) { // thread must check to see if it should stop itself
File propFile = new File(confDir + File.separator + propFileName);
if (propFile.lastModified() > lastPropUpdate) { // need to re-init properties
if (log.isDebugEnabled() && lastPropUpdate != -1)
log.debug("Re-reading Properties file");
Properties newProps = new Properties();
newProps.load(new FileInputStream(propFile));
AppProperties._props = newProps;
lastPropUpdate = propFile.lastModified();
try {
propFileRefreshTime = Integer.parseInt(AppProperties._instance.getProperty("property-file-refresh-time"));
} catch (Exception e) {
log.warn("Property property-file-refresh-time not defined in property file");
}
}
initComplete = true;
Thread.sleep(propFileRefreshTime * 60 * 1000); // minutes
}
} catch (Exception ex) { //
log.error("EmtProperties Initialisation Failed", ex);
}
} // end of Thread
}
}
|
That's it. When shutting down the servlet, don't forget to call stopUpdates() like this. This is important when shutting down or redeploying the application (including hot-deploying during development
public void destroy() {
AppProperties.getInstance().stopUpdates();
}
|