package org.openntf.domino.config;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.openntf.domino.Database;
import org.openntf.domino.Document;
import org.openntf.domino.thread.DominoExecutor;
import org.openntf.domino.xots.Tasklet;
/**
*
* @author Roland Praml, FOCONIS AG
*
*/
public abstract class ConfigurationObject {
private long nextDocAccess = 0;
private Map<String, Object> cache_ = new FastMap<String, Object>().atomic();
private Set<String> dirty_ = new FastSet<String>();
// pair of value name & class
protected abstract Object[] schema();
protected abstract Document getDocument(boolean create);
@SuppressWarnings("unchecked")
protected <T> T get(final String key) {
initCache();
return (T) cache_.get(key);
}
protected <T> T get(final String key, final T defaultValue) {
T ret = get(key);
if (ret == null) {
ret = defaultValue;
put(key, ret);
}
return ret;
}
protected boolean put(final String key, final Object value) {
initCache();
if (value.equals(cache_.get(key))) {
return false;
} else {
if (nextDocAccess == Long.MAX_VALUE) {
cache_.put(key, value);
} else {
synchronized (dirty_) {
dirty_.add(key);
cache_.put(key, value);
}
// Notify the configuration, that this object is dirty
Configuration.addDirty(this);
}
return true;
}
}
@Tasklet(session = Tasklet.Session.NATIVE, threadConfig = Tasklet.ThreadConfig.STRICT)
protected class CacheSyncer implements Callable<Void> {
@Override
public Void call() {
syncCache();
return null;
}
}
/**
* initializes the chache
*/
protected synchronized void initCache() {
if (System.currentTimeMillis() > nextDocAccess) {
nextDocAccess = System.currentTimeMillis() + 15 * 1000;
DominoExecutor executor = Configuration.getExecutor();
if (executor != null) {
Future<Void> f = executor.submit(new CacheSyncer());
try {
f.get(2, TimeUnit.SECONDS);
} catch (Exception e) {
//nextDocAccess = Long.MAX_VALUE; // do not read the doc again before Sun Aug 17 17:12:55 EST 292278994
e.printStackTrace();
}
}
}
}
/**
* Syncs the <code>cache_<code> with the backend document. must be called from a proper initialized thread!
*/
public void syncCache() {
Database odaDb_ = Configuration.getOdaDb();
if (odaDb_ == null) {
// we don't have an ODA-DB. so it's useless to query it again;
nextDocAccess = Long.MAX_VALUE; // do not read the doc again before Sun Aug 17 17:12:55 EST 292278994
return;
}
synchronized (dirty_) {
Document doc = getDocument(true); // Create the document, if cache is dirty;
if (doc == null)
return;
// write back all dirty keys to the document
if (!dirty_.isEmpty()) {
for (String dirtyKey : dirty_) {
doc.put(dirtyKey, cache_.get(dirtyKey));
}
doc.save();
dirty_.clear();
}
// read all keys from the document that are specified in the schema
Object[] s = schema();
for (int i = 0; i < s.length; i += 2) {
cache_.put((String) s[i], doc.getItemValue((String) s[i], (Class<?>) s[i + 1]));
}
}
}
}