package org.commons.jconfig.internal; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; import org.apache.log4j.Logger; import org.commons.jconfig.config.ConfigContext; import org.commons.jconfig.config.ConfigManager; import org.commons.jconfig.config.ConfigRuntimeException; import org.commons.jconfig.internal.ConfigAdapterJson.CONST; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; /** * Encapsulation for parsing and saving config values from ConfigLoader which * calls via JMX operations */ @ThreadSafe public class ConfigManagerCache { private final Logger logger = Logger.getLogger(ConfigManagerCache.class); private final AtomicReference<Map<String, ClassMemConfig>> readableCacheRef = new AtomicReference<Map<String, ClassMemConfig>>( new ConcurrentHashMap<String, ClassMemConfig>()); private final ConcurrentHashMap<String, ClassMemConfig> writableCache = new ConcurrentHashMap<String, ClassMemConfig>(); private final ConfigManager configManager; private class ClassMemConfig { @Override public String toString() { return keyMap.toString(); } /** * setType = FARM * * Structure of keyMap: * * { FARM1 : { a:b, d:e }, FARM2 : {f:d, z:y },_Defs_ : {} } */ private final Map<String, Map<String, JsonElement>> keyMap = new HashMap<String, Map<String, JsonElement>>(); /** setType defined for this module or null if no setType is defined */ private final TreeSet<String> contextTypes = new TreeSet<String>(); private String setType = null; public ClassMemConfig(final JsonObject configValue) { parseAndSaveValues(configValue); } public JsonElement get(final ConfigContext context, final String fileId) { JsonElement value = null; if (setType != null) { Map<String, JsonElement> values = keyMap.get(context.get(setType)); if (values != null) { value = values.get(fileId); } } if (value == null) { Map<String, JsonElement> values = keyMap.get(CONST.DEFAULTS.toString()); value = values.get(fileId); } if (value != null) { return value; } else { return null; } } /** * Parse and Save config values * @param value Json data to be parsed and inserted. e.g below: { "_Sets_Type_": "COLO", "_Sets_": [ { "key": [ "ne1"], "keyList": { "SonoraHostname" : "google.com" } } ], "SonoraHostname" : "localhost" } } */ public void parseAndSaveValues(final JsonObject value) { JsonArray sets = value.getAsJsonArray(CONST.SETS.toString()); if (sets != null) { JsonPrimitive localSetType = value.getAsJsonPrimitive(CONST.SETS_TYPE.toString()); if (localSetType != null) { if (setType != null && !setType.equals(localSetType.getAsString())) { throw new ConfigRuntimeException("Cannot override registered " + "_Sets_Type_ " + setType + " by " + localSetType.getAsString()); } else { setType = localSetType.getAsString(); contextTypes.add(setType); } } else { throw new ConfigRuntimeException("Config object is missing " + CONST.SETS_TYPE.toString() + " property : " + value); } for (JsonElement node : sets) { Map<String, JsonElement> values = new HashMap<String, JsonElement>(); if (node.isJsonObject() && node.getAsJsonObject().getAsJsonObject("keyList") != null) { for (Entry<String, JsonElement> entry : node.getAsJsonObject().getAsJsonObject("keyList") .entrySet()) { if (entry.getKey().equalsIgnoreCase(CONST.SETS.toString()) || entry.getKey().equalsIgnoreCase(CONST.SETS_TYPE.toString())) { continue; } if (entry.getValue().isJsonPrimitive()) { values.put(entry.getKey(), entry.getValue()); } else if (entry.getValue().isJsonObject()) { values.put(entry.getKey(), entry.getValue()); } } } if (node.isJsonObject() && node.getAsJsonObject().getAsJsonArray("key") != null) { for (JsonElement key : node.getAsJsonObject().getAsJsonArray("key")) { if (keyMap.containsKey(key.getAsString())) { /* * If key is present than "merge" new values * with values already present in keyMap */ Map<String, JsonElement> setValues = keyMap.get(key.getAsString()); for (Map.Entry<String, JsonElement> entry : values.entrySet()) { setValues.put(entry.getKey(), entry.getValue()); } } else { /* * create new hashmap for every element in list. * this eliminates editing same map for * different keys */ keyMap.put(key.getAsString(), new HashMap<String, JsonElement>(values)); } } } } } // parse and save defaults Map<String, JsonElement> values = new HashMap<String, JsonElement>(); if (value.isJsonObject()) { for (Entry<String, JsonElement> entry : value.entrySet()) { // sets and set_type are already being processed above if (entry.getKey().equalsIgnoreCase(CONST.SETS.toString()) || entry.getKey().equalsIgnoreCase(CONST.SETS_TYPE.toString())) { continue; } if (entry.getValue().isJsonPrimitive()) { values.put(entry.getKey(), entry.getValue()); } else if (entry.getValue().isJsonObject()) { values.put(entry.getKey(), entry.getValue()); } } } if (keyMap.containsKey(CONST.DEFAULTS.toString())) { /* * If key is present than "merge" new values * with values already present in keyMap */ Map<String, JsonElement> defaultValues = keyMap.get(CONST.DEFAULTS.toString()); for (Map.Entry<String, JsonElement> entry : values.entrySet()) { defaultValues.put(entry.getKey(), entry.getValue()); } } else { keyMap.put(CONST.DEFAULTS.toString(), values); } } /** * Returns the list of content types used by this config object * * @return */ public SortedSet<String> getContextTypes() { return contextTypes; } } public ConfigManagerCache(final ConfigManager configManager) { this.configManager = configManager; } /* * (non-Javadoc) * * @see common.config.internal.ConfigFormat#get(java.lang.Object, * common.config.ConfigContext, java.lang.String, * java.lang.String) */ public String get(final Object config, final ConfigContext context, final String field, final String defaultValue) { Map<String, ClassMemConfig> localCache = readableCacheRef.get(); // TODO: Find which application it is and if application is not present // that use modules under DEFAULT_APP String configName = config.getClass().getName(); ClassMemConfig classConfig = null; if (localCache.containsKey(configName)) { classConfig = localCache.get(configName); } else if (localCache.containsKey(CONST._PROP_.toString())) { classConfig = localCache.get(CONST._PROP_.toString()); } else { return defaultValue; } JsonElement value = classConfig.get(context, field); if (value == null) { return defaultValue; } else { if (value.isJsonPrimitive()) { return value.getAsJsonPrimitive().getAsString(); } else if (value.isJsonObject()) { return value.getAsJsonObject().toString(); } else { return null; } } } /** * Returns the list of Context Types used by current ConfigManager cache * * @param config * @return */ public <T> SortedSet<String> getContextTypes(final Class<T> classDefinition) { Map<String, ClassMemConfig> localCache = readableCacheRef.get(); // TODO: Find which application it is and if application is not present // that use modules under DEFAULT_APP String configName = classDefinition.getName(); ClassMemConfig classConfig = null; if (localCache.containsKey(configName)) { classConfig = localCache.get(configName); } else if (localCache.containsKey(CONST._PROP_.toString())) { classConfig = localCache.get(CONST._PROP_.toString()); } else { return EMPTY_SET; } return classConfig.getContextTypes(); } private final SortedSet<String> EMPTY_SET = Collections.unmodifiableSortedSet(new TreeSet<String>()); /** * /** Add/update an autoConf value to our memory structure. Gets called via * ConfigObjectMBean setAttribute JMX calls. * * e.g string for value parameter { "_Sets_Type_": "COLO", "_Sets_": [ { "key": [ "ne1" ], "keyList": { "SonoraHostname" : "google.com" } } ], "SonoraHostname" : "localhost" } } * @param moduleName * Module for which value needs to be set * @param jsonValue * Config value in json format */ public void insertValue(@Nonnull final String moduleName, @Nonnull final String jsonValue) { // convert JSON into java object if (logger.isDebugEnabled()) { logger.debug("Set value " + jsonValue + " for module " + moduleName); } JsonParser parser = new JsonParser(); JsonObject json = (JsonObject) parser.parse(jsonValue); // Block all writers on a flipcache operation synchronized (writeLock) { if (writableCache.containsKey(moduleName)) { writableCache.get(moduleName).parseAndSaveValues(json); } else { writableCache.put(moduleName, new ClassMemConfig(json)); } } } private final Object writeLock = new Object(); public void flipCache() { synchronized (writeLock) { ConcurrentHashMap<String, ClassMemConfig> newReadableCache = new ConcurrentHashMap<String, ClassMemConfig>( writableCache); writableCache.clear(); readableCacheRef.lazySet(newReadableCache); configManager.setLoadingDone(); } logger.info("Loading new config values from JMX. " + readableCacheRef.get().toString()); } /** * Checks if module is loaded * @param config * @return true if Module is loaded else return false */ public boolean isModuleLoaded(Object config) { return readableCacheRef.get().containsKey(config.getClass().getName()); } }