Saturday, October 31, 2009

Service provider framework for properties file

Many a times we need to use properties file in order to store configuration information.The information stored is usually in the form of key=value pair.The properties files can be organised according to the type of information that they store.For example, one property file called app.properties may store information regarding the application while a database specific property file called db.properties may stored the information pertaining to database access.

The number of property files may vary as new property files may be added as and when the need arises.Hence when designing the implementation we need to use the Open-Close design principle.

The Open-Closed design principle states that "Classes should be open for extension but closed for modification".

Lets look at how to implement the code in Java using the Open-Close principle.

Consider you have the following 2 property files -

1] app.properties

appurl=appurl
appname=appname
apppass=apppass
appdriver=appdriver

2] db.properties

dburl=dburl
dbname=dbname
dbpass=dbpass
dbdriver=dbdriver


Properties implementation
Since the logic to read these properties is common for both the files we will have an abstract class called AbstractProperties.java and two implemetation classes extending the abstract class for each of the property files viz AppProperties.java and DbProperties.java.

The following are the files and their source code -

1] AbstractProperties.java

package com.org.ops.tel.props;

import java.io.IOException;
import java.io.InputStream;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

public abstract class AbstractProperties {
private ResourceBundle resourceBundle;
protected String name;

protected void init(String name, InputStream inputStream) throws IOException{
resourceBundle = new PropertyResourceBundle(inputStream);
this.name = name;
}

abstract protected void validate();

protected ResourceBundle getResourceBundle() {
return resourceBundle;
}
}

2] AppProperties.java

package com.org.ops.tel.props;

public class AppProperties extends AbstractProperties{
private final String appUrl = "appurl";
private final String appName = "appname";
private final String appPass = "apppass";
private final String appDriver = "appdriver";

protected void validate() {
if (appUrl.isEmpty() || !getResourceBundle().containsKey(appUrl) || getResourceBundle().getString(appUrl).isEmpty()) {
throw new InvalidPropertiesException(appUrl + " needs to be present in " + name);
}
if (appName.isEmpty() || !getResourceBundle().containsKey(appName) || getResourceBundle().getString(appName).isEmpty()) {
throw new InvalidPropertiesException(appName + " needs to be present in " + name);
}
if (appPass.isEmpty() || !getResourceBundle().containsKey(appPass) || getResourceBundle().getString(appPass).isEmpty()) {
throw new InvalidPropertiesException(appPass + " needs to be present in " + name);
}
if (appDriver.isEmpty() || !getResourceBundle().containsKey(appDriver) || getResourceBundle().getString(appDriver).isEmpty()) {
throw new InvalidPropertiesException(appDriver + " needs to be present in " + name);
}
}

public String getAppUrl() {
return getResourceBundle().getString(appUrl);
}

public String getAppName() {
return getResourceBundle().getString(appName);
}

public String getAppPass() {
return getResourceBundle().getString(appPass);
}

public String getAppDriver() {
return getResourceBundle().getString(appDriver);
}
}


3] DbProperties.java

package com.org.ops.tel.props;

public class DbProperties extends AbstractProperties{
private final String dbUrl = "dburl";
private final String dbName = "dbname";
private final String dbPass = "dbpass";
private final String dbDriver = "dbdriver";

protected void validate() {
if (dbUrl.isEmpty() || !getResourceBundle().containsKey(dbUrl) || getResourceBundle().getString(dbUrl).isEmpty()) {
throw new InvalidPropertiesException(dbUrl + " needs to be present in " + name);
}
if (dbName.isEmpty() || !getResourceBundle().containsKey(dbName) || getResourceBundle().getString(dbName).isEmpty()) {
throw new InvalidPropertiesException(dbName + " needs to be present in " + name);
}
if (dbPass.isEmpty() || !getResourceBundle().containsKey(dbPass) || getResourceBundle().getString(dbPass).isEmpty()) {
throw new InvalidPropertiesException(dbPass + " needs to be present in " + name);
}
if (dbDriver.isEmpty() || !getResourceBundle().containsKey(dbDriver) || getResourceBundle().getString(dbDriver).isEmpty()) {
throw new InvalidPropertiesException(dbDriver + " needs to be present in " + name);
}
}

public String getDbUrl() {
return getResourceBundle().getString(dbUrl);
}

public String getDbName() {
return getResourceBundle().getString(dbName);
}

public String getDbPass() {
return getResourceBundle().getString(dbPass);
}

public String getDbDriver() {
return getResourceBundle().getString(dbDriver);
}

}

