An approach to configurable parameters

It is annoying how much work you need to do just to get all the properties from the file mapped to a class catching all the exceptions and repeating many times the same keys thorough the configuration files. For example, normally I had before a class that has static String variables each one mapping a key in the property file and then I use those variables when loading the properties and validating, etc.

When you need to load some properties from a file to your application in order to have some configurable parameters normally you use an external file in case of having a Java SE application or a resource bundled in your Java package such as yourwar/META-INF/applicationProperties.xml.

I have write some dirty (ugly) code just to give you an idea of how that can be easier.

Supose the next scenario.
you have a applicationProperties.xml file
---

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>
<comment>
The good thing of using the xml version of properties file is that
it is "easy" to see the whitespaces that normally endup at the end of
the strings and most of the times we forget to use value.trim() ...
and then some time is spent for that stupid bug
</comment>

<entry key="connectionString">jdbc:h2:file:c:\h2db\dbName</entry>
<entry key="user">sa</entry>
<entry key="password">saadmin</entry>
<entry key="poolSize">7</entry>
<entry key="timeoutMillis">7000</entry>
<entry key="retryInterval">3</entry>

</properties>

---

and the next class mapping your file properties
---

public class ConfigurableParameters {


   public String _connectionString;
   public String _user;
   public String _password;
   public Integer _poolSize;
   public Long _timeoutMillis;
   public Integer _retryInterval;
   
   private static ConfigurableParameters instance;
   public static ConfigurableParameters getInstance() {
      if (instance == null) {
         instance = new ConfigurableParameters();
      }
      return instance;
   }
}

---

You see, the only time where you repeated the keys are in the name of the variables of the class. Now, wouldn't it be nice to load all the properties like this ?

---

public static void main(String[] args) {
      boolean confSuccess = ConfigurationHelper.mapConfigurableProperties(
                           ConfigurationSource.FILE,
                           "applicationProperties.xml",
                           ConfigurableParameters.getInstance());
      if (!confSuccess) {
         System.out.println("Error loading configuration");
         return;
      }
      
      System.out.println(ConfigurableParameters.getInstance()._connectionString);
      System.out.println(ConfigurableParameters.getInstance()._user);
      System.out.println(ConfigurableParameters.getInstance()._password);
      System.out.println(ConfigurableParameters.getInstance()._poolSize);
      System.out.println(ConfigurableParameters.getInstance()._timeoutMillis);
      System.out.println(ConfigurableParameters.getInstance()._retryInterval);
   }

---

The reason I make that the function recognizes all the fields starting with an underscore is just to make it clear that it will expect that those fields have a key in the properties file.

Actually You can make the fields of ConfigurableParameters class to be private and just provide public getters, it will still set the parameters. Also you can load the properties from a bundle file resource of the packaged jar, just call the method with ConfigurationSource.RESOURCE, and your sourceAddress would be something like "/META-INF/applicationProperties.xml", depending where did you put the file.


The implementation for the ConfigurationHelper is here

---

public class ConfigurationHelper {


   public enum ConfigurationSource {
      RESOURCE, FILE
   }


   public static boolean mapConfigurableProperties(ConfigurationSource configurationSource, String sourceAddress, Object target) {


      if (configurationSource == null || sourceAddress == null || target == null )
         return false;
      
      try {
         Properties sourceProperties = 
            getSourceProperties(configurationSource, sourceAddress, target);


         if (sourceProperties.isEmpty())
            return false;


         for (Field f : target.getClass().getDeclaredFields()) {
            String fieldName = f.getName();
      
            if (!fieldName.startsWith("_"))
               continue;


            fieldName = fieldName.substring(1);


            if (!sourceProperties.containsKey(fieldName))
               continue;


            boolean originalAccesibleValue = f.isAccessible();


            f.setAccessible(true);
            f.set(target, getConvertedValueForType((String) sourceProperties.get(fieldName), f.getType()));
            f.setAccessible(originalAccesibleValue);
         }


      } catch (Exception e) {
         e.printStackTrace();
         return false;
      }
      return true;
   }


   private static Properties getSourceProperties(ConfigurationSource configurationSource, String sourceAddress, Object target) {
      Properties prop = new Properties();
      try {
         InputStream is = 
            configurationSource == ConfigurationSource.FILE ? 
               new FileInputStream(sourceAddress) : 
                  target.getClass().getResourceAsStream(sourceAddress);
               
         prop.loadFromXML(is);
         is.close();
      } catch (Exception e) {
         e.printStackTrace();
      }


      return prop;
   }


   private static Object getConvertedValueForType(String sourceValue, Class c) {
      Object convertedValue = null;
      if (c == String.class) {
         convertedValue = sourceValue;
      } 
      else if (c == Long.class) {
         convertedValue = new Long((String) sourceValue);
      }
      else if (c == Integer.class) {
         convertedValue = new Integer((String) sourceValue);
      } 
      else if (c == BigDecimal.class) {
         convertedValue = new BigDecimal((String) sourceValue);
      }


      return convertedValue;
   }


}

---

This all may seem out of the standard conventions and I just didn't think of another way of doing this. It is just an idea of how can you make the work of loading configurable properties easier.

Have fun trying it and please give me feedback about how you are doing it.

4 comments:

Anonymous said...

http://commons.apache.org/configuration/

would be one easy way to go

Anonymous said...

Why not use annotations instead of the underscore prefix naming convention for fields?

David Hofmann said...

Using annotations is something I didn't think of, really interesting.

About using commons.configuration from apache, hmmm, you end up with something like Double double = config.getDouble("number"); and that is exactly what I don't like. By the way, I though something like this existed on apache but I didn't google it, my wrong :( But thanks to point this out here !

seo.media said...

Ecommerce and offshore software development are burgeoning trends these days and several India-based offshore software development companies have gained reputation for offering excellent services to the clientele throughout the world. However, it would be extremely advantageous for you to assign your software development project to one of the popular India-based company because services offered by Indian software companies are cost-effective and flexible that ensure future growth and transparency.http://www.eberrymedia.com