Java tutorial
/** * Copyright (C) 2012 Ness Computing, Inc. * * 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.nesscomputing.config; import java.net.URI; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import org.apache.commons.configuration.AbstractConfiguration; import org.apache.commons.configuration.CombinedConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.MapConfiguration; import org.apache.commons.configuration.SystemConfiguration; import org.apache.commons.configuration.tree.OverrideCombiner; import org.skife.config.CommonsConfigSource; import org.skife.config.ConfigurationObjectFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.nesscomputing.config.util.ImmutableConfiguration; /** * Load configurations from a hierarchy of configuration files. A hierarchy is defined as "a/b/c/d/..." more local * configurations being further right. A property defined in a more local configuration overrides a more global * definition. * * Configuration is loaded either as "<name>.properties" files or from "<name>/config.properties". All configuration * is loaded relative to a "configuration location". * * A configuration location can be given using the explicit constructor or through environment variables (ness.config and ness.config.location). */ public final class Config { private static final Logger LOG = LoggerFactory.getLogger(Config.class); /** Java system property for setting the configuration. */ public static final String CONFIG_PROPERTY_NAME = "ness.config"; public static final String CONFIG_LOCATION_PROPERTY_NAME = "ness.config.location"; private static final Object NULL_OBJECT = new Object(); private final ConcurrentMap<Object, ConfigurationObjectFactory> objectFactories = Maps.newConcurrentMap(); private final CombinedConfiguration config; /** * Creates a fixed configuration for the supplied {@link AbstractConfiguration} objects. Only key/value * pairs from these objects will be present in the final configuration. * * There is no implicit override from system properties. */ public static Config getFixedConfig(@Nullable final AbstractConfiguration... configs) { final CombinedConfiguration cc = new CombinedConfiguration(new OverrideCombiner()); if (configs != null) { for (final AbstractConfiguration config : configs) { cc.addConfiguration(config); } } return new Config(cc); } /** * Creates a fixed configuration for the supplied Map. Only key/value * pairs from this map will be present in the final configuration. * * There is no implicit override from system properties. */ public static Config getFixedConfig(@Nonnull Map<String, String> config) { return getFixedConfig(new MapConfiguration(config)); } /** * Creates a fixed configuration for the supplied key/value pairs. The number of elements passed in must be an * even number of elements, otherwise the last one is ignored. * * Config objects created from this method will not accept overrides from system properties. */ public static Config getFixedConfig(final String... keyValuePairs) { final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); if (keyValuePairs != null) { for (final Iterator<String> it = Arrays.asList(keyValuePairs).iterator(); it.hasNext();) { final String key = it.next(); if (it.hasNext()) { builder.put(key, it.next()); } else { LOG.warn("Found an odd number of arguments for key/value pairs. Ignoring key %s", key); break; } } } return getFixedConfig(new MapConfiguration(builder.build())); } /** * Return an empty configuration object without any properties set. This object is immutable so every * bean created from this config object will only have the defaults. This is mostly useful for testing. */ public static Config getEmptyConfig() { return new Config(new CombinedConfiguration(new OverrideCombiner())); } /** * Loads the configuration. The no-args method uses system properties to determine which configurations * to load. * * -Dness.config=x/y/z defines a hierarchy of configurations from general * to detail. * * The ness.config.location variable must be set. * If the ness.config variable is unset, the default value "default" is used. * * @throws IllegalStateException If the ness.config.location variable is not set. */ public static Config getConfig() { final Configuration systemConfig = new SystemConfiguration(); final String configName = systemConfig.getString(CONFIG_PROPERTY_NAME); final String configLocation = systemConfig.getString(CONFIG_LOCATION_PROPERTY_NAME); Preconditions.checkState(configLocation != null, "Config location must be set!"); final ConfigFactory configFactory = new ConfigFactory(URI.create(configLocation), configName); return new Config(configFactory.load()); } /** * Load Configuration, using the supplied URI as base. The loaded configuration can be overridden using * system properties. */ public static Config getConfig(@Nonnull final URI configLocation, @Nullable final String configName) { final ConfigFactory configFactory = new ConfigFactory(configLocation, configName); return new Config(configFactory.load()); } /** * Create a new configuration object from an existing object using overrides. If no overrides are passed in, the same object is returned. * * the */ public static Config getOverriddenConfig(@Nonnull final Config config, @Nullable final AbstractConfiguration... overrideConfigurations) { if (overrideConfigurations == null || overrideConfigurations.length == 0) { return config; } final CombinedConfiguration cc = new CombinedConfiguration(new OverrideCombiner()); int index = 0; final AbstractConfiguration first = config.config.getNumberOfConfigurations() > 0 ? AbstractConfiguration.class.cast(config.config.getConfiguration(index)) // cast always succeeds, internally this returns cd.getConfiguration() which is AbstractConfiguration : null; // If the passed in configuration has a system config, add this as the very first one so // that system properties override still works. if (first != null && first.getClass() == SystemConfiguration.class) { cc.addConfiguration(first); index++; } else { // Otherwise, if any of the passed in configuration objects is a SystemConfiguration, // put that at the very beginning. for (AbstractConfiguration c : overrideConfigurations) { if (c.getClass() == SystemConfiguration.class) { cc.addConfiguration(c); } } } for (AbstractConfiguration c : overrideConfigurations) { if (c.getClass() != SystemConfiguration.class) { cc.addConfiguration(c); // Skip system configuration objects, they have been added earlier. } } // Finally, add the existing configuration elements at lowest priority. while (index < config.config.getNumberOfConfigurations()) { final AbstractConfiguration c = AbstractConfiguration.class .cast(config.config.getConfiguration(index++)); if (c.getClass() != SystemConfiguration.class) { cc.addConfiguration(c); } } return new Config(cc); } /** * Load system configuration, using the supplied configLocation as base. The config location is converted * into an URI first. */ public static Config getConfig(@Nonnull final String configLocation, @Nullable final String configName) { final ConfigFactory configFactory = new ConfigFactory(URI.create(configLocation), configName); return new Config(configFactory.load()); } private Config(@Nonnull final CombinedConfiguration config) { this.config = config; } public AbstractConfiguration getConfiguration() { return new ImmutableConfiguration(config); } public AbstractConfiguration getConfiguration(final String prefix) { return new ImmutableConfiguration(config.subset(prefix)); } public <T> T getBean(Class<T> classType) { return getBean(null, classType, null); } public <T> T getBean(final String prefix, final Class<T> classType) { return getBean(prefix, classType, null); } public <T> T getBean(final Class<T> classType, final Map<String, String> replacements) { return getBean(null, classType, replacements); } public <T> T getBean(final String prefix, final Class<T> classType, final Map<String, String> replacements) { ConfigurationObjectFactory factory = null; Object key = Objects.firstNonNull(prefix, NULL_OBJECT); factory = objectFactories.get(key); if (factory == null) { Configuration cfg = prefix == null ? config : config.subset(prefix); factory = new ConfigurationObjectFactory(new CommonsConfigSource(cfg)); ConfigurationObjectFactory newFactory = objectFactories.putIfAbsent(key, factory); factory = Objects.firstNonNull(newFactory, factory); } return factory.buildWithReplacements(classType, replacements); } private transient String toStringValue = null; @Override public String toString() { if (config == null) { return "<uninitialized>"; } if (toStringValue == null) { StringBuilder sb = new StringBuilder("["); for (Iterator<?> it = config.getKeys(); it.hasNext();) { String key = (String) it.next(); sb.append(key); sb.append("->"); sb.append(config.getString(key)); if (it.hasNext()) { sb.append(", "); } } sb.append(']'); toStringValue = sb.toString(); } return toStringValue; } }