Decoupling Java applications and config/properties files

March 11, 2010

Hello,
I have been developing a stand-alone Java application packaged as an executable JAR file.
The application had,

  1. Database interaction using iBatis APIs.
  2. Log the various activities in a log file using log4j API.

I had packaged the “my-db.properties” file that feeds the driver/username/password information to “SqlMapConfig.xml(iBatis configuration file)” within the JAR itself.Also the “log4j-myapp.properties” file was placed inside the JAR.

This introduced strong coupling/dependency between these files and JAR. Whenever I need to change database or log4j configuration, I had to re-package the JAR.

I was searching for a solution for this and based on google searches and interpretation of Java documentation for Class.getResourceAsStream() addressed this problem in the following way.

Solution:

  1. Consider that we are generating this executable jar (myapp.jar) in  “myapp/dist” directory.
  2. To resolve dependencies on iBatis and log4j, put respective JARs in the same i.e “myapp/dist” directory.
  3. Add Class-Path attribute in “Manifest.mf” file that lists these JAR files.
  4. This can be done by providing the custom manifest file while generating the JAR file. #2 and #3 are widely used in various enterprise as well as standalone applications so doing this is not an issue.

  5. Create a directory named “config” in “myapp/dist” where we will keep properties file. Of course this can be any name you like.
  6. Add the entry “config/” to the Class-Path attribute in “Manifest.mf” file. This brings “config” directory in the classpath while executing the application.
  7. Put the “log4j-myapp.properties” and “my-db.properties” in “config” directory.
  8. Your application(may be constructor of your main class) can load the log4j configuration as shown below –
  9. 	/*Initialize log4j */
    	Properties props = new Properties();
    	props.load(this.getClass().getResourceAsStream("/log4j-myapp.properties"));
    	props.list(System.out);
    	PropertyConfigurator.configure(props);
    
  10. SqlMapConfig.xml” can point to the resource properties file as shown below. This does not change much as compared to log4j.
  11. <sqlMapConfig>
       <!-- This is where we specify the properties file where DB configuration is specified -->
       <properties resource="my-db.properties"/>
       <transactionManager type="JDBC" commitRequired="false">
       <dataSource type="SIMPLE">
         <property name="JDBC.Driver" value="${driver}"/>
         <property name="JDBC.ConnectionURL" value="${url}"/>
         <property name="JDBC.Username" value="${username}"/>
         <property name="JDBC.Password" value="${password}"/>
       </dataSource>
      </transactionManager>
      <sqlMap resource="com/amit/test/xyz.xml"/>
    </sqlMapConfig>
    
    
  12. Now when you run your application (java -jar myapp.jar), the properties files will be picked up from the config directory.

This ensures you do not need to change the JAR file for making Database and log4j configuration changes.

The Directory structure for “dist” looks as shown below –

Directory Strcture for "dist": myapp |----dist | |----myapp.jar | |----ibatis-2.3.4.726.jar |----log4j-1.2.14.jar |----ojdbc14_g.jar |----config | |---log4j-myapp.properties |---my-db.properties

Manifest.mf classpath entry as below –

Class-Path: config/ log4j-1.2.14.jar ibatis-2.3.4.726.jar ojdbc14_g.jar

Why this works?
If you see the line that loads log4j configuration file, it has “/” at the start of filename.

props.load(this.getClass().getResourceAsStream("/log4j-myapp.properties"));

As per the Java documentation(see below) for the method, we have to give “/” at the start and then only the resource will be located using absolute name calculated as mentioned.

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String)

I was facing issues earlier because,

  1. I was only giving “log4j-myapp.properties” while loading the log4j configuration in my program.
  2. In the Manifest.mf, I was not appending “/” at the end of “config” directory. Without this the resource was not getting located.

Of course this is not just limited to iBatis and/or log4j, but any properties file that needs to be controlled outside the realm of the JAR file especially in stand-alone application can be configured this way.
Java API documentation to the rescue once again. Many thanks to them.

Cheers !!
Amit