How to develop a new Configuration Utils Implementation

The IConfigurations interface

The Configuration Utils API is defined by the IConfigurations interface.

All configuration functionality is defined by this interface and all implementations must implement it. For a list of all the features this interface defines refer to it's JavaDoc documentation.

The BaseConfigurationsImpl abstract class

The BaseConfigurationsImpl is a standard base implementation of the Configuration Utils API as specified by the IConfigurations interface. The goal of this class is to facilitate the development of new Implementation classes of the Configuration Utils API by providing default implementations of most of it's methods. These implementations represent the tasks that are normally handled the same way in any Configuration repository we may want to implement, like the parsing of POJOs, type conversion, default values, etc.

This class is provided as a helper in the implementation of a new Configuration Utils Implementation. Using the BaseConfigurationsImpl as a starting point to our Implementation one no longer needs to implement all methods of the API IConfigurations, since BaseConfigurationsImpl takes care of the large majority of them for us.

All that's left for us to do is:

  • Repository instanciation: As needed by the underlying platform.
  • Read method, that should return the result in a simple Properties object, ignoring all rules, default values, etc.
  • Write method, that writes a properties file to the repository
  • Remove method, that removes from the repository the identified configuration values.

The BaseConfigurationsImpl abstract class

For a sample implementation let's create an implementation that simply writes messages to a fixed "configurations.properties". This is of course a bad solution since this poor file will grow immensely with the addition of more and more configurations. This is only so we don't over-complicate our example.

Good implementations could for instance be:

  • Database repository
  • A directory where several separated configuration properties would be saved (not much more work than this example)
  • Etc.

We will use BaseConfigurationsImpl as a starting point for our implementation since it will save us the trouble of implementing all the helper methods and standard features like, for instance, POJO parsing.

The empty class

The newly created class that extends BaseConfigurationsImpl:

 1  public class ConfigurationsPropertyFileImpl extends BaseConfigurationsImpl {
 2  }

This of course is a class with a handful of compilation errors. We must implement several methods before it is ready. Let's continue.

Attributes and initialization

Let's give our class a constructor and a private attribute for the repository.

 1  public class ConfigurationsPropertyFileImpl extends BaseConfigurationsImpl {
 2
 3      /** the filePath of the repository on disk */
 4      private String repositoryFilePath;
 5
 6      /** The properties object in memory, our memory representation of the repository */
 7      private Properties repository;
 8
 9      /**
10       * Default constructor
11       */
12      public ConfigurationsPropertyFileImpl() {
13
14          // Set the file path based on the current user home directory
15          repositoryFilePath = System.getProperty("user.dir") + "\\configurations.properties";
16  
17          // Initialize the repository
18          repository = new Properties();
19
20          try {
21              repository.load(new FileInputStream(repositoryFilePath));
22          } catch (FileNotFoundException e) {
23              System.out.print("Repository file does not exist. Starting a new repository from scratch");
24  
25          } catch (IOException e) {
26              System.out.print("Error reading repository. Using a new, empty one.");
27          }
28      }
29  }

Our constructor initializes our repository object and loads all existing configurations from disk.

Implementing required methods

We must next implement three methods. These three methods are the only ones BaseConfigurationsImpl could not provide for us.

For all of these we'll have to implement a little workaround for the fact that properties files only allow a simple key to organize values. There are no sections in the files. So if we want to organize keys by section and allow equal keys to exists in different sections, we will have to use a compiled key with the configID, sectionID and the key itself. All three methods implement this workaround.

repositoryKey = configID + "." + sectionID + "." + key

First an utility method. saveRepository

Since this will be needed more than once, lets provide an utility that saves the repository in memory to the disk.

30      /**
31       * @return T if the repository was successfully saved
32       */
33      private boolean saveRepository() {
34          try {
35              // Update the file on disk
36              repository.store(new FileOutputStream(repositoryFilePath), "");
37  
38          } catch (FileNotFoundException e) {
39              System.out.print("Error writing repository. Changes will not persist.");
40              return false;
41  
42          } catch (IOException e) {
43              System.out.print("Error writing repository. Changes will not persist.");
44              return false;
45          }
46  
47          return true;
48      }
readConfiguration: the configurations reader method

A simple implementation that searches the repository for keys that contain the required prefix.

50      /**
51       * @see pt.digitalis.utils.config.BaseConfigurationsImpl#readConfiguration(java.lang.String, java.lang.String)
52       */
53      @Override
54      public Properties readConfiguration(String configID, String sectionID) {
55          Properties props = new Properties();
56  
57          String prefix = configID + "." + sectionID + ".";
58  
59          // Reads every key that starts with "configID.sectionID." and adds it without this prefix
60          for (Object key : repository.keySet())
61              if (((String) key).startsWith(prefix))
62                  props.put(((String) key).substring(prefix.length()), repository.get(key));
63  
64          return props;
65      }
writeConfiguration: the configurations writer method

