package org.commons.jconfig.loader.adapters;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.commons.jconfig.config.ConfigException;
import org.commons.jconfig.configloader.ConfigLoaderConfig;
/**
* AutoConf json configuration file source class.<BR>
* Handles config file changes on disk, Accessor methods return latest copy of configurations<BR><BR>
*
* standard autoConf is defined by two tiers<BR><BR>
*
* Application : all the module configurations belonging to a single application.<br>
* (as named in the @ConfigManagerMXBean)<BR>
* Modules : all the configurations belonging to a module. configurations in a module are defined per module. <BR><BR>
*
* Also supports module configurations shared across multiple applications.<BR>
* configurations share across multiple applications belong to a reserved appName "Modules"<br><br>
*
* Singleton usage model.<BR><BR>
*
* <B>Usage</B><BR>
*
* AutoConf config = AutoConf.instance();<BR>
* if ( config.hasModule(appName, module) ) {<BR><BR>
*
* JsonNode moduleNode = config.getModule("Imap", "FilerGateConfig");<BR>
* moduleNode.doSomething()<BR>
* }<BR><BR>
*
*/
public class AutoConf {
private final ConfigLoaderConfig config;
/** Synchronize on this object before updating any of the static atomic
* variables in this class, or before doing file read. */
private final Object autoConfLock = new Object();
/** Last contents successfully loaded from the clusters.conf file.
* Null if we've never successfully read that file. */
private final AtomicReference<JsonNode> confRef = new AtomicReference<JsonNode>(null);
/** Next time we should check for a new clusters.conf file.
* Zero means: "check at next opportunity". */
private final AtomicLong nextCheckTimeRef = new AtomicLong(0);
/** The "last modified" time stamp of file {@link #confFilenameRef} the
* we last time we read it (even if that version of the file was
* unreadable). Zero if we've never seen the file. */
private final AtomicLong fileLastModifiedTimeRef = new AtomicLong(0);
/**
*
*/
public AutoConf(final ConfigLoaderConfig config) {
this.config = config;
}
/**
* Are there configurations available for appName
*
* @param appName
* @throws ConfigException
*/
public boolean hasApplication(final String appName) throws ConfigException {
if (null == getApplication(appName)) {
return false;
} else {
return true;
}
}
/**
* Are there configurations available for module in appName
* @param appName
* @param module
* @throws ConfigException
*/
public boolean hasModule(final String appName, final String module) throws ConfigException {
if (null == getModule(appName, module)) {
return false;
} else {
return true;
}
}
/**
* Get configuration Object for appName
*
* @param appName
* @return Json Object. null if missing
* @throws ConfigSourceException
*/
public JsonNode getApplication(final String appName) throws ConfigException {
return getConf().get(appName);
}
/**
* Get configuration Object for module in appName
*
* @param appName
* @return Json Object. null if missing
* @throws ConfigSourceException
*/
public JsonNode getModule(final String appName, final String module) throws ConfigException {
JsonNode appNode = getApplication(appName);
JsonNode modNode = null;
if (appNode != null) {
modNode = appNode.get(module);
}
return modNode;
}
/**
* @return The current conf dictionary. Never null.
* @throws Exception
* @throws AutoConfException if clusters.conf
* does not exist, or cannot be parsed.
*/
public JsonNode getConf() throws ConfigException
{
// If we should check for a new conf file,
long now = System.currentTimeMillis();
if (now > nextCheckTimeRef.get())
{
// Synchronize and check again. ("Double-checked locking".
// We only need to lock when its time to check the file again.)
synchronized (autoConfLock)
{
// If we should check for a new conf file,
if (now > nextCheckTimeRef.get())
{
// Throw if conf file missing.
String filename = config.getConfigFileName();
File f = new File(filename);
long modTime = f.lastModified();
if (modTime == 0) {
throw new ConfigException("AutoConf file (" + filename + ") not found");
}
// If conf file has changed,
if (modTime != fileLastModifiedTimeRef.get())
{
try {
ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
mapper.configure( DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
confRef.set(mapper.readValue(f, JsonNode.class));
fileLastModifiedTimeRef.set(modTime);
} catch (JsonParseException e) {
throw new ConfigException("Error parsing AutoConf file (" + filename + ")", e);
} catch (JsonMappingException e) {
throw new ConfigException("Error parsing AutoConf file (" + filename + ")", e);
} catch (IOException e) {
throw new ConfigException("Error parsing AutoConf file (" + filename + ")", e);
}
}
// Don't check again for x time interval
nextCheckTimeRef.set(now + config.getConfigSyncInterval().toMillis());
}
}
}
// Return current conf. Throw if none.
JsonNode conf = confRef.get();
if (conf == null) {
throw new ConfigException("AutoConf file (" + config.getConfigFileName() + ") is not loaded");
}
return conf;
}
}