com.helger.config.Config Maven / Gradle / Ivy
/*
* Copyright (C) 2014-2024 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed 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.helger.config;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.impl.CommonsLinkedHashSet;
import com.helger.commons.collection.impl.ICommonsOrderedSet;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.ToStringGenerator;
import com.helger.commons.text.util.TextVariableHelper;
import com.helger.config.source.IConfigurationSource;
import com.helger.config.source.MultiConfigurationValueProvider;
import com.helger.config.value.ConfiguredValue;
import com.helger.config.value.IConfigurationValueProvider;
import com.helger.config.value.IConfigurationValueProviderWithPriorityCallback;
/**
* Default implementation of {@link IConfig}. It is recommended to use
* {@link ConfigFactory} for accessing {@link IConfig} objects.
*
* @author Philip Helger
*/
public class Config implements IConfig
{
/**
* For backwards compatibility reason, variable replacement is enabled by
* default since v11.0.2.
*/
public static final boolean DEFAULT_REPLACE_VARIABLES = true;
/**
* By default the usage of variable default is allowed. Since 11.1.11
*/
public static final boolean DEFAULT_USE_VARIABLE_DEFAULT_VALUES = true;
public static final UnaryOperator DEFAULT_UNRESOLVED_VARIABLE_PROVIDER = x -> "unresolved-var(" + x + ")";
private static final Logger LOGGER = LoggerFactory.getLogger (Config.class);
private final IConfigurationValueProvider m_aValueProvider;
private BiConsumer m_aKeyFoundConsumer;
private Consumer m_aKeyNotFoundConsumer;
private boolean m_bReplaceVariables = DEFAULT_REPLACE_VARIABLES;
private boolean m_bUseVariableDefaultValues = DEFAULT_USE_VARIABLE_DEFAULT_VALUES;
private UnaryOperator m_aUnresolvedVariableProvider = DEFAULT_UNRESOLVED_VARIABLE_PROVIDER;
/**
* Constructor
*
* @param aValueProvider
* The main configuration value provider. May not be null
.
*/
public Config (@Nonnull final IConfigurationValueProvider aValueProvider)
{
ValueEnforcer.notNull (aValueProvider, "ValueProvider");
m_aValueProvider = aValueProvider;
}
/**
* @return The configuration value provider as provided in the constructor.
* Never null
.
*/
@Nonnull
public final IConfigurationValueProvider getConfigurationValueProvider ()
{
return m_aValueProvider;
}
/**
* @return The callback to be invoked if a configuration value was found. May
* be null
.
*/
@Nullable
public final BiConsumer getFoundKeyConsumer ()
{
return m_aKeyFoundConsumer;
}
/**
* @param aKeyFoundConsumer
* The callback to be invoked if a configuration value was found. The
* parameters are key and value. May be null
.
* @return this for chaining
* @since 9.4.5
*/
@Nullable
public final Config setFoundKeyConsumer (@Nullable final BiConsumer aKeyFoundConsumer)
{
m_aKeyFoundConsumer = aKeyFoundConsumer;
return this;
}
/**
* @return The callback to be invoked if a configuration value was not
* found. May be null
.
*/
@Nullable
public final Consumer getKeyNotFoundConsumer ()
{
return m_aKeyNotFoundConsumer;
}
/**
* @param aKeyNotFoundConsumer
* The callback to be invoked if a configuration value was not
* found. The parameter is the key. May be null
.
* @return this for chaining
* @since 9.4.5
*/
@Nullable
public final Config setKeyNotFoundConsumer (@Nullable final Consumer aKeyNotFoundConsumer)
{
m_aKeyNotFoundConsumer = aKeyNotFoundConsumer;
return this;
}
/**
* @return true
if variables in configuration properties should
* be replaced, false
if not. The default value is
* {@value #DEFAULT_REPLACE_VARIABLES}.
* @since 10.2.0
*/
public final boolean isReplaceVariables ()
{
return m_bReplaceVariables;
}
/**
* Enable or disable the replacement of variables in configuration values.
*
* @param bReplaceVariables
* true
to enable replacement, false
to
* disable it.
* @return this for chaining
* @since 10.2.0
*/
@Nonnull
public final Config setReplaceVariables (final boolean bReplaceVariables)
{
m_bReplaceVariables = bReplaceVariables;
return this;
}
/**
* @return true
if variables are allowed to have default values
* in the form ${variable:default}
, false
if
* not. The default value is
* {@value #DEFAULT_USE_VARIABLE_DEFAULT_VALUES}.
* @since 11.1.1
*/
public final boolean isUseVariableDefaultValues ()
{
return m_bUseVariableDefaultValues;
}
/**
* Enable or disable the usage of default values in variables of configuration
* values.
*
* @param bUseVariableDefaultValues
* true
to allow the usage, false
to disable
* it.
* @return this for chaining
* @since 11.1.1
*/
@Nonnull
public final Config setUseVariableDefaultValues (final boolean bUseVariableDefaultValues)
{
m_bUseVariableDefaultValues = bUseVariableDefaultValues;
return this;
}
/**
* @return The unresolved variable provider to be used. Never
* null
.
*/
@Nonnull
public final UnaryOperator getUnresolvedVariableProvider ()
{
return m_aUnresolvedVariableProvider;
}
/**
* Set the handler to be invoked when a variable could not be resolved.
*
* @param aUnresolvedVariableProvider
* The unresolved variable provider to be used. May not be
* null
.
* @return this for chaining
* @since 10.2.0
*/
@Nonnull
public final Config setUnresolvedVariableProvider (@Nonnull final UnaryOperator aUnresolvedVariableProvider)
{
ValueEnforcer.notNull (aUnresolvedVariableProvider, "UnresolvedVariableProvider");
m_aUnresolvedVariableProvider = aUnresolvedVariableProvider;
return this;
}
@Nullable
public ConfiguredValue getConfiguredValue (@Nullable final String sKey)
{
// Resolve value
final ConfiguredValue ret;
if (StringHelper.hasNoText (sKey))
ret = null;
else
ret = m_aValueProvider.getConfigurationValue (sKey);
// Call consumers if configured
if (ret != null)
{
if (m_aKeyFoundConsumer != null)
m_aKeyFoundConsumer.accept (sKey, ret);
}
else
{
if (m_aKeyNotFoundConsumer != null)
m_aKeyNotFoundConsumer.accept (sKey);
}
return ret;
}
@Nonnull
@Nonempty
private String _getWithVariablesReplacedRecursive (@Nonnull @Nonempty final String sConfiguredValue,
@Nonnull final ICommonsOrderedSet aUsedVarContainer)
{
final UnaryOperator aVarProvider = sVarDecl -> {
final String sVarName;
final String sVarDefaultValue;
if (m_bUseVariableDefaultValues)
{
// Default values are allowed
final int nColonIdx = sVarDecl.indexOf (':');
sVarName = nColonIdx < 0 ? sVarDecl : sVarDecl.substring (0, nColonIdx);
sVarDefaultValue = nColonIdx < 0 ? null : sVarDecl.substring (nColonIdx + 1);
}
else
{
// Default values are disabled
sVarName = sVarDecl;
sVarDefaultValue = null;
}
if (!aUsedVarContainer.add (sVarName))
{
// Variable is used more then once
throw new IllegalStateException ("Found a variable cyclic dependency: " +
StringHelper.imploder ()
.source (aUsedVarContainer, y -> '"' + y + '"')
.separator (" -> ")
.build () +
" -> \"" +
sVarName +
'"');
}
// First time usage of variable name
final ConfiguredValue aCV = getConfiguredValue (sVarName);
String sNestedConfiguredValue;
if (aCV == null)
{
// Failed to resolve variable
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Failed to resolve configuration variable '" + sVarName + "'");
if (sVarDefaultValue != null)
{
// Default value might be an empty string
sNestedConfiguredValue = sVarDefaultValue;
}
else
{
// No default value provided
return m_aUnresolvedVariableProvider.apply (sVarName);
}
}
else
{
// We take the configured value
sNestedConfiguredValue = aCV.getValue ();
}
if (StringHelper.hasText (sNestedConfiguredValue))
{
// Recursive call to replace variables in the resolved configuration
// value
sNestedConfiguredValue = _getWithVariablesReplacedRecursive (sNestedConfiguredValue, aUsedVarContainer);
}
// Remove the variable again, because resolution worked so far
aUsedVarContainer.remove (sVarName);
return sNestedConfiguredValue;
};
// Main replacement with
return TextVariableHelper.getWithReplacedVariables (sConfiguredValue, aVarProvider);
}
@Nullable
public String getValue (@Nullable final String sKey)
{
final ConfiguredValue aCV = getConfiguredValue (sKey);
if (aCV == null)
return null;
String sConfiguredValue = aCV.getValue ();
if (m_bReplaceVariables && StringHelper.hasText (sConfiguredValue))
{
if (LOGGER.isTraceEnabled ())
LOGGER.trace ("Resolving variables in configuration value '" + sConfiguredValue + "'");
try
{
sConfiguredValue = _getWithVariablesReplacedRecursive (sConfiguredValue, new CommonsLinkedHashSet <> ());
}
catch (final IllegalStateException ex)
{
// Handle exception only on top-level
LOGGER.error ("Failed to replace variables in configuration value '" +
sConfiguredValue +
"': " +
ex.getMessage ());
}
}
return sConfiguredValue;
}
private static void _forEachConfigurationValueProviderRecursive (@Nonnull final IConfigurationValueProvider aValueProvider,
final int nParentPriority,
@Nonnull final IConfigurationValueProviderWithPriorityCallback aCallback)
{
if (aValueProvider instanceof MultiConfigurationValueProvider)
{
final MultiConfigurationValueProvider aMulti = (MultiConfigurationValueProvider) aValueProvider;
// Descend recursively
aMulti.forEachConfigurationValueProvider ( (cvp, prio) -> _forEachConfigurationValueProviderRecursive (cvp,
prio,
aCallback));
}
else
{
// By default no priority
int nPriority = nParentPriority;
if (nPriority < 0 && aValueProvider instanceof IConfigurationSource)
{
// Top-level configuration source
final IConfigurationSource aSource = (IConfigurationSource) aValueProvider;
nPriority = aSource.getPriority ();
}
aCallback.onConfigurationValueProvider (aValueProvider, nPriority);
}
}
public static void forEachConfigurationValueProviderRecursive (@Nonnull final IConfigurationValueProvider aValueProvider,
@Nonnull final IConfigurationValueProviderWithPriorityCallback aCallback)
{
_forEachConfigurationValueProviderRecursive (aValueProvider, -1, aCallback);
}
public void forEachConfigurationValueProvider (@Nonnull final IConfigurationValueProviderWithPriorityCallback aCallback)
{
ValueEnforcer.notNull (aCallback, "Callback");
forEachConfigurationValueProviderRecursive (m_aValueProvider, aCallback);
}
@Override
public String toString ()
{
return new ToStringGenerator (this).append ("ValueProvider", m_aValueProvider)
.append ("KeyFoundConsumer", m_aKeyFoundConsumer)
.append ("KeyNotFoundConsumer", m_aKeyNotFoundConsumer)
.append ("ReplaceVariables", m_bReplaceVariables)
.append ("UseVariableDefaultValues", m_bUseVariableDefaultValues)
.append ("UnresolvedVariableProvider", m_aUnresolvedVariableProvider)
.getToString ();
}
@Nonnull
public static Config create (@Nonnull final IConfigurationValueProvider aCVP)
{
return new Config (aCVP);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy