org.lockss.config.Configuration Maven / Gradle / Ivy
Show all versions of lockss-core Show documentation
/*
Copyright (c) 2000-2017 Board of Trustees of Leland Stanford Jr. University,
all rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.lockss.config;
import java.io.*;
import java.util.*;
import org.lockss.config.Tdb.TdbException;
import org.lockss.util.*;
import org.lockss.util.os.PlatformUtil;
/** Configuration
provides access to the LOCKSS configuration
* parameters. Instances of (concrete subclasses of)
* Configuration
hold a set of configuration parameters, and
* have a standard set of accessors. Static methods on this class provide
* convenient access to parameter values in the "current" configuration;
* these accessors all have Param
in their name. (If called
* on a Configuration
instance, they will return values
* from the current configuration, not that instance. So don't do that.)
*/
public abstract class Configuration {
/** The common prefix string of all LOCKSS configuration parameters. */
public static final String PREFIX = "org.lockss.";
/** Common prefix of platform config params */
public static final String PLATFORM = PREFIX + "platform.";
public static final String DAEMON = PREFIX + "daemon.";
protected static Logger log = Logger.getLogger();
private static final String noProp = new String(); // guaranteed unique obj
/**
* The title database for this configuration
*/
private Tdb tdb;
/**
* Convenience methods for getting useful platform settings.
*/
public static Configuration getPlatformConfig() {
return ConfigManager.getPlatformConfig();
}
public static PlatformVersion getPlatformVersion() {
return ConfigManager.getPlatformVersion();
}
public String getPlatformGroups() {
return get(ConfigManager.PARAM_DAEMON_GROUPS,
ConfigManager.DEFAULT_DAEMON_GROUP);
}
public List getPlatformGroupList() {
return getList(ConfigManager.PARAM_DAEMON_GROUPS,
ConfigManager.DEFAULT_DAEMON_GROUP_LIST);
}
public static String getPlatformHostname() {
return ConfigManager.getPlatformHostname();
}
/**
* Gets the title database (TDB) for this configuration
* @return
*/
public Tdb getTdb() {
return tdb;
}
/**
* Return number of TdbAus in this instance's Tdb.
*
* @return the number of TdbAus in this instances's Tdb
*/
public int getTdbAuCount() {
return (tdb == null) ? 0 : tdb.getTdbAuCount();
}
/**
* Return number of TdbTitles in this instance's Tdb.
*
* @return the number of TdbTitles in this instances's Tdb
*/
public int getTdbTitleCount() {
return (tdb == null) ? 0 : tdb.getTdbTitleCount();
}
/**
* Return number of TdbPublishers in this instance's Tdb.
*
* @return the number of TdbPublishers in this instances's Tdb
*/
public int getTdbPublisherCount() {
return (tdb == null) ? 0 : tdb.getTdbPublisherCount();
}
/**
* Determine whether Tdbs for the two configurations are different.
*
* @param config the other configuration
* @return true
if different
*/
public boolean isTdbDifferent(Configuration config) {
Tdb configTdb = config.getTdb();
return (tdb == null) ? (configTdb != null) : !tdb.equals(configTdb);
}
/**
* Sets the Tdb for this configuration.
*
* @param tdb the Tdb to set
*/
public void setTdb(Tdb tdb) {
if (isSealed()) {
throw new IllegalStateException("Can't modify sealed configuration");
}
this.tdb = tdb;
}
/**
* Special operation allows setting the title database of one
* configuration from the title database of another. This operation
* is for use only within support routines in the "org.lockss.config"
* package.
*
* @param config another Configuration
*/
protected void setTdbFromConfig(Configuration config) {
this.tdb = config.getTdb();
}
/** Return a copy of the configuration with the specified prefix
* prepended to all keys. */
public Configuration addPrefix(String prefix) {
if (!prefix.endsWith(".")) {
prefix = prefix + ".";
}
Configuration res = ConfigManager.newConfiguration();
res.addAsSubTree(this, prefix);
return res;
}
/** Add to this all values in config, prepending the prefix to all keys */
public void addAsSubTree(Configuration config, String prefix) {
if (!prefix.endsWith(".")) {
prefix = prefix + ".";
}
for (Iterator iter = config.keyIterator(); iter.hasNext();) {
String key = (String)iter.next();
put(prefix + key, config.get(key));
}
}
/** Return a copy of the Configuration that does not share structure with
* the original. The copy is not sealed, even if the original was.
* @return a copy
*/
public Configuration copy() {
Configuration copy = ConfigManager.newConfiguration();
copy.copyFrom(this, null, null);
return copy;
}
public Configuration copyIntern(StringPool pool) {
Configuration copy = ConfigManager.newConfiguration();
copy.copyFrom(this, null, pool);
return copy;
}
interface ParamCopyEvent {
public void paramCopied(String name, String val);
}
/** Copy contents of the argument into this config. Duplicate
* keys will be overwritten.
* @other the config from which to copy
*/
public void copyFrom(Configuration other) {
copyFrom(other, null);
}
/** Copy contents of the argument into this config. Duplicate
* keys will be overwritten.
* @other the config from which to copy
* @pse null, or an event to be called for every param copied
*/
public void copyFrom(Configuration other, ParamCopyEvent pse) {
copyFrom(other, pse, null);
}
public void copyFrom(Configuration other, ParamCopyEvent pse, StringPool pool) {
// merge other config tree into this one
for (Iterator iter = other.keyIterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = other.get(key);
if (pool != null) {
key = pool.intern(key);
val = pool.internMapValue(key, val);
}
if (pse != null) {
pse.paramCopied(key, val);
}
put(key, val);
}
// merge other config Tdb into this one
Tdb otherTdb = other.getTdb();
if (otherTdb != null) {
if (tdb == null) {
tdb = new Tdb();
}
try {
tdb.copyFrom(otherTdb);
} catch (TdbException ex) {
log.error("Error copying into Tdb", ex);
}
}
}
/** Copy contents of the argument into this config. Duplicate keys
* will not be overwritten. Title Database is not copied.
*/
public void copyFromNonDestructively(Configuration other) {
for (Iterator iter = other.keyIterator(); iter.hasNext(); ) {
String key = (String)iter.next();
if (!containsKey(key)) {
put(key, other.get(key));
}
}
}
private ConfigCache getConfigCache() {
return ConfigManager.getConfigManager().getConfigCache();
}
/**
* Given a Config File, load its configuration into this one. This
* will overwrite any existing properties with the properties from
* the Config File.
*
* @throws IOException if error reading configuration
*/
void load(ConfigFile cf) throws IOException {
copyFrom(cf.getConfiguration());
}
/** Return the first ConfigFile that got an error */
public ConfigFile getFirstErrorFile(List urls) {
ConfigCache configCache = getConfigCache();
for (Iterator iter = urls.iterator(); iter.hasNext();) {
String url = (String)iter.next();
if (StringUtil.endsWithIgnoreCase(url, ".opt")) {
continue;
}
ConfigFile cf = configCache.get(url);
if (cf != null && !cf.isLoaded()) {
return cf;
}
}
return null;
}
/** Store as a properties file with a leading comment */
public abstract boolean store(OutputStream ostr, String header)
throws IOException;
/** Store as a properties file, including the properties in
* additionalProps, and with a leading comment, */
public abstract boolean store(OutputStream ostr, String header,
Properties additionalProps)
throws IOException;
/** Return the difference between this instance and another Configuration.
*
* An empty otherConfig is treated specially. if otherConfig is empty
* (or null), the contains() method always returns true
and
* the isEmpty() method always returns false
. Many parts of
* the system rely on the difference between any configuration and an
* empty configuration behaving in this way.
*
* @param otherConfig the config to compare with. May be null.
*/
public Configuration.Differences differences(Configuration otherConfig) {
return new Configuration.Differences(this, otherConfig);
}
/** Return the set of keys whose values differ.
*
* @param otherConfig the config to compare with. May be null.
*/
public abstract Set differentKeys(Configuration otherConfig);
/** Return true iff config has no keys or title database
*
* @return true
iff config has no keys or title databases
* */
public boolean isEmpty() {
if (keyIterator().hasNext()) {
return false;
}
Tdb tdb = getTdb();
if ((tdb != null) && !tdb.isEmpty()) {
return false;
}
return true;
}
/** Return the config value associated with key
.
* If the value is null or the key is missing, return dfault
.
*/
public String get(String key, String dfault) {
String val = get(key);
if (val == null) {
val = dfault;
}
return val;
}
/** Return the config value associated with key
.
* If the value is null or the empty string or the key is missing, return
* dfault
.
*/
public String getNonEmpty(String key, String dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
val = dfault;
}
return val;
}
private static Map boolStrings = new HashMap();
static {
boolStrings.put("true", Boolean.TRUE);
boolStrings.put("yes", Boolean.TRUE);
boolStrings.put("y", Boolean.TRUE);
boolStrings.put("on", Boolean.TRUE);
boolStrings.put("1", Boolean.TRUE);
boolStrings.put("false", Boolean.FALSE);
boolStrings.put("no", Boolean.FALSE);
boolStrings.put("n", Boolean.FALSE);
boolStrings.put("off", Boolean.FALSE);
boolStrings.put("0", Boolean.FALSE);
}
private Boolean stringToBool(String s) {
if (s == null) {
return null;
}
Boolean res = (Boolean)boolStrings.get(s);
if (res != null) {
return res;
} else {
return (Boolean)boolStrings.get(s.toLowerCase());
}
}
/** Return the config value as a boolean.
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as a boolean.
*/
public boolean getBoolean(String key) throws InvalidParam {
String val = get(key);
Boolean bool = stringToBool(val);
if (bool != null) {
return bool.booleanValue();
}
throw newInvalid("Not a boolean value: ", key, val);
}
/** Return the config value as a boolean. If it's missing, return the
* default value. If it's present but not parsable as a boolean, log a
* warning and return the default value.
*/
public boolean getBoolean(String key, boolean dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
return dfault;
}
Boolean bool = stringToBool(val);
if (bool != null) {
return bool.booleanValue();
}
log.warning("getBoolean(\"" + key + "\") = \"" + val + "\"");
return dfault;
}
/** Return the config value as an int.
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as an int.
*/
public int getInt(String key) throws InvalidParam {
String val = get(key);
try {
return Integer.parseInt(val);
} catch (NumberFormatException e) {
throw newInvalid("Not an int value: ", key, val);
}
}
/** Return the config value as an int. If it's missing, return the
* default value. If it's present but not parsable as an int, log a
* warning and return the default value
*/
public int getInt(String key, int dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
return dfault;
}
try {
return Integer.parseInt(val);
} catch (NumberFormatException e) {
log.warning("getInt(\'" + key + "\") = \"" + val + "\"");
return dfault;
}
}
/** Return the config value as an enum.
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as an enum.
*/
public T getEnum(final Class enumType, final String key)
throws InvalidParam {
String name = get(key);
try {
return (T)Enum.valueOf(enumType, name);
} catch (IllegalArgumentException e) {
throw newInvalid("Not an enum of type: " + enumType, key, name);
}
}
/** Return the config value as an enum. If it's missing, return the
* default value. If it's present but not parsable as an enum, log a
* warning and return the default value
*/
public T getEnum(final Class enumType,
final String key, T dfault) {
String name = get(key);
if (name == null) {
return dfault;
}
try {
return (T)Enum.valueOf(enumType, name);
} catch (IllegalArgumentException e) {
log.warning("getEnum(\'" + key + "\") illegal val: \"" + name + "\"");
return dfault;
}
}
/** Return the config value as an enum, matching case-independetly.
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as an enum.
*/
public T getEnumIgnoreCase(final Class enumType,
final String key)
throws InvalidParam {
String name = get(key);
try {
return (T)Enum.valueOf(enumType, name);
} catch (IllegalArgumentException e) {
for (T c : enumType.getEnumConstants()) {
if (c.toString().equalsIgnoreCase(name)) {
return c;
}
}
throw newInvalid("Not an enum of type: " + enumType, key, name);
}
}
/** Return the config value as an enum, matching case-independetly.
* If it's missing, return the default value. If it's present but
* not parsable as an enum, log a warning and return the default
* value
*/
public T getEnumIgnoreCase(final Class enumType,
final String key,
final T dfault) {
String name = get(key);
if (name == null) {
return dfault;
}
try {
return (T)Enum.valueOf(enumType, name);
} catch (IllegalArgumentException e) {
for (T c : enumType.getEnumConstants()) {
if (c.toString().equalsIgnoreCase(name)) {
return c;
}
}
log.warning("getEnum(\'" + key + "\") illegal val: \"" + name + "\"");
return dfault;
}
}
/**
* Return a list of values for the specified key.
*/
public abstract List getList(String key);
public List getList(String key, List dfault) {
if (get(key) != null) {
return getList(key);
} else {
return dfault;
}
}
/** Return the config value as a long.
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as a long.
*/
public long getLong(String key) throws InvalidParam {
String val = get(key);
try {
return Long.parseLong(val);
} catch (NumberFormatException e) {
throw newInvalid("Not a long value: ", key, val);
}
}
/** Return the config value as a long. If it's missing, return the
* default value. If it's present but not parsable as a long, log a
* warning and return the default value
*/
public long getLong(String key, long dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
return dfault;
}
try {
return Long.parseLong(val);
} catch (NumberFormatException e) {
log.warning("getLong(\'" + key + "\") = \"" + val + "\"");
return dfault;
}
}
/** Parse the config value as a time interval. An interval is specified
* as an integer with an optional suffix. No suffix means milliseconds,
* s, m, h, d, w indicates seconds, minutes, hours, days and weeks
* respectively.
* @param key the configuration parameter name
* @return time interval
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as a time interval.
*/
public long getTimeInterval(String key) throws InvalidParam {
String val = get(key);
try {
return StringUtil.parseTimeInterval(val);
} catch (Exception e) {
throw newInvalid("Not a time interval value: ", key, val);
}
}
/** Parse the config value as a time interval. An interval is specified
* as an integer with an optional suffix. No suffix means milliseconds,
* s, m, h, d, w indicates seconds, minutes, hours, days and weeks
* respectively. If the parameter is not present, return the
* default value. If it's present but not parsable as a long, log a
* warning and return the default value.
* @param key the configuration parameter name
* @param dfault the default value in milliseconds
* @return time interval
*/
public long getTimeInterval(String key, long dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
return dfault;
}
try {
return StringUtil.parseTimeInterval(val);
} catch (Exception e) {
log.warning("getTimeInterval(\'" + key + "\") = \"" + val + "\"");
return dfault;
}
}
/** Parse the config value as a size-in-bytes, specified
* as an integer with an optional suffix. No suffix means bytes,
* kb, mb, gb, tb indicate kilo-, mega-, giga, tera-bytes respectively.
* @param key the configuration parameter name
* @return size in bytes
* @throws Configuration.InvalidParam if the value is missing or
* not parsable as a time interval.
*/
public long getSize(String key) throws InvalidParam {
String val = get(key);
try {
return StringUtil.parseSize(val);
} catch (Exception e) {
throw newInvalid("Not a valid size: ", key, val);
}
}
/** Parse the config value as a size-in-bytes, specified
* as an integer with an optional suffix. No suffix means bytes,
* kb, mb, gb, tb indicate kilo-, mega-, giga, tera-bytes respectively.
* @param key the configuration parameter name
* @param dfault the default value in milliseconds
* @return size in bytes
*/
public long getSize(String key, long dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
return dfault;
}
try {
return StringUtil.parseSize(val);
} catch (Exception e) {
log.warning("getSize(\'" + key + "\") = \"" + val + "\"");
return dfault;
}
}
/** Parse the config value (which should be a non-negative integer) as a
* percentage, returning a positive float or 0.0. (Ie, "100"
* returns 1.0.) Percentages greater then 100 are allowed.
* @param key the configuration parameter name
* @return a float between 0.0 and 1.0
* @throws Configuration.InvalidParam if the value is missing or
* not an integer between 0 and 100.
*/
public float getPercentage(String key) throws InvalidParam {
int val = getInt(key);
if (val < 0) {
throw newInvalid("Not an integer >= 0: ", key, Integer.toString(val));
}
return ((float)val) / (float)100.0;
}
/** Parse the config value (which should be a non-negative integer) as a
* percentage, returning a positive float or 0.0. (Ie, "100"
* returns 1.0.) Percentages greater then 100 are allowed. If the
* parameter is not present, return the default value. If it's present
* but not parsable as an int, log a warning and return the default
* value.
* @param key the configuration parameter name
* @return a float between 0.0 and 1.0
*/
public float getPercentage(String key, double dfault) {
int val;
if (!containsKey(key)) {
return (float)dfault;
}
try {
val = getInt(key);
} catch (InvalidParam e) {
log.warning("getPercentage(\'" + key + "\") = \"" + get(key) + "\"");
return (float)dfault;
}
if (val < 0) {
log.warning("getPercentage(\'" + key + "\") = \"" + val + "\"");
return (float)dfault;
}
return ((float)val) / 100.0f;
}
/** Parse the config value as a floating point value
* @param key the configuration parameter name
* @return a double
* @throws Configuration.InvalidParam if the value is missing or not an
* float.
*/
public double getDouble(String key) throws InvalidParam {
String val = get(key);
try {
return PlatformUtil.parseDouble(val);
} catch (NumberFormatException e) {
throw newInvalid("Not a double value: ", key, val);
}
}
/** Parse the config value as a floating point value
* @param key the configuration parameter name
* @param dfault the default value
* @return a double
*/
public double getDouble(String key, double dfault) {
String val = get(key);
if (StringUtil.isNullString(val)) {
return dfault;
}
try {
return PlatformUtil.parseDouble(val);
} catch (NumberFormatException e) {
log.warning("getDouble(\'" + key + "\") = \"" + val + "\": " +
e.toString());
return dfault;
}
}
/** If val is of the form "@param_name
", and
* param_name is the name of a parameter set in this
* Configuration, return its value. If it is not set, return
* dfault. If val is not of the form
* "@param_name
" return it verbatim.
* @param key any String value, possibly beginning with @
.
* @param dfault the default value to return if the named param is not set
* @return a String
*/
public String getIndirect(String val, String dfault) {
if (val != null && val.startsWith("@")) {
String param = val.substring(1);
val = get(param, dfault);
}
return val;
}
InvalidParam newInvalid(String msg, String key, String val) {
return new InvalidParam(msg + key + " = " + quoteVal(val));
}
String quoteVal(String val) {
return val == null ? "(null)" : "\"" + val + "\"";
}
/** Remove the subtree below the specified key.
* @param rootKey The key at the root of the subtree to be deleted. This
* key and all below it are removed.
*/
public void removeConfigTree(String rootKey) {
Configuration subtree = getConfigTree(rootKey);
for (Iterator iter = subtree.keyIterator(); iter.hasNext(); ) {
String key = (String)iter.next();
remove(rootKey + "." + key);
}
remove(rootKey);
}
/**
* Copy the contents of another configuration relative to the
* specified "root" key.
*
* @param fromConfig The Configuration from which to copy.
* @param root The root key from which to copy.
*
*/
public void copyConfigTreeFrom(Configuration fromConfig, String root) {
Configuration subtree = fromConfig.getConfigTree(root);
if (subtree.isEmpty()) {
return;
}
for (Iterator iter = subtree.keyIterator(); iter.hasNext(); ) {
String relkey = (String)iter.next();
String key = root + "." + relkey;
put(key, fromConfig.get(key));
}
if (fromConfig.containsKey(root)) {
put(root, fromConfig.get(root));
}
}
void reset() {
tdb = null;
}
/** Return true iff the configurations have no differences.
* @param o the other object
* @return true
iff the configurations have no
* differences
*/
public boolean equals(Object o) {
// check for identity
if (this == o) {
return true;
}
if (! (o instanceof Configuration)) {
return false;
}
Configuration config = (Configuration)o;
// ensure the configurations have the same size
Set thisKeySet = keySet();
if (thisKeySet.size() != config.keySet().size()) {
return false;
}
// ensure the keys have the same values
for (String key : thisKeySet) {
Object thisVal = get(key);
Object configVal = config.get(key, noProp);
if ( (thisVal != configVal)
// configVal == noProp means config didn't have key that this config has.
// we know thisVal != configVal, so if thisVal == null, configVal != null.
&& ((configVal == noProp || thisVal == null || !thisVal.equals(configVal)))) {
return false;
}
}
// compare Tdbs
Tdb thisTdb = getTdb();
Tdb otherTdb = config.getTdb();
if (thisTdb == null) {
return (otherTdb == null);
}
return thisTdb.equals(otherTdb);
}
/** Throws UnsupportedOperationException always.
* @throws UnsupportedOperationException always
*/
public int hashCode() {
throw new UnsupportedOperationException();
}
// must be implemented by implementation subclass
/** Return true if the key is present
* @param key the key to test
* @return true if the key is present.
*/
public abstract boolean containsKey(String key);
/** Return the config value associated with key
.
* @return the string, or null if the key is not present
* or its value is null.
*/
public abstract String get(String key);
/** Return the config value associated with key
, returning
* null in place of the empty string.
* @return the string, or null if the key is not present
* or its value is null or the empty string.
*/
public abstract String getNonEmpty(String key);
/** Set the config value associated with key
.
* @param key the config key
* @param val the new value
*/
public abstract void put(String key, String val);
/** Remove the value associated with key
.
* @param key the config key to remove
*/
public abstract void remove(String key);
/** Seal the configuration so that no changes can be made */
public abstract void seal();
/** Return true iff the configuration is sealed */
public abstract boolean isSealed();
/** Returns a Configuration instance containing all the keys at or
* below key
. If none, returns an empty Configuration.
*/
public abstract Configuration getConfigTree(String key);
/** Returns the set of keys in the configuration.
*/
public abstract Set keySet();
/** Returns an Iterator
over all the keys in the configuration.
*/
public abstract Iterator keyIterator();
/** Returns an Iterator
over all the top level
keys in the configuration.
*/
public abstract Iterator nodeIterator();
/** Returns an Iterator
over the top-level keys in the
* configuration subtree below key
*/
public abstract Iterator nodeIterator(String key);
public Map toStringMap() {
Map res = new HashMap();
for (Iterator iter = keyIterator(); iter.hasNext();) {
String key = (String)iter.next();
String val = get(key);
res.put(key, val);
}
return res;
}
/**
* Provides a loggable version of the contents of a Configuration.
*
* @param config
* A Configuration with the configuartion to be logged.
* @return a String with the loggable version of the contents of the
* configuration.
*/
public static String loggableConfiguration(Configuration config,
String name) {
if (config == null || config.keySet().size() < 10) {
return name + " = " + config;
}
return name + ".keySet().size() = " + config.keySet().size();
}
// static convenience methods
/**
* The Configuration.Callback
interface defines the
* callback registered by clients of Configuration
* who want to know when the configuration has changed.
*/
public interface Callback {
/**
* Callback used to inform clients that something in the configuration
* has changed. It is called after the new config is installed as
* current, as well as upon registration (if there is a current
* configuration at the time). It is thus safe to rely solely on a
* configuration callback to receive configuration information.
* @param newConfig the new (just installed) Configuration
.
* @param oldConfig the previous Configuration
, or null
* if there was no previous config.
* @param changes the set of keys whose value has changed.
*/
public void configurationChanged(Configuration newConfig,
Configuration oldConfig,
Configuration.Differences changes);
/** Callback used to inform clients that an AU's config has been
* created anew or changed. */
default public void auConfigChanged(String auid) {
}
/** Callback used to inform clients that an AU's config has been
* deleted. */
default public void auConfigRemoved(String auid) {
}
}
/** Differences represents the changes in a Configuration from the
* previous Configuration. It allows Configuration.Callbacks to quickly
* determine whether a key of interest, or any key in a subtree of
* interest, has changed value since the previous call to
* configurationChanged().
*/
static public class Differences {
public static Differences ALL =
new Differences(ConfigManager.newConfiguration(), null);
private final boolean containsAllKeys;
private final Set diffKeys;
private final Tdb.Differences tdbDiffs;
/**
* Create instance for differences between thisConfig and otherConfig
* (generally the previous config, or null)
*
* A Configuration with no keys is treated specially. If otherConfig is
* null, or either configuration has no keys, the contains() method always
* returns true
. Many parts of the system rely on this behavior.
*
* @param thisConfig this configuration
* @param otherConfig the previous configuration
*/
protected Differences(Configuration thisConfig, Configuration otherConfig) {
if (thisConfig == null) {
throw new IllegalArgumentException("thisConfig cannot be null");
}
// contains all keys if other config is null, or either config has no keys
// (ensures Configuration differences() and equals() are translative)
if ((otherConfig == null) || otherConfig.keySet().isEmpty()) {
this.containsAllKeys = true;
this.diffKeys = thisConfig.keySet();
} else if (thisConfig.keySet().isEmpty()) {
this.containsAllKeys = true;
this.diffKeys = otherConfig.keySet();
} else {
// call differentKeys() iff necessary. It's expensive on huge
// config trees and this gets called repeatedly at start as plugins
// register their config callbacks.
this.containsAllKeys = false;
this.diffKeys = thisConfig.differentKeys(otherConfig);
}
this.tdbDiffs = Tdb.computeDifferences(thisConfig.getTdb(),
otherConfig == null ? null :
otherConfig.getTdb());
}
/**
* Determine whether this instance has no differences.
*
* @return true
if this instance has no differences
*/
public boolean isEmpty() {
return (getTdbAuDifferenceCount() == 0) && diffKeys.isEmpty();
}
/**
* Determine whether the value of a key has changed. Can also be used
* to determine whether there have been any changes in a named
* Configuration subtree. (Eg, if contains() is true of
* "org.lockss.foo.bar", it will also be true of "org.lockss.foo.",
* "org.lockss.foo", "org.lockss.", "org.lockss", "org." and "org".)
*
* @param key the key or key prefix
* @return true iff the value of the key, or any key in the tree below
* it, has changed.
*/
public boolean contains(String key) {
return (containsAllKeys) ? true : diffKeys.contains(key);
}
/**
* Return the set of changed keys.
* This method should not generally be used.
*
* @return the set of keys that have changed.
*/
public Set getDifferenceSet() {
return diffKeys;
}
/**
* Return the {@link Tdb.Differences} describing the changes in the Tdb
* between this configuration and the previous one
*
* @return a {@link Tdb.Differences}
*/
public Tdb.Differences getTdbDifferences() {
return tdbDiffs;
}
/**
* Return a set of pluginIDs for differences between two Tdbs.
* This method is normally used by plugins.
*
* @return a set of pluginIDs for differences between two Tdbs.
*/
public Set getTdbDifferencePluginIds() {
return tdbDiffs.getPluginIdsForDifferences();
}
/**
* Determines whether a plugin ID is involved in differences between
* to Tdbs. This method is normally used by plugins.
*
* @param pluginID the pluginID
* @return true