com.helger.config.source.MultiConfigurationValueProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-config Show documentation
Show all versions of ph-config Show documentation
General purpose configuration library with different sources
The newest version!
/*
* 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.source;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Function;
import javax.annotation.Nonnegative;
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.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.CommonsHashSet;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsSet;
import com.helger.commons.io.resource.FileSystemResource;
import com.helger.commons.lang.ClassLoaderHelper;
import com.helger.commons.lang.ICloneable;
import com.helger.commons.string.ToStringGenerator;
import com.helger.config.value.ConfiguredValue;
import com.helger.config.value.IConfigurationValueProvider;
import com.helger.config.value.IConfigurationValueProviderWithPriorityCallback;
/**
* An implementation of {@link IConfigurationValueProvider} that supports
* multiple sources, ordered by priority, descending.
*
* @author Philip Helger
*/
public class MultiConfigurationValueProvider implements
IConfigurationValueProvider,
ICloneable
{
private static final class ConfigValueProviderWithPrio
{
private final IConfigurationValueProvider m_aCVP;
private final int m_nPriority;
public ConfigValueProviderWithPrio (@Nonnull final IConfigurationValueProvider aCVP, final int nPrio)
{
m_aCVP = aCVP;
m_nPriority = nPrio;
}
@Override
public String toString ()
{
return new ToStringGenerator (null).append ("ConfigValProvider", m_aCVP)
.append ("Priority", m_nPriority)
.getToString ();
}
}
public static final boolean DEFAULT_USE_ONLY_INTIIALIZED_CONFIG_SOURCES = true;
private static final Logger LOGGER = LoggerFactory.getLogger (MultiConfigurationValueProvider.class);
private final ICommonsList m_aSources = new CommonsArrayList <> ();
private boolean m_bUseOnlyInitializedConfigSources = DEFAULT_USE_ONLY_INTIIALIZED_CONFIG_SOURCES;
/**
* Default constructor without any configuration source.
*/
public MultiConfigurationValueProvider ()
{}
/**
* Constructor with a list of existing configuration sources.
*
* @param aSources
* The list of existing sources to be added. The order will be
* maintained. May be null
but may not contain
* null
values.
* @see #addConfigurationSource(IConfigurationSource)
*/
public MultiConfigurationValueProvider (@Nullable final List extends IConfigurationSource> aSources)
{
if (aSources != null)
for (final IConfigurationSource aSource : aSources)
addConfigurationSource (aSource);
}
/**
* Constructor with an array of existing configuration sources.
*
* @param aSources
* The array of existing sources to be added. The order will be
* maintained. May be null
but may not contain
* null
values.
* @see #addConfigurationSource(IConfigurationSource)
*/
public MultiConfigurationValueProvider (@Nullable final IConfigurationSource... aSources)
{
if (aSources != null)
for (final IConfigurationSource aSource : aSources)
addConfigurationSource (aSource);
}
/**
* @return true
if this multi value provider accepts only
* configuration sources that were initialized and are usable. The
* default is {@value #DEFAULT_USE_ONLY_INTIIALIZED_CONFIG_SOURCES}.
* @see IConfigurationSource#isInitializedAndUsable()
*/
public final boolean isUseOnlyInitializedConfigSources ()
{
return m_bUseOnlyInitializedConfigSources;
}
/**
* Enable or disable the usage of only initialized configuration sources.
*
* @param bUseOnlyInitializedConfigSources
* true
to only allow the usage of initialized
* configuration sources.
* @return this for chaining
*/
@Nonnull
public final MultiConfigurationValueProvider setUseOnlyInitializedConfigSources (final boolean bUseOnlyInitializedConfigSources)
{
m_bUseOnlyInitializedConfigSources = bUseOnlyInitializedConfigSources;
return this;
}
/**
* Add a configuration source. The priority of the configuration source is
* used.
*
* @param aSource
* The source to be added. May not be null
.
* @return this for chaining
*/
@Nonnull
public final MultiConfigurationValueProvider addConfigurationSource (@Nonnull final IConfigurationSource aSource)
{
ValueEnforcer.notNull (aSource, "ConfigSource");
if (m_bUseOnlyInitializedConfigSources && !aSource.isInitializedAndUsable ())
{
// Don't add
LOGGER.warn ("Not adding the configuration source " + aSource + " because it is not yet initialized");
return this;
}
return addConfigurationSource (aSource, aSource.getPriority ());
}
/**
* Add a configuration value provider and a priority. The passed priority
* overwrites the priority contained in the value provider. This method sorts
* the internal list of sources based on the registered priorities.
*
* @param aCVP
* The configuration value provider to be added. May be
* null
.
* @param nPriority
* The priority to be used.
* @return this for chaining
*/
@Nonnull
public final MultiConfigurationValueProvider addConfigurationSource (@Nullable final IConfigurationValueProvider aCVP,
final int nPriority)
{
if (aCVP != null)
{
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Adding configuration source " + aCVP + " with priority " + nPriority);
m_aSources.add (new ConfigValueProviderWithPrio (aCVP, nPriority));
// Ensure entry with highest priority comes first
m_aSources.sort ( (x, y) -> y.m_nPriority - x.m_nPriority);
}
return this;
}
/**
* @return The number of contained configuration sources. Always ≥ 0.
*/
@Nonnegative
public final int getConfigurationSourceCount ()
{
return m_aSources.size ();
}
@Nullable
public ConfiguredValue getConfigurationValue (@Nonnull @Nonempty final String sKey)
{
if (LOGGER.isTraceEnabled ())
LOGGER.trace ("Trying to resolve configuration value of key '" +
sKey +
"' in " +
m_aSources.size () +
" sources");
ConfiguredValue ret = null;
for (final ConfigValueProviderWithPrio aSource : m_aSources)
{
ret = aSource.m_aCVP.getConfigurationValue (sKey);
if (ret != null)
{
// Use the first one that is not null
break;
}
}
if (ret != null)
{
if (LOGGER.isTraceEnabled ())
LOGGER.trace ("Successfully resolved configuration value of key '" +
sKey +
"' to '" +
ret.getValue () +
"' from " +
ret.getConfigurationSource ().getSourceType () +
" with prio " +
ret.getConfigurationSource ().getPriority ());
}
else
{
if (LOGGER.isTraceEnabled ())
LOGGER.trace ("Failed to resolve configuration value of key '" + sKey + "'");
}
return ret;
}
/**
* Scan through all added configuration sources and invoke the callback.
*
* @param aCallback
* The callback to be invoked. May not be null
.
*/
public void forEachConfigurationValueProvider (@Nonnull final IConfigurationValueProviderWithPriorityCallback aCallback)
{
ValueEnforcer.notNull (aCallback, "aCallback");
for (final ConfigValueProviderWithPrio aSource : m_aSources)
aCallback.onConfigurationValueProvider (aSource.m_aCVP, aSource.m_nPriority);
}
/**
* Return a deep clone of this {@link MultiConfigurationValueProvider}. If the
* contained {@link IConfigurationValueProvider} implements {@link ICloneable}
* it is cloned as well.
*/
@Nonnull
@ReturnsMutableCopy
public MultiConfigurationValueProvider getClone ()
{
final MultiConfigurationValueProvider ret = new MultiConfigurationValueProvider ();
for (final ConfigValueProviderWithPrio aSource : m_aSources)
{
if (aSource.m_aCVP instanceof ICloneable >)
{
final IConfigurationValueProvider aCVPClone = (IConfigurationValueProvider) ((ICloneable >) aSource.m_aCVP).getClone ();
ret.m_aSources.add (new ConfigValueProviderWithPrio (aCVPClone, aSource.m_nPriority));
}
else
ret.m_aSources.add (aSource);
}
return ret;
}
@Override
public String toString ()
{
return new ToStringGenerator (this).append ("Sources", m_aSources)
.append ("UseOnlyInitializedConfigSources", m_bUseOnlyInitializedConfigSources)
.getToString ();
}
/**
* Load all classpath elements with the same name.
*
* @param aClassLoader
* The class loader to be used. May not be null
.
* @param sClassPathElement
* The name of the class path element(s) to load. May not be
* null
.
* @param aLoader
* The loader that converts all matching URLs to
* {@link IConfigurationSource} objects. With this implementation you
* can differentiate the type of the content.
* @return May be null
if no resource was found.
*/
@Nullable
public static MultiConfigurationValueProvider createForClassPath (@Nonnull final ClassLoader aClassLoader,
@Nonnull final String sClassPathElement,
@Nonnull final Function aLoader)
{
return createForAllOccurrances (aClassLoader, sClassPathElement, aLoader, false);
}
/**
* Load all classpath elements and files with the same name.
*
* @param aClassLoader
* The class loader to be used. May not be null
.
* @param sPathName
* The name of the class path element(s) to load. May not be
* null
.
* @param aLoader
* The loader that converts all matching URLs to
* {@link IConfigurationSource} objects. With this implementation you
* can differentiate the type of the content.
* @param bCheckForFile
* true
to also check for a file with the same name.
* @return May be null
if no resource was found.
*/
@Nullable
public static MultiConfigurationValueProvider createForAllOccurrances (@Nonnull final ClassLoader aClassLoader,
@Nonnull final String sPathName,
@Nonnull final Function aLoader,
final boolean bCheckForFile)
{
ValueEnforcer.notNull (aClassLoader, "ClassLoader");
ValueEnforcer.notNull (sPathName, "ClassPathElement");
ValueEnforcer.notNull (aLoader, "Loader");
final ICommonsSet aUsedURLs = new CommonsHashSet <> ();
final MultiConfigurationValueProvider ret = new MultiConfigurationValueProvider ();
try
{
final Enumeration aEnum = ClassLoaderHelper.getResources (aClassLoader, sPathName);
while (aEnum.hasMoreElements ())
{
final URL aURL = aEnum.nextElement ();
if (!aUsedURLs.add (aURL.toExternalForm ()))
{
LOGGER.warn ("Ignoring duplicate configuration source URL '" + aURL.toExternalForm () + "'");
continue;
}
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Try to load configuration source from '" + aURL.toExternalForm () + "'");
final IConfigurationSource aConfigSource = aLoader.apply (aURL);
if (aConfigSource == null)
throw new IllegalStateException ("Failed to load configration source '" + aURL.toExternalForm () + "'");
ret.addConfigurationSource (aConfigSource);
}
}
catch (final IOException ex)
{
throw new UncheckedIOException (ex);
}
if (bCheckForFile)
{
// Check for file system as well
final FileSystemResource aRes = new FileSystemResource (sPathName);
if (aRes.exists ())
{
final URL aURL = aRes.getAsURL ();
if (!aUsedURLs.add (aURL.toExternalForm ()))
{
LOGGER.warn ("Ignoring duplicate configuration source URL '" + aURL.toExternalForm () + "'");
}
else
{
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Try to load configuration source from '" + aURL.toExternalForm () + "'");
final IConfigurationSource aConfigSource = aLoader.apply (aURL);
if (aConfigSource == null)
throw new IllegalStateException ("Failed to load configration source '" + aURL.toExternalForm () + "'");
ret.addConfigurationSource (aConfigSource);
}
}
}
if (ret.getConfigurationSourceCount () == 0)
{
// Avoid returning an empty object
return null;
}
return ret;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy