/* * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * 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 org.hawkular.inventory.api; import static java.util.stream.Collectors.toMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; /** * @author Lukas Krejci * @since 0.0.1 */ public final class Configuration { private final FeedIdStrategy feedIdStrategy; private final Map<String, String> implementationConfiguration; private final ResultFilter resultFilter; public static Builder builder() { return new Builder(); } public Configuration(FeedIdStrategy feedIdStrategy, ResultFilter resultFilter, Map<String, String> implementationConfiguration) { this.feedIdStrategy = feedIdStrategy; this.resultFilter = resultFilter; this.implementationConfiguration = implementationConfiguration; } public FeedIdStrategy getFeedIdStrategy() { return feedIdStrategy; } /** * @return the result filter to be used to filter the query results or null if none necessary */ public ResultFilter getResultFilter() { return resultFilter; } /** * Returns the value of the property. * * <p>If there is a system property with the name from {@link Property#getSystemPropertyNames()} ()}, then its value * is returned, otherwise if the property defines an environment variable and that env variable exists, then its * value is returned otherwise the value of a property with a name from {@link Property#getPropertyName()} * from the values loaded using {@link Builder#withConfiguration(Map)} or * {@link Builder#withConfiguration(Properties)} is returned. * * @param property the property to obtain * @return the value or null if none found */ public String getProperty(Property property, String defaultValue) { String value = null; List<String> systemProps = property.getSystemPropertyNames(); if (systemProps != null) { for (String sp : systemProps) { value = System.getProperty(sp); if (value != null) { break; } } } if (value == null) { List<String> envVars = property.getEnvironmentVariableNames(); if (envVars != null) { for (String ev : envVars) { value = System.getenv(ev); if (value != null) { break; } } } } if (value == null && property.getPropertyName() != null) { value = implementationConfiguration.get(property.getPropertyName()); } return value == null ? defaultValue : value; } public boolean getFlag(Property property, String defaultValue) { return Boolean.valueOf(getProperty(property, defaultValue)); } public boolean getFlag(String key, String defaultValue) { return getFlag(Property.builder().withPropertyNameAndSystemProperty(key).build(), defaultValue); } /** * Filters out all the configuration entries whose keys don't start with any of the white-listed prefixes * * @param prefixes * @return */ public Configuration prefixedWith(String... prefixes) { if (prefixes == null || prefixes.length == 0) { return this; } Map<String, String> filteredConfig = implementationConfiguration.entrySet().stream() .filter(e -> Arrays.stream(prefixes).anyMatch(p -> e.getKey() .startsWith(p))).collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); return new Configuration(feedIdStrategy, resultFilter, filteredConfig); } /** * Returns the implementation configuration with the values changed to conform the provided properties. * <p> * I.e. if a system property defined by some of the provided property objects overrides a property in the * implementation configuration, the value of the system property is used instead for that property (i.e. the key * stays the same but the value gets overridden to the value of the system property). * <p> * Additionally, if some of the {@code overridableProperties} are not present in this configuration, an attempt is * made to load them from the system properties or environment variables according to the rules specified at * {@link #getProperty(Property, String)}. * * @param overridableProperties the properties to override the implementation configuration with. * @return an overridden implementation configuration */ public Map<String, String> getImplementationConfiguration(Collection<? extends Property> overridableProperties) { return getOverriddenImplementationConfiguration(overridableProperties); } //this hoop is needed so that we can type-check the "T" through the stream manipulations private <T extends Property> Map<String, String> getOverriddenImplementationConfiguration( Collection<T> overridableProperties) { Map<String, String> ret = new HashMap<>(); overridableProperties.forEach(p -> { String val = getProperty(p, null); if (val != null) { ret.put(p.getPropertyName(), val); } }); implementationConfiguration.forEach(ret::putIfAbsent); return ret; } public static final class Builder { private FeedIdStrategy strategy; private ResultFilter resultFilter; private Map<String, String> configuration = new HashMap<>(); private Builder() { } public Builder withFeedIdStrategy(FeedIdStrategy strategy) { this.strategy = strategy; return this; } public Builder withResultFilter(ResultFilter resultFilter) { this.resultFilter = resultFilter; return this; } public Builder withConfiguration(Map<String, String> configuration) { this.configuration = configuration; return this; } public Builder withConfiguration(Properties properties) { Map<String, String> map = new HashMap<>(); properties.forEach((k, v) -> map.put(k.toString(), v.toString())); return withConfiguration(map); } public Builder addConfigurationProperty(String key, String value) { configuration.put(key, value); return this; } public Configuration build() { return new Configuration(strategy, resultFilter, configuration); } } /** * Represents a single configuration property. A property can be read either using a property name from a file, * a system property name (with potentially different name from the one used in the file) or potentially from an * environment variable. * * <p>See {@link Configuration#getProperty(Property, String)} for how the value of a property is resolved. */ public interface Property { /** * @return the name of the property */ String getPropertyName(); /** * @return the names of the system properties to try. */ List<String> getSystemPropertyNames(); /** * @return the names of an environment variables to try. By default this is empty, meaning there is no env * variable to use for this property. */ default List<String> getEnvironmentVariableNames() { return Collections.emptyList(); } static Builder builder() { return new Builder(); } class Builder { private String propertyName; private List<String> sysProps; private List<String> envVars; public Builder withPropertyName(String propertyName) { this.propertyName = propertyName; return this; } public Builder withPropertyNameAndSystemProperty(String propertyName) { return withPropertyName(propertyName).withSystemProperties(propertyName); } public Builder withSystemProperties(String... sysProps) { this.sysProps = Arrays.asList(sysProps); return this; } public Builder withEnvironmentVariables(String... envVars) { this.envVars = Arrays.asList(envVars); return this; } public Property build() { if (propertyName == null) { throw new IllegalStateException("A property needs to have a name defined."); } //capture the values in the builder as they are at this very moment - further changes to the builder //won't affect the constructed instance String propertyName = this.propertyName; List<String> sysProps = this.sysProps == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(this.sysProps)); List<String> envVars = this.envVars == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(this.envVars)); return new Property() { @Override public String getPropertyName() { return propertyName; } @Override public List<String> getSystemPropertyNames() { return sysProps; } @Override public List<String> getEnvironmentVariableNames() { return envVars; } }; } } } }