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

Improve Archaius integration #102

Open
wants to merge 1 commit into
base: master
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 @@ -9,13 +9,54 @@
import java.lang.annotation.Target;

/**
* Governator bootstrap annotation to enable property loading via archaius
*
* Example,
* <pre>
* {@code
* @KaryonBootstrap(name="foo")
* @ArchaiusBootstrap
* public class MyApplication {
* }
* }
* </pre>
*
* The above example will initialize Archaius with properties using cascading property
* loading based on the KaryonBootstrap application name 'foo'.
*
* @author Nitesh Kant
* @author elandau
*/
@Documented
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Bootstrap(ArchaiusSuite.class)
public @interface ArchaiusBootstrap {

@Deprecated
Class<? extends Provider<PropertiesLoader>> loader() default DefaultPropertiesLoaderProvider.class;

/**
* Enable including/excluding of modules from properties. Modules may be specified as follows
*
* karyon.modules.com.example.TestModule=include
* karyon.modules.com.example.TestModule=exclude
*
* where 'karyon.modules' may be overridden by changing prefix()
*
*/
boolean enableModuleLoading() default true;

/**
* Prefix to use when loading modules from properties (enableModuleLoading=true)
* @return
*/
String prefix() default "karyon.modules";

/**
* List of {@link PropertiesLoader}'s that are loaded after Archaius is initialized but before dynamic
* modules or any other operation is performed
* @return
*/
Class<? extends PropertiesLoader>[] overrides() default {};
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,120 @@
package com.netflix.karyon.archaius;

import java.io.IOException;
import java.util.Iterator;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.ProvisionException;
import com.netflix.config.ConfigurationManager;
import com.netflix.governator.configuration.ArchaiusConfigurationProvider;
import com.netflix.governator.configuration.ConfigurationOwnershipPolicies;
import com.netflix.governator.guice.BootstrapBinder;
import com.netflix.governator.guice.BootstrapModule;
import com.netflix.governator.guice.LifecycleInjectorBuilder;
import com.netflix.governator.guice.LifecycleInjectorBuilderSuite;

import javax.inject.Inject;
import com.netflix.karyon.KaryonBootstrap;

/**
* A guice module that defines all bindings required by karyon. Applications must use this to bootstrap karyon.
*
* @author Nitesh Kant
* @author elandau
*/
@Singleton
public class ArchaiusSuite implements LifecycleInjectorBuilderSuite {

private static final Logger logger = LoggerFactory.getLogger(ArchaiusSuite.class);

public static enum Action {
include,
exclude
}

private final KaryonBootstrap karyonBootstrap;
private final ArchaiusBootstrap archaiusBootstrap;

private final Injector injector;

@Inject
public ArchaiusSuite(ArchaiusBootstrap archaiusBootstrap) {
public ArchaiusSuite(Injector injector, ArchaiusBootstrap archaiusBootstrap, KaryonBootstrap karyonBootstrap) {
this.karyonBootstrap = karyonBootstrap;
this.archaiusBootstrap = archaiusBootstrap;
this.injector = injector;
}

@Override
public void configure(LifecycleInjectorBuilder builder) {
builder.withAdditionalBootstrapModules(new BootstrapModule() {
// First, load the properties from archaius
try {
logger.info(String.format("Loading application properties with app id: %s and environment: %s",
karyonBootstrap.name(),
ConfigurationManager.getDeploymentContext().getDeploymentEnvironment()));
ConfigurationManager.loadCascadedPropertiesFromResources(karyonBootstrap.name());
} catch (IOException e) {
logger.error(
"Failed to load properties for application id: {} and environment: {}. This is ok, if you do not have application level properties.",
karyonBootstrap.name(),
ConfigurationManager.getDeploymentContext().getDeploymentEnvironment(),
e);
}

// Load overrides
for (Class<? extends PropertiesLoader> overrides : archaiusBootstrap.overrides()) {
injector.getInstance(overrides).load();
}

// Next, load any dynamic modules from the properties
if (archaiusBootstrap.enableModuleLoading()) {
Configuration subset = ConfigurationManager.getConfigInstance().subset(archaiusBootstrap.prefix());
Iterator<String> iter = subset.getKeys();
while (iter.hasNext()) {
String key = iter.next();
Action action = Action.valueOf(subset.getString(key));
switch (action) {
case include:
logger.debug("Including module {}", key);
try {
@SuppressWarnings("unchecked")
Class<? extends Module> moduleClass = (Class<? extends Module>) Class.forName(key);
builder.withAdditionalModuleClasses(moduleClass);
}
catch (Exception e) {
throw new ProvisionException("Unable to load module '" + key + "'", e);
}
break;
case exclude:
logger.debug("Excluding module {}", key);
try {
@SuppressWarnings("unchecked")
Class<? extends Module> moduleClass = (Class<? extends Module>) Class.forName(key);
builder.withoutModuleClass(moduleClass);
}
catch (Exception e) {
logger.error("Can't find excluded module '{}'. This is ok because the module would be excluded anyway", e);
}
break;
}
}
}

// Finally add the BootstrapModule bindings to Archaius
builder.withAdditionalBootstrapModules(new BootstrapModule() {
@Override
public void configure(BootstrapBinder bootstrapBinder) {
bootstrapBinder.bind(ArchaiusBootstrap.class).toInstance(archaiusBootstrap);
bootstrapBinder.bind(PropertiesLoader.class).toProvider(archaiusBootstrap.loader());
bootstrapBinder.bind(PropertiesInitializer.class).asEagerSingleton();
ArchaiusConfigurationProvider.Builder builder = ArchaiusConfigurationProvider.builder();
builder.withOwnershipPolicy(ConfigurationOwnershipPolicies.ownsAll());
bootstrapBinder.bindConfigurationProvider().toInstance(builder.build());
bootstrapBinder
.bindConfigurationProvider()
.toInstance(ArchaiusConfigurationProvider.builder()
.withOwnershipPolicy(ConfigurationOwnershipPolicies.ownsAll())
.build());
}
});
}

public static class PropertiesInitializer {


@Inject
public PropertiesInitializer(PropertiesLoader loader) {
loader.load();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

/**
* @author Nitesh Kant
*
* @deprecated Behavior is implicit in ArchaiusConfigurationSuite
*/
public class DefaultPropertiesLoaderProvider implements Provider<PropertiesLoader> {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.netflix.karyon.archaius;

/**
* Simple abstraction to allow for property overrides to be loaded. Overrides are loaded after
* Archaius properties are loaded but before any other operation is performed in ArchaiusSuite
*
* Note that concrete {@link PropertiesLoader} implementations are loaded within the context
* of the bootstrap injector so it is possible to inject dependencies such as KaryonBootstrap into
* the property loaders.
*
* @author Nitesh Kant
*/
public interface PropertiesLoader {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.netflix.karyon.archaius;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
import com.netflix.config.ConfigurationManager;
import com.netflix.governator.guice.LifecycleInjector;
import com.netflix.karyon.KaryonBootstrap;

public class ArchaiusSuiteTest {
public static class ModuleInPropertiesFile extends AbstractModule {
@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("prop1")).toInstance("prop1_value");
}
}

public static class ModuleInPropertiesLoader extends AbstractModule {
@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("prop2")).toInstance("prop2_value");
}
}

public static class CustomPropertiesLoader implements PropertiesLoader {
@Override
public void load() {
ConfigurationManager.getConfigInstance().setProperty("karyon.modules." + ModuleInPropertiesLoader.class.getName(), "include");
}
}

@KaryonBootstrap(name="foo")
@ArchaiusBootstrap(overrides={CustomPropertiesLoader.class})
public static class MyApplicationWithOverrides {

}

@Before
public void before() {
ConfigurationManager.getConfigInstance().clear();
}

@Test
public void shouldLoadOverrides() {
Injector injector = LifecycleInjector.bootstrap(MyApplicationWithOverrides.class);
String value1 = injector.getInstance(Key.get(String.class, Names.named("prop1")));
String value2 = injector.getInstance(Key.get(String.class, Names.named("prop2")));
Assert.assertEquals("prop1_value", value1);
Assert.assertEquals("prop2_value", value2);
}

@KaryonBootstrap(name="bar")
@ArchaiusBootstrap()
public static class MyApplicationWithoutOverrides {

}
@Test
public void shouldNotLoadOverrides() {
Injector injector = LifecycleInjector.bootstrap(MyApplicationWithoutOverrides.class);
String value1 = injector.getInstance(Key.get(String.class, Names.named("prop1")));
Binding<String> value2 = injector.getExistingBinding(Key.get(String.class, Names.named("prop2")));
Assert.assertEquals("prop1_value", value1);
Assert.assertNull(value2);
}
}
1 change: 1 addition & 0 deletions karyon-archaius/src/test/resources/bar.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
karyon.modules.com.netflix.karyon.archaius.ArchaiusSuiteTest$ModuleInPropertiesFile=include
1 change: 1 addition & 0 deletions karyon-archaius/src/test/resources/foo.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
karyon.modules.com.netflix.karyon.archaius.ArchaiusSuiteTest$ModuleInPropertiesFile=include
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.netflix.karyon;

import com.netflix.governator.guice.LifecycleInjectorBuilderSuite;
import com.netflix.governator.guice.annotations.Bootstrap;
import com.netflix.karyon.health.AlwaysHealthyHealthCheck;
import com.netflix.karyon.health.HealthCheckHandler;
Expand All @@ -17,8 +18,20 @@
@Target({ElementType.TYPE})
@Bootstrap(KaryonBootstrapSuite.class)
public @interface KaryonBootstrap {

/**
* THE name of the application. This name is used throughout for things like configuration
* loading.
*/
String name();

/**
* Provide a custom implementation for HealthCheckHandler.
*/
Class<? extends HealthCheckHandler> healthcheck() default AlwaysHealthyHealthCheck.class;

/**
* Provide additional LifecycleInjectorBuilderSuite's that will be configuration. Use
* this for suites that don't have a Bootstrap annotation.
*/
Class<? extends LifecycleInjectorBuilderSuite>[] suites() default {};
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package com.netflix.karyon;

import javax.inject.Inject;

import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.ProvisionException;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.netflix.governator.guice.BootstrapBinder;
import com.netflix.governator.guice.BootstrapModule;
import com.netflix.governator.guice.LifecycleInjectorBuilder;
import com.netflix.governator.guice.LifecycleInjectorBuilderSuite;
import com.netflix.governator.guice.LifecycleInjectorMode;
import com.netflix.karyon.health.AlwaysHealthyHealthCheck;
import com.netflix.karyon.health.HealthCheckHandler;
import com.netflix.karyon.health.HealthCheckInvocationStrategy;
import com.netflix.karyon.health.SyncHealthCheckInvocationStrategy;

import javax.inject.Inject;

/**
* A guice module that defines all bindings required by karyon. Applications must use this to bootstrap karyon.
*
Expand All @@ -23,10 +24,12 @@
public class KaryonBootstrapSuite implements LifecycleInjectorBuilderSuite {

private final KaryonBootstrap karyonBootstrap;

private final Injector injector;

@Inject
public KaryonBootstrapSuite(KaryonBootstrap karyonBootstrap) {
public KaryonBootstrapSuite(Injector injector, KaryonBootstrap karyonBootstrap) {
this.karyonBootstrap = karyonBootstrap;
this.injector = injector;
}

@Override
Expand All @@ -45,6 +48,14 @@ protected void configure() {
bindHealthCheckInvocationStrategy(bind(HealthCheckInvocationStrategy.class));
}
});

for (Class<? extends LifecycleInjectorBuilderSuite> suite : karyonBootstrap.suites()) {
try {
injector.getInstance(suite).configure(builder);
} catch (Exception e) {
throw new ProvisionException("Unable to invoke suite '" + suite.getName() + "'", e);
}
}
}

private void bindHealthCheckInvocationStrategy(AnnotatedBindingBuilder<HealthCheckInvocationStrategy> bindingBuilder) {
Expand Down