liquibase.configuration.LiquibaseConfiguration Maven / Gradle / Ivy
Show all versions of liquibase-core Show documentation
package liquibase.configuration;
import liquibase.Scope;
import liquibase.SingletonObject;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.servicelocator.ServiceLocator;
import liquibase.util.StringUtil;
import java.util.*;
/**
* Provides unified management of configuration properties within Liquibase core and in extensions.
*
* Because this class focuses on raw/untyped access to what is actually configured, it is usually best to interact with {@link ConfigurationDefinition} instances
* which provide type safety, standardized key naming, default values, and more.
*
* "Registered" configuration definitions will be available for generated help.
*
* This class will search through the configured {@link ConfigurationValueProvider}s. Standard value providers are auto-loaded on startup, but more can be added/removed at runtime.
*
*/
public class LiquibaseConfiguration implements SingletonObject {
private final SortedSet configurationValueProviders;
private final SortedSet> definitions = new TreeSet<>();
/**
* Track looked up values we have logged to avoid infinite loops between this and the log system using configurations
* and to limit logged messages.
* Only re-log when values changed from the last time they were logged.
*/
private final Map lastLoggedKeyValues = new HashMap<>();
protected LiquibaseConfiguration() {
configurationValueProviders = new TreeSet<>((o1, o2) -> {
if (o1.getPrecedence() < o2.getPrecedence()) {
return -1;
} else if (o1.getPrecedence() > o2.getPrecedence()) {
return 1;
}
return o1.getClass().getName().compareTo(o2.getClass().getName());
});
}
/**
* @deprecated use {@link Scope#getSingleton(Class)}
*/
public static LiquibaseConfiguration getInstance() {
return Scope.getCurrentScope().getSingleton(LiquibaseConfiguration.class);
}
/**
* Finishes configuration of this service. Called as the root scope is set up, should not be called elsewhere.
*/
public void init(Scope scope) {
configurationValueProviders.clear();
ServiceLocator serviceLocator = scope.getServiceLocator();
final List containers = serviceLocator.findInstances(AutoloadedConfigurations.class);
for (AutoloadedConfigurations container : containers) {
Scope.getCurrentScope().getLog(getClass()).fine("Found ConfigurationDefinitions in " + container.getClass().getName());
}
configurationValueProviders.addAll(serviceLocator.findInstances(ConfigurationValueProvider.class));
}
/**
* Adds a new {@link ConfigurationValueProvider} to the active collection of providers.
*/
public void registerProvider(ConfigurationValueProvider valueProvider) {
this.configurationValueProviders.add(valueProvider);
}
/**
* Removes the given {@link ConfigurationValueProvider} from the active collection of providers.
*
* @return true if the given provider was previously registered.
*/
public boolean unregisterProvider(ConfigurationValueProvider valueProvider) {
return this.configurationValueProviders.remove(valueProvider);
}
/**
* Removes a specific {@link ConfigurationValueProvider} from the active collection of providers.
*
* @return true if the provider was removed.
*/
public boolean removeProvider(ConfigurationValueProvider provider) {
return this.configurationValueProviders.remove(provider);
}
/**
* @deprecated use {@link ConfigurationDefinition} instances directly
*/
public T getConfiguration(Class type) {
try {
return type.newInstance();
} catch (Throwable e) {
throw new UnexpectedLiquibaseException(e);
}
}
public SortedSet getProviders() {
return Collections.unmodifiableSortedSet(this.configurationValueProviders);
}
/**
* Searches for the given keys in the current providers.
*
* @param keyAndAliases The first element should be the canonical key name, with later elements being aliases. At least one element must be provided.
* @return the value for the key, or null if not configured.
*/
public ConfiguredValue getCurrentConfiguredValue(ConfigurationValueConverter converter, ConfigurationValueObfuscator obfuscator, String... keyAndAliases) {
if (keyAndAliases == null || keyAndAliases.length == 0) {
throw new IllegalArgumentException("Must specify at least one key");
}
ConfiguredValue details = new ConfiguredValue<>(keyAndAliases[0], converter, obfuscator);
for (ConfigurationValueProvider provider : configurationValueProviders) {
final ProvidedValue providerValue = provider.getProvidedValue(keyAndAliases);
if (providerValue != null) {
details.override(providerValue);
}
}
final String foundValue = String.valueOf(details.getValue());
if (!foundValue.equals(lastLoggedKeyValues.get(keyAndAliases[0]))) {
lastLoggedKeyValues.put(keyAndAliases[0], foundValue);
//avoid infinite loop when logging is getting set up
if (details.found()) {
StringBuilder logMessage = new StringBuilder("Found '" + keyAndAliases[0] + "' configuration of '" + details.getValueObfuscated() + "'");
boolean foundFirstValue = false;
for (ProvidedValue providedValue : details.getProvidedValues()) {
logMessage.append("\n ");
if (foundFirstValue) {
logMessage.append("Overrides ");
}
logMessage.append(StringUtil.lowerCaseFirst(providedValue.describe()));
Object value = providedValue.getValue();
if (value != null) {
if (converter != null) {
value = converter.convert(value);
}
if (obfuscator != null) {
try {
value = obfuscator.obfuscate((DataType) value);
} catch (ClassCastException e) {
value = "*****";
}
}
logMessage.append(" of '").append(value).append("'");
}
foundFirstValue = true;
}
Scope.getCurrentScope().getLog(getClass()).fine(logMessage.toString());
} else {
Scope.getCurrentScope().getLog(getClass()).fine("No configuration value for " + StringUtil.join(keyAndAliases, " aka ") + " found");
}
}
return details;
}
/**
* Registers a {@link ConfigurationDefinition} so it will be returned by {@link #getRegisteredDefinitions(boolean)}
*/
public void registerDefinition(ConfigurationDefinition> definition) {
this.definitions.add(definition);
}
/**
* Returns all registered {@link ConfigurationDefinition}s. Registered definitions are used for generated help documentation.
* @param includeInternal if true, include {@link ConfigurationDefinition#isInternal()} definitions.
*/
public SortedSet> getRegisteredDefinitions(boolean includeInternal) {
SortedSet> returnSet = new TreeSet<>();
for (ConfigurationDefinition> definition : this.definitions) {
if (includeInternal || !definition.isInternal()) {
returnSet.add(definition);
}
}
return Collections.unmodifiableSortedSet(returnSet);
}
/**
* @return the registered {@link ConfigurationDefinition} asssociated with this key. Null if none match.
*/
public ConfigurationDefinition> getRegisteredDefinition(String key) {
for (ConfigurationDefinition> def : getRegisteredDefinitions(true)) {
if (def.getKey().equalsIgnoreCase(key)) {
return def;
}
final Set aliasKeys = def.getAliasKeys();
if (aliasKeys != null && aliasKeys.contains(key)) {
return def;
}
if(def.getKey().replace(".","").equalsIgnoreCase(key)) {
return def;
}
}
return null;
}
}