Adds a set of key/values to the current repository and persists it to disk.

67      /**
68       * @see pt.digitalis.utils.config.BaseConfigurationsImpl#writeConfiguration(java.lang.String, java.lang.String,
69       *      java.util.Properties)
70       */
71      @Override
72      public boolean writeConfiguration(String configID, String sectionID, Properties values) {
73  
74          String prefix = configID + "." + sectionID + ".";
75  
76          // Adds all key/values adding the prefix "configID.sectionID." to each key
77          for (Object key : values.keySet())
78              repository.put(prefix + key, values.get(key));
79  
80          return saveRepository();
81      }
removeConfiguration: the configurations remove method. Discarts a configurations.

Removes all key/value for the corresponding configID/sectionID from the repository and persists it.

83      /**
84       * @see pt.digitalis.utils.config.IConfigurations#removeConfiguration(java.lang.String, java.lang.String)
85       */
86      public boolean removeConfiguration(String configID, String sectionID) {
87          Properties props = new Properties();
88  
89          String prefix = configID + "." + sectionID + ".";
90  
91          // Remove every key that starts with "configID.sectionID."
92          for (Object key : repository.keySet())
93              if (((String) key).startsWith(prefix))
94                  props.remove(key);
95  
96          return saveRepository();
97      }

That's it!

And this ends our implementation. Of course this is not a very intelligent implementation. The performance will be exponential, since it writes the full repository for each write/remove operation. The repository fully in memory may also become a problem. Still, this is only a sample implementation in no way intended to be a valid alternative to the default implementation provided by Configuration Utils.

You can see how easy it is create your own Configuration implementation, if you need to.

For a better view we will present the complete listing of our example implementation bellow. If you would like to play with the code, you can download the java source file here.

 1  public class ConfigurationsPropertyFileImpl extends BaseConfigurationsImpl {
 2
 3      /** the filePath of the repository on disk */
 4      private String repositoryFilePath;
 5
 6      /** The properties object in memory, our memory representation of the repository */
 7      private Properties repository;
 8
 9      /**
10       * Default constructor
11       */
12      public ConfigurationsPropertyFileImpl() {
13
14          // Set the file path based on the current user home directory
15          repositoryFilePath = System.getProperty("user.dir") + "\\configurations.properties";
16  
17          // Initialize the repository
18          repository = new Properties();
19
20          try {
21              repository.load(new FileInputStream(repositoryFilePath));
22          } catch (FileNotFoundException e) {
23              System.out.print("Repository file does not exist. Starting a new repository from scratch");
24  
25          } catch (IOException e) {
26              System.out.print("Error reading repository. Using a new, empty one.");
27          }
28      }
29  
30      /**
31       * @return T if the repository was successfully saved
32       */
33      private boolean saveRepository() {
34          try {
35              // Update the file on disk
36              repository.store(new FileOutputStream(repositoryFilePath), "");
37  
38          } catch (FileNotFoundException e) {
39              System.out.print("Error writing repository. Changes will not persist.");
40              return false;
41  
42          } catch (IOException e) {
43              System.out.print("Error writing repository. Changes will not persist.");
44              return false;
45          }
46  
47          return true;
48      }
49  
50      /**
51       * @see pt.digitalis.utils.config.BaseConfigurationsImpl#readConfiguration(java.lang.String, java.lang.String)
52       */
53      @Override
54      public Properties readConfiguration(String configID, String sectionID) {
55          Properties props = new Properties();
56  
57          String prefix = configID + "." + sectionID + ".";
58  
59          // Reads every key that starts with "configID.sectionID." and adds it without this prefix
60          for (Object key : repository.keySet())
61              if (((String) key).startsWith(prefix))
62                  props.put(((String) key).substring(prefix.length()), repository.get(key));
63  
64          return props;
65      }
66  
67      /**
68       * @see pt.digitalis.utils.config.BaseConfigurationsImpl#writeConfiguration(java.lang.String, java.lang.String,
69       *      java.util.Properties)
70       */
71      @Override
72      public boolean writeConfiguration(String configID, String sectionID, Properties values) {
73  
74          String prefix = configID + "." + sectionID + ".";
75  
76          // Adds all key/values adding the prefix "configID.sectionID." to each key
77          for (Object key : values.keySet())
78              repository.put(prefix + key, values.get(key));
79  
80          return saveRepository();
81      }
82  
83      /**
84       * @see pt.digitalis.utils.config.IConfigurations#removeConfiguration(java.lang.String, java.lang.String)
85       */
86      public boolean removeConfiguration(String configID, String sectionID) {
87          Properties props = new Properties();
88  
89          String prefix = configID + "." + sectionID + ".";
90  
91          // Remove every key that starts with "configID.sectionID."
92          for (Object key : repository.keySet())
93              if (((String) key).startsWith(prefix))
94                  props.remove(key);
95  
96          return saveRepository();
97      }
98  }