Now we need to design the core part "properties" file which will ensure adherence to the Open-Close design principle I talked about earlier.This new file will store the mapping of the actual .properties file to its corresponding java implementation class.Also this is the only file that we need to update whenever we decide to add a new properties file.The following is the content of this file.

4] properties

app.properties=com.org.ops.tel.props.AppProperties
db.properties=com.org.ops.tel.props.DbProperties

The following is the java implementation which does the dynamic loading of the classes defined in the above created "properties" file and they by ensures an Open-Closed adherence.

5] Properties.java

package com.org.ops.tel.props;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

public class Properties {
private static Map properties;
private static ResourceBundle resourceBundle;
private static final String FLAG_FOLDER = "props";
private static final String FLAG_NAME = "properties";
private static final String FLAG_DIR_SEPERATOR = "/";

public Properties () {
properties = new HashMap();
try {
resourceBundle = new PropertyResourceBundle(new FileInputStream(FLAG_FOLDER + FLAG_DIR_SEPERATOR + FLAG_NAME));
} catch (FileNotFoundException e) {
throw new InvalidPropertiesException("Error reading " + FLAG_NAME + " file",e);
} catch (IOException e) {
throw new InvalidPropertiesException("Error reading " + FLAG_NAME + " file",e);
}
}
public void load() {
for (String key: resourceBundle.keySet()){
try {
AbstractProperties ap = (AbstractProperties) Class.forName(resourceBundle.getString(key)).newInstance();
ap.init(key,new FileInputStream(FLAG_FOLDER + FLAG_DIR_SEPERATOR + key));
ap.validate();
properties.put(key,ap);
} catch (InstantiationException e) {
throw new InvalidPropertiesException("Error initializing " + key + " file",e);
} catch (IllegalAccessException e) {
throw new InvalidPropertiesException("Error initializing " + key + " file",e);
} catch (ClassNotFoundException e) {
throw new InvalidPropertiesException("Error initializing " + key + " file",e);
} catch (FileNotFoundException e) {
throw new InvalidPropertiesException("Error initializing " + key + " file",e);
} catch (IOException e) {
throw new InvalidPropertiesException("Error initializing " + key + " file",e);
}
}
}

public AbstractProperties getProperty(String name) {
if(null == properties.get(name))
throw new InvalidPropertiesException(" The property file " + name + " is not defined in " + FLAG_FOLDER + FLAG_DIR_SEPERATOR + FLAG_NAME);
return properties.get(name);
}
}

The following is an exception class used by this application.

6] InvalidPropertiesException

package com.org.ops.tel.props;

import java.io.FileNotFoundException;
import java.io.IOException;

public class InvalidPropertiesException extends RuntimeException {
private static final long serialVersionUID = 1L;

public InvalidPropertiesException(String message) {
super(message);
}
public InvalidPropertiesException(String message, FileNotFoundException e) {
super(message,e);
}
public InvalidPropertiesException(String message,IOException e){
super(message,e);
}
public InvalidPropertiesException(String message,InstantiationException e){
super(message,e);
}
public InvalidPropertiesException(String message,IllegalAccessException e){
super(message,e);
}
public InvalidPropertiesException(String message,ClassNotFoundException e){
super(message,e);
}
}

and finally the client code...


7] Client.java

package com.org.ops.tel.client;

import com.org.ops.tel.props.AppProperties;
import com.org.ops.tel.props.DbProperties;
import com.org.ops.tel.props.Properties;

public class Client {
public static void main(String args[]){
Properties properties = new Properties();
properties.load();
AppProperties appProperty = (AppProperties) properties.getProperty("app.properties");
DbProperties dbProperty = (DbProperties) properties.getProperty("db.properties");
System.out.println(appProperty.getAppDriver());
System.out.println(dbProperty.getDbPass());
}
}

With this Open-Close design we can add more property files with no change to the existing java source.For example, if we want to add one more property file say webservice.properties , all I need to do is write a new class say WebserviceProperties.java which extends the AbstractProperties.java class and update the following entry in the properties file -

webservice.properties=com.org.ops.tel.props.WebserviceProperties

So the new "properties" file would be -

app.properties=com.org.ops.tel.props.AppProperties
db.properties=com.org.ops.tel.props.DbProperties
webservice.properties=com.org.ops.tel.props.WebserviceProperties

The following is the screen shot of the folder structure

No comments: