org.apache.deltaspike.core.api.config.ConfigResolver 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 org.apache.deltaspike.core.api.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.inject.Typed;
import org.apache.deltaspike.core.api.projectstage.ProjectStage;
import org.apache.deltaspike.core.spi.config.ConfigFilter;
import org.apache.deltaspike.core.spi.config.ConfigSource;
import org.apache.deltaspike.core.spi.config.ConfigSourceProvider;
import org.apache.deltaspike.core.util.ClassUtils;
import org.apache.deltaspike.core.util.ProjectStageProducer;
import org.apache.deltaspike.core.util.ServiceUtils;
/**
* Resolve the configuration via their well defined ordinals.
*
* You can provide your own lookup paths by implementing
* and registering additional {@link PropertyFileConfig} or
* {@link ConfigSource} or {@link ConfigSourceProvider} implementations.
*/
@Typed()
public final class ConfigResolver
{
private static final Logger LOG = Logger.getLogger(ConfigResolver.class.getName());
/**
* The content of this map will get lazily initiated and will hold the
* sorted List of ConfigSources for each WebApp/EAR, etc (thus the
* ClassLoader).
*/
private static Map configSources
= new ConcurrentHashMap();
/**
* The content of this map will hold the List of ConfigFilters
* for each WebApp/EAR, etc (thus the ClassLoader).
*/
private static Map> configFilters
= new ConcurrentHashMap>();
private static volatile ProjectStage projectStage = null;
private ConfigResolver()
{
// this is a utility class which doesn't get instantiated.
}
/**
* This method can be used for programmatically adding {@link ConfigSource}s.
* It is not needed for normal 'usage' by end users, but only for Extension Developers!
*
* @param configSourcesToAdd the ConfigSources to add
*/
public static synchronized void addConfigSources(List configSourcesToAdd)
{
// we first pickup all pre-configured ConfigSources...
getConfigSources();
// and now we can easily add our own
ClassLoader currentClassLoader = ClassUtils.getClassLoader(null);
ConfigSource[] configuredConfigSources = configSources.get(currentClassLoader);
List allConfigSources = new ArrayList();
allConfigSources.addAll(Arrays.asList(configuredConfigSources));
allConfigSources.addAll(configSourcesToAdd);
// finally put all the configSources back into the map
configSources.put(currentClassLoader, sortDescending(allConfigSources));
}
/**
* Clear all ConfigSources for the current ClassLoader
*/
public static synchronized void freeConfigSources()
{
configSources.remove(ClassUtils.getClassLoader(null));
}
/**
* Add a {@link ConfigFilter} to the ConfigResolver.
* This will only affect the current WebApp
* (or more precisely the current ClassLoader and it's children).
* @param configFilter
*/
public static void addConfigFilter(ConfigFilter configFilter)
{
List currentConfigFilters = getConfigFilters();
currentConfigFilters.add(configFilter);
}
/**
* @return the {@link ConfigFilter}s for the current application.
*/
public static List getConfigFilters()
{
ClassLoader cl = ClassUtils.getClassLoader(null);
List currentConfigFilters = configFilters.get(cl);
if (currentConfigFilters == null)
{
currentConfigFilters = new ArrayList();
configFilters.put(cl, currentConfigFilters);
}
return currentConfigFilters;
}
/**
* Resolve the property value by going through the list of configured {@link ConfigSource}s
* and use the one with the highest priority. If no configured value has been found that
* way we will use the defaultValue.
*
* @param key the property key.
* @param defaultValue will be used if no configured value for the key could be found.
* @return the configured property value from the {@link ConfigSource} with the highest ordinal or
* the defaultValue if there is no value explicitly configured.
*/
public static String getPropertyValue(String key, String defaultValue)
{
String value = getPropertyValue(key);
return fallbackToDefaultIfEmpty(key, value, defaultValue);
}
/**
* Resolve the property value by going through the list of configured {@link ConfigSource}s
* and use the one with the highest priority.
*
* @param key the property key.
* @return the configured property value from the {@link ConfigSource} with the highest ordinal or
* null if there is no configured value for it.
*/
public static String getPropertyValue(String key)
{
ConfigSource[] appConfigSources = getConfigSources();
String value;
for (ConfigSource configSource : appConfigSources)
{
value = configSource.getPropertyValue(key);
if (value != null)
{
LOG.log(Level.FINE, "found value {0} for key {1} in ConfigSource {2}.",
new Object[]{filterConfigValueForLog(key, value), key, configSource.getConfigName()});
return filterConfigValue(key, value);
}
LOG.log(Level.FINER, "NO value found for key {0} in ConfigSource {1}.",
new Object[]{key, configSource.getConfigName()});
}
return null;
}
/**
* Search for the configured value in all {@link ConfigSource}s and take the
* current {@link org.apache.deltaspike.core.api.projectstage.ProjectStage}
* into account.
*
* It first will search if there is a configured value of the given key prefixed
* with the current ProjectStage (e.g. 'myproject.myconfig.Production') and if this didn't
* find anything it will lookup the given key without any prefix.
*
* Attention This method must only be used after all ConfigSources
* got registered and it also must not be used to determine the ProjectStage itself.
* @param key
* @return the configured value or if non found the defaultValue
*
*/
public static String getProjectStageAwarePropertyValue(String key)
{
ProjectStage ps = getProjectStage();
String value = getPropertyValue(key + '.' + ps);
if (value == null)
{
value = getPropertyValue(key);
}
return value;
}
/**
* {@link #getProjectStageAwarePropertyValue(String)} which returns the defaultValue
* if the property is null
or empty.
* @param key
* @param defaultValue
* @return the configured value or if non found the defaultValue
*
*/
public static String getProjectStageAwarePropertyValue(String key, String defaultValue)
{
String value = getProjectStageAwarePropertyValue(key);
return fallbackToDefaultIfEmpty(key, value, defaultValue);
}
/**
* Search for the configured value in all {@link ConfigSource}s and take the
* current {@link org.apache.deltaspike.core.api.projectstage.ProjectStage}
* and the value configured for the given property into account.
*
* The first step is to resolve the value of the given property. This will
* take the current ProjectStage into account. E.g. given the property is 'dbvendor'
* and the ProjectStage is 'UnitTest', the first lookup is
*
- 'dbvendor.UnitTest'
.
* If this value is not found then we will do a 2nd lookup for
* - 'dbvendor'
*
* If a value was found for the given property (e.g. dbvendor = 'mysql'
* then we will use this value to lookup in the following order until we
* found a non-null value. If there was no value found for the property
* we will only do the key+ProjectStage and key lookup.
* In the following sample 'dataSource' is used as key parameter:
*
*
* - 'datasource.mysql.UnitTest'
* - 'datasource.mysql'
* - 'datasource.UnitTest'
* - 'datasource'
*
*
*
*
* Attention This method must only be used after all ConfigSources
* got registered and it also must not be used to determine the ProjectStage itself.
* @param key
* @param property the property to look up first
* @return the configured value or if non found the defaultValue
*
*/
public static String getPropertyAwarePropertyValue(String key, String property)
{
String propertyValue = getProjectStageAwarePropertyValue(property);
String value = null;
if (propertyValue != null && propertyValue.length() > 0)
{
value = getProjectStageAwarePropertyValue(key + '.' + propertyValue);
}
if (value == null)
{
value = getProjectStageAwarePropertyValue(key);
}
return value;
}
/*
* Attention This method must only be used after all ConfigSources
* got registered and it also must not be used to determine the ProjectStage itself.
* @param key
* @param property the property to look up first
* @param defaultValue
* @return the configured value or if non found the defaultValue
*
*/
public static String getPropertyAwarePropertyValue(String key, String property, String defaultValue)
{
String value = getPropertyAwarePropertyValue(key, property);
return fallbackToDefaultIfEmpty(key, value, defaultValue);
}
/**
* Resolve all values for the given key, from all registered ConfigSources ordered by their
* ordinal value in ascending ways. If more {@link ConfigSource}s have the same ordinal, their
* order is undefined.
*
* @param key under which configuration is stored
* @return List with all found property values, sorted in ascending order of their ordinal.
* @see org.apache.deltaspike.core.spi.config.ConfigSource#getOrdinal()
*/
public static List getAllPropertyValues(String key)
{
List appConfigSources =
sortAscending(new ArrayList(Arrays.asList(getConfigSources())));
List result = new ArrayList();
String value;
for (ConfigSource configSource : appConfigSources)
{
value = configSource.getPropertyValue(key);
if (value != null)
{
value = filterConfigValue(key, value);
if (!result.contains(value))
{
result.add(value);
}
}
}
return result;
}
public static Map getAllProperties()
{
List appConfigSources =
sortAscending(new ArrayList(Arrays.asList(getConfigSources())));
Map result = new HashMap();
for (ConfigSource configSource : appConfigSources)
{
if (configSource.isScannable())
{
result.putAll(configSource.getProperties());
}
}
return Collections.unmodifiableMap(result);
}
private static synchronized ConfigSource[] getConfigSources()
{
ClassLoader currentClassLoader = ClassUtils.getClassLoader(null);
ConfigSource[] appConfigSources = configSources.get(currentClassLoader);
if (appConfigSources == null)
{
appConfigSources = sortDescending(resolveConfigSources());
if (LOG.isLoggable(Level.FINE))
{
for (ConfigSource cs : appConfigSources)
{
LOG.log(Level.FINE, "Adding ordinal {0} ConfigSource {1}",
new Object[]{cs.getOrdinal(), cs.getConfigName()});
}
}
configSources.put(currentClassLoader, appConfigSources);
}
return appConfigSources;
}
private static List resolveConfigSources()
{
List appConfigSources = ServiceUtils.loadServiceImplementations(ConfigSource.class);
List configSourceProviderServiceLoader =
ServiceUtils.loadServiceImplementations(ConfigSourceProvider.class);
for (ConfigSourceProvider configSourceProvider : configSourceProviderServiceLoader)
{
appConfigSources.addAll(configSourceProvider.getConfigSources());
}
return appConfigSources;
}
private static ConfigSource[] sortDescending(List configSources)
{
Collections.sort(configSources, new Comparator()
{
/**
* {@inheritDoc}
*/
@Override
public int compare(ConfigSource configSource1, ConfigSource configSource2)
{
return (configSource1.getOrdinal() > configSource2.getOrdinal()) ? -1 : 1;
}
});
return configSources.toArray(new ConfigSource[configSources.size()]);
}
private static List sortAscending(List configSources)
{
Collections.sort(configSources, new Comparator()
{
/**
* {@inheritDoc}
*/
@Override
public int compare(ConfigSource configSource1, ConfigSource configSource2)
{
return (configSource1.getOrdinal() > configSource2.getOrdinal()) ? 1 : -1;
}
});
return configSources;
}
private static ProjectStage getProjectStage()
{
if (projectStage == null)
{
synchronized (ConfigResolver.class)
{
projectStage = ProjectStageProducer.getInstance().getProjectStage();
}
}
return projectStage;
}
private static String fallbackToDefaultIfEmpty(String key, String value, String defaultValue)
{
if (value == null || value.length() == 0)
{
LOG.log(Level.FINE, "no configured value found for key {0}, using default value {1}.",
new Object[]{key, defaultValue});
return defaultValue;
}
return value;
}
private static String filterConfigValue(String key, String value)
{
List currentConfigFilters = getConfigFilters();
String filteredValue = value;
for (ConfigFilter filter : currentConfigFilters)
{
filteredValue = filter.filterValue(key, filteredValue);
}
return filteredValue;
}
private static String filterConfigValueForLog(String key, String value)
{
List currentConfigFilters = getConfigFilters();
String logValue = value;
for (ConfigFilter filter : currentConfigFilters)
{
logValue = filter.filterValueForLog(key, logValue);
}
return logValue;
}
}