Custom ConfigStores
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.
Completing it is left as an exercise:
public class ConfigSqlStore extends ConfigStore {
// Configurable properties
static final String
CONFIGSQLSTORE_jdbcUrl = "ConfigSqlStore.jdbcUrl.s",
CONFIGSQLSTORE_tableName = "ConfigSqlStore.tableName.s",
CONFIGSQLSTORE_nameColumn = "ConfigSqlStore.nameColumn.s",
CONFIGSQLSTORE_valueColumn = "ConfigSqlStore.valueColumn.s",
CONFIGSQLSTORE_pollInterval = "ConfigSqlStore.pollInterval.i";
// Instance fields
private final String jdbcUrl;
private final String tableName, nameColumn, valueColumn;
private final Timer watcher;
private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
// Constructor called from builder.
protected ConfigSqlStore(PropertyStore ps) {
super(ps);
this.jdbcUrl = getStringProperty(CONFIGSQLSTORE_jdbcUrl, "jdbc:derby:mydb");
this.tableName = getStringProperty(CONFIGSQLSTORE_tableName);
this.nameColumn = getStringProperty(CONFIGSQLSTORE_nameColumn);
this.valueColumn = getStringProperty(CONFIGSQLSTORE_valueColumn);
int pollInterval = getStringProperty(CONFIGSQLSTORE_pollInterval, 600); // Time in seconds.
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
ConfigSqlStore.this.poll();
}
};
this.watcher = new Timer("MyTimer");
watcher.scheduleAtFixedRate(timerTask, 0, pollInterval * 1000);
}
private synchronized void poll() {
// Loop through all our entries and find the latest values.
for (Map.Entry<String,String> 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!
}
@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 (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 ConfigSqlStore 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();
}
}
The purpose of the builder class is to simply set values in the {@link oaj.PropertyStore}
that's passed to the ConfigStore:
public class ConfigSqlStoreBuilder extends ConfigStoreBuilder {
public ConfigSqlStoreBuilder() {
super();
}
public ConfigSqlStoreBuilder(PropertyStore ps) {
super(ps);
}
public ConfigSqlStoreBuilder jdbcUrl(String value) {
super.set(CONFIGSQLSTORE_jdbcUrl, value);
return this;
}
public ConfigSqlStoreBuilder tableName(String value) {
super.set(CONFIGSQLSTORE_tableName, value);
return this;
}
public ConfigSqlStoreBuilder nameColumn(String value) {
super.set(CONFIGSQLSTORE_nameColumn, value);
return this;
}
public ConfigSqlStoreBuilder valueColumn(String value) {
super.set(CONFIGSQLSTORE_valueColumn, value);
return this;
}
public ConfigSqlStoreBuilder pollInterval(int value) {
super.set(CONFIGSQLSTORE_pollInterval, value);
return this;
}
@Override /* ContextBuilder */
public ConfigFileStore build() {
return new ConfigFileStore(getPropertyStore());
}
}