All Downloads are FREE. Search and download functionalities are using the official Maven repository.

liquibase.configuration.ConfigurationDefinition Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package liquibase.configuration;

import liquibase.Scope;
import liquibase.command.CommandArgumentDefinition;
import liquibase.util.ObjectUtil;
import liquibase.util.StringUtil;

import java.util.*;
import java.util.regex.Pattern;

/**
 * A higher-level/detailed definition to provide type-safety, metadata, default values, etc..
 * Any code that is working with configurations should be using an instance of this class, rather than the lower-level, generic {@link LiquibaseConfiguration}
 * 

* ConfigurationDefinitions that are registered with {@link LiquibaseConfiguration#registerDefinition(ConfigurationDefinition)} will * be available in generated help etc. *

* These objects are immutable, so to construct definitions, use {@link Builder} *

* The definition keys should be dot-separated, camelCased names, using a unique "namespace" as part of it. * For example:

yourCorp.yourProperty
or
yourCorp.sub.otherProperty
. * Liquibase uses "liquibase" as the base namespace like
liquibase.shouldRun
*/ public class ConfigurationDefinition implements Comparable> { private final String key; private final Set aliasKeys = new TreeSet<>(); private final Class dataType; private String description; private DataType defaultValue; private String defaultValueDescription; private boolean commonlyUsed; private boolean internal; private ConfigurationValueConverter valueConverter; private ConfigurationValueObfuscator valueObfuscator; private static final String ALLOWED_KEY_REGEX = "[a-zA-Z0-9._]+"; private static final Pattern ALLOWED_KEY_PATTERN = Pattern.compile(ALLOWED_KEY_REGEX); private boolean loggedUsingDefault = false; private boolean hidden = false; /** * Constructor private to force {@link Builder} usage * * @throws IllegalArgumentException if an invalid key is specified. */ private ConfigurationDefinition(String key, Class dataType) throws IllegalArgumentException { if (!ALLOWED_KEY_PATTERN.matcher(key).matches()) { throw new IllegalArgumentException("Invalid key format: " + key); } this.key = key; this.dataType = dataType; this.valueConverter = value -> ObjectUtil.convert(value, dataType); } /** * Convenience method around {@link #getCurrentConfiguredValue(ConfigurationValueProvider...)} to return the value. */ public DataType getCurrentValue() { final Object value = getCurrentConfiguredValue().getProvidedValue().getValue(); try { return (DataType) value; } catch (ClassCastException e) { throw new IllegalArgumentException("The current value of " + key + " not the expected type: " + e.getMessage(), e); } } public ConfigurationValueConverter getValueConverter() { return valueConverter; } /** * Convenience method around {@link #getCurrentConfiguredValue(ConfigurationValueProvider...)} to return the obfuscated version of the value. * * @return the obfuscated value, or the plain-text value if no obfuscator is defined for this definition. */ public DataType getCurrentValueObfuscated() { return getCurrentConfiguredValue().getValueObfuscated(); } /** * @return Full details on the current value for this definition. * Will always return a {@link ConfiguredValue}, */ public ConfiguredValue getCurrentConfiguredValue() { return getCurrentConfiguredValue(new ConfigurationValueProvider[]{}); } /** * @return Full details on the current value for this definition. * Will always return a {@link ConfiguredValue}, * * @param additionalValueProviders additional {@link ConfigurationValueProvider}s to use with higher priority than the ones registered in {@link LiquibaseConfiguration}. The higher the array index, the higher the priority. */ public ConfiguredValue getCurrentConfiguredValue(ConfigurationValueProvider... additionalValueProviders) { final LiquibaseConfiguration liquibaseConfiguration = Scope.getCurrentScope().getSingleton(LiquibaseConfiguration.class); List keyList = new ArrayList<>(); keyList.add(this.getKey()); keyList.addAll(this.getAliasKeys()); ConfiguredValue configurationValue = liquibaseConfiguration.getCurrentConfiguredValue(valueConverter, valueObfuscator, additionalValueProviders, keyList.toArray(new String[0])); if (!configurationValue.found()) { defaultValue = this.getDefaultValue(); if (defaultValue != null) { DataType obfuscatedValue; if (valueObfuscator == null) { obfuscatedValue = defaultValue; } else { obfuscatedValue = valueObfuscator.obfuscate(defaultValue); } if (!loggedUsingDefault) { Scope.getCurrentScope().getLog(getClass()).fine("Configuration " + key + " is using the default value of " + obfuscatedValue); loggedUsingDefault = true; } configurationValue.override(new DefaultValueProvider(this.getDefaultValue()).getProvidedValue(key)); } } final ProvidedValue providedValue = configurationValue.getProvidedValue(); final Object originalValue = providedValue.getValue(); try { final DataType finalValue = valueConverter.convert(originalValue); if (originalValue != finalValue) { configurationValue.override(new ConvertedValueProvider<>(finalValue, providedValue).getProvidedValue(key)); } return (ConfiguredValue) configurationValue; } catch (IllegalArgumentException e) { throw new IllegalArgumentException("An invalid " + (providedValue.getSourceDescription().toLowerCase() + " value " + providedValue.getActualKey() + " detected: " + StringUtil.lowerCaseFirst(e.getMessage())), e); } } /** * The standard configuration key for this definition. See the {@link ConfigurationDefinition} class-level docs on key format. */ public String getKey() { return key; } /** * @return alternate configuration keys to check for values. Used for backwards compatibility. */ public Set getAliasKeys() { return aliasKeys; } /** * @return the type of data this definition returns. */ public Class getDataType() { return dataType; } /** * A user-friendly description of this definition. * This will be exposed to end-users in generated help. */ public String getDescription() { return description; } /** * The default value used by this definition if no value is currently configured. *

* NOTE: this is only used if none of the {@link ConfigurationValueProvider}s have a configuration for the property. * Even if some return "null", that is still considered a provided value to use rather than this default. */ public DataType getDefaultValue() { return defaultValue; } /** * A description of the default value. Defaults to {@link String#valueOf(Object)} of {@link #getDefaultValue()} but * can be explicitly with {@link CommandArgumentDefinition.Building#defaultValue(Object, String)}. */ public String getDefaultValueDescription() { return defaultValueDescription; } /** * Returns true if this is configuration users are often interested in setting. * Used to simplify generated help by hiding less commonly used settings. */ public boolean getCommonlyUsed() { return commonlyUsed; } /** * Return true if this configuration is for internal and/or programmatic use only. * End-user facing integrations should not expose internal configurations directly. */ public boolean isInternal() { return internal; } /** * Return true if this configuration should not be printed to the console for any help command. */ public boolean isHidden() { return hidden; } @Override public int compareTo(ConfigurationDefinition o) { return this.getKey().compareTo(o.getKey()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ConfigurationDefinition that = (ConfigurationDefinition) o; return Objects.equals(key, that.key); } @Override public int hashCode() { return Objects.hash(key); } /** * Return true if the given key matches this definition. */ public boolean equalsKey(String key) { if (key == null) { return false; } if (getKey().equalsIgnoreCase(key)) { return true; } for (String alias : getAliasKeys()) { if (alias.equalsIgnoreCase(key)) { return true; } } return false; } /** * Used to construct new {@link ConfigurationDefinition} instances. */ public static class Builder { private final String defaultKeyPrefix; /** * @param defaultKeyPrefix the prefix to add to new keys that are not fully qualified */ public Builder(String defaultKeyPrefix) { if (!ALLOWED_KEY_PATTERN.matcher(defaultKeyPrefix).matches()) { throw new IllegalArgumentException("Invalid prefix format: " + defaultKeyPrefix); } this.defaultKeyPrefix = defaultKeyPrefix; } /** * Starts a new definition with the given key. Always adds the defaultKeyPrefix. */ public Building define(String key, Class dataType) { final ConfigurationDefinition definition = new ConfigurationDefinition<>(defaultKeyPrefix + "." + key, dataType); return new Building<>(definition, defaultKeyPrefix); } } public static class Building { private final ConfigurationDefinition definition; private final String defaultKeyPrefix; private Building(ConfigurationDefinition definition, String defaultKeyPrefix) { this.definition = definition; this.defaultKeyPrefix = defaultKeyPrefix; } public Building addAliasKey(String alias) { if (!ALLOWED_KEY_PATTERN.matcher(alias).matches()) { throw new IllegalArgumentException("Invalid alias format: " + alias); } definition.aliasKeys.add(alias); return this; } public Building setDescription(String description) { definition.description = description; return this; } public Building setDefaultValue(DataType defaultValue, String defaultValueDescription) { definition.defaultValue = defaultValue; definition.defaultValueDescription = defaultValueDescription; if (defaultValue != null && defaultValueDescription == null) { definition.defaultValueDescription = String.valueOf(defaultValue); } return this; } public Building setDefaultValue(DataType defaultValue) { definition.defaultValue = defaultValue; return this; } public Building setValueHandler(ConfigurationValueConverter handler) { definition.valueConverter = handler; return this; } public Building setValueObfuscator(ConfigurationValueObfuscator handler) { definition.valueObfuscator = handler; return this; } public Building setCommonlyUsed(boolean commonlyUsed) { definition.commonlyUsed = commonlyUsed; return this; } public Building setInternal(boolean internal) { definition.internal = internal; return this; } public Building setHidden(boolean hidden) { definition.hidden = hidden; return this; } public Building addAliases(Collection aliases) { for (String alias : aliases) { if (!alias.contains(".")) { alias = defaultKeyPrefix + "." + alias; addAliasKey(alias); } } return this; } /** * Finishes building this definition AND registers it with {@link LiquibaseConfiguration#registerDefinition(ConfigurationDefinition)}. * To not register this definition, use {@link #buildTemporary()} */ public ConfigurationDefinition build() { Scope.getCurrentScope().getSingleton(LiquibaseConfiguration.class).registerDefinition(definition); return definition; } /** * Finishes building this definition WITHOUT registering it with {@link LiquibaseConfiguration#registerDefinition(ConfigurationDefinition)}. * To automatically register this definition, use {@link #build()} */ public ConfigurationDefinition buildTemporary() { return definition; } } /** * Used to track configuration values set by a default */ static final class DefaultValueProvider extends AbstractConfigurationValueProvider { private final Object value; public DefaultValueProvider(Object value) { this.value = value; } @Override public int getPrecedence() { return -1; } @Override public ProvidedValue getProvidedValue(String... keyAndAliases) { return new ProvidedValue(keyAndAliases[0], keyAndAliases[0], value, "Default value", this); } } /** * Used to track configuration values converted by a handler */ private static final class ConvertedValueProvider extends AbstractConfigurationValueProvider { private final DataType value; private final String originalSource; private final String actualKey; public ConvertedValueProvider(DataType value, ProvidedValue originalProvidedValue) { this.value = value; this.actualKey = originalProvidedValue.getActualKey(); this.originalSource = originalProvidedValue.getSourceDescription(); } @Override public int getPrecedence() { return -1; } @Override public ProvidedValue getProvidedValue(String... keyAndAliases) { return new ProvidedValue(keyAndAliases[0], actualKey, value, originalSource, this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy