Skip to content

Commit

Permalink
Make configuration reloadable
Browse files Browse the repository at this point in the history
  • Loading branch information
olim7t committed Jun 13, 2017
1 parent a6b6f68 commit c871465
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 74 deletions.
Expand Up @@ -26,11 +26,11 @@
*
* <p>Getters (such as {@link #getBoolean(DriverOption)}) are self-explanatory.
*
* <p>{@code withXxx} methods (such as {@link #withBoolean(DriverOption, boolean)}) create an
* on-the-fly <b>copy</b> of the profile with the new value (which might be a new option, or
* overwrite an existing one). Such on-the-fly profiles should be used sparingly: each call creates
* a new instance; if you have multiple option to customize, it is better to create a profile in the
* base configuration.
* <p>{@code withXxx} methods (such as {@link #withBoolean(DriverOption, boolean)}) create a
* "derived" profile, which is an on-the-fly <b>copy</b> of the profile with the new value (which
* might be a new option, or overwrite an existing one). If the original configuration is reloaded,
* all derived profiles get updated as well. For best performance, such derived profiles should be
* used sparingly; it is better to have built-in profiles for common scenarios.
*
* @see DriverConfig
*/
Expand Down
Expand Up @@ -18,54 +18,115 @@
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import com.datastax.oss.driver.api.core.config.DriverOption;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.Collections;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.typesafe.config.ConfigValueType.OBJECT;

public class TypeSafeDriverConfig implements DriverConfig {

private final TypesafeDriverConfigProfile defaultProfile;
private final ConcurrentMap<String, TypesafeDriverConfigProfile> profiles;
private static final Logger LOG = LoggerFactory.getLogger(TypeSafeDriverConfig.class);
private static final String DEFAULT_PROFILE_KEY = "__default_internal__";

private final Collection<DriverOption> options;
private final TypesafeDriverConfigProfile.Base defaultProfile;
private final Map<String, TypesafeDriverConfigProfile.Base> profiles;

public TypeSafeDriverConfig(Config config, DriverOption[]... optionArrays) {
Collection<DriverOption> options = merge(optionArrays);

// Process the raw configuration to extract profiles. For example:
// {
// foo = 1, bar = 2
// profiles {
// custom1 { bar = 3 }
// }
// }
// Would produce a map with the following entries:
// "default" => { foo = 1, bar = 2 }
// "custom1" => { foo = 1, bar = 3 }

Config defaultProfileConfig = config.withoutPath("profiles");
this.options = merge(optionArrays);

Map<String, Config> profileConfigs = extractProfiles(config);
this.defaultProfile =
new TypesafeDriverConfigProfile.Base(profileConfigs.get(DEFAULT_PROFILE_KEY));

if (profileConfigs.size() == 1) {
this.profiles = Collections.emptyMap();
} else {
ImmutableMap.Builder<String, TypesafeDriverConfigProfile.Base> builder =
ImmutableMap.builder();
for (Map.Entry<String, Config> entry : profileConfigs.entrySet()) {
String profileName = entry.getKey();
if (!profileName.equals(DEFAULT_PROFILE_KEY)) {
builder.put(profileName, new TypesafeDriverConfigProfile.Base(entry.getValue()));
}
}
this.profiles = builder.build();
}
}

public void reload(Config config) {
Map<String, Config> profileConfigs = extractProfiles(config);
this.defaultProfile.refresh(profileConfigs.get(DEFAULT_PROFILE_KEY));
if (profileConfigs.size() > 1) {
for (Map.Entry<String, Config> entry : profileConfigs.entrySet()) {
String profileName = entry.getKey();
if (!profileName.equals(DEFAULT_PROFILE_KEY)) {
TypesafeDriverConfigProfile.Base profile = this.profiles.get(profileName);
if (profile == null) {
LOG.warn(
String.format(
"Unknown profile '%s' while reloading configuration. "
+ "Adding profiles at runtime is not supported.",
profileName));
} else {
profile.refresh(entry.getValue());
}
}
}
}
}

/*
* Processes the raw configuration to extract profiles. For example:
* {
* foo = 1, bar = 2
* profiles {
* custom1 { bar = 3 }
* }
* }
* Would produce:
* DEFAULT_PROFILE_KEY => { foo = 1, bar = 2 }
* "custom1" => { foo = 1, bar = 3 }
*/
private Map<String, Config> extractProfiles(Config sourceConfig) {
ImmutableMap.Builder<String, Config> result = ImmutableMap.builder();

Config defaultProfileConfig = sourceConfig.withoutPath("profiles");
validateRequired(defaultProfileConfig, options);
this.defaultProfile = new TypesafeDriverConfigProfile(defaultProfileConfig);
result.put(DEFAULT_PROFILE_KEY, defaultProfileConfig);

this.profiles = new ConcurrentHashMap<>();
ConfigObject rootObject = config.root();
// The rest of the method is a bit confusing because we navigate between Typesafe config's two
// APIs, see https://github.com/typesafehub/config#understanding-config-and-configobject
// In an attempt to clarify:
// xxxObject = `ConfigObject` API (config as a hierarchical structure)
// xxxConfig = `Config` API (config as a flat set of options with hierarchical paths)
ConfigObject rootObject = sourceConfig.root();
if (rootObject.containsKey("profiles") && rootObject.get("profiles").valueType() == OBJECT) {
ConfigObject profileConfigs = (ConfigObject) rootObject.get("profiles");
for (String profileName : profileConfigs.keySet()) {
ConfigValue profileValue = profileConfigs.get(profileName);
if (profileValue.valueType() == OBJECT) {
Config profileConfig = ((ConfigObject) profileValue).toConfig();
this.profiles.put(
profileName,
new TypesafeDriverConfigProfile(profileConfig.withFallback(defaultProfileConfig)));
ConfigObject profilesObject = (ConfigObject) rootObject.get("profiles");
for (String profileName : profilesObject.keySet()) {
if (profileName.equals(DEFAULT_PROFILE_KEY)) {
throw new IllegalArgumentException(
String.format(
"Can't have %s as a profile name because it's used internally. Pick another name.",
profileName));
}
ConfigValue profileObject = profilesObject.get(profileName);
if (profileObject.valueType() == OBJECT) {
Config profileConfig = ((ConfigObject) profileObject).toConfig();
result.put(profileName, profileConfig.withFallback(defaultProfileConfig));
}
}
}
return result.build();
}

@Override
Expand Down
Expand Up @@ -18,38 +18,34 @@
import com.datastax.oss.driver.api.core.ConsistencyLevel;
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import com.datastax.oss.driver.api.core.config.DriverOption;
import com.google.common.collect.MapMaker;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public class TypesafeDriverConfigProfile implements DriverConfigProfile {
// The base options loaded from the driver's configuration
private volatile Config base;
// Any extras that were configured manually using withXxx methods
private final Config extras;
// The actual options returned by getXxx methods (which is a merge of the previous two)
private volatile Config actual;
public abstract class TypesafeDriverConfigProfile implements DriverConfigProfile {

public TypesafeDriverConfigProfile(Config base) {
this(base, ConfigFactory.empty());
}
/** The original profile in the driver's configuration that this profile was derived from. */
protected abstract Base getBaseProfile();

private TypesafeDriverConfigProfile(Config base, Config extras) {
this.base = base;
this.extras = extras;
this.actual = extras.withFallback(base);
}
/** The extra options that were added with {@code withXxx} methods. */
protected abstract Config getAddedOptions();

/** The actual options that will be used to answer {@code getXxx} calls. */
protected abstract Config getEffectiveOptions();

@Override
public boolean isDefined(DriverOption option) {
return actual.hasPath(option.getPath());
return getEffectiveOptions().hasPath(option.getPath());
}

@Override
public boolean getBoolean(DriverOption option) {
return actual.getBoolean(option.getPath());
return getEffectiveOptions().getBoolean(option.getPath());
}

@Override
Expand All @@ -59,7 +55,7 @@ public DriverConfigProfile withBoolean(DriverOption option, boolean value) {

@Override
public int getInt(DriverOption option) {
return actual.getInt(option.getPath());
return getEffectiveOptions().getInt(option.getPath());
}

@Override
Expand All @@ -69,7 +65,7 @@ public DriverConfigProfile withInt(DriverOption option, int value) {

@Override
public Duration getDuration(DriverOption option) {
return actual.getDuration(option.getPath());
return getEffectiveOptions().getDuration(option.getPath());
}

@Override
Expand All @@ -79,7 +75,7 @@ public DriverConfigProfile withDuration(DriverOption option, Duration value) {

@Override
public String getString(DriverOption option) {
return actual.getString(option.getPath());
return getEffectiveOptions().getString(option.getPath());
}

@Override
Expand All @@ -89,7 +85,7 @@ public DriverConfigProfile withString(DriverOption option, String value) {

@Override
public List<String> getStringList(DriverOption option) {
return actual.getStringList(option.getPath());
return getEffectiveOptions().getStringList(option.getPath());
}

@Override
Expand All @@ -99,7 +95,7 @@ public DriverConfigProfile withStringList(DriverOption option, List<String> valu

@Override
public long getBytes(DriverOption option) {
return actual.getBytes(option.getPath());
return getEffectiveOptions().getBytes(option.getPath());
}

@Override
Expand All @@ -118,8 +114,102 @@ public DriverConfigProfile withConsistencyLevel(DriverOption option, Consistency
return with(option, value.toString());
}

private DriverConfigProfile with(DriverOption option, Object v) {
return new TypesafeDriverConfigProfile(
base, extras.withValue(option.getPath(), ConfigValueFactory.fromAnyRef(v)));
private DriverConfigProfile with(DriverOption option, Object value) {
Base base = getBaseProfile();
// Add the new option to any already derived options
Config newAdded =
getAddedOptions().withValue(option.getPath(), ConfigValueFactory.fromAnyRef(value));
Derived derived = new Derived(base, newAdded);
base.register(derived);
return derived;
}

/** A profile that was loaded directly from the driver's configuration. */
static class Base extends TypesafeDriverConfigProfile {

private volatile Config options;
private volatile Set<Derived> derivedProfiles;

Base(Config options) {
this.options = options;
}

@Override
protected Base getBaseProfile() {
return this;
}

@Override
protected Config getAddedOptions() {
return ConfigFactory.empty();
}

@Override
protected Config getEffectiveOptions() {
return options;
}

void refresh(Config newOptions) {
this.options = newOptions;
if (derivedProfiles != null) {
for (Derived derivedProfile : derivedProfiles) {
derivedProfile.refresh();
}
}
}

void register(Derived derivedProfile) {
getDerivedProfiles().add(derivedProfile);
}

// Lazy init
private Set<Derived> getDerivedProfiles() {
Set<Derived> result = derivedProfiles;
if (result == null) {
synchronized (this) {
result = derivedProfiles;
if (result == null) {
derivedProfiles =
result = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
}
}
}
return result;
}
}

/**
* A profile that was copied from another profile programatically using {@code withXxx} methods.
*/
static class Derived extends TypesafeDriverConfigProfile {

private final Base baseProfile;
private final Config addedOptions;
private volatile Config effectiveOptions;

Derived(Base baseProfile, Config addedOptions) {
this.baseProfile = baseProfile;
this.addedOptions = addedOptions;
refresh();
}

void refresh() {
this.effectiveOptions = addedOptions.withFallback(baseProfile.getEffectiveOptions());
}

@Override
protected Base getBaseProfile() {
return baseProfile;
}

@Override
protected Config getAddedOptions() {
return addedOptions;
}

@Override
protected Config getEffectiveOptions() {
return effectiveOptions;
}
}
}

0 comments on commit c871465

Please sign in to comment.