Skip to main content

Config Stores

Configuration files are stored in entities called Stores.

The methods that need to be implemented on a store are:

ConfigStoreread(String)write(String,String,String)update(String,String)

Read is self-explanatory:

public String read(String name) {
// Return the contents of the specified configuration.
}

Write is slightly trickier:

public String write(String name, String oldContents, String newContents) {

// If the old contents match the current stored contents, the new contents will get stored,
// and the method returns null indicating success.

// If the old contents DO NOT match the current stored contents (i.e. it was modified in some way),
// the new contents are NOT stored, and the method returns the current stored contents.

// If the old contents are null, then just always write the new contents.
}

The update method is called whenever the stored file gets modified externally:

public String update(String name, String newContents) {
// Do something with the updated contents.
}

Two configuration stores are provided by default:

FileStoreMemoryStore

The store is defined on the Config object via the following setting:

Config.Builderstore(ConfigStore)
Example
// Create a config with in-memory storage.
Config config = Config.create("MyConfig.cfg").store(MemoryStore.DEFAULT).build();

The default store used is FileStore.DEFAULT which defines the execution directory as the file system directory to store and retrieve files.

MemoryStore​

The MemoryStore class is simply an in-memory storage location for configuration files.

There is no hard persistence and is used primarily for testing purposes. However, the implementation provides a good idea on how stores work (especially the write method):

public class MemoryStore extends ConfigStore {

// Some methods ommitted.

private final ConcurrentHashMap cache = new ConcurrentHashMap();

@Override /* ConfigStore */
public synchronized String read(String name) {
return emptyIfNull(cache.get(name));
}

@Override /* ConfigStore */
public synchronized String write(String name, String expectedContents, String newContents) {

// This is a no-op.
if (isEquals(expectedContents, newContents))
return null;

String currentContents = read(name);

if (expectedContents != null && ! isEquals(currentContents, expectedContents))
return currentContents;

update(name, newContents);

// Success!
return null;
}

@Override /* ConfigStore */
public synchronized MemoryStore update(String name, String newContents) {
cache.put(name, newContents);
super.update(name, newContents); // Trigger any listeners.
return this;
}
}

FileStore​

The FileStore is the typical store used for configuration files.

It provides the following configurable settings:

FileStore.Buildercharset(Charset)directory(File)updateOnWrite()enableWatcher()watcherSensitivity(WatcherSensitivity)
Example
// Create a config store with a watcher thread and high sensitivity.
FileStore fileStore = FileStore
.create()
.directory("configs")
.enableWatcher()
.watcherSensitivity(HIGH)
.build();

// Create a config using the store defined above.
Config config = Config
.create("MyConfig.cfg")
.store(fileStore)
.build();

Custom config stores​

The ConfigStore API has been written to allow easy development of custom configuration storage classes. The example below shows a starting point for an implementation based on polling a relational database. The source can be found here: SqlStore.

Completing it is left as an exercise:

Example Store Class
public class SqlStore extends ConfigStore {

private final String jdbcUrl;
private final String tableName, nameColumn, valueColumn;
private final Timer watcher;
private final ConcurrentHashMap cache = new ConcurrentHashMap();

protected SqlStore(ConfigStore.Builder builder) {
super(builder);
this.jdbcUrl = builder.jdbcUrl;
this.tableName = builder.tableName;
this.nameColumn = builder.nameColumn;
this.valueColumn = builder.valueColumn;

int pollInterval = builder.pollInterval;

TimerTask timerTask = new TimerTask() {
@Override
public void run() {
SqlStore.this.poll();
}
};

this.watcher = new Timer("MyTimer");
watcher.scheduleAtFixedRate(timerTask, 0, pollInterval * 1000);
}

synchronized void poll() {
// Loop through all our entries and find the latest values.
for (Map.Entry e : cache.entrySet()) {
String name = e.getKey();
String cacheContents = e.getValue();
String newContents = getDatabaseValue(name);

// Change detected!
if (! cacheContents.equals(newContents))
update(name, newContents);
}
}

// Reads the value from the database.
protected String getDatabaseValue(String name) {
// Implement me!
return null;
}

@Override /* ConfigStore */
public boolean exists(String name) {
// Implement me!
return false;
}

@Override /* ConfigStore */
public synchronized String read(String name) {
String contents = cache.get(name);
if (contents == null) {
contents = getDatabaseValue(name);
update(name, contents);
}
return contents;
}

@Override /* ConfigStore */
public synchronized String write(String name, String expectedContents, String newContents) {

// This is a no-op.
if (StringUtils.eq(expectedContents, newContents))
return null;

String currentContents = read(name);

if (expectedContents != null && StringUtils.ne(currentContents, expectedContents))
return currentContents;

update(name, newContents);

// Success!
return null;
}

@Override /* ConfigStore */
public synchronized SqlStore update(String name, String newContents) {
cache.put(name, newContents);
super.update(name, newContents); // Trigger any listeners.
return this;
}

@Override /* Closeable */
public synchronized void close() {
if (watcher != null)
watcher.cancel();
}
}

Config store listeners​

The ConfigStore class has the following listener methods:

ConfigStoreregister(String,ConfigStoreListener)unregister(String,ConfigStoreListener)

Note that this is a different listener than ConfigEventListener. In this case, we're just listening for changed files:

ConfigStoreListeneronChange(String)

This listener is used by the Config class to listen for changes on the file system so that it can be updated in real-time.

In This Section​