com.datastax.oss.driver.internal.core.config.typesafe.TypesafeDriverConfig Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.oss.driver.internal.core.config.typesafe;
import static com.typesafe.config.ConfigValueType.OBJECT;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.config.DriverOption;
import com.datastax.oss.driver.internal.core.util.Loggers;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigOriginFactory;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueFactory;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.net.URL;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ThreadSafe
public class TypesafeDriverConfig implements DriverConfig {
private static final Logger LOG = LoggerFactory.getLogger(TypesafeDriverConfig.class);
private static final ConfigOrigin DEFAULT_OVERRIDES_ORIGIN =
ConfigOriginFactory.newSimple("default was overridden programmatically");
private final ImmutableMap profiles;
// Only used to detect if reload saw any change
private volatile Config lastLoadedConfig;
private final Map defaultOverrides = new ConcurrentHashMap<>();
private final TypesafeDriverExecutionProfile.Base defaultProfile;
public TypesafeDriverConfig(Config config) {
this.lastLoadedConfig = config;
Map profileConfigs = extractProfiles(config);
ImmutableMap.Builder builder =
ImmutableMap.builder();
for (Map.Entry entry : profileConfigs.entrySet()) {
builder.put(
entry.getKey(),
new TypesafeDriverExecutionProfile.Base(entry.getKey(), entry.getValue()));
}
this.profiles = builder.build();
this.defaultProfile = profiles.get(DriverExecutionProfile.DEFAULT_NAME);
}
/** @return whether the configuration changed */
public boolean reload(Config config) {
config = applyDefaultOverrides(config);
if (config.equals(lastLoadedConfig)) {
return false;
} else {
lastLoadedConfig = config;
try {
Map profileConfigs = extractProfiles(config);
for (Map.Entry entry : profileConfigs.entrySet()) {
String profileName = entry.getKey();
TypesafeDriverExecutionProfile.Base profile = this.profiles.get(profileName);
if (profile == null) {
LOG.warn(
"Unknown profile '{}' while reloading configuration. "
+ "Adding profiles at runtime is not supported.",
profileName);
} else {
profile.refresh(entry.getValue());
}
}
return true;
} catch (Throwable t) {
Loggers.warnWithException(LOG, "Error reloading configuration, keeping previous one", t);
return false;
}
}
}
/*
* Processes the raw configuration to extract profiles. For example:
* {
* foo = 1, bar = 2
* profiles {
* custom1 { bar = 3 }
* }
* }
* Would produce:
* "default" => { foo = 1, bar = 2 }
* "custom1" => { foo = 1, bar = 3 }
*/
private Map extractProfiles(Config sourceConfig) {
ImmutableMap.Builder result = ImmutableMap.builder();
Config defaultProfileConfig = sourceConfig.withoutPath("profiles");
result.put(DriverExecutionProfile.DEFAULT_NAME, defaultProfileConfig);
// 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 profilesObject = (ConfigObject) rootObject.get("profiles");
for (String profileName : profilesObject.keySet()) {
if (profileName.equals(DriverExecutionProfile.DEFAULT_NAME)) {
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
public DriverExecutionProfile getDefaultProfile() {
return defaultProfile;
}
@NonNull
@Override
public DriverExecutionProfile getProfile(@NonNull String profileName) {
if (profileName.equals(DriverExecutionProfile.DEFAULT_NAME)) {
return defaultProfile;
}
return Optional.ofNullable(profiles.get(profileName))
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Unknown profile '%s'. Check your configuration.", profileName)));
}
@NonNull
@Override
public Map getProfiles() {
return profiles;
}
/**
* Replace the given options, only if the original values came from {@code
* reference.conf}: if the option was set explicitly in {@code application.conf}, then the
* override is ignored.
*
* The overrides are also taken into account in profiles, and survive reloads. If this method
* is invoked multiple times, the last value for each option will be used. Note that it is
* currently not possible to use {@code null} as a value.
*/
public void overrideDefaults(@NonNull Map overrides) {
defaultOverrides.putAll(overrides);
reload(lastLoadedConfig);
}
private Config applyDefaultOverrides(Config source) {
Config result = source;
for (Map.Entry entry : defaultOverrides.entrySet()) {
String path = entry.getKey().getPath();
Object value = entry.getValue();
if (isDefault(source, path)) {
LOG.debug("Replacing default value for {} by {}", path, value);
result =
result.withValue(
path, ConfigValueFactory.fromAnyRef(value).withOrigin(DEFAULT_OVERRIDES_ORIGIN));
} else {
LOG.debug(
"Ignoring default override for {} because the user has overridden the value", path);
}
}
return result;
}
// Whether the value in the given path comes from the reference.conf in the driver JAR.
private static boolean isDefault(Config config, String path) {
if (!config.hasPath(path)) {
return false;
}
ConfigOrigin origin = config.getValue(path).origin();
if (origin.equals(DEFAULT_OVERRIDES_ORIGIN)) {
// Same default was overridden twice, should use the last value
return true;
}
URL url = origin.url();
return url != null && url.toString().endsWith("reference.conf");
}
}