Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load properties for @ConfigurationSource on @Provides methods #575

Open
wants to merge 1 commit into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* @author elandau
*
*/
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigurationSource {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.netflix.archaius.guice;

import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.name.Names;
import com.google.inject.spi.DefaultBindingTargetVisitor;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProvidesMethodBinding;
import com.google.inject.spi.ProvisionListener;
import com.netflix.archaius.ConfigMapper;
import com.netflix.archaius.api.CascadeStrategy;
Expand All @@ -16,15 +20,18 @@
import com.netflix.archaius.api.exceptions.ConfigException;
import com.netflix.archaius.api.inject.LibrariesLayer;
import com.netflix.archaius.cascade.NoCascadeStrategy;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Provider;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;

public class ConfigurationInjectingListener implements ProvisionListener {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationInjectingListener.class);
Expand All @@ -43,7 +50,11 @@ public class ConfigurationInjectingListener implements ProvisionListener {

@com.google.inject.Inject(optional = true)
private CascadeStrategy cascadeStrategy;


private Map<Binding<?>, ConfigurationSourceHolder> bindingToHolder = new ConcurrentHashMap<>();

private ConfigMapper mapper = new ConfigMapper();

@Inject
public static void init(ConfigurationInjectingListener listener) {
LOG.info("Initializing ConfigurationInjectingListener");
Expand All @@ -52,63 +63,114 @@ public static void init(ConfigurationInjectingListener listener) {
CascadeStrategy getCascadeStrategy() {
return cascadeStrategy != null ? cascadeStrategy : NoCascadeStrategy.INSTANCE;
}

private ConfigMapper mapper = new ConfigMapper();

@Override
public <T> void onProvision(ProvisionInvocation<T> provision) {
Class<?> clazz = provision.getBinding().getKey().getTypeLiteral().getRawType();

//
// Configuration Loading
//
final ConfigurationSource source = clazz.getDeclaredAnnotation(ConfigurationSource.class);
if (source != null) {
if (injector == null) {
LOG.warn("Can't inject configuration into {} until ConfigurationInjectingListener has been initialized", clazz.getName());
return;
}

CascadeStrategy strategy = source.cascading() != ConfigurationSource.NullCascadeStrategy.class
? injector.getInstance(source.cascading()) : getCascadeStrategy();

List<String> sources = Arrays.asList(source.value());
Collections.reverse(sources);
for (String resourceName : sources) {
LOG.debug("Trying to loading configuration resource {}", resourceName);
try {
CompositeConfig loadedConfig = loader
.newLoader()
.withCascadeStrategy(strategy)
.load(resourceName);
libraries.addConfig(resourceName, loadedConfig);
} catch (ConfigException e) {
throw new ProvisionException("Unable to load configuration for " + resourceName, e);
}

private static class ConfigurationSourceHolder {
private final ConfigurationSource configurationSource;
private final Configuration configuration;
private final AtomicBoolean loaded = new AtomicBoolean();

public ConfigurationSourceHolder(ConfigurationSource configurationSource, Configuration configuration) {
this.configurationSource = configurationSource;
this.configuration = configuration;
}

public void ifNotLoaded(BiConsumer<ConfigurationSource, Configuration> consumer) {
if (loaded.compareAndSet(false, true) && (configurationSource != null || configuration != null)) {
consumer.accept(configurationSource, configuration);
}
}

//
// Configuration binding
//
Configuration configAnnot = clazz.getAnnotation(Configuration.class);
if (configAnnot != null) {
if (injector == null) {
LOG.warn("Can't inject configuration into {} until ConfigurationInjectingListener has been initialized", clazz.getName());
return;
}

private ConfigurationSourceHolder getConfigurationSource(Binding<?> binding) {
final ConfigurationSourceHolder holder = bindingToHolder.get(binding);
if (holder != null) {
return holder;
}
return bindingToHolder.computeIfAbsent(binding, this::createConfigurationSourceHolder);
}

private ConfigurationSourceHolder createConfigurationSourceHolder(Binding<?> binding) {
return binding.acceptTargetVisitor(new DefaultBindingTargetVisitor<Object, ConfigurationSourceHolder>() {
@Override
protected ConfigurationSourceHolder visitOther(Binding binding) {
final Class<?> clazz = binding.getKey().getTypeLiteral().getRawType();
return new ConfigurationSourceHolder(
clazz.getDeclaredAnnotation(ConfigurationSource.class),
clazz.getDeclaredAnnotation(Configuration.class)
);
}

@Override
public ConfigurationSourceHolder visit(ProviderInstanceBinding providerInstanceBinding) {
final Provider provider = providerInstanceBinding.getUserSuppliedProvider();
if (provider instanceof ProvidesMethodBinding) {
final ProvidesMethodBinding providesMethodBinding = (ProvidesMethodBinding)provider;
ConfigurationSource configurationSource = providesMethodBinding.getMethod().getAnnotation(ConfigurationSource.class);
if (configurationSource != null) {
return new ConfigurationSourceHolder(
configurationSource,
null);
}
}

return super.visit(providerInstanceBinding);
}

try {
mapper.mapConfig(provision.provision(), config, new IoCContainer() {
@Override
public <S> S getInstance(String name, Class<S> type) {
return injector.getInstance(Key.get(type, Names.named(name)));
});
}

@Override
public <T> void onProvision(ProvisionInvocation<T> provision) {
getConfigurationSource(provision.getBinding()).ifNotLoaded((source, configAnnot) -> {
Class<?> clazz = provision.getBinding().getKey().getTypeLiteral().getRawType();
//
// Configuration Loading
//
if (source != null) {
if (injector == null) {
LOG.warn("Can't inject configuration into {} until ConfigurationInjectingListener has been initialized", clazz.getName());
return;
}

CascadeStrategy strategy = source.cascading() != ConfigurationSource.NullCascadeStrategy.class
? injector.getInstance(source.cascading()) : getCascadeStrategy();

List<String> sources = Arrays.asList(source.value());
Collections.reverse(sources);
for (String resourceName : sources) {
LOG.debug("Trying to loading configuration resource {}", resourceName);
try {
CompositeConfig loadedConfig = loader
.newLoader()
.withCascadeStrategy(strategy)
.load(resourceName);
libraries.addConfig(resourceName, loadedConfig);
} catch (ConfigException e) {
throw new ProvisionException("Unable to load configuration for " + resourceName, e);
}
});
}
}
catch (Exception e) {
throw new ProvisionException("Unable to bind configuration to " + clazz, e);

//
// Configuration binding
//
if (configAnnot != null) {
if (injector == null) {
LOG.warn("Can't inject configuration into {} until ConfigurationInjectingListener has been initialized", clazz.getName());
return;
}

try {
mapper.mapConfig(provision.provision(), config, new IoCContainer() {
@Override
public <S> S getInstance(String name, Class<S> type) {
return injector.getInstance(Key.get(type, Names.named(name)));
}
});
}
catch (Exception e) {
throw new ProvisionException("Unable to bind configuration to " + clazz.getName(), e);
}
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.netflix.archaius.guice;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.netflix.archaius.api.Config;
import com.netflix.archaius.api.annotations.ConfigurationSource;
import com.netflix.archaius.visitor.PrintStreamVisitor;
Expand All @@ -25,4 +28,34 @@ public void confirmLoadOrder() {
config.accept(new PrintStreamVisitor());
Assert.assertEquals("prod", config.getString("moduleTest.value"));
}

public static class Bar {

}

public static class BarModule extends AbstractModule {

@Override
protected void configure() {

}

@ConfigurationSource({"moduleTest"})
@Provides
@Singleton
public Bar getBar() {
return new Bar();
}
}

@Test
public void configProvidesMethodConfigLoaded() {
Injector injector = Guice.createInjector(new ArchaiusModule(), new BarModule());
injector.getInstance(Bar.class);

Config config = injector.getInstance(Config.class);
config.accept(new PrintStreamVisitor());
Assert.assertEquals("true", config.getString("moduleTest.loaded"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ default String getDefault() {
}
}

public static interface MySubConfig {
public interface MySubConfig {
@DefaultValue("0")
int getInteger();
}

@Configuration(prefix="foo")
public static interface MyConfigWithPrefix {
public interface MyConfigWithPrefix {
@DefaultValue("0")
int getInteger();

Expand Down Expand Up @@ -107,9 +107,9 @@ public MyConfig getMyConfig(ConfigProxyFactory factory) {

@Configuration(prefix="prefix-${env}", allowFields=true)
@ConfigurationSource(value={"moduleTest"}, cascading=MyCascadingStrategy.class)
public static interface ModuleTestConfig {
public Boolean isLoaded();
public String getProp1();
public interface ModuleTestConfig {
Boolean isLoaded();
String getProp1();
}

@Test
Expand All @@ -130,7 +130,7 @@ public ModuleTestConfig getMyConfig(ConfigProxyFactory factory) {
injector.getInstance(Config.class).accept(new PrintStreamVisitor());
}

public static interface DefaultMethodWithAnnotation {
public interface DefaultMethodWithAnnotation {
@DefaultValue("fromAnnotation")
default String getValue() {
return "fromDefault";
Expand Down