Tuesday, May 20, 2014

Properties signleton


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();
  }

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home