/* * Copyright 2015-2016 Norbert Potocki (norbert.potocki@nort.pl) * * 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.cfg4j.provider; import static java.util.Objects.requireNonNull; import com.github.drapostolos.typeparser.NoSuchRegisteredParserException; import com.github.drapostolos.typeparser.TypeParser; import com.github.drapostolos.typeparser.TypeParserException; import org.cfg4j.source.ConfigurationSource; import org.cfg4j.source.context.environment.Environment; import org.cfg4j.source.context.environment.MissingEnvironmentException; import org.cfg4j.validator.BindingValidator; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.NoSuchElementException; import java.util.Properties; /** * Basic implementation of {@link ConfigurationProvider}. To construct this provider use {@link ConfigurationProviderBuilder}. */ class SimpleConfigurationProvider implements ConfigurationProvider { private final ConfigurationSource configurationSource; private final Environment environment; /** * {@link ConfigurationProvider} backed by provided {@link ConfigurationSource} and using {@code environment} * to select environment. To construct this provider use {@link ConfigurationProviderBuilder}. * * @param configurationSource source for configuration * @param environment {@link Environment} to use */ SimpleConfigurationProvider(ConfigurationSource configurationSource, Environment environment) { this.configurationSource = requireNonNull(configurationSource); this.environment = requireNonNull(environment); } @Override public Properties allConfigurationAsProperties() { try { return configurationSource.getConfiguration(environment); } catch (IllegalStateException | MissingEnvironmentException e) { throw new IllegalStateException("Couldn't fetch configuration from configuration source", e); } } @Override public <T> T getProperty(String key, Class<T> type) { String propertyStr = getProperty(key); try { TypeParser parser = TypeParser.newBuilder().build(); return parser.parse(propertyStr, type); } catch (TypeParserException | NoSuchRegisteredParserException e) { throw new IllegalArgumentException("Unable to cast value \'" + propertyStr + "\' to " + type, e); } } @Override public <T> T getProperty(String key, GenericTypeInterface genericType) { String propertyStr = getProperty(key); try { TypeParser parser = TypeParser.newBuilder().build(); @SuppressWarnings("unchecked") T property = (T) parser.parseType(propertyStr, genericType.getType()); return property; } catch (TypeParserException | NoSuchRegisteredParserException e) { throw new IllegalArgumentException("Unable to cast value \'" + propertyStr + "\' to " + genericType, e); } } private String getProperty(String key) { try { Object property = configurationSource.getConfiguration(environment).get(key); if (property == null) { throw new NoSuchElementException("No configuration with key: " + key); } return property.toString(); } catch (IllegalStateException e) { throw new IllegalStateException("Couldn't fetch configuration from configuration source for key: " + key, e); } } @Override public <T> T bind(String prefix, Class<T> type) { return bind(this, prefix, type); } /** * Create an instance of a given {@code type} that will be bound to the {@code configurationProvider}. Each time configuration changes the * bound object will be updated with the new values. Use {@code prefix} to specify the relative path to configuration * values. Please note that each method of returned object can throw runtime exceptions. For details see javadoc for * {@link BindInvocationHandler#invoke(Object, Method, Object[])}. * * @param <T> interface describing configuration object to bind * @param prefix relative path to configuration values (e.g. "myContext" will map settings "myContext.someSetting", * "myContext.someOtherSetting") * @param type {@link Class} for {@code <T>} * @return configuration object bound to this {@link ConfigurationProvider} * @throws NoSuchElementException when the provided {@code key} doesn't have a corresponding config value * @throws IllegalArgumentException when property can't be coverted to {@code type} * @throws IllegalStateException when provider is unable to fetch configuration value for the given {@code key} */ <T> T bind(ConfigurationProvider configurationProvider, String prefix, Class<T> type) { @SuppressWarnings("unchecked") T proxy = (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, new BindInvocationHandler(configurationProvider, prefix)); new BindingValidator().validate(proxy, type); return proxy; } @Override public String toString() { return "SimpleConfigurationProvider{" + "configurationSource=" + configurationSource + ", environment=" + environment + '}'; } }