diff --git a/README.md b/README.md index 9faac0b3..e8e3c3bf 100644 --- a/README.md +++ b/README.md @@ -115,37 +115,40 @@ public class HelloWorld { ``` ---------- -Conditional module loading +Conditional bindings ---------------------------------- -Karyon supports conditional module loading to auto install Guice modules based on the application runtime environment. Conditionals can depend on property values, modules having been installed, bindings, etc. An example use case would be to set the appropriate bindings for running Eureka locally as opposed to running in the cloud without requiring the developer to know which specific bindings to override. For conditional bindings to work all the jars must be in the classpath and the modules made known to Karyonvia a ModuleListProvider (such as ClassPathModuleListProvider, ServiceLoaderModuleListProvider, etc...). By default karyon will include any modules under the 'com.netflix.karyon' package. +Applications and libraries frequently need different bindings based on the context in which the application is running. +Conditional bindings may be specified in a Guice module using @ProvidesConditionally. This features is syntactic sugar +instead of having to write a complex Provider, put conditional logic in a Guice module or write complex conditional code +when determining which Guice modules to install. ```java Karyon.create() - .addModuleListProvider(ModuleListProvides.forPackage("org.example") + .addModules(new AbstractModule() { + @Override + protected void configure() { + } + + @ProvidesConditionally(isDefault=true) + @ConditionalOnProfile("local") + protected Foo getFooWhenRunningLocally() { + return new FooForLocalDevelopment(); + } + + @ProvidesConditionally + @ConditionalOnEc2 + protected Foo getFooWhenRunningInEc2() { + return new FooForEc2(); + } + }) .start() ``` -In addition to the conditionals built in to Karyon offers two key conditionals, ConditionalOnLocalDev and ConditionalOnEc2 that can be used to load specific modules (i.e. bindings) for local development and unit tests or when running in an EC2 environment. - -For example, -```java -@ConditionalOnLocalDev -public class Module extends AbstractModule { - @Override - protected void configure() { - bind(Foo.class).to(FooLocalDevImpl.class); - } -} - -@ConditionalOnEc2 -public class Module extends AbstractModule { - @Override - protected void configure() { - bind(Foo.class).to(FooEc2Impl.class); - } -} -``` -You can run in either configuration via setting the properties karyon.profiles=localDev or karyon.profiles=ec2 +Note the following restrictions when using conditional bindings +* ALL bindings for the type must be conditional otherwise Guice will fail with duplicate bindings errors +* No more than 1 conditional may be true otherwise Guice will fail with duplicate bindings errors +* If no conditions are met the @ProvidesConditionally with isDefault=true will be used. Only one @ProvidesConditional may be default. +* If no conditions are met and no isDefault is set Guice will throw a ProvisionException ---------- Archaius Configuration diff --git a/karyon3-admin-simple/src/main/java/com/netflix/karyon/admin/rest/AdminHttpHandler.java b/karyon3-admin-simple/src/main/java/com/netflix/karyon/admin/rest/AdminHttpHandler.java index b80bb69a..37d8fc59 100644 --- a/karyon3-admin-simple/src/main/java/com/netflix/karyon/admin/rest/AdminHttpHandler.java +++ b/karyon3-admin-simple/src/main/java/com/netflix/karyon/admin/rest/AdminHttpHandler.java @@ -113,6 +113,7 @@ public Object invoke(InputStream stream, Map queryParameters) th if (stream.available() > 0 || !queryParameters.isEmpty()) { throw new UnsupportedOperationException("Query parameters or request object not supported yet"); } + method.setAccessible(true); return method.invoke(services.getService(serviceName)); } }; diff --git a/karyon3-admin/src/main/java/com/netflix/karyon/admin/AdminModule.java b/karyon3-admin/src/main/java/com/netflix/karyon/admin/AdminModule.java index 6e717925..ceedd0a4 100644 --- a/karyon3-admin/src/main/java/com/netflix/karyon/admin/AdminModule.java +++ b/karyon3-admin/src/main/java/com/netflix/karyon/admin/AdminModule.java @@ -18,12 +18,12 @@ protected void configure() { @Override public boolean equals(Object obj) { - return AdminModule.class.equals(obj.getClass()); + return getClass().equals(obj.getClass()); } @Override public int hashCode() { - return AdminModule.class.hashCode(); + return getClass().hashCode(); } } diff --git a/karyon3-api/build.gradle b/karyon3-api/build.gradle new file mode 100644 index 00000000..f19e0746 --- /dev/null +++ b/karyon3-api/build.gradle @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Netflix, 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. + */ + +apply plugin: 'java' + +dependencies { + compile 'javax.inject:javax.inject:1' +} + +eclipse { + classpath { + downloadSources = true + downloadJavadoc = true + } +} diff --git a/karyon3-api/src/main/java/com/netflix/karyon/annotations/Arguments.java b/karyon3-api/src/main/java/com/netflix/karyon/annotations/Arguments.java new file mode 100644 index 00000000..c6b29540 --- /dev/null +++ b/karyon3-api/src/main/java/com/netflix/karyon/annotations/Arguments.java @@ -0,0 +1,20 @@ +package com.netflix.karyon.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * Qualifier associated with String[] arguments passed to Karyon.start(args) + */ +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier +public @interface Arguments { + +} diff --git a/karyon3-api/src/main/java/com/netflix/karyon/annotations/Profiles.java b/karyon3-api/src/main/java/com/netflix/karyon/annotations/Profiles.java new file mode 100644 index 00000000..bc8ba125 --- /dev/null +++ b/karyon3-api/src/main/java/com/netflix/karyon/annotations/Profiles.java @@ -0,0 +1,20 @@ +package com.netflix.karyon.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * Qualifier associated with Set{@literal <}String{@literal >} of active profiles + */ +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier +public @interface Profiles { + +} diff --git a/karyon3-archaius2/build.gradle b/karyon3-archaius2/build.gradle index dac49f6f..6d7e8fc1 100644 --- a/karyon3-archaius2/build.gradle +++ b/karyon3-archaius2/build.gradle @@ -26,6 +26,8 @@ dependencies { compile group: "com.netflix.archaius", name: "archaius2-archaius1-bridge", version: "${archaius_version}", changing: true testCompile project(':karyon3-junit') + testCompile "org.hamcrest:hamcrest-core:${hamcrest_version}" + testCompile "org.hamcrest:hamcrest-library:${hamcrest_version}" } eclipse { diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusKaryonModule.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusKaryonModule.java index f05eb963..b98e11ea 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusKaryonModule.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusKaryonModule.java @@ -7,11 +7,27 @@ import java.util.Set; import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provides; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.matcher.Matchers; import com.google.inject.multibindings.MapBinder; import com.google.inject.multibindings.Multibinder; -import com.google.inject.name.Names; import com.netflix.archaius.CascadeStrategy; import com.netflix.archaius.Config; +import com.netflix.archaius.ConfigListener; +import com.netflix.archaius.ConfigLoader; +import com.netflix.archaius.ConfigProxyFactory; +import com.netflix.archaius.ConfigReader; +import com.netflix.archaius.Decoder; +import com.netflix.archaius.DefaultConfigLoader; +import com.netflix.archaius.DefaultDecoder; +import com.netflix.archaius.DefaultPropertyFactory; +import com.netflix.archaius.PropertyFactory; +import com.netflix.archaius.annotations.Configuration; import com.netflix.archaius.config.CompositeConfig; import com.netflix.archaius.config.DefaultSettableConfig; import com.netflix.archaius.config.EnvironmentConfig; @@ -19,26 +35,63 @@ import com.netflix.archaius.config.SettableConfig; import com.netflix.archaius.config.SystemConfig; import com.netflix.archaius.exceptions.ConfigException; -import com.netflix.archaius.guice.ArchaiusModule; +import com.netflix.archaius.guice.ArchaiusConfiguration; import com.netflix.archaius.guice.ConfigSeeder; -import com.netflix.archaius.guice.ConfigSeeders; -import com.netflix.archaius.guice.RootLayer; +import com.netflix.archaius.guice.ConfigurationInjectingListener; import com.netflix.archaius.inject.ApplicationLayer; -import com.netflix.archaius.inject.DefaultsLayer; import com.netflix.archaius.inject.LibrariesLayer; import com.netflix.archaius.inject.RemoteLayer; import com.netflix.archaius.inject.RuntimeLayer; -import com.netflix.karyon.AbstractKaryonModule; +import com.netflix.archaius.interpolate.ConfigStrLookup; +import com.netflix.archaius.readers.PropertiesConfigReader; import com.netflix.karyon.AbstractPropertySource; -import com.netflix.karyon.PropertySource; -import com.netflix.karyon.ServerContext; +import com.netflix.karyon.TypeLiteralMatchers; +import com.netflix.karyon.annotations.Profiles; +import com.netflix.karyon.archaius.admin.ArchaiusAdminModule; +import com.netflix.karyon.spi.KaryonBinder; +import com.netflix.karyon.spi.KaryonModule; +import com.netflix.karyon.spi.PropertySource; /** - * Entry point for applications using Archaius as the configuration mechanism. This - * DSL provided here extends the core Karyon.Dsl with method specific to customizing - * archaius + * Module to set up archaius in a Karyon3 application. + * + * Note that this module has state and should therefore only be installed once. Also, + * note that configure() does follow Guice best practices and can therefore be called + * multiple times without side effects. + * + * By default this module will create a top level Config that is a CompositeConfig of the following layers, + * RUNTIME - properties set from code + * REMOTE - properties loaded from a remote source + * SYSTEM - System properties + * ENVIRONMENT - Environment properties + * APPLICATION - Configuration loaded by the application + * LIBRARIES - Configuration loaded by libraries used by the application + * + * Runtime properties may be set in code by injecting and calling one of the + * setters for, + * {@literal @}RuntimeLayer SettableConfig config + * + * A remote configuration may be specified by binding to {@literal @}RemoteLayer Config + * + * public class FooRemoteModule extends AbstractModule { + * {@literal @}Override + * protected void configure() {} + * + * {@literal @}Provides + * {@literal @}RemoteLayer + * // When setting up a remote configuration that need access to archaius's Config + * // make sure to inject the qualifier {@literal @}Raw otherwise the injector will fail + * // with a circular dependency error. Note that the injected config will have + * // system, environment and application properties loaded into it. + * Config getRemoteConfig({@literal @}Raw Config config) { + * return new FooRemoteConfigImplementaiton(config); + * } + * } + * */ -public class ArchaiusKaryonModule extends AbstractKaryonModule { +public final class ArchaiusKaryonModule extends AbstractModule implements KaryonModule { + private static final String KARYON_PROFILES = "karyon.profiles"; + private static final String DEFAULT_CONFIG_NAME = "application"; private static final String RUNTIME_LAYER_NAME = "RUNTIME"; @@ -47,23 +100,42 @@ public class ArchaiusKaryonModule extends AbstractKaryonModule { private static final String ENVIRONMENT_LAYER_NAME = "ENVIRONMENT"; private static final String APPLICATION_LAYER_NAME = "APPLICATION"; private static final String LIBRARIES_LAYER_NAME = "LIBRARIES"; - private static final String DEFAULTS_LAYER_NAME = "DEFAULTS"; static { System.setProperty("archaius.default.configuration.class", "com.netflix.archaius.bridge.StaticAbstractConfiguration"); System.setProperty("archaius.default.deploymentContext.class", "com.netflix.archaius.bridge.StaticDeploymentContext"); } - private String configName = DEFAULT_CONFIG_NAME; + private String configName = DEFAULT_CONFIG_NAME; private Config applicationOverrides = null; - private Map libraryOverrides = new HashMap<>(); - private Set runtimeOverrides = new HashSet<>(); - private Set defaultSeeders = new HashSet<>(); - private Properties props = new Properties(); - + private Map libraryOverrides = new HashMap<>(); + private Set runtimeOverrides = new HashSet<>(); private Class cascadeStrategy = KaryonCascadeStrategy.class; - private String appName; + private final SettableConfig runtimeLayer; + private final CompositeConfig remoteLayer; + private final CompositeConfig applicationLayer; + private final CompositeConfig librariesLayer; + private final CompositeConfig rawConfig; + + private final String[] profiles; + + public ArchaiusKaryonModule() throws ConfigException { + this.runtimeLayer = new DefaultSettableConfig(); + this.applicationLayer = new CompositeConfig(); + this.librariesLayer = new CompositeConfig(); + this.remoteLayer = new CompositeConfig(); + this.rawConfig = CompositeConfig.builder() + .withConfig(RUNTIME_LAYER_NAME, runtimeLayer) + .withConfig(REMOTE_LAYER_NAME, remoteLayer) + .withConfig(SYSTEM_LAYER_NAME, SystemConfig.INSTANCE) + .withConfig(ENVIRONMENT_LAYER_NAME, EnvironmentConfig.INSTANCE) + .withConfig(APPLICATION_LAYER_NAME, applicationLayer) + .withConfig(LIBRARIES_LAYER_NAME, librariesLayer) + .build(); + + this.profiles = this.rawConfig.getString(KARYON_PROFILES, "").split(","); + } /** * Configuration name to use for property loading. Default configuration @@ -78,17 +150,6 @@ public ArchaiusKaryonModule withConfigName(String value) { this.configName = value; return this; } - - /** - * @deprecated Call Karyon.forApplication('appname') instead - * @param value - * @return - */ - @Deprecated - public ArchaiusKaryonModule withApplicationName(String value) { - this.appName = value; - return this; - } public ArchaiusKaryonModule withApplicationOverrides(Properties prop) throws ConfigException { return withApplicationOverrides(MapConfig.from(prop)); @@ -108,15 +169,6 @@ public ArchaiusKaryonModule withRuntimeOverrides(Config config) throws ConfigExc return this; } - public ArchaiusKaryonModule withDefaults(Properties prop) throws ConfigException { - return withDefaults(MapConfig.from(prop)); - } - - public ArchaiusKaryonModule withDefaults(Config config) throws ConfigException { - this.defaultSeeders.add(config); - return this; - } - public ArchaiusKaryonModule withLibraryOverrides(String name, Properties prop) throws ConfigException { return withLibraryOverrides(name, MapConfig.from(prop)); } @@ -133,109 +185,193 @@ public ArchaiusKaryonModule withCascadeStrategy(Class @Override protected void configure() { - try { - if (appName != null) { - props.put(ServerContext.APP_ID, appName); + install(new ArchaiusAdminModule()); + + bindListener(Matchers.any(), new ConfigurationInjectingListener()); + bind(ConfigLifecycleListener.class).asEagerSingleton(); + bind(CascadeStrategy.class).to(cascadeStrategy); + + for (String profile : profiles) { + if (!profile.isEmpty()) + Multibinder.newSetBinder(binder(), String.class, Profiles.class).addBinding().toInstance(profile); + } + + Multibinder.newSetBinder(binder(), ConfigReader.class) + .addBinding().to(PropertiesConfigReader.class).in(Scopes.SINGLETON); + + MapBinder libraries = MapBinder.newMapBinder(binder(), String.class, Config.class, LibrariesLayer.class); + for (Map.Entry c : libraryOverrides.entrySet()) { + libraries.addBinding(c.getKey()).toInstance(c.getValue()); + } + + } + + @Provides + @Singleton + @RuntimeLayer + SettableConfig getSettableConfig() { + return runtimeLayer; + } + + @Provides + @Singleton + @ApplicationLayer + CompositeConfig getApplicationLayer() { + return applicationLayer; + } + + @Provides + @Singleton + @LibrariesLayer + CompositeConfig getLibrariesLayer() { + return librariesLayer; + } + + @Provides + @Singleton + PropertySource getPropertySource() { + return new AbstractPropertySource() { + @Override + public String get(String key) { + return rawConfig.getString(key, null); } - else if (getKaryon().getApplicationName() != null) { - props.put(ServerContext.APP_ID, getKaryon().getApplicationName()); + + @Override + public String get(String key, String defaultValue) { + return rawConfig.getString(key, defaultValue); } + }; + } + + @Provides + @Singleton + @Raw + Config getRawConfig() { + return rawConfig; + } + + @Provides + @Singleton + public Config getConfig(ConfigLoader loader, Injector injector) throws Exception { + // load any runtime overrides + for (Config c : runtimeOverrides) { + runtimeLayer.setProperties(c); + } + + if (applicationOverrides != null) { + applicationLayer.addConfig("overrides", applicationOverrides); + } + + // First load archaius2 configuration, which sets up all sort of env + librariesLayer.addConfig("archaius2", loader + .newLoader() + .load("archaius2")); - addModules(new ArchaiusModule()); - - if (!props.isEmpty()) { - try { - withRuntimeOverrides(MapConfig.from(props)); - } catch (ConfigException e) { - throw new RuntimeException(e); - } + // + this.applicationLayer.addConfig("loaded", loader + .newLoader() + .load(configName)); + + // load any runtime overrides + Binding binding = injector.getExistingBinding(Key.get(Config.class, RemoteLayer.class)); + if (binding != null) { + // TODO: Ideally this should replace the remoteLayer in config but there is a bug in archaius + // where the replaced layer moves to the end of the hierarchy + remoteLayer.addConfig("remote", binding.getProvider().get()); + } + + return rawConfig; + } + + @Provides + @Singleton + ConfigLoader getLoader( + @LibrariesLayer CompositeConfig libraries, + CascadeStrategy cascadingStrategy, + Set readers + ) throws ConfigException { + + return DefaultConfigLoader.builder() + .withConfigReader(readers) + .withDefaultCascadingStrategy(cascadingStrategy) + .withStrLookup(ConfigStrLookup.from(rawConfig)) + .build(); + } + + @Provides + @Singleton + public Decoder getDecoder() { + return DefaultDecoder.INSTANCE; + } + + + @Provides + @Singleton + PropertyFactory getPropertyFactory(Config config) { + return DefaultPropertyFactory.from(config); + } + + @Provides + @Singleton + ConfigProxyFactory getProxyFactory(Decoder decoder, PropertyFactory factory) { + return new ConfigProxyFactory(decoder, factory); + } + + @Provides + @Singleton + @Deprecated + // This is needed for ConfigurationInjectingListener + ArchaiusConfiguration getArchaiusConfiguration(CascadeStrategy cascadeStrategy, @LibrariesLayer Map overrides) { + return new ArchaiusConfiguration() { + @Override + public Set getRuntimeLayerSeeders() { + return null; } - - SettableConfig runtimeLayer = new DefaultSettableConfig(); - SettableConfig defaultsLayer = new DefaultSettableConfig(); - CompositeConfig overrideLayer = new CompositeConfig(); - CompositeConfig applicationLayer = new CompositeConfig(); - CompositeConfig librariesLayer = new CompositeConfig(); - - if (applicationOverrides != null) { - applicationLayer.addConfig("overrides", applicationOverrides); + + @Override + public Set getRemoteLayerSeeders() { + return null; } - - final CompositeConfig rootConfig = CompositeConfig.builder() - .withConfig(RUNTIME_LAYER_NAME, runtimeLayer) - .withConfig(REMOTE_LAYER_NAME, overrideLayer) - .withConfig(SYSTEM_LAYER_NAME, SystemConfig.INSTANCE) - .withConfig(ENVIRONMENT_LAYER_NAME, EnvironmentConfig.INSTANCE) - .withConfig(APPLICATION_LAYER_NAME, applicationLayer) - .withConfig(LIBRARIES_LAYER_NAME, librariesLayer) - .withConfig(DEFAULTS_LAYER_NAME, defaultsLayer) - .build(); - ; - - PropertySource propertySource = new AbstractPropertySource() { - @Override - public String get(String key) { - return rootConfig.getString(key, null); - } - - @Override - public String get(String key, String defaultValue) { - return rootConfig.getString(key, defaultValue); - } - }; - - setPropertySource(propertySource); - - addOverrideModules(new AbstractModule() { - @Override - protected void configure() { - bind(SettableConfig.class).annotatedWith(RuntimeLayer.class).toInstance(runtimeLayer); - bind(SettableConfig.class).annotatedWith(DefaultsLayer.class).toInstance(defaultsLayer); - bind(CompositeConfig.class).annotatedWith(ApplicationLayer.class).toInstance(applicationLayer); - bind(CompositeConfig.class).annotatedWith(RemoteLayer.class).toInstance(overrideLayer); - bind(CompositeConfig.class).annotatedWith(LibrariesLayer.class).toInstance(librariesLayer); - - bind(Config.class).annotatedWith(RootLayer.class).toInstance(rootConfig); - - bind(String.class).annotatedWith(ApplicationLayer.class).toInstance(configName); - bindConstant().annotatedWith(Names.named("karyon.configName")).to(configName); - - MapBinder libraries = MapBinder.newMapBinder(binder(), String.class, Config.class, LibrariesLayer.class); - for (Map.Entry config : libraryOverrides.entrySet()) { - libraries.addBinding(config.getKey()).toInstance(config.getValue()); - } - - Multibinder runtime = Multibinder.newSetBinder(binder(), ConfigSeeder.class, RuntimeLayer.class); - for (Config config : runtimeOverrides) { - runtime.addBinding().toInstance(ConfigSeeders.from(config)); - } - - Multibinder defaults = Multibinder.newSetBinder(binder(), ConfigSeeder.class, DefaultsLayer.class); - for (Config config : defaultSeeders) { - defaults.addBinding().toInstance(ConfigSeeders.from(config)); - } - - bind(CascadeStrategy.class).to(cascadeStrategy); - } - - @Override - public boolean equals(Object obj) { - return getClass().equals(obj.getClass()); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "ArchaiusKaryonConfigurationModule"; - } - }); - } - catch (Exception e) { - throw new RuntimeException("Failed to configure archaius", e); - } + + @Override + public Set getDefaultsLayerSeeders() { + return null; + } + + @Override + public String getConfigName() { + return null; + } + + @Override + public CascadeStrategy getCascadeStrategy() { + return cascadeStrategy; + } + + @Override + public Decoder getDecoder() { + return null; + } + + @Override + public Set getConfigListeners() { + return null; + } + + @Override + public Map getLibraryOverrides() { + return overrides; + } + + @Override + public Config getApplicationOverride() { + return null; + } + }; + } + + @Override + public void configure(KaryonBinder binder) { + binder.bindAutoBinder(TypeLiteralMatchers.annotatedWith(Configuration.class), new ArchaiusProxyAutoBinder()); } } diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusProxyAutoBinder.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusProxyAutoBinder.java new file mode 100644 index 00000000..060673a3 --- /dev/null +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ArchaiusProxyAutoBinder.java @@ -0,0 +1,30 @@ +package com.netflix.karyon.archaius; + +import javax.inject.Inject; +import javax.inject.Provider; + +import com.google.inject.Binder; +import com.google.inject.Key; +import com.netflix.archaius.ConfigProxyFactory; +import com.netflix.karyon.spi.AutoBinder; + +/** + * Autobinder to create proxies for any interface containing the archaius + * annotations + */ +final class ArchaiusProxyAutoBinder implements AutoBinder { + @Override + public boolean configure(Binder binder, Key key) { + binder.bind(key).toProvider(new Provider() { + @Inject + ConfigProxyFactory factory; + + @SuppressWarnings("unchecked") + @Override + public T get() { + return (T) factory.newProxy(key.getTypeLiteral().getRawType()); + } + }); + return true; + } +} diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ConfigLifecycleListener.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ConfigLifecycleListener.java index 98d81aca..367b9c2f 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ConfigLifecycleListener.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/ConfigLifecycleListener.java @@ -1,5 +1,6 @@ package com.netflix.karyon.archaius; +import javax.inject.Inject; import javax.inject.Singleton; import org.slf4j.Logger; @@ -17,15 +18,24 @@ public class ConfigLifecycleListener extends DefaultLifecycleListener { private static final Logger LOG = LoggerFactory.getLogger(ConfigLifecycleListener.class); - private Config config; - - public void setConfig(Config config) { + private final Config config; + + @Inject + public ConfigLifecycleListener(Config config) { this.config = config; } + + @Override + public void onStarted() { + LOG.debug("Injector created with final configuration "); + LOG.debug("========================================= "); + config.accept(new SLF4JConfigVisitor()); + } @Override public void onStartFailed(Throwable t) { LOG.debug("Injector failed with final configuration "); + LOG.debug("======================================== "); config.accept(new SLF4JConfigVisitor()); } } diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Ec2ServerContextConfigSeeder.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Ec2ServerContextConfigSeeder.java deleted file mode 100644 index 3a699a3a..00000000 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Ec2ServerContextConfigSeeder.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.netflix.karyon.archaius; - -import javax.inject.Singleton; - -import com.netflix.archaius.Config; -import com.netflix.archaius.config.MapConfig; -import com.netflix.archaius.guice.ConfigSeeder; -import com.netflix.karyon.ServerContext; - -@Singleton -public class Ec2ServerContextConfigSeeder implements ConfigSeeder { - @Override - public Config get(Config config) { - return MapConfig.builder() - .put("@domain", "${" + ServerContext.DOMAIN + "}") - .put("@hostname", "${" + ServerContext.HOSTNAME + "}") - .put("@publicHostname", "${" + ServerContext.PUBLIC_HOSTNAME + "}") - .put("@publicIpv4", "${" + ServerContext.PUBLIC_IPV4 + "}") - .put("@localHostname", "${" + ServerContext.LOCAL_HOSTNAME + "}") - .put("@localIpv4", "${" + ServerContext.LOCAL_IPV4 + "}") - .put("@datacenter", "${" + ServerContext.DATACENTER + "}") - .put("@region", "${" + ServerContext.REGION + "}") - .put("@zoneId", "${" + ServerContext.ZONE + "}") - .put("@cluster", "${" + ServerContext.CLUSTER + "}") - .put("@ami", "${" + ServerContext.AMI + "}") - .put("@asg", "${" + ServerContext.ASG + "}") - .put("@serverId", "${" + ServerContext.SERVER_ID + "}") // i- - .put("@stack", "${" + ServerContext.STACK + "}") - .put("@environment", "${" + ServerContext.ENVIRONMENT + "}") // test, prod, .. - .put("@appId", "${" + ServerContext.APP_ID + "}") - .put("@countries", "${" + ServerContext.COUNTRIES + "}") - - // Amazon specific metadata - .put(ServerContext.DOMAIN, "${EC2_DOMAIN}") - .put(ServerContext.HOSTNAME, "${EC2_HOSTNAME}") - .put(ServerContext.PUBLIC_HOSTNAME, "${EC2_PUBLIC_HOSTNAME}") - .put(ServerContext.PUBLIC_IPV4, "${EC2_PUBLIC_IPV4}") - .put(ServerContext.LOCAL_HOSTNAME, "${EC2_LOCAL_HOSTNAME}") - .put(ServerContext.LOCAL_IPV4, "${EC2_LOCAL_IPV4}") - .put(ServerContext.DATACENTER, "cloud") - .put(ServerContext.ZONE, "${EC2_AVAILABILITY_ZONE}") - .put(ServerContext.SERVER_ID, "${EC2_INSTANCE_ID}") - .put(ServerContext.AMI, "${EC2_AMI_ID}") - .put(ServerContext.REGION, "${EC2_REGION}") - .build(); - } -} diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Ec2ServerContextModule.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Ec2ServerContextModule.java deleted file mode 100644 index ff7e759b..00000000 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Ec2ServerContextModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.karyon.archaius; - -import com.google.inject.AbstractModule; -import com.google.inject.multibindings.Multibinder; -import com.netflix.archaius.guice.ConfigSeeder; -import com.netflix.archaius.inject.DefaultsLayer; -import com.netflix.karyon.conditional.ConditionalOnEc2; - -@ConditionalOnEc2 -public class Ec2ServerContextModule extends AbstractModule { - - @Override - protected void configure() { - Multibinder.newSetBinder(binder(), ConfigSeeder.class, DefaultsLayer.class).addBinding().to(Ec2ServerContextConfigSeeder.class); - } - -} diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/KaryonCascadeStrategy.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/KaryonCascadeStrategy.java index 72e9be37..c1e00537 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/KaryonCascadeStrategy.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/KaryonCascadeStrategy.java @@ -3,27 +3,28 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import javax.inject.Inject; import com.google.inject.Singleton; -import com.netflix.karyon.KaryonAutoContext; +import com.netflix.karyon.annotations.Profiles; @Singleton public class KaryonCascadeStrategy extends InterpolatingCascadeStrategy { - private KaryonAutoContext context; + private final Set profiles; @Inject - public KaryonCascadeStrategy(KaryonAutoContext context) { - this.context = context; + public KaryonCascadeStrategy(@Profiles Set profiles) { + this.profiles = profiles; } @Override protected List getPermutations() { List permuatations = new ArrayList<>(); permuatations.add("%s"); - for (String profile : context.getProfiles()) { + for (String profile : profiles) { permuatations.add("%s-" + profile); } permuatations.addAll(Arrays.asList( diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/LocalServerContextConfigSeeder.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/LocalServerContextConfigSeeder.java deleted file mode 100644 index d4b1b81a..00000000 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/LocalServerContextConfigSeeder.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.netflix.karyon.archaius; - -import java.net.InetAddress; - -import javax.inject.Singleton; - -import com.netflix.archaius.Config; -import com.netflix.archaius.config.MapConfig; -import com.netflix.archaius.guice.ConfigSeeder; -import com.netflix.karyon.ServerContext; - -/** - * Used to seed a configuration layer for ServerContext keys with local server - * information - * - * @author elandau - */ -@Singleton -public class LocalServerContextConfigSeeder implements ConfigSeeder { - - @Override - public Config get(Config rootConfig) throws Exception { - InetAddress IP = InetAddress.getLocalHost(); - return MapConfig.builder() - // Amazon specific metadata - .put(ServerContext.DOMAIN, "") - .put(ServerContext.HOSTNAME, "localhost") - .put(ServerContext.PUBLIC_HOSTNAME, IP.getHostName()) - .put(ServerContext.PUBLIC_IPV4, IP.getHostAddress()) - .put(ServerContext.LOCAL_HOSTNAME, IP.getHostName()) - .put(ServerContext.LOCAL_IPV4, IP.getHostAddress()) - .put(ServerContext.DATACENTER, "cloud") - .put(ServerContext.REGION, "us-west-2") - .put(ServerContext.ZONE, "us-west-2a") - .put(ServerContext.SERVER_ID, "${" + ServerContext.HOSTNAME + "}") - .put(ServerContext.AMI, "ami-dev") - .put(ServerContext.ENVIRONMENT, "test") - .put(ServerContext.ASG, "asg-dev") - .put(ServerContext.CLUSTER, "cluster-dev") - .put(ServerContext.STACK, "") - - .put("@domain", "${" + ServerContext.DOMAIN + "}") - .put("@hostname", "${" + ServerContext.HOSTNAME + "}") - .put("@publicHostname", "${" + ServerContext.PUBLIC_HOSTNAME + "}") - .put("@publicIpv4", "${" + ServerContext.PUBLIC_IPV4 + "}") - .put("@localHostname", "${" + ServerContext.LOCAL_HOSTNAME + "}") - .put("@localIpv4", "${" + ServerContext.LOCAL_IPV4 + "}") - .put("@datacenter", "${" + ServerContext.DATACENTER + "}") - .put("@region", "${" + ServerContext.REGION + "}") - .put("@zoneId", "${" + ServerContext.ZONE + "}") - .put("@cluster", "${" + ServerContext.CLUSTER + "}") - .put("@ami", "${" + ServerContext.AMI + "}") - .put("@asg", "${" + ServerContext.ASG + "}") - .put("@serverId", "${" + ServerContext.SERVER_ID + "}") // i- - .put("@stack", "${" + ServerContext.STACK + "}") - .put("@environment", "${" + ServerContext.ENVIRONMENT + "}") // test, prod, .. - .put("@appId", "${" + ServerContext.APP_ID + "}") - .put("@countries", "${" + ServerContext.COUNTRIES + "}") - - .put("EC2_DOMAIN", "${" + ServerContext.DOMAIN + "}") - .put("EC2_HOSTNAME", "${" + ServerContext.HOSTNAME + "}") - .put("EC2_PUBLIC_HOSTNAME", "${" + ServerContext.PUBLIC_HOSTNAME + "}") - .put("EC2_PUBLIC_IPV4", "${" + ServerContext.PUBLIC_IPV4 + "}") - .put("EC2_LOCAL_HOSTNAME", "${" + ServerContext.LOCAL_HOSTNAME + "}") - .put("EC2_LOCAL_IPV4", "${" + ServerContext.LOCAL_IPV4 + "}") - .put("EC2_AVAILABILITY_ZONE", "${" + ServerContext.ZONE + "}") - .put("EC2_INSTANCE_ID", "${" + ServerContext.SERVER_ID + "}") - .put("EC2_AMI_ID", "${" + ServerContext.AMI + "}") - .put("EC2_REGION", "${" + ServerContext.REGION + "}") - - .build(); - } -} diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/LocalServerContextModule.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/LocalServerContextModule.java deleted file mode 100644 index 11ca5f12..00000000 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/LocalServerContextModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.karyon.archaius; - -import com.google.inject.multibindings.Multibinder; -import com.netflix.archaius.guice.ConfigSeeder; -import com.netflix.archaius.inject.DefaultsLayer; -import com.netflix.governator.DefaultModule; -import com.netflix.karyon.conditional.ConditionalOnLocalDev; - -@ConditionalOnLocalDev -public class LocalServerContextModule extends DefaultModule { - - @Override - protected void configure() { - Multibinder.newSetBinder(binder(), ConfigSeeder.class, DefaultsLayer.class).addBinding().to(LocalServerContextConfigSeeder.class); - } - -} diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Raw.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Raw.java new file mode 100644 index 00000000..b89f993f --- /dev/null +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/Raw.java @@ -0,0 +1,19 @@ +package com.netflix.karyon.archaius; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * To be used only for components meant to extend Archaius's functionality via bindings + * where injecting a named Config will result in a circular dependency. + */ +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface Raw { + +} diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusAdminModule.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusAdminModule.java index aac595e2..287cdff3 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusAdminModule.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusAdminModule.java @@ -1,13 +1,11 @@ package com.netflix.karyon.archaius.admin; import com.google.inject.AbstractModule; -import com.netflix.karyon.admin.AdminModule; -import com.netflix.karyon.conditional.ConditionalOnModule; -@ConditionalOnModule(AdminModule.class) -public final class ArchaiusAdminModule extends AbstractModule { +public class ArchaiusAdminModule extends AbstractModule { @Override protected void configure() { bind(ArchaiusResource.class); + bind(MetaAdminResource.class); } } diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusResource.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusResource.java index 5c1d0b5b..411ddcdc 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusResource.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/ArchaiusResource.java @@ -16,7 +16,7 @@ @Singleton @AdminService(name="props", index="getAllProperties") -public class ArchaiusResource { +final class ArchaiusResource { private final CompositeConfig config; @Inject diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/MetaAdminResource.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/MetaAdminResource.java similarity index 84% rename from karyon3-core/src/main/java/com/netflix/karyon/admin/MetaAdminResource.java rename to karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/MetaAdminResource.java index ee79811d..4d342c21 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/MetaAdminResource.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/MetaAdminResource.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.admin; +package com.netflix.karyon.archaius.admin; import java.util.HashMap; import java.util.Iterator; @@ -8,10 +8,11 @@ import javax.inject.Singleton; import com.netflix.archaius.Config; +import com.netflix.karyon.admin.AdminService; @Singleton @AdminService(name="meta", index="list") -public class MetaAdminResource { +final class MetaAdminResource { private Map prop = new HashMap<>(); @Inject diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropertyRequest.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropertyRequest.java index d86bc57e..4695560a 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropertyRequest.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropertyRequest.java @@ -1,6 +1,6 @@ package com.netflix.karyon.archaius.admin; -public class PropertyRequest { +final class PropertyRequest { String layerName; String key; String regex; diff --git a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropsModel.java b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropsModel.java index 0eb35b37..26491caa 100644 --- a/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropsModel.java +++ b/karyon3-archaius2/src/main/java/com/netflix/karyon/archaius/admin/PropsModel.java @@ -2,7 +2,7 @@ import java.util.Map; -public class PropsModel { +final class PropsModel { private final Map props; public PropsModel(Map props) { diff --git a/karyon3-archaius2/src/main/resources/archaius2-local.properties b/karyon3-archaius2/src/main/resources/archaius2-local.properties new file mode 100644 index 00000000..8c40b6ad --- /dev/null +++ b/karyon3-archaius2/src/main/resources/archaius2-local.properties @@ -0,0 +1,47 @@ +# Default values for internal karyon environment properties + +karyon.domain= +karyon.hostname=localhost +karyon.publicHostname=localhost +karyon.publicIpv4=localhost +karyon.localHostname=localhost +karyon.localIpv4=localhost +karyon.datacenter=cloud +karyon.region=us-west-2 +karyon.zoneId=us-west-2a +karyon.serverId=${karyon.hostname} +karyon.ami=ami-dev +karyon.environment=test +karyon.asg=asg-dev +karyon.cluster=cluster-dev +karyon.stack= + +# Backwards compatible mappings for the legacy @ prefixed properties +@domain=${karyon.domain} +@hostname=${karyon.hostname} +@publicHostname=${karyon.publicHostname} +@publicIpv4=${karyon.publicIpv4} +@localHostname=${karyon.localHostname} +@localIpv4=${karyon.localIpv4} +@datacenter=${karyon.datacenter} +@region=${karyon.region} +@zoneId=${karyon.zoneId} +@cluster=${karyon.cluster} +@ami=${karyon.ami} +@asg=${karyon.asg} +@serverId=${karyon.serverId} +@stack=${karyon.stack} +@environment=${karyon.environment} +@appId=${karyon.appId} +@countries=${karyon.countries} + +EC2_DOMAIN=${karyon.domain} +EC2_HOSTNAME=${karyon.hostname} +EC2_PUBLIC_HOSTNAME=${karyon.publicHostname} +EC2_PUBLIC_IPV4=${karyon.publicIpv4} +EC2_LOCAL_HOSTNAME=${karyon.localHostname} +EC2_LOCAL_IPV4=${karyon.localIpv4} +EC2_AVAILABILITY_ZONE=${karyon.zoneId} +EC2_INSTANCE_ID=${karyon.serverId} +EC2_AMI_ID=${karyon.ami} +EC2_REGION=${karyon.region} diff --git a/karyon3-archaius2/src/main/resources/archaius2.properties b/karyon3-archaius2/src/main/resources/archaius2.properties new file mode 100644 index 00000000..16ff6577 --- /dev/null +++ b/karyon3-archaius2/src/main/resources/archaius2.properties @@ -0,0 +1,34 @@ +# This final contains the default mapping of Netflix EC2 environment settings +# to Karyon's environment agnostic naming. + +karyon.domain=${EC2_DOMAIN} +karyon.hostname=${EC2_HOSTNAME} +karyon.publicHostname=${EC2_PUBLIC_HOSTNAME} +karyon.publicIpv4=${EC2_PUBLIC_IPV4} +karyon.localHostname=${EC2_LOCAL_HOSTNAME} +karyon.localIpv4=${EC2_LOCAL_IPV4} +karyon.datacenter=cloud +karyon.zoneId=${EC2_AVAILABILITY_ZONE} +karyon.serverId=${EC2_INSTANCE_ID} +karyon.ami=${EC2_AMI_ID} +karyon.region=${EC2_REGION} + +# Backwards compatible mappings for the legacy @ prefixed properties +@domain=${karyon.domain} +@hostname=${karyon.hostname} +@publicHostname=${karyon.publicHostname} +@publicIpv4=${karyon.publicIpv4} +@localHostname=${karyon.localHostname} +@localIpv4=${karyon.localIpv4} +@datacenter=${karyon.datacenter} +@region=${karyon.region} +@zoneId=${karyon.zoneId} +@cluster=${karyon.cluster} +@ami=${karyon.ami} +@asg=${karyon.asg} +@serverId=${karyon.serverId} +@stack=${karyon.stack} +@environment=${karyon.environment} +@appId=${karyon.appId} +@countries=${karyon.countries} + diff --git a/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusKaryonModuleTest.java b/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusKaryonModuleTest.java index f9e0f9c3..f810e54b 100644 --- a/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusKaryonModuleTest.java +++ b/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusKaryonModuleTest.java @@ -1,21 +1,150 @@ package com.netflix.karyon.archaius; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertTrue; + +import java.util.Properties; + +import javax.inject.Inject; +import javax.inject.Provider; + import junit.framework.Assert; import org.junit.Test; +import com.google.inject.AbstractModule; import com.google.inject.Injector; +import com.google.inject.Key; import com.netflix.archaius.Config; +import com.netflix.archaius.annotations.ConfigurationSource; +import com.netflix.archaius.config.DefaultSettableConfig; +import com.netflix.archaius.config.MapConfig; +import com.netflix.archaius.config.SettableConfig; +import com.netflix.archaius.exceptions.ConfigException; +import com.netflix.archaius.inject.RemoteLayer; import com.netflix.karyon.Karyon; public class ArchaiusKaryonModuleTest { + @ConfigurationSource("foo") + public static class Foo { + } + @Test - public void test() { - Injector injector = Karyon.forApplication("test").addProfile("test").start(); + public void test() throws ConfigException { + Injector injector = Karyon.forApplication("test").addModules(new ArchaiusKaryonModule()).addProfile("test").start(); Config config = injector.getInstance(Config.class); Assert.assertTrue(config.getBoolean("application_loaded", false)); Assert.assertTrue(config.getBoolean("application_test_loaded", false)); Assert.assertEquals("application_test", config.getString("application_override")); } + + @Test + public void testApplicationOverrideConfig() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addModules(new ArchaiusKaryonModule() + .withApplicationOverrides(singletonConfig("application_test", "code_override")) + ) + .start(); + + Config config = injector.getInstance(Config.class); + assertThat(config.getString("application_test"), equalTo("code_override")); + + } + + @Test + public void testApplicationOverrideProperties() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addModules(new ArchaiusKaryonModule() + .withApplicationOverrides(singletonProperties("application_test", "code_override")) + ) + .start(); + + Config config = injector.getInstance(Config.class); + assertThat(config.getString("application_test"), equalTo("code_override")); + + } + + @Test + public void testLibrariesConfig() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addModules(new ArchaiusKaryonModule()) + .start(); + + Foo foo = injector.getInstance(Foo.class); + Config config = injector.getInstance(Config.class); + + assertTrue(config.getBoolean("foo_loaded")); + assertThat(config.getString("foo_override"), equalTo("file")); + } + + @Test + public void testLibrariesOverrideConfig() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addModules(new ArchaiusKaryonModule() + .withLibraryOverrides("foo", singletonConfig("foo_override", "code")) + ) + .start(); + + Foo foo = injector.getInstance(Foo.class); + Config config = injector.getInstance(Config.class); + + System.out.println("Value: " + config.getString("foo_override")); + assertThat(config.getString("foo_override"), equalTo("code")); + } + + @Test + public void testLibrariesOverrideProperties() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addProfile("local") + .addModules(new ArchaiusKaryonModule() + .withLibraryOverrides("foo", singletonProperties("foo_override", "code")) + ) + .start(); + + Foo foo = injector.getInstance(Foo.class); + Config config = injector.getInstance(Config.class); + assertThat(config.getString("foo_override"), equalTo("code")); + } + + @Test + public void testMockRemoteConfig() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addModules(new ArchaiusKaryonModule(), new AbstractModule() { + @Override + protected void configure() { + bind(Key.get(Config.class, RemoteLayer.class)).toProvider(new Provider() { + @Inject + @Raw + Config rawConfig; + + @Override + public Config get() { + final SettableConfig remote = new DefaultSettableConfig(); + remote.setProperty("foo", "foo-" + rawConfig.getString("application_loaded")); + return remote; + } + }); + } + }) + .start(); + + Foo foo = injector.getInstance(Foo.class); + Config config = injector.getInstance(Config.class); + + assertThat(config.getString("foo"), equalTo("foo-true")); + } + + Properties singletonProperties(String key, String value) { + Properties props = new Properties(); + props.put(key, value); + return props; + } + + Config singletonConfig(String key, String value) { + return MapConfig.builder() + .put(key, value) + .build(); + } } diff --git a/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusProxyAutoBinderTest.java b/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusProxyAutoBinderTest.java new file mode 100644 index 00000000..a151a3dc --- /dev/null +++ b/karyon3-archaius2/src/test/java/com/netflix/karyon/archaius/ArchaiusProxyAutoBinderTest.java @@ -0,0 +1,50 @@ +package com.netflix.karyon.archaius; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.junit.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.Injector; +import com.netflix.archaius.annotations.Configuration; +import com.netflix.archaius.config.MapConfig; +import com.netflix.archaius.exceptions.ConfigException; +import com.netflix.karyon.Karyon; + +public class ArchaiusProxyAutoBinderTest { + @Configuration + public static interface Foo { + int timeout(); + } + + @Singleton + public static class Bar { + @Inject + Bar(Foo foo) { + + } + } + + @Test + public void testAutoBinder() throws ConfigException { + Injector injector = Karyon.newBuilder() + .addModules(new ArchaiusKaryonModule().withApplicationOverrides(MapConfig.builder() + .put("timeout", 123) + .build() + )) + .addModules(new AbstractModule() { + @Override + protected void configure() { + bind(Bar.class); + } + }) + .start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.timeout(), equalTo(123)); + } +} diff --git a/karyon3-archaius2/src/test/resources/foo.properties b/karyon3-archaius2/src/test/resources/foo.properties new file mode 100644 index 00000000..fb2f343e --- /dev/null +++ b/karyon3-archaius2/src/test/resources/foo.properties @@ -0,0 +1,3 @@ +foo_loaded=true +foo_override=file + diff --git a/karyon3-core/build.gradle b/karyon3-core/build.gradle index 6e30817b..9c964797 100644 --- a/karyon3-core/build.gradle +++ b/karyon3-core/build.gradle @@ -17,6 +17,7 @@ apply plugin: 'java' dependencies { + compile project(":karyon3-api") compile project(":karyon3-admin") compile "com.netflix.governator:governator-core:${governator_version}" @@ -26,6 +27,7 @@ dependencies { testCompile "org.hamcrest:hamcrest-core:${hamcrest_version}" testCompile "org.hamcrest:hamcrest-library:${hamcrest_version}" + testCompile "org.mockito:mockito-all:1.9.5" } eclipse { diff --git a/karyon3-core/src/main/java/com/netflix/karyon/AbstractKaryonModule.java b/karyon3-core/src/main/java/com/netflix/karyon/AbstractKaryonModule.java deleted file mode 100644 index 8b21355f..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/AbstractKaryonModule.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.netflix.karyon; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import com.google.inject.Module; -import com.google.inject.Stage; - -public abstract class AbstractKaryonModule implements KaryonModule { - private Karyon karyon; - - @Override - final public void configure(Karyon karyon) { - this.karyon = karyon; - try { - configure(); - } - finally { - this.karyon = null; - } - } - - protected abstract void configure(); - - protected void addAutoModuleListProvider(ModuleListProvider finder) { - karyon.addAutoModuleListProvider(finder); - } - - protected void addModules(Module ... modules) { - karyon.addModules(Arrays.asList(modules)); - } - - protected void addModules(List modules) { - karyon.addModules(modules); - } - - protected void addOverrideModules(Module ... modules) { - karyon.addOverrideModules(Arrays.asList(modules)); - } - - protected void addOverrideModules(List modules) { - karyon.addOverrideModules(modules); - } - - protected void addProfile(String profile) { - karyon.addProfile(profile); - } - - protected void addProfiles(String... profiles) { - if (profiles != null) { - karyon.addProfiles(Arrays.asList(profiles)); - } - } - - protected void addProfiles(Collection profiles) { - karyon.addProfiles(profiles); - } - - protected void inStage(Stage stage) { - karyon.inStage(stage); - } - - protected void enableFeature(KaryonFeature feature) { - karyon.enableFeature(feature); - } - - protected void disableFeature(KaryonFeature feature) { - karyon.disableFeature(feature); - } - - protected void setPropertySource(PropertySource propertySource) { - karyon.setPropertySource(propertySource); - } - - protected PropertySource getPropertySource() { - return karyon.getPropertySource(); - } - - protected Karyon getKaryon() { - return karyon; - } - -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/AbstractPropertySource.java b/karyon3-core/src/main/java/com/netflix/karyon/AbstractPropertySource.java index 06a2ef9d..d8b33721 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/AbstractPropertySource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/AbstractPropertySource.java @@ -2,6 +2,8 @@ import java.lang.reflect.Method; +import com.netflix.karyon.spi.PropertySource; + public abstract class AbstractPropertySource implements PropertySource { @Override public T get(String key, Class type) { diff --git a/karyon3-core/src/main/java/com/netflix/karyon/ClassPathConditionalModuleListProvider.java b/karyon3-core/src/main/java/com/netflix/karyon/ClassPathConditionalModuleListProvider.java deleted file mode 100644 index 721d80cb..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/ClassPathConditionalModuleListProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.netflix.karyon; - -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.List; - -import com.google.inject.Module; -import com.netflix.karyon.conditional.Conditional; - -/** - * ClassPath scanner using Guava's ClassPath - * - * @author elandau - */ -public class ClassPathConditionalModuleListProvider extends ClassPathModuleListProvider { - - public ClassPathConditionalModuleListProvider(String... packages) { - super(Arrays.asList(packages)); - } - - public ClassPathConditionalModuleListProvider(List packages) { - super(packages); - } - - @Override - protected boolean isAllowed(Class cls) { - for (Annotation annot : cls.getAnnotations()) { - if (null != annot.annotationType().getAnnotation(Conditional.class)) { - return true; - } - } - return false; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/ClassPathModuleListProvider.java b/karyon3-core/src/main/java/com/netflix/karyon/ClassPathModuleListProvider.java index eb6c6294..1af5b756 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/ClassPathModuleListProvider.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/ClassPathModuleListProvider.java @@ -13,7 +13,7 @@ * * @author elandau */ -public class ClassPathModuleListProvider implements ModuleListProvider { +class ClassPathModuleListProvider implements ModuleListProvider { private List packages; @@ -38,7 +38,10 @@ public List get() { try { // Include Modules that have at least on Conditional Class cls = Class.forName(classInfo.getName(), false, loader); - if (!cls.isInterface() && !Modifier.isAbstract( cls.getModifiers() ) && Module.class.isAssignableFrom(cls)) { + if ( Modifier.isPublic(cls.getModifiers()) + && !cls.isInterface() + && !Modifier.isAbstract( cls.getModifiers() ) + && Module.class.isAssignableFrom(cls)) { if (isAllowed((Class) cls)) { modules.add((Module) cls.newInstance()); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/DefaultKaryonModule.java b/karyon3-core/src/main/java/com/netflix/karyon/DefaultKaryonModule.java deleted file mode 100644 index 0f5f1870..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/DefaultKaryonModule.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.netflix.karyon; - -public class DefaultKaryonModule extends AbstractKaryonModule { - @Override - protected void configure() { - // TODO: This should probably not be added for ALL instances of KaryonBuilder. - addAutoModuleListProvider(ModuleListProviders.forPackagesConditional("com.netflix.karyon")); - addAutoModuleListProvider(ModuleListProviders.forPackagesConditional("com.google.inject.servlet")); - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/Karyon.java b/karyon3-core/src/main/java/com/netflix/karyon/Karyon.java index c716d997..89fa347c 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/Karyon.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/Karyon.java @@ -4,41 +4,60 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.inject.AbstractModule; +import com.google.inject.ConfigurationException; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; import com.google.inject.Module; +import com.google.inject.ProvisionException; import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matcher; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.spi.Element; +import com.google.inject.spi.Elements; +import com.google.inject.util.Modules; +import com.netflix.governator.ElementsEx; import com.netflix.governator.LifecycleInjector; -import com.netflix.karyon.conditional.ConditionalOnProfile; +import com.netflix.governator.LifecycleManager; +import com.netflix.karyon.annotations.Arguments; +import com.netflix.karyon.annotations.Profiles; +import com.netflix.karyon.conditional.ConditionalSupportModule; +import com.netflix.karyon.spi.AutoBinder; +import com.netflix.karyon.spi.KaryonBinder; +import com.netflix.karyon.spi.KaryonModule; import com.netflix.karyon.spi.ModuleListTransformer; +import com.netflix.karyon.spi.PropertySource; /** - * Base entry point for creating a LifecycleInjector with module auto-loading capabilities. - * Module auto-loading makes it possible to load bindings that are contextual to the - * environment in which the application is running based on things like profiles, - * properties and existing bindings. - * - * The LifecycleInjector created here uses a layered approach to construct the Guice Injector - * so that bindings can be overridden at a high level based on the runtime environment - * as opposed to sprinkling Modules.overrides and conditionals throughout the modules themselves - * since using Modules.overrides directly looses the original module's context and can easily result - * in difficult to debug duplicate binding errors. + * Main entry point for creating a LifecycleInjector with guice extensions such as + * conditional bindings. * * This injector is constructed in two phases. The first bootstrap phase determines which core * Guice modules should be installed based on processing of conditional annotations. The final * list of auto discovered modules is appended to the main list of modules and installed on the - * main injector. Application level override modules may be applied to this final list from the - * list of modules returned from {@link KaryonConfiguration#getOverrideModules()}. + * main injector. * * - Karyon.forApplication("foo") + Karyon.newBuilder() .addModules( - new JettyModule(), - new JerseyServletModule() { + new ArchaiusKaryonModule(), + new JettyModule(), + new JerseyServletModule() { {@literal @}@Override protected void configureServlets() { serve("/*").with(GuiceContainer.class); @@ -51,30 +70,16 @@ protected void configureServlets() { .start() .awaitTermination(); * - * - * +-------------------+ - * | Override | - * +-------------------+ - * | Auto Override | - * +-------------------+ - * | Core + Auto | - * +-------------------+ - * | Bootstrap Exposed | - * +-------------------+ */ public class Karyon { - private static final String KARYON_PROFILES = "karyon.profiles"; - - protected final String applicationName; - protected PropertySource propertySource = DefaultPropertySource.INSTANCE; protected Set profiles = new LinkedHashSet<>(); - protected List moduleProviders = new ArrayList<>(); - protected Map features = new HashMap<>(); protected Stage stage = Stage.DEVELOPMENT; protected List modules = new ArrayList<>(); protected List overrideModules = new ArrayList<>(); protected List transformers = new ArrayList<>(); - + protected List autoBinders = new ArrayList<>(); + protected IdentityHashMap, Object> features = new IdentityHashMap<>(); + // This is a hack to make sure that if archaius is used at some point we make use // of the bridge so any access to the legacy Archaius1 API is actually backed by // the Archaius2 implementation @@ -83,19 +88,47 @@ public class Karyon { System.setProperty("archaius.default.deploymentContext.class", "com.netflix.archaius.bridge.StaticDeploymentContext"); } - @Deprecated + @Singleton + class KaryonFeatureSetImpl implements KaryonFeatureSet { + private final IdentityHashMap, Object> features; + + @Inject + private PropertySource properties; + + @Inject + public KaryonFeatureSetImpl(IdentityHashMap, Object> features) { + this.features = features; + } + + @SuppressWarnings("unchecked") + @Override + public T get(KaryonFeature feature) { + if (features.containsKey(feature)) { + return (T) features.get(feature); + } + else if (properties == null) { + return feature.getDefaultValue(); + } + else { + return (T) properties.get(feature.getKey(), feature.getType(), feature.getDefaultValue()); + } + } + } + private Karyon() { this(null); } + @Deprecated protected Karyon(String applicationName) { - this.applicationName = applicationName; } /** - * Add main Guice modules to your application - * @param modules - * @return + * Add Guice modules to karyon. + * + * @param modules Guice modules to add. These modules my also implement KaryonModule to further + * extend Karyon. + * @return this */ public Karyon addModules(Module ... modules) { if (modules != null) { @@ -105,9 +138,11 @@ public Karyon addModules(Module ... modules) { } /** - * Add main Guice modules to your application - * @param modules - * @return + * Add Guice modules to karyon. + * + * @param modules Guice modules to add. These modules my also implement KaryonModule to further + * extend Karyon. + * @return this */ public Karyon addModules(List modules) { if (modules != null) { @@ -121,8 +156,9 @@ public Karyon addModules(List modules) { * conditionally loaded. This is useful for testing or when an application * absolutely needs to override a binding to fix a binding problem in the * code modules - * @param modules - * @return + * @param modules Modules that will be applied as overrides to modules add + * or installed via {@link Karyon#addModules(Module...)} + * @return this */ public Karyon addOverrideModules(Module ... modules) { if (modules != null) { @@ -136,8 +172,9 @@ public Karyon addOverrideModules(Module ... modules) { * conditionally loaded. This is useful for testing or when an application * absolutely needs to override a binding to fix a binding problem in the * code modules - * @param modules - * @return + * @param modules Modules that will be applied as overrides to modules add + * or installed via {@link Karyon#addModules(Module...)} + * @return this */ public Karyon addOverrideModules(List modules) { if (modules != null) { @@ -149,8 +186,8 @@ public Karyon addOverrideModules(List modules) { /** * Specify the Guice stage in which the application is running. By default Karyon * runs in Stage.DEVELOPMENT to achieve default lazy singleton behavior. - * @param stage - * @return + * @param stage Guice stage + * @return this */ public Karyon inStage(Stage stage) { this.stage = stage; @@ -160,19 +197,21 @@ public Karyon inStage(Stage stage) { /** * Add a module finder such as a ServiceLoaderModuleFinder or ClassPathScannerModuleFinder * @param provider - * @return + * + * @deprecated Module auto loading no longer supported. Install modules directly and use {@literal @}ProvidesConditionally + * @return this */ + @Deprecated public Karyon addAutoModuleListProvider(ModuleListProvider provider) { - if (provider != null) { - this.moduleProviders.add(provider); - } return this; } /** - * Add a runtime profile. @see {@link ConditionalOnProfile} + * Add a runtime profile. Profiles are processed by the conditional binding {@literal @}ConditionalOnProfile and + * are injectable as {@literal @}Profiles Set{@literal <}String{@literal >}. * - * @param profile + * @param profile A profile + * @return this */ public Karyon addProfile(String profile) { if (profile != null) { @@ -182,11 +221,14 @@ public Karyon addProfile(String profile) { } /** - * Add a runtime profiles. @see {@link ConditionalOnProfile} + * Add a runtime profiles. Profiles are processed by the conditional binding {@literal @}ConditionalOnProfile and + * are injectable as {@literal @}Profiles Set{@literal <}String{@literal >}. * - * @param profiles + * @param profiles Set of profiles + * @return this */ - public Karyon addProfiles(String... profiles) { + public Karyon addProfiles(String profile, String... profiles) { + this.profiles.add(profile); if (profiles != null) { this.profiles.addAll(Arrays.asList(profiles)); } @@ -194,9 +236,11 @@ public Karyon addProfiles(String... profiles) { } /** - * Add a runtime profiles. @see {@link ConditionalOnProfile} + * Add a runtime profiles. Profiles are processed by the conditional binding {@literal @}ConditionalOnProfile and + * are injectable as {@literal @}Profiles Set{@literal <}String{@literal >}. * - * @param profiles + * @param profiles Set of profiles + * @return this */ public Karyon addProfiles(Collection profiles) { if (profiles != null) { @@ -207,50 +251,48 @@ public Karyon addProfiles(Collection profiles) { /** * Enable the specified feature - * @param feature + * @param feature Boolean feature to enable + * @return this */ - public Karyon enableFeature(KaryonFeature feature) { - return enableFeature(feature, true); + public Karyon enableFeature(KaryonFeature feature) { + return setFeature(feature, true); } /** * Enable or disable the specified feature - * @param feature + * @param feature Boolean feature to disable + * @return this */ - public Karyon enableFeature(KaryonFeature feature, boolean enabled) { - if (feature != null) { - this.features.put(feature, enabled); - } - return this; + public Karyon enableFeature(KaryonFeature feature, boolean enabled) { + return setFeature(feature, enabled); } /** * Disable the specified feature - * @param feature + * @param feature Boolean feature to enable/disable + * @return this */ - public Karyon disableFeature(KaryonFeature feature) { - return enableFeature(feature, false); + public Karyon disableFeature(KaryonFeature feature) { + return setFeature(feature, false); } - public Karyon setPropertySource(PropertySource propertySource) { - this.propertySource = propertySource; + /** + * Set a feature + * @param feature Feature to set + * @return this + */ + public Karyon setFeature(KaryonFeature feature, T value) { + this.features.put(feature, value); return this; } - - public PropertySource getPropertySource() { - return this.propertySource; - } - - public String getApplicationName() { - return applicationName; - } /** * Add a ModuleListTransformer that will be invoked on the final list of modules * prior to creating the injectors. Multiple transformers may be added with each * transforming the result of the previous one. * - * @param transformer + * @param transformer A transformer + * @return this */ public Karyon addModuleListTransformer(ModuleListTransformer transformer) { if (transformer != null) { @@ -260,145 +302,155 @@ public Karyon addModuleListTransformer(ModuleListTransformer transformer) { } /** - * Call this anywhere in the process of manipulating the builder to apply a reusable - * sequence of calls to the builder + * Add an AutoBinder that will be called for any missing bindings. * - * @param modules - * @return The builder + * @param matcher Matcher to restrict the types for which the AutoBinder can be used. See {@link TypeLiteralMatchers} for + * specifying common matchers. + * @param autoBinder The auto binder + * @return this */ - public Karyon apply(KaryonModule ... modules) { - if (modules != null) { - for (KaryonModule module : modules) { - module.configure(this); - } - } + public > Karyon addAutoBinder(Matcher matcher, AutoBinder autoBinder) { + this.autoBinders.add(new MatchingAutoBinder(matcher, autoBinder)); + return this; + } + + @Deprecated + public Karyon apply(Module ... modules) { + addModules(modules); return this; } /** - * + * Create the injector and call any LifecycleListeners + * @return the LifecycleInjector for this run */ public LifecycleInjector start() { return start(null); } /** - * Shortcut to creating the injector - * @return The builder + * Create the injector and call any LifecycleListeners + * @param args - Runtime parameter (from main) injectable as {@literal @}Arguments String[] + * @return the LifecycleInjector for this run */ - public LifecycleInjector start(String[] args) { - if (this.getPropertySource().equals(DefaultPropertySource.INSTANCE) && isFeatureEnabled(KaryonFeatures.USE_ARCHAIUS)) { - try { - apply((KaryonModule) Class.forName("com.netflix.karyon.archaius.ArchaiusKaryonModule").newInstance()); + public LifecycleInjector start(final String[] args) { + for (Module module : modules) { + if (module instanceof KaryonModule) { + ((KaryonModule)module).configure(new KaryonBinder() { + @Override + public > void bindAutoBinder( + Matcher matcher, AutoBinder autoBinder) { + Karyon.this.addAutoBinder(matcher, autoBinder); + } + }); } - catch (ClassNotFoundException e) { - throw new RuntimeException("Unable to bootstrap using archaius. Either add a dependency on 'com.netflix.karyon:karyon3-archaius2' or disable the feature KaryonFeatures.USE_ARCHAIUS"); - } - catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException("Unable to bootstrap using archaius"); - } - } - - String karyonProfiles = getPropertySource().get(KARYON_PROFILES); - if (karyonProfiles != null) { - addProfiles(karyonProfiles); } - - if (isFeatureEnabled(KaryonFeatures.USE_DEFAULT_KARYON_MODULE)) - apply(new DefaultKaryonModule()); - for (ModuleListTransformer transformer : transformers) { modules = transformer.transform(Collections.unmodifiableList(modules)); } - return LifecycleInjectorCreator.createInjector(new KaryonConfiguration() { - @Override - public List getModules() { - return modules; - } - - @Override - public List getOverrideModules() { - return overrideModules; - } - - @Override - public List getAutoModuleListProviders() { - return moduleProviders; + this.addAutoBinder(TypeLiteralMatchers.subclassOf(PropertySource.class), new PropertySourceAutoBinder()); + + final Logger LOG = LoggerFactory.getLogger(Karyon.class); + + // Create the main LifecycleManager to be used by all levels + final LifecycleManager manager = new LifecycleManager(); + + final KaryonFeatureSetImpl featureSet = new KaryonFeatureSetImpl(new IdentityHashMap<>(features)); + + // Construct the injector using our override structure + try { + final Module coreModule = Modules.override( + ImmutableList.builder() + .addAll(modules) + .add(new LifecycleModule()) + .add(new ConditionalSupportModule()) + .add(new AbstractModule() { + @Override + protected void configure() { + bind(KaryonFeatureSet.class).toInstance(featureSet); + bind(LifecycleManager.class).toInstance(manager); + + Multibinder profiles = Multibinder.newSetBinder(binder(), String.class, Profiles.class); + for (String profile : Karyon.this.profiles) { + profiles.addBinding().toInstance(profile); + } + + if (args != null) { + bind(String[].class).annotatedWith(Arguments.class).toInstance(args); + } + else { + bind(String[].class).annotatedWith(Arguments.class).toInstance(new String[]{}); + } + } + }) + .build()) + .with(overrideModules); + + for (Element binding : Elements.getElements(coreModule)) { + LOG.debug("Binding : {}", binding); } - - @Override - public Set getProfiles() { - return profiles; + + Injector injector = Guice.createInjector( + stage, + coreModule, + new AbstractModule() { + @Override + protected void configure() { + Set> boundKeys = ElementsEx.getAllBoundKeys(Elements.getElements(coreModule)); + Set> injectionKeys = ElementsEx.getAllInjectionKeys(Elements.getElements(coreModule)); + injectionKeys.removeAll(boundKeys); + + for (Key key : injectionKeys) { + for (MatchingAutoBinder factory : Karyon.this.autoBinders) { + if (factory.configure(binder(), key)) { + break; + } + } + } + } + } + ); + manager.notifyStarted(); + return new LifecycleInjector(injector, manager); + } + catch (ProvisionException|CreationException|ConfigurationException e) { + LOG.error("Failed to create injector", e); + try { + manager.notifyStartFailed(e); } - - @Override - public PropertySource getPropertySource() { - return propertySource; + catch (Exception e2) { + LOG.error("Failed to notify injector creation failure", e2 ); } - - @Override - public Stage getStage() { - return stage; + if (!featureSet.get(KaryonFeatures.SHUTDOWN_ON_ERROR)) { + return new LifecycleInjector(null, manager); } - - @Override - public boolean isFeatureEnabled(KaryonFeature feature) { - return Karyon.this.isFeatureEnabled(feature); - } - }); - } - - private boolean isFeatureEnabled(KaryonFeature feature) { - if (propertySource != null) { - Boolean value = propertySource.get(feature.getKey(), Boolean.class); - if (value != null && value == true) { - return true; + else { + throw e; } } - Boolean value = features.get(feature); - return value == null - ? feature.isEnabledByDefault() - : value; } - + /** - * Starting point for creating a Karyon application. - * - * @param applicationName - * @return + * Construct a new Karyon instance + * @return Karyon instance */ + public static Karyon newBuilder() { + return new Karyon(); + } + + @Deprecated public static Karyon forApplication(String applicationName) { return new Karyon(applicationName); } - /** - * @deprecated Call Karyon.forApplication("foo") - */ @Deprecated public static Karyon create() { return new Karyon(); } - /** - * @deprecated Call Karyon.forApplication("foo").addModules(modules) - */ @Deprecated public static Karyon create(Module ... modules) { return new Karyon().addModules(modules); } - - /** - * @deprecated Call Karyon.forApplication("foo").apply(modules) - */ - @Deprecated - public static Karyon from(KaryonModule ... modules) { - Karyon karyon = new Karyon(); - if (modules != null) { - for (KaryonModule module : modules) { - karyon.apply(module); - } - } - return karyon; - } } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/KaryonAutoContext.java b/karyon3-core/src/main/java/com/netflix/karyon/KaryonAutoContext.java deleted file mode 100644 index c5d4e623..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/KaryonAutoContext.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.netflix.karyon; - -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Set; - -import com.google.inject.spi.Element; - -/** - * Context for the auto module to provide context to any Condition - */ -public interface KaryonAutoContext { - /** - * @param className - * @return Return true if the module was installed - */ - boolean hasModule(String className); - - /** - * @param profile - * @return Return true if profile was set - */ - boolean hasProfile(String profile); - - /** - * @param type - * @return Return true if a binding exists for a key - */ - boolean hasBinding(Class type); - - /** - * - * @param type - * @param qualifier - * @return Return true if a binding exists for a type and qualifier - */ - boolean hasBinding(Class type, Class qualifier); - - /** - * @param type - * @return Return true if there exists an injection point for the specified type - */ - boolean hasInjectionPoint(Class type); - - /** - * @param type - * @param qualifier - * @return Return true if there exists an injection point for the specified type and qualifier - */ - boolean hasInjectionPoint(Class type, Class qualifier); - - /** - * Get all elements that are part of the core modules - * @return - */ - List getElements(); - - /** - * Return a complete list of configured profiles - * @return - */ - Set getProfiles(); - - /** - * Return a complete list of modules, including installed modules - * @return - */ - Set getModules(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/KaryonConfiguration.java b/karyon3-core/src/main/java/com/netflix/karyon/KaryonConfiguration.java deleted file mode 100644 index caff6878..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/KaryonConfiguration.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.netflix.karyon; - -import java.util.List; -import java.util.Set; - -import com.google.inject.Module; -import com.google.inject.Stage; - -/** - * Configuration contract needed to bootstrap a Governator based application. - */ -public interface KaryonConfiguration { - /** - * Return the list of core application modules to be used - */ - List getModules(); - - /** - * Return a list of override modules to be used as the final override for any bindings - * specified in modules returned by getModules(). Override modules are useful when an - * application has to resolve a binding conflict or when testing. This method is - * recommended over Guice's Modules.override since the later can result in duplicate - * bindings due to the loss of context for Guice's built in module de-duping using - * equals() and hashCode() on a module class. - * @return - */ - List getOverrideModules(); - - /** - * Return a list of ModuleListProviders through which modules may be auto-loaded. - */ - List getAutoModuleListProviders(); - - /** - * Return a list of active profiles for the injector. These profiles are used when - * processing @ConditionalOnProfile annotations - */ - Set getProfiles(); - - /** - * Return the main property source to be used during the bootstrap phase - * @return - */ - PropertySource getPropertySource(); - - /** - * Return the Guice injector stage. The recommended default is Stage.DEVELOPMENT - * otherwise all singletons are eager, including lazy injection using Provider - */ - Stage getStage(); - - /** - * Determine if a core karyon feature has been enabled. See {@link KaryonFeatures} - * for available features. - */ - boolean isFeatureEnabled(KaryonFeature feature); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeature.java b/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeature.java index e0ed51aa..80608e0c 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeature.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeature.java @@ -1,14 +1,27 @@ package com.netflix.karyon; -/** - * Base interface for all {@link Karyon} features to be implemented by an - * enum, such as {@link KaryonFeatures}. Each feature has an implicit - * default value. - * - * @see Karyon - * @author elandau - */ -public interface KaryonFeature { - boolean isEnabledByDefault(); - String getKey(); +public class KaryonFeature { + private final String key; + private final T defaultValue; + + public static KaryonFeature create(String key, T defaultValue) { + return new KaryonFeature(key, defaultValue); + } + + public KaryonFeature(String key, T defaultValue) { + this.key = key; + this.defaultValue = defaultValue; + } + + public String getKey() { + return key; + } + + public Class getType() { + return (Class) defaultValue.getClass(); + } + + public T getDefaultValue() { + return defaultValue; + } } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatureSet.java b/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatureSet.java new file mode 100644 index 00000000..61a49bff --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatureSet.java @@ -0,0 +1,14 @@ +package com.netflix.karyon; + +/** + * Container of karyon features. + */ +public interface KaryonFeatureSet { + /** + * Get the value of the feature or the default if none is set + * + * @param feature + * @return + */ + T get(KaryonFeature feature); +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatures.java b/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatures.java index 87d676bb..9fb92c0e 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatures.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/KaryonFeatures.java @@ -1,46 +1,18 @@ package com.netflix.karyon; /** - * Core karyon features. Features are configured/enabled on {@link KaryonConfiguration} + * Core karyon features. Features are configured/enabled on {@link Karyon} * * @author elandau */ -public enum KaryonFeatures implements KaryonFeature { +public final class KaryonFeatures { /** * When disable the Karyon process will continue running even if there is a catastrophic * startup failure. This allows the admin page to stay up so that the process may be * debugged more easily. */ - SHUTDOWN_ON_ERROR(true, "karyon.features.shutdownOnError"), + public final static KaryonFeature SHUTDOWN_ON_ERROR = KaryonFeature.create("karyon.features.shutdownOnError", true); - /** - * When enabled will auto install Karyon's default settings including classpath - * scanning of 'com.netflix.karyon' for conditional modules - */ - USE_DEFAULT_KARYON_MODULE(true, "karyon.features.defaultModule"), - - /** - * When enabled will auto install the Karyon-Archaius integration unless already - * installed. - */ - USE_ARCHAIUS(true, "karyon.features.archaius"), - ; - - private final boolean enabled; - private final String key; - - KaryonFeatures(boolean enabled, String key) { - this.enabled = enabled; - this.key = key; - } - - @Override - public boolean isEnabledByDefault() { - return enabled; - } - - @Override - public String getKey() { - return key; - } + @Deprecated + public final static KaryonFeature USE_ARCHAIUS = KaryonFeature.create("karyon.features.archaius", false); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/KaryonModule.java b/karyon3-core/src/main/java/com/netflix/karyon/KaryonModule.java deleted file mode 100644 index af9fc5c6..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/KaryonModule.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.netflix.karyon; - -/** - * Sequence of calls to Karyon captured in a single class that can be applied in one - * call to the KaryonDsl. - * - * - - Karyon.newServer() - .using(new MyKaryonDslModule()) - .start(); - - * - * - * @author elandau - * - */ -public interface KaryonModule { - void configure(Karyon karyon); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/LifecycleInjectorCreator.java b/karyon3-core/src/main/java/com/netflix/karyon/LifecycleInjectorCreator.java deleted file mode 100644 index 4d7a68dd..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/LifecycleInjectorCreator.java +++ /dev/null @@ -1,290 +0,0 @@ -package com.netflix.karyon; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableList; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Key; -import com.google.inject.Module; -import com.google.inject.Stage; -import com.google.inject.spi.Element; -import com.google.inject.spi.Elements; -import com.google.inject.util.Modules; -import com.netflix.governator.ElementsEx; -import com.netflix.governator.LifecycleInjector; -import com.netflix.governator.LifecycleManager; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.Conditional; -import com.netflix.karyon.conditional.OverrideModule; - -/** - * Utility class matching Guice's {@link Guice} but providing shutdown capabilities. - * Note that the injector being created will not by default support @PreDestory and - * @PostConstruct. Those are supported by adding LifecycleModule to the list of modules. - * - * @author elandau - * - */ -class LifecycleInjectorCreator { - public static LifecycleInjector createInjector(final KaryonConfiguration config) { - Logger LOG = LoggerFactory.getLogger(LifecycleInjectorCreator.class); - LOG.info("Using profiles : " + config.getProfiles()); - - // Load all candidate modules for auto-loading/override - final Set candidateModules = new HashSet<>(); - for (ModuleListProvider loader : config.getAutoModuleListProviders()) { - candidateModules.addAll(loader.get()); - } - - // Create the main LifecycleManager to be used by all levels - final LifecycleManager manager = new LifecycleManager(); - - // Construct the injector using our override structure - try { - final List elements = Elements.getElements(Stage.DEVELOPMENT, config.getModules()); - final Set> injectionKeys = ElementsEx.getAllInjectionKeys(elements); - final Set> boundKeys = ElementsEx.getAllBoundKeys(elements); - final Set moduleNames = new HashSet<>(ElementsEx.getAllSourceModules(elements)); - - final KaryonAutoContext context = new KaryonAutoContext() { - @Override - public boolean hasModule(String className) { - return moduleNames.contains(className); - } - - @Override - public boolean hasProfile(String profile) { - return config.getProfiles().contains(profile); - } - - @Override - public boolean hasBinding(Class type) { - return boundKeys.contains(Key.get(type)); - } - - @Override - public boolean hasBinding(Class type, Class qualifier) { - if (qualifier != null) { - return boundKeys.contains(Key.get(type, qualifier)); - } - else { - if (Modifier.isAbstract( type.getModifiers() ) || type.isInterface()) { - return boundKeys.contains(Key.get(type)); - } - return true; - } - } - - @Override - public List getElements() { - return elements; - } - - @Override - public Set getProfiles() { - return config.getProfiles(); - } - - @Override - public Set getModules() { - return moduleNames; - } - - @Override - public boolean hasInjectionPoint(Class type) { - return hasInjectionPoint(type); - } - - @Override - public boolean hasInjectionPoint(Class type, Class qualifier) { - if (qualifier != null) { - return injectionKeys.contains(Key.get(type, qualifier)); - } - else { - return injectionKeys.contains(Key.get(type)); - } - } - }; - - Module coreModule = Modules.override( - createAutoModule(LOG, context, config, candidateModules, config.getModules())) - .with(config.getOverrideModules()); - - for (Element binding : Elements.getElements(coreModule)) { - LOG.debug("Binding : {}", binding); - } - - LOG.info("Configured override modules : " + config.getOverrideModules()); - - Injector injector = Guice.createInjector( - config.getStage(), - new LifecycleModule(), - new AbstractModule() { - @Override - protected void configure() { - bind(LifecycleManager.class).toInstance(manager); - bind(KaryonConfiguration.class).toInstance(config); - bind(PropertySource.class).toInstance(config.getPropertySource()); - bind(KaryonAutoContext.class).toInstance(context); - } - }, - coreModule - ); - manager.notifyStarted(); - return new LifecycleInjector(injector, manager); - } - catch (Throwable e) { - e.printStackTrace(System.err); - try { - manager.notifyStartFailed(e); - } - catch (Exception e2) { - System.err.println("Failed to notify injector creation failure!"); - e2.printStackTrace(System.err); - } - if (config.isFeatureEnabled(KaryonFeatures.SHUTDOWN_ON_ERROR)) - throw new RuntimeException(e); - return new LifecycleInjector(null, manager); - } - } - - private static Module createAutoModule(final Logger LOG, final KaryonAutoContext context, final KaryonConfiguration config, final Set candidateModules, final List coreModules) throws Exception { - LOG.info("Creating {} injector"); - - // Temporary injector to used to construct the condition checks - final Injector injector = Guice.createInjector(config.getStage(), - new AbstractModule() { - @Override - protected void configure() { - bind(KaryonConfiguration.class).toInstance(config); - bind(PropertySource.class).toInstance(config.getPropertySource()); - bind(KaryonAutoContext.class).toInstance(context); - } - }); - - PropertySource propertySource = config.getPropertySource(); - - // Iterate through all loaded modules and filter out any modules that - // have failed the condition check. Also, keep track of any override modules - // for already installed modules. - final List overrideModules = new ArrayList<>(); - final List autoModules = new ArrayList<>(); - for (Module module : candidateModules) { - if (!isModuleEnabled(propertySource, module)) { - LOG.info("(IGNORING) {}", module.getClass().getName()); - continue; - } - - if (shouldInstallModule(LOG, injector, module)) { - OverrideModule override = module.getClass().getAnnotation(OverrideModule.class); - if (override != null) { - LOG.info(" (ADDING) {}", module.getClass().getSimpleName()); - overrideModules.add(module); - } - else { - LOG.info(" (ADDING) {}", module.getClass().getSimpleName()); - autoModules.add(module); - } - } - else { - LOG.info(" (DISCARD) {}", module.getClass().getSimpleName()); - } - } - - LOG.info("Core Modules : " + context.getModules()); - LOG.info("Auto Modules : " + autoModules); - LOG.info("Override Modules : " + overrideModules); - - return Modules.override(ImmutableList.builder().addAll(coreModules).addAll(autoModules).build()) - .with(overrideModules); - } - - /** - * Determine if a module should be installed based on the conditional annotations - * @param LOG - * @param injector - * @param module - * - * @return - * @throws Exception - */ - private static boolean shouldInstallModule(Logger LOG, Injector injector, Module module) throws Exception { - LOG.info("Evaluating module {}", module.getClass().getName()); - - // The class may have multiple Conditional annotations - for (Annotation annot : module.getClass().getAnnotations()) { - Conditional conditional = annot.annotationType().getAnnotation(Conditional.class); - if (conditional != null) { - // A Conditional may have a list of multiple Conditions - for (Class> condition : conditional.value()) { - try { - // Construct the condition using Guice so that anything may be injected into - // the condition - Condition c = injector.getInstance(condition); - // Look for method signature : boolean check(T annot) - // where T is the annotation type. Note that the same checker will be used - // for all conditions of the same annotation type. - try { - Method check = condition.getDeclaredMethod("check", annot.annotationType()); - if (!(boolean)check.invoke(c, annot)) { - LOG.info(" FAIL {}", formatConditional(annot)); - return false; - } - } - // If not found, look for method signature - // boolean check(); - catch (NoSuchMethodException e) { - Method check = condition.getDeclaredMethod("check"); - if (!(boolean)check.invoke(c)) { - LOG.info(" FAIL {}", formatConditional(annot)); - return false; - } - } - - LOG.info(" (PASS) {}", formatConditional(annot)); - } - catch (Exception e) { - LOG.info(" (FAIL) {}", formatConditional(annot), e); - throw new Exception("Failed to check condition '" + condition + "' on module '" + module.getClass() + "'", e); - } - } - } - } - return true; - } - - private static Boolean isModuleEnabled(final PropertySource propertySource, final Module module) { - String name = module.getClass().getName(); - int pos = name.length(); - do { - if (propertySource.get("governator.module.disabled." + name.substring(0, pos), Boolean.class, false)) { - return false; - } - pos = name.lastIndexOf(".", pos-1); - } while (pos > 0); - return true; - } - - private static String formatConditional(Annotation a) { - String str = a.toString(); - int pos = str.indexOf("("); - if (pos != -1) { - pos = str.lastIndexOf(".", pos); - if (pos != -1) { - return str.substring(pos+1); - } - } - return str; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/LifecycleModule.java b/karyon3-core/src/main/java/com/netflix/karyon/LifecycleModule.java index b38016a6..c8fc3ba4 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/LifecycleModule.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/LifecycleModule.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.inject.AbstractModule; import com.google.inject.Key; import com.google.inject.ProvisionException; import com.google.inject.matcher.Matchers; @@ -28,11 +29,11 @@ import com.netflix.governator.LifecycleListener; import com.netflix.governator.LifecycleManager; import com.netflix.governator.ProvisionMetrics; -import com.netflix.governator.SingletonModule; import com.netflix.governator.guice.lazy.FineGrainedLazySingleton; import com.netflix.governator.guice.lazy.FineGrainedLazySingletonScope; import com.netflix.governator.guice.lazy.LazySingleton; import com.netflix.governator.guice.lazy.LazySingletonScope; +import com.netflix.karyon.spi.PropertySource; /** * Adds support for standard lifecycle annotations @PostConstruct and @PreDestroy to Guice. @@ -56,7 +57,7 @@ * @author elandau * */ -public final class LifecycleModule extends SingletonModule { +public final class LifecycleModule extends AbstractModule { private static final Logger LOG = LoggerFactory.getLogger(LifecycleModule.class); /** @@ -82,17 +83,19 @@ static class LifecycleProvisionListener extends DefaultLifecycleListener impleme @Inject public static void initialize( + KaryonFeatureSet karyonFeatures, LifecycleManager manager, LifecycleProvisionListener listener, Set features, - ProvisionMetrics metrics, - KaryonConfiguration config) { - LOG.debug("LifecycleProvisionListener initialized {}", features); + ProvisionMetrics metrics, + PropertySource property) { listener.metrics = metrics; listener.manager = manager; listener.manager.addListener(listener); listener.features = features; - listener.shutdownOnFailure = config.isFeatureEnabled(KaryonFeatures.SHUTDOWN_ON_ERROR); + listener.shutdownOnFailure = karyonFeatures.get(KaryonFeatures.SHUTDOWN_ON_ERROR); + + LOG.debug("LifecycleProvisionListener initialized {}", features); LifecycleListener l; while (null != (l = listener.pendingLifecycleListeners.poll())) { @@ -223,5 +226,20 @@ protected void configure() { // and DEVELOPMENT mode makes everything lazy. bindScope(FineGrainedLazySingleton.class, FineGrainedLazySingletonScope.get()); bindScope(LazySingleton.class, LazySingletonScope.get()); - } + } + + @Override + public boolean equals(Object obj) { + return getClass().equals(obj.getClass()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public String toString() { + return "LifecycleModule"; + } } \ No newline at end of file diff --git a/karyon3-core/src/main/java/com/netflix/karyon/MatchingAutoBinder.java b/karyon3-core/src/main/java/com/netflix/karyon/MatchingAutoBinder.java new file mode 100644 index 00000000..9966ca07 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/MatchingAutoBinder.java @@ -0,0 +1,25 @@ +package com.netflix.karyon; + +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matcher; +import com.netflix.karyon.spi.AutoBinder; + +class MatchingAutoBinder> implements AutoBinder { + final Matcher matcher; + final AutoBinder factory; + + MatchingAutoBinder(Matcher matcher, AutoBinder factory) { + this.matcher = matcher; + this.factory = factory; + } + + @Override + public boolean configure(Binder binder, Key key) { + if (matcher.matches((T) key.getTypeLiteral())) { + return factory.configure(binder, key); + } + return false; + } +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/ModuleListProviders.java b/karyon3-core/src/main/java/com/netflix/karyon/ModuleListProviders.java index a8e1fed8..13e68896 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/ModuleListProviders.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/ModuleListProviders.java @@ -44,10 +44,6 @@ public static ModuleListProvider forPackages(final String... packages) { return new ClassPathModuleListProvider(packages); } - public static ModuleListProvider forPackagesConditional(final String... packages) { - return new ClassPathConditionalModuleListProvider(packages); - } - /** * Provider that will use Guava's ClassPath scanner to scan the provided * packages. @@ -59,10 +55,6 @@ public static ModuleListProvider forPackages(final List packages) { return new ClassPathModuleListProvider(packages); } - public static ModuleListProvider forPackagesConditional(List packages) { - return new ClassPathConditionalModuleListProvider(packages); - } - /** * Provider using the ServiceLoader for class Module * diff --git a/karyon3-core/src/main/java/com/netflix/karyon/PropertiesPropertySource.java b/karyon3-core/src/main/java/com/netflix/karyon/PropertiesPropertySource.java index 1be85a38..624548a9 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/PropertiesPropertySource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/PropertiesPropertySource.java @@ -7,6 +7,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Module; import com.google.inject.Provides; +import com.netflix.karyon.spi.PropertySource; public class PropertiesPropertySource extends AbstractPropertySource { private Properties props; diff --git a/karyon3-core/src/main/java/com/netflix/karyon/PropertySourceAutoBinder.java b/karyon3-core/src/main/java/com/netflix/karyon/PropertySourceAutoBinder.java new file mode 100644 index 00000000..48906460 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/PropertySourceAutoBinder.java @@ -0,0 +1,14 @@ +package com.netflix.karyon; + +import com.google.inject.Binder; +import com.google.inject.Key; +import com.netflix.karyon.spi.AutoBinder; +import com.netflix.karyon.spi.PropertySource; + +public class PropertySourceAutoBinder implements AutoBinder { + @Override + public boolean configure(Binder binder, Key key) { + binder.bind(PropertySource.class).toInstance(DefaultPropertySource.INSTANCE); + return true; + } +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/ServiceLoaderModuleListProvider.java b/karyon3-core/src/main/java/com/netflix/karyon/ServiceLoaderModuleListProvider.java index 855bdd11..932c07cb 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/ServiceLoaderModuleListProvider.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/ServiceLoaderModuleListProvider.java @@ -1,13 +1,11 @@ package com.netflix.karyon; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; import com.google.inject.Module; -import com.netflix.karyon.conditional.Conditional; /** * Load Module.class modules from the ServerLoader but filter out any modules @@ -33,21 +31,9 @@ public List get() { List modules = new ArrayList<>(); Iterator iter = ServiceLoader.load(type).iterator(); while (iter.hasNext()) { - Module module = iter.next(); - if (hasConditionalAnnotation(module.getClass())) { - modules.add(module); - } + modules.add(iter.next()); } return modules; } - - private boolean hasConditionalAnnotation(Class type) { - for (Annotation annot : type.getAnnotations()) { - if (annot.annotationType().isAnnotationPresent(Conditional.class)) { - return true; - } - } - return false; - } } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/TypeLiteralMatchers.java b/karyon3-core/src/main/java/com/netflix/karyon/TypeLiteralMatchers.java new file mode 100644 index 00000000..7fe15044 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/TypeLiteralMatchers.java @@ -0,0 +1,38 @@ +package com.netflix.karyon; + +import java.lang.annotation.Annotation; + +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; + +/** + * Utility class for creating matchers of TypeLiteral + */ +public final class TypeLiteralMatchers { + /** + * Create a matcher that succeeds if the type contains the specified annotation + * @param annot Annotation to match + */ + public static > Matcher annotatedWith(Class annot) { + return new AbstractMatcher() { + @Override + public boolean matches(T t) { + return null != t.getRawType().getAnnotation(annot); + } + }; + } + + /** + * Create a matcher that succeeds if the type is a or is a subclass of a specific type + * @param type + */ + public static > Matcher subclassOf(Class type) { + return new AbstractMatcher() { + @Override + public boolean matches(T t) { + return t.getRawType().isAssignableFrom(type); + } + }; + } +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/CoreAdminModule.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/CoreAdminModule.java index fbc38b4f..232601ef 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/CoreAdminModule.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/CoreAdminModule.java @@ -5,9 +5,7 @@ import com.google.inject.grapher.ShortNameFactory; import com.google.inject.grapher.graphviz.PortIdFactory; import com.google.inject.grapher.graphviz.PortIdFactoryImpl; -import com.netflix.karyon.conditional.ConditionalOnModule; -@ConditionalOnModule(AdminModule.class) public final class CoreAdminModule extends AbstractModule { @Override protected void configure() { @@ -15,7 +13,6 @@ protected void configure() { bind(DIGraphResource.class); bind(EnvAdminResource.class); bind(JarsAdminResource.class); - bind(MetaAdminResource.class); bind(HealthCheckResource.class); bind(GuiceLifecycleResource.class); bind(DIProvisionResource.class); diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/DIGraphResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/DIGraphResource.java index 684e8c11..59ba4331 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/DIGraphResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/DIGraphResource.java @@ -7,10 +7,11 @@ import javax.inject.Singleton; import com.google.inject.Injector; +import com.netflix.karyon.admin.AdminService; @Singleton @AdminService(name="di-graph", index="list") -public class DIGraphResource { +final class DIGraphResource { private final JsonGrapher grapher; private final Injector injector; diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/DIProvisionResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/DIProvisionResource.java index 826c0ab0..831c7e28 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/DIProvisionResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/DIProvisionResource.java @@ -11,10 +11,11 @@ import com.netflix.governator.ProvisionMetrics; import com.netflix.governator.ProvisionMetrics.Element; import com.netflix.governator.ProvisionMetrics.Visitor; +import com.netflix.karyon.admin.AdminService; @Singleton @AdminService(name="di-provision", index="list") -public class DIProvisionResource { +final class DIProvisionResource { private final ProvisionMetrics metrics; @Inject diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/EnvAdminResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/EnvAdminResource.java index 8450a0f1..202ceb6d 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/EnvAdminResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/EnvAdminResource.java @@ -2,11 +2,13 @@ import javax.inject.Singleton; +import com.netflix.karyon.admin.AdminService; + import java.util.Map; @Singleton @AdminService(name="env", index="list") -public class EnvAdminResource { +final class EnvAdminResource { public Map list() throws Exception { return System.getenv(); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceKeysAdminResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceKeysAdminResource.java index ea41d7b3..d534c984 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceKeysAdminResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceKeysAdminResource.java @@ -10,10 +10,11 @@ import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; +import com.netflix.karyon.admin.AdminService; @Singleton @AdminService(name="guice-keys", index="list") -public class GuiceKeysAdminResource { +final class GuiceKeysAdminResource { private final Injector injector; @Inject diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceLifecycleResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceLifecycleResource.java index 0931fff6..a2ff4900 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceLifecycleResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/GuiceLifecycleResource.java @@ -5,10 +5,11 @@ import com.netflix.governator.LifecycleManager; import com.netflix.governator.LifecycleManager.State; +import com.netflix.karyon.admin.AdminService; @Singleton @AdminService(name="guice-lifecycle", index="current") -public class GuiceLifecycleResource { +final class GuiceLifecycleResource { private LifecycleManager manager; public static interface Response { diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/HealthCheckResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/HealthCheckResource.java index d7c326ea..5e2ac2f4 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/HealthCheckResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/HealthCheckResource.java @@ -3,6 +3,7 @@ import javax.inject.Inject; import javax.inject.Singleton; +import com.netflix.karyon.admin.AdminService; import com.netflix.karyon.health.HealthCheck; import com.netflix.karyon.health.HealthCheckStatus; @@ -15,7 +16,7 @@ */ @Singleton @AdminService(name="health", index="current") -public class HealthCheckResource { +final class HealthCheckResource { private final HealthCheck healthCheck; @Inject diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/JarsAdminResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/JarsAdminResource.java index 8030320e..22fdec69 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/JarsAdminResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/JarsAdminResource.java @@ -15,9 +15,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.netflix.karyon.admin.AdminService; + @Singleton @AdminService(name="jars", index="list") -public class JarsAdminResource { +final class JarsAdminResource { private static final Logger LOG = LoggerFactory.getLogger(JarsAdminResource.class); private static final String JAR_PATTERN = "^jar:file:(.+)!/META-INF/MANIFEST.MF$"; diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/JsonGrapher.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/JsonGrapher.java index deb9a1e6..66f31ab0 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/JsonGrapher.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/JsonGrapher.java @@ -26,7 +26,7 @@ import com.netflix.governator.ProvisionMetrics.Element; import com.netflix.governator.ProvisionMetrics.Visitor; -class JsonGrapher extends KaryonAbstractInjectorGrapher { +final class JsonGrapher extends KaryonAbstractInjectorGrapher { private final Map, GraphNode> nodes = Maps.newHashMap(); private final NameFactory nameFactory; private PrintWriter out; diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/SystemInfoResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/SystemInfoResource.java index 46b6de76..c079bfb1 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/SystemInfoResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/SystemInfoResource.java @@ -5,9 +5,11 @@ import javax.inject.Singleton; +import com.netflix.karyon.admin.AdminService; + @Singleton @AdminService(name="system-info", index="get") -public class SystemInfoResource { +final class SystemInfoResource { public OperatingSystemMXBean get() { return ManagementFactory.getOperatingSystemMXBean(); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/admin/ThreadsAdminResource.java b/karyon3-core/src/main/java/com/netflix/karyon/admin/ThreadsAdminResource.java index 8fac6863..53a9dba4 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/admin/ThreadsAdminResource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/admin/ThreadsAdminResource.java @@ -7,9 +7,11 @@ import javax.inject.Singleton; +import com.netflix.karyon.admin.AdminService; + @Singleton @AdminService(name="threads", index="list") -public class ThreadsAdminResource { +final class ThreadsAdminResource { static class Details { private Thread thread; diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/Condition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/Condition.java deleted file mode 100644 index b7db2c88..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/Condition.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netflix.karyon.conditional; - -/** - * Module condition that must be paired with a Conditional annotation - * - * @author elandau - * - * @param - */ -public interface Condition { - /** - * Check if the Condition is true. - * - * @param param The annotation instance - * @return - */ - boolean check(T param); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/Conditional.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/Conditional.java deleted file mode 100644 index d6fd2253..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/Conditional.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Meta annotation for a ConditionalXXX annotation used to indicate which conditions implement - * processing of the annotation. - * - * @author elandau - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -public @interface Conditional { - - /** - * All {@link Condition}s that must be true in order for the component to be registered. - */ - Class>[] value(); - -} \ No newline at end of file diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalBinder.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalBinder.java new file mode 100644 index 00000000..84d8b609 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalBinder.java @@ -0,0 +1,112 @@ +package com.netflix.karyon.conditional; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Inject; +import javax.inject.Provider; + +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.util.Types; + +/** + * Binding for a single conditional T. + * + * @param + */ +public class ConditionalBinder { + private static final AtomicInteger counter = new AtomicInteger(); + + /** + * Used to generate unique bindings for conditional T keys that will not conflict + * with annotated T + * @return Unique annotation + */ + public static IdQualifier newIdQualifier() { + final int id = counter.incrementAndGet(); + return new IdQualifier() { + @Override + public Class annotationType() { + return IdQualifier.class; + } + + @Override + public int id() { + return id; + } + + @Override + public String toString() { + return "IdQualifier(id=" + id + ")"; + } + }; + } + + @Inject + private Injector injector; + + private final IdQualifier id; + private final Key idKey; + private final List conditionals; + private final Object source; + private final boolean isDefault; + + public ConditionalBinder(Binder binder, Key key, boolean isDefault, List conditionals, Object source) { + this.id = newIdQualifier(); + this.idKey = Key.get(key.getTypeLiteral(), id); + this.conditionals = conditionals; + this.source = source; + this.isDefault = isDefault; + + TypeLiteral> providerType = (TypeLiteral>) TypeLiteral.get(Types.newParameterizedType( + ConditionalBinder.class, + key.getTypeLiteral().getRawType())); + + Multibinder> mapBinder = key.getAnnotation() == null + ? Multibinder.newSetBinder(binder, providerType) + : Multibinder.newSetBinder(binder, providerType, key.getAnnotation()); + + mapBinder.addBinding().toInstance(this); + } + + boolean matches() { + for (Annotation conditional : conditionals) { + ConditionalMatcher evaluator = (ConditionalMatcher) injector.getInstance(Key.get(Types.newParameterizedType(ConditionalMatcher.class, conditional.annotationType()))); + if (!evaluator.evaluate(conditional)) { + return false; + } + } + return true; + } + + public T get() { + return injector.getInstance(idKey); + } + + public Provider getProvider() { + return injector.getProvider(idKey); + } + + public Key getIdKey() { + return idKey; + } + + public Object getSource() { + return source; + } + + public boolean isDefault() { + return isDefault; + } + + @Override + public String toString() { + return "ConditionalProvider[id=" + id.id() + " at " + source + " isDefault=" + isDefault + "]"; + } + +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalMatcher.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalMatcher.java new file mode 100644 index 00000000..ba128f40 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalMatcher.java @@ -0,0 +1,7 @@ +package com.netflix.karyon.conditional; + +import java.lang.annotation.Annotation; + +public interface ConditionalMatcher { + boolean evaluate(T conditional); +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnClass.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnClass.java deleted file mode 100644 index 5613d224..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnClass.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnClassCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnClassCondition.class) -public @interface ConditionalOnClass { - String[] value(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnEnvironment.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnEnvironment.java deleted file mode 100644 index 19242679..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnEnvironment.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnEnvironmentCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnEnvironmentCondition.class) -public @interface ConditionalOnEnvironment { - String name(); - String value(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnLocalDev.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnLocalDev.java deleted file mode 100644 index f9cb1dd8..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnLocalDev.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnLocalDevCondition; - -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnLocalDevCondition.class) -public @interface ConditionalOnLocalDev { -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMacOS.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMacOS.java deleted file mode 100644 index a9e4e808..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMacOS.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnMacOSCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnMacOSCondition.class) -public @interface ConditionalOnMacOS { -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingBinding.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingBinding.java deleted file mode 100644 index 8c8ba258..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingBinding.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnMissingBindingCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnMissingBindingCondition.class) -public @interface ConditionalOnMissingBinding { - String value(); - String qualifier() default ""; -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingClass.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingClass.java deleted file mode 100644 index 912580a1..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingClass.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnMissingClassCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnMissingClassCondition.class) -public @interface ConditionalOnMissingClass { - String[] value(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingModule.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingModule.java deleted file mode 100644 index d3bc619c..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnMissingModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnMissingModuleCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnMissingModuleCondition.class) -public @interface ConditionalOnMissingModule { - String[] value(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnModule.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnModule.java deleted file mode 100644 index 33fae9eb..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnModule.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.google.inject.Module; -import com.netflix.karyon.conditional.impl.OnModuleCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnModuleCondition.class) -public @interface ConditionalOnModule { - Class[] value(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProfile.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProfile.java deleted file mode 100644 index 07a111aa..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProfile.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnProfileCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnProfileCondition.class) -public @interface ConditionalOnProfile { - String[] value(); - - /** - * Match if context in all specified profiles - * - * @return - */ - boolean matchAll() default true; -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProfileMatcher.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProfileMatcher.java new file mode 100644 index 00000000..3e5c814f --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProfileMatcher.java @@ -0,0 +1,26 @@ +package com.netflix.karyon.conditional; + +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import com.netflix.karyon.annotations.Profiles; +import com.netflix.karyon.conditional.annotations.ConditionalOnProfile; + +@Singleton +class ConditionalOnProfileMatcher implements ConditionalMatcher{ + @Inject + @Profiles + Set profiles; + + @Override + public boolean evaluate(ConditionalOnProfile conditional) { + for (String profile : conditional.value()) { + if (profiles.contains(profile)) { + return true; + } + } + return false; + } +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnPropertyMatcher.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnPropertyMatcher.java new file mode 100644 index 00000000..4593a392 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnPropertyMatcher.java @@ -0,0 +1,18 @@ +package com.netflix.karyon.conditional; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import com.netflix.karyon.conditional.annotations.ConditionalOnProperty; +import com.netflix.karyon.spi.PropertySource; + +@Singleton +public class ConditionalOnPropertyMatcher implements ConditionalMatcher{ + @Inject + PropertySource properties; + + @Override + public boolean evaluate(ConditionalOnProperty conditional) { + return conditional.value().equals(properties.get(conditional.key(), (String)null)); + } +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnSystem.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnSystem.java deleted file mode 100644 index 948a6af5..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnSystem.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnSystemCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnSystemCondition.class) -public @interface ConditionalOnSystem { - String name(); - String value(); -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnTestNG.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnTestNG.java deleted file mode 100644 index 82a08b95..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnTestNG.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.netflix.karyon.conditional.impl.OnTestNGCondition; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(OnTestNGCondition.class) -public @interface ConditionalOnTestNG { -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalResolvingProvider.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalResolvingProvider.java new file mode 100644 index 00000000..c17e01d1 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalResolvingProvider.java @@ -0,0 +1,102 @@ +package com.netflix.karyon.conditional; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Provider; + +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.ProvisionException; +import com.google.inject.util.Types; + +/** + * Provider for type T that evaluates multiple conditional bindings and returns T iff + * exactly one of the conditions is met or a default has been indicated. + * + * @param + */ +final class ConditionalResolvingProvider implements Module, Provider { + @Inject + Injector injector; + + private final Key key; + + public ConditionalResolvingProvider(Key key) { + this.key = key; + } + + @Override + public T get() { + return resolveProvider().get(); + } + + @Override + public void configure(Binder binder) { + binder.bind(key).toProvider(this); + } + + public Provider resolveProvider() { + Type providerType = Types.newParameterizedType( + ConditionalBinder.class, + key.getTypeLiteral().getRawType()); + + Set> providers = (Set>) injector.getInstance(key.ofType(Types.setOf(providerType))); + ConditionalBinder defaultProvider = null; + + List> matchedBindings = new ArrayList<>(); + for (ConditionalBinder provider : providers) { + if (provider.matches()) { + matchedBindings.add(provider); + } + else if (provider.isDefault()) { + if (defaultProvider != null) { + throw new ProvisionException("Only one default provider allowed for " + key.getTypeLiteral()); + } + defaultProvider = provider; + } + } + + if (matchedBindings.size() == 0) { + if (defaultProvider != null) { + return defaultProvider.getProvider(); + } + else { + throw new ProvisionException("No binding found for " + key.getTypeLiteral()); + } + } + else if (matchedBindings.size() == 1) { + return matchedBindings.get(0).getProvider(); + } + else { + throw new ProvisionException("Multiple (" + matchedBindings.size() + ") bindings found for " + key + "\n. " + matchedBindings); + } + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ConditionalResolvingProvider other = (ConditionalResolvingProvider) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + return true; + } +} \ No newline at end of file diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalSupportModule.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalSupportModule.java new file mode 100644 index 00000000..2070578d --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalSupportModule.java @@ -0,0 +1,74 @@ +package com.netflix.karyon.conditional; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.Sets; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; +import com.netflix.karyon.conditional.annotations.Conditional; +import com.netflix.karyon.conditional.annotations.ConditionalOnProfile; +import com.netflix.karyon.conditional.annotations.ConditionalOnProperty; +import com.netflix.karyon.conditional.annotations.ProvidesConditionally; + +/** + * This module enabled conditional bindings via {@link ProvidesConditionally}. + */ +public class ConditionalSupportModule extends AbstractModule { + @Override + protected void configure() { + binder().scanModulesForAnnotatedMethods(new ModuleAnnotatedMethodScanner() { + @SuppressWarnings("unchecked") + @Override + public Set> annotationClasses() { + return Sets.newHashSet(ProvidesConditionally.class); + } + + @Override + public Key prepareMethod(Binder binder, + Annotation annotation, Key key, + InjectionPoint injectionPoint) { + + ProvidesConditionally conditionally = (ProvidesConditionally)annotation; + + Method m = (Method) injectionPoint.getMember(); + List annotations = new ArrayList<>(); + for (Annotation annot : m.getAnnotations()) { + if (null != annot.annotationType().getAnnotation(Conditional.class)) { + annotations.add(annot); + } + } + + if (annotations.isEmpty()) { + binder.addError("Method " + m.toString() + " must have at least one @Conditional annotated annotation."); + } + + binder.install(new ConditionalResolvingProvider(key)); + return new ConditionalBinder(binder, key, conditionally.isDefault(), annotations, injectionPoint.toString()).getIdKey(); + } + }); + + // Add one of these to register the evaluator for each Conditional + bind(new TypeLiteral>() {}) + .to(ConditionalOnProfileMatcher.class); + bind(new TypeLiteral>() {}) + .to(ConditionalOnPropertyMatcher.class); + } + + @Override + public boolean equals(Object obj) { + return getClass().equals(obj.getClass()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnEc2.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/IdQualifier.java similarity index 64% rename from karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnEc2.java rename to karyon3-core/src/main/java/com/netflix/karyon/conditional/IdQualifier.java index 0efd9bc7..092a8074 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnEc2.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/IdQualifier.java @@ -6,11 +6,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.netflix.karyon.conditional.impl.OnEc2Condition; +import javax.inject.Qualifier; -@Target({ ElementType.TYPE }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented -@Conditional(OnEc2Condition.class) -public @interface ConditionalOnEc2 { +@Qualifier +public @interface IdQualifier { + int id(); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/OverrideModule.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/OverrideModule.java deleted file mode 100644 index b9f7d4e9..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/OverrideModule.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.netflix.karyon.conditional; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface OverrideModule { -} \ No newline at end of file diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnJUnit.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/Conditional.java similarity index 55% rename from karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnJUnit.java rename to karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/Conditional.java index 8e072a8e..2a2647e8 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnJUnit.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/Conditional.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.conditional; +package com.netflix.karyon.conditional.annotations; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -6,11 +6,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.netflix.karyon.conditional.impl.OnJUnitCondition; - -@Target({ElementType.TYPE}) +@Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented -@Conditional(OnJUnitCondition.class) -public @interface ConditionalOnJUnit { +public @interface Conditional { + } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnBinding.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ConditionalOnProfile.java similarity index 53% rename from karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnBinding.java rename to karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ConditionalOnProfile.java index bcc59f92..bad8e492 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnBinding.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ConditionalOnProfile.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.conditional; +package com.netflix.karyon.conditional.annotations; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -6,12 +6,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.netflix.karyon.conditional.impl.OnBindingCondition; - -@Target({ ElementType.TYPE, ElementType.METHOD }) +/** + * Conditional that is met if the specified profile is set + * + * @see ProvidesConditionally + */ +@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented -@Conditional(OnBindingCondition.class) -public @interface ConditionalOnBinding { +@Conditional +public @interface ConditionalOnProfile { String[] value(); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProperty.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ConditionalOnProperty.java similarity index 62% rename from karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProperty.java rename to karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ConditionalOnProperty.java index 2809fcd7..5cfdcc52 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/ConditionalOnProperty.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ConditionalOnProperty.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.conditional; +package com.netflix.karyon.conditional.annotations; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -6,13 +6,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.netflix.karyon.conditional.impl.OnPropertyCondition; - -@Target({ElementType.TYPE}) +@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented -@Conditional(OnPropertyCondition.class) +@Conditional public @interface ConditionalOnProperty { - String name(); + String key(); String value(); } + diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ProvidesConditionally.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ProvidesConditionally.java new file mode 100644 index 00000000..7710d3e2 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/conditional/annotations/ProvidesConditionally.java @@ -0,0 +1,61 @@ +package com.netflix.karyon.conditional.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Conditional binding makes it possible to provide duplicate bindings for a type + * with each binding being conditional on things like profiles, properties, etc. + * + * To enable, install ConditionalSupportModule in Guice to enable conditional binding. + * Note that Karyon auto-installs this module. + * + * install(new ConditionalSupportModule()) + * + * + * + * public static class MyModule extends AbstractModule { + * {@literal @}Override + * protected void configure() { + * } + * + * {@literal @}ProvidesConditionally(isDefault=true) + * {@literal @}ConditionalOnProfile("test") + * public Foo getFooForTest() { + * return new FooForTest(); + * } + * + * {@literal @}ProvidesConditionally + * {@literal @}ConditionalOnProfile("prod") + * public Foo getFooForProd() { + * return new FooForProd(); + * } + * } + * + * + * Foo can now be injected directly with the + * + * + * {@literal @}Inject + * public Bar(Foo foo) { + * } + * + * + * Note that a ProvisionException will be thrown unless exactly one of the conditions is met. + * If no conditions are met the ProvidesConditionally with isDefault set to true will be used, + * otherwise a missing bindings exception will be thrown. Only one ProvidesConditionally may + * have isDefault set to true. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ProvidesConditionally { + /** + * @return If set to true this bindings will be used if not other condition is met (even if this bindings + * condition is not met). Only one ProvidesConditionally for a type may hav isDefault set to true + */ + boolean isDefault() default false; +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnBindingCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnBindingCondition.java deleted file mode 100644 index 7c054b27..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnBindingCondition.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonAutoContext; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnBinding; - -@Singleton -public class OnBindingCondition implements Condition { - private final KaryonAutoContext context; - - @Inject - public OnBindingCondition(KaryonAutoContext context) { - this.context = context; - } - - @Override - public boolean check(ConditionalOnBinding condition) { - for (String name : condition.value()) { - try { - if (!context.hasBinding(Class.forName(name, false, ClassLoader.getSystemClassLoader()))) { - return false; - } - } catch (ClassNotFoundException e) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "OnBindingCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnClassCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnClassCondition.java deleted file mode 100644 index 0d5626df..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnClassCondition.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import com.google.inject.Singleton; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnClass; - -@Singleton -public class OnClassCondition implements Condition { - @Override - public boolean check(ConditionalOnClass condition) { - for (String name : condition.value()) { - try { - Class.forName(name, false, ClassLoader.getSystemClassLoader()); - } catch (ClassNotFoundException e) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "OnClassCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnEc2Condition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnEc2Condition.java deleted file mode 100644 index 63adcdf8..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnEc2Condition.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonConfiguration; -import com.netflix.karyon.PropertySource; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnEc2; - -@Singleton -public class OnEc2Condition implements Condition { - private final PropertySource source; - private final KaryonConfiguration config; - - @Inject - public OnEc2Condition(PropertySource source, KaryonConfiguration config) { - this.source = source; - this.config = config; - } - - @Override - public boolean check(ConditionalOnEc2 condition) { - return config.getProfiles().contains("ec2") || source.get("EC2_INSTANCE_ID") != null; - } - - @Override - public String toString() { - return "OnCloudCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnEnvironmentCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnEnvironmentCondition.java deleted file mode 100644 index de01d760..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnEnvironmentCondition.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import com.google.inject.Singleton; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnEnvironment; - -@Singleton -public class OnEnvironmentCondition implements Condition { - @Override - public boolean check(ConditionalOnEnvironment condition) { - String value = System.getenv(condition.name()); - if (value == null || condition.value() == null) { - return false; - } - return condition.value().equals(value); - } - - @Override - public String toString() { - return "OnEnvironmentCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnJUnitCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnJUnitCondition.java deleted file mode 100644 index 404ebf76..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnJUnitCondition.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Singleton; - -import com.netflix.karyon.conditional.Condition; - -@Singleton -public class OnJUnitCondition implements Condition{ - @Override - public boolean check(OnJUnitCondition param) { - String cmd = System.getProperty("sun.java.command"); - if (cmd == null) { - return false; - } - - return cmd.startsWith("org.eclipse.jdt.internal.junit.runner"); - // TODO: Add additional checks for other IDEs - } - - @Override - public String toString() { - return "OnJUnitCondition[]"; - } - -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnLocalDevCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnLocalDevCondition.java deleted file mode 100644 index 886132be..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnLocalDevCondition.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonConfiguration; -import com.netflix.karyon.PropertySource; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnLocalDev; - -/** - * Conditional to add to a module that should be loaded when running in 'local' profile - * or in a JUnit test (in eclipse or gradle build). - * - * @author elandau - * - */ -@Singleton -public class OnLocalDevCondition implements Condition { - private final KaryonConfiguration config; - private final boolean inTest; - - @Inject - public OnLocalDevCondition(PropertySource source, KaryonConfiguration config, OnJUnitCondition junitCondition) { - this.config = config; - this.inTest = isInTest(); - } - - private boolean isInTest() { - String cmd = System.getProperty("sun.java.command"); - if (cmd == null) { - return false; - } - - return cmd.startsWith("org.eclipse.jdt.internal.junit.runner") || - cmd.contains("Gradle Test Executor"); - } - - @Override - public boolean check(ConditionalOnLocalDev condition) { - return config.getProfiles().contains("localDev") - || inTest - || config.getProfiles().contains("local"); // @deprecated maintain "local" for backward compatibility - } - - @Override - public String toString() { - return "OnLocalDevCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMacOSCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMacOSCondition.java deleted file mode 100644 index f1caf38a..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMacOSCondition.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Singleton; - -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnMacOS; - -@Singleton -public class OnMacOSCondition implements Condition{ - @Override - public boolean check(ConditionalOnMacOS param) { - return "Mac OS X".equals(System.getProperty("os.name")); - } - - @Override - public String toString() { - return "OnMacOSCondition[]"; - } - -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingBindingCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingBindingCondition.java deleted file mode 100644 index 34cd8c47..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingBindingCondition.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import java.lang.annotation.Annotation; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonAutoContext; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnMissingBinding; - -/** - * Use this condition when providing the binding for a specific context. This binding - * is usually specified with additional conditions such as {@literal @ConditionalOnProfile} - * - */ -@Singleton -public class OnMissingBindingCondition implements Condition { - private final KaryonAutoContext context; - - @Inject - public OnMissingBindingCondition(KaryonAutoContext context) { - this.context = context; - } - - @Override - public boolean check(ConditionalOnMissingBinding condition) { - try { - Class type = Class.forName(condition.value(), false, ClassLoader.getSystemClassLoader()); - Class annot = condition.qualifier().isEmpty() - ? null - : (Class)Class.forName(condition.qualifier(), false, ClassLoader.getSystemClassLoader()); - - return context.hasInjectionPoint(type, annot) - && !context.hasBinding(type, annot); - } catch (ClassNotFoundException e) { - } - return false; - } - - @Override - public String toString() { - return "OnMissingBindingCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingClassCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingClassCondition.java deleted file mode 100644 index fe908703..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingClassCondition.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnMissingClass; - -public class OnMissingClassCondition implements Condition { - @Override - public boolean check(ConditionalOnMissingClass condition) { - for (String name : condition.value()) { - try { - Class.forName(name, false, ClassLoader.getSystemClassLoader()); - return false; - } catch (ClassNotFoundException e) { - } - } - return true; - } - - @Override - public String toString() { - return "OnMissingClassCondition[]"; - } - -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingModuleCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingModuleCondition.java deleted file mode 100644 index a047099a..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnMissingModuleCondition.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonAutoContext; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnMissingModule; - -@Singleton -public class OnMissingModuleCondition implements Condition{ - private final KaryonAutoContext context; - - @Inject - public OnMissingModuleCondition(KaryonAutoContext context) { - this.context = context; - } - - @Override - public boolean check(ConditionalOnMissingModule param) { - for (String module : param.value()) { - if (context.hasModule(module)) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "OnMissingModuleCondition[]"; - } - -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnModuleCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnModuleCondition.java deleted file mode 100644 index 6517cda1..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnModuleCondition.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonAutoContext; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnModule; - -@Singleton -public class OnModuleCondition implements Condition { - private final KaryonAutoContext context; - - @Inject - public OnModuleCondition(KaryonAutoContext context) { - this.context = context; - } - - @Override - public boolean check(ConditionalOnModule param) { - for (Class module : param.value()) { - if (!context.hasModule(module.getName())) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "OnModuleCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnProfileCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnProfileCondition.java deleted file mode 100644 index ddd0a9c5..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnProfileCondition.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.netflix.karyon.KaryonAutoContext; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnProfile; - -@Singleton -public class OnProfileCondition implements Condition { - - private KaryonAutoContext context; - - @Inject - public OnProfileCondition(KaryonAutoContext context) { - this.context = context; - } - - @Override - public boolean check(ConditionalOnProfile condition) { - if (condition.matchAll()) { - for (String profile : condition.value()) { - if (!context.hasProfile(profile)) - return false; - } - return true; - } - else { - for (String profile : condition.value()) { - if (!context.hasProfile(profile)) - return true; - } - return false; - } - } - - @Override - public String toString() { - return "OnProfileCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnPropertyCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnPropertyCondition.java deleted file mode 100644 index 9e45a47a..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnPropertyCondition.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Inject; - -import com.google.inject.Singleton; -import com.netflix.karyon.PropertySource; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnProperty; - -@Singleton -public class OnPropertyCondition implements Condition { - - private PropertySource config; - - @Inject - public OnPropertyCondition(PropertySource config) { - this.config = config; - } - - @Override - public boolean check(ConditionalOnProperty condition) { - String value = config.get(condition.name()); - if (value == null || condition.value() == null) { - return false; - } - return condition.value().equals(value); - } - - @Override - public String toString() { - return "OnPropertyCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnSystemCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnSystemCondition.java deleted file mode 100644 index 972e57e6..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnSystemCondition.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import com.google.inject.Singleton; -import com.netflix.karyon.conditional.Condition; -import com.netflix.karyon.conditional.ConditionalOnSystem; - -@Singleton -public class OnSystemCondition implements Condition { - @Override - public boolean check(ConditionalOnSystem condition) { - String value = System.getProperty(condition.name()); - if (value == null || condition.value() == null) { - return false; - } - return condition.value().equals(value); - } - - @Override - public String toString() { - return "OnSystemCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnTestNGCondition.java b/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnTestNGCondition.java deleted file mode 100644 index 89106c7d..00000000 --- a/karyon3-core/src/main/java/com/netflix/karyon/conditional/impl/OnTestNGCondition.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.netflix.karyon.conditional.impl; - -import javax.inject.Singleton; - -import com.netflix.karyon.conditional.Condition; - -@Singleton -public class OnTestNGCondition implements Condition{ - @Override - public boolean check(OnTestNGCondition param) { - String cmd = System.getProperty("sun.java.command"); - if (cmd == null) { - return false; - } - - return cmd.startsWith("org.testng.remote.RemoteTestNG"); - } - - @Override - public String toString() { - return "OnTestNGCondition[]"; - } -} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/health/AbstractHealthIndicator.java b/karyon3-core/src/main/java/com/netflix/karyon/health/AbstractHealthIndicator.java index f72819fc..2d367726 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/health/AbstractHealthIndicator.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/health/AbstractHealthIndicator.java @@ -7,10 +7,9 @@ public abstract class AbstractHealthIndicator implements HealthIndicator { private final String name; /** - * Creates an AbstractHealthIndicator with the specified name - * - * @param name name Name of this Health Indicator + * Creates an AbstractHealthIndicator with the specified name * + * @param name Name of this Health Indicator */ public AbstractHealthIndicator(String name) { this.name = name; @@ -18,7 +17,6 @@ public AbstractHealthIndicator(String name) { /** * Creates an AbstractHealthIndicator with a default name - * */ public AbstractHealthIndicator() { this.name = getClass().getSimpleName(); @@ -30,20 +28,16 @@ public String getName() { } /** - * * Create a healthy status * * @return a healthy status */ - protected final HealthIndicatorStatus healthy() { return HealthIndicatorStatuses.create(getName(), true, Collections. emptyMap(), null); } /** - * * Create a healthy status - * * @param attr Map of the attributes describing status * @return a healthy status */ @@ -52,9 +46,7 @@ protected final HealthIndicatorStatus healthy(Map attr) { } /** - * * Create an unhealthy status - * * @return a unhealthy status */ protected final HealthIndicatorStatus unhealthy() { @@ -62,9 +54,8 @@ protected final HealthIndicatorStatus unhealthy() { } /** - * * Create an unhealthy status - * + * @param attr Map of the attributes describing status * @return a unhealthy status */ protected final HealthIndicatorStatus unhealthy(Map attr) { @@ -72,9 +63,8 @@ protected final HealthIndicatorStatus unhealthy(Map attr) { } /** - * * Create an unhealthy status - * + * @param t Error that caused the unhealthy status * @return a unhealthy status */ protected final HealthIndicatorStatus unhealthy(Throwable t) { @@ -82,13 +72,12 @@ protected final HealthIndicatorStatus unhealthy(Throwable t) { } /** - * * Create an unhealthy status - * + * @param attr Map of the attributes describing status + * @param t Error that caused the unhealthy status * @return a unhealthy status */ protected final HealthIndicatorStatus unhealthy(Map attr, Throwable t) { return HealthIndicatorStatuses.create(getName(), false, attr, t); } - } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicator.java b/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicator.java index 552f4b9b..b154f7c3 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicator.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicator.java @@ -43,11 +43,12 @@ public interface HealthIndicator { /** * Perform the health check asynchronously. + * @return Future of health status result */ CompletableFuture check(); /** - * Return the name of the health indicator. Note that health indicators with duplicate names are allowed. + * @return Return the name of the health indicator. Note that health indicators with duplicate names are allowed. */ String getName(); } diff --git a/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorRegistry.java b/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorRegistry.java index b18b73e2..3d66b2fa 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorRegistry.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorRegistry.java @@ -26,8 +26,7 @@ @ImplementedBy(AllHealthIndicatorRegistry.class) public interface HealthIndicatorRegistry { /** - * Return a list of all active health checks - * @return + * @return Return a list of all active health checks */ List getHealthIndicators(); diff --git a/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorStatus.java b/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorStatus.java index aa2251af..6a148a99 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorStatus.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/health/HealthIndicatorStatus.java @@ -10,7 +10,7 @@ */ public interface HealthIndicatorStatus { /** - * Map of named attributes that provide additional information regarding the health. + * @return Map of named attributes that provide additional information regarding the health. * For example, a CPU health check may return Unhealthy with attribute "usage"="90%" */ public Map getAttributes(); @@ -21,7 +21,7 @@ public interface HealthIndicatorStatus { public boolean isHealthy(); /** - * Exception providing additional information regarding the failure state. This could be + * @return Exception providing additional information regarding the failure state. This could be * the last known exception. */ public String getError(); diff --git a/karyon3-core/src/main/java/com/netflix/karyon/spi/AutoBinder.java b/karyon3-core/src/main/java/com/netflix/karyon/spi/AutoBinder.java new file mode 100644 index 00000000..b5547102 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/spi/AutoBinder.java @@ -0,0 +1,70 @@ +package com.netflix.karyon.spi; + +import com.google.inject.Binder; +import com.google.inject.Key; + +/** + * Automatically create bindings when none exist. This is an extension to Guice which does not + * provide a catch-all provider for missing bindings. + * + * For example, the following code will fail with a missing binding error for Foo + * + * + public interface Foo { + } + + public class Bar { + {@literal @}Inject + public Bar(Foo foo) { + } + } + + public static void main() { + Karyon.create() + .addModule(new AbstractModule() { + protected void configure() { + bind(Bar.class).asEagerSingleton(); + } + } + .start(); + } + * + * + * A catch-all binder for Foo can can added as follows + * + * + public static void main() { + Karyon.create() + .addModule(new AbstractModule() { + protected void configure() { + bind(Bar.class).asEagerSingleton(); + } + } + .addTypeBindingFactory( + TypeLiteralMatchers.subclassOf(Bar.class), + new KeyAutoBinder() { + {@literal @}Override + public {@literal <}T{@literal >} boolean bind(Binder binder, final Key{@literal <}T{@literal >} key) { + binder.bind(key).toInstance((T)new Foo() {}); + return true; + } + } + ) + * .start(); + * } + * + * + * This functionality is useful to reduce boiler plate code. + * + */ +public interface AutoBinder { + /** + * Create a bindings for the specified type literal. + * + * @param binder Binder on which bindings may be created + * @param key Key for which no binding was found + * + * @return True if bindings was created or false if not. + */ + boolean configure(Binder binder, Key key); +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/spi/KaryonBinder.java b/karyon3-core/src/main/java/com/netflix/karyon/spi/KaryonBinder.java new file mode 100644 index 00000000..dc753d25 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/spi/KaryonBinder.java @@ -0,0 +1,19 @@ +package com.netflix.karyon.spi; + +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matcher; +import com.netflix.karyon.TypeLiteralMatchers; + +/** + * @see KaryonModule + */ +public interface KaryonBinder { + /** + * Add an AutoBinder to be used when no binding is provided for a type matched by the matcher + * + * @param matcher Logic for matching an unbound TypeLiteral. See {@link TypeLiteralMatchers} for + * pre-defined matchers. + * @param autoBinder AutoBinder that will created the bindings for any type matched by the matcher + */ + > void bindAutoBinder(Matcher matcher, AutoBinder autoBinder); +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/spi/KaryonModule.java b/karyon3-core/src/main/java/com/netflix/karyon/spi/KaryonModule.java new file mode 100644 index 00000000..ce3a56f4 --- /dev/null +++ b/karyon3-core/src/main/java/com/netflix/karyon/spi/KaryonModule.java @@ -0,0 +1,29 @@ +package com.netflix.karyon.spi; + +import com.netflix.karyon.Karyon; + +/** + * Contract for adding functionality to Karyon as part of adding a Guice module. This interface + * must be implemented by a module added to a call to {@link Karyon#addModules(java.util.List)} + * to avoid having to make multiple calls to Karyon when adding functionality. + * + * + * public class FooModule extends AbstractModule implements KaryonModule { + * {@literal @}Override + * protected void configure() { + * // ... Bindings for Guice + * } + * + * {@literal @}Override + * public void configure(KaryonBinder binder) { + * // ... calls to extend/configure Karyon + * } + * } + * + * + * @author elandau + * + */ +public interface KaryonModule { + void configure(KaryonBinder binder); +} diff --git a/karyon3-core/src/main/java/com/netflix/karyon/PropertySource.java b/karyon3-core/src/main/java/com/netflix/karyon/spi/PropertySource.java similarity index 59% rename from karyon3-core/src/main/java/com/netflix/karyon/PropertySource.java rename to karyon3-core/src/main/java/com/netflix/karyon/spi/PropertySource.java index 3bf69c65..411cd9be 100644 --- a/karyon3-core/src/main/java/com/netflix/karyon/PropertySource.java +++ b/karyon3-core/src/main/java/com/netflix/karyon/spi/PropertySource.java @@ -1,6 +1,7 @@ -package com.netflix.karyon; +package com.netflix.karyon.spi; + +import com.netflix.karyon.PropertiesPropertySource; -import com.google.inject.ImplementedBy; /** * Very simple config interface to be used by Conditions to gain access @@ -12,31 +13,30 @@ * @author elandau * */ -@ImplementedBy(DefaultPropertySource.class) public interface PropertySource { /** * Get the value of a property or null if not found * - * @param key - * @return + * @param key Name of property to fetch + * @return Value or null if not found */ public String get(String key); /** * Get the value of a property or default if not found * - * @param key + * @param key Name of property to fetch * @param defaultValue - * @return + * @return Value or defaultValue if not found */ public String get(String key, String defaultValue); /** * Get a property value of a specific type * - * @param key - * @param type - * @return + * @param key Name of property to fetch + * @param type Type of value requested + * @return Value of the request type or null if not found */ public T get(String key, Class type); @@ -44,10 +44,10 @@ public interface PropertySource { * Get a property value of a specific type while returning a * default value if the property is not set. * - * @param key - * @param type - * @param defaultValue - * @return + * @param key Name of property to fetch + * @param type Type of value requested + * @param defaultValue Default value to return if key not found + * @return Value or defaultValue if not found */ public T get(String key, Class type, T defaultValue); } diff --git a/karyon3-core/src/test/java/com/netflix/karyon/ConditionalProvidesTest.java b/karyon3-core/src/test/java/com/netflix/karyon/ConditionalProvidesTest.java new file mode 100644 index 00000000..68e259fa --- /dev/null +++ b/karyon3-core/src/test/java/com/netflix/karyon/ConditionalProvidesTest.java @@ -0,0 +1,313 @@ +package com.netflix.karyon; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.inject.Named; +import javax.inject.Singleton; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.ConfigurationException; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.ProvisionException; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.name.Names; +import com.netflix.karyon.annotations.Profiles; +import com.netflix.karyon.conditional.ConditionalSupportModule; +import com.netflix.karyon.conditional.annotations.ConditionalOnProfile; +import com.netflix.karyon.conditional.annotations.ConditionalOnProperty; +import com.netflix.karyon.conditional.annotations.ProvidesConditionally; +import com.netflix.karyon.spi.PropertySource; + +public class ConditionalProvidesTest { + public static interface Foo { + String getName(); + } + + public static Foo createFoo(String name) { + return new Foo() { + @Override + public String getName() { + return name; + } + }; + } + + public class TestModule extends AbstractModule { + @Override + protected void configure() { + install(new ConditionalSupportModule()); + Multibinder.newSetBinder(binder(), String.class, Profiles.class).addBinding() + .toInstance("test"); + } + } + + @Test(expected=ConfigurationException.class) + public void failOnNoBindings() { + Injector injector = Karyon.newBuilder().addModules(new TestModule()).start(); + + injector.getInstance(Foo.class); + } + + @Test + public void succeedOnSingleBinding() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getFooTest() { + return createFoo("test"); + } + }).start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.getName(), equalTo("test")); + } + + @Test(expected=ProvisionException.class) + public void failOnNoMetConditionals() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("prod") + public Foo getFooTest() { + return createFoo("prod"); + } + }).start(); + + injector.getInstance(Foo.class); + } + + @Test(expected=ProvisionException.class) + public void failOnDuplicateMetConditionals() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getFooTest1() { + return createFoo("test1"); + } + + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getFooTest2() { + return createFoo("test2"); + } + }).start(); + + injector.getInstance(Foo.class); + } + + @Test + public void succeedMatchOneConditional() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getFooTest1() { + return createFoo("test"); + } + + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("prod") + public Foo getFooTest2() { + return createFoo("prod"); + } + }).start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.getName(), equalTo("test")); + } + + @Test + public void succeedMatchNoneWithConditional() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @ProvidesConditionally(isDefault=true) + @ConditionalOnProfile("none1") + public Foo getFooTest1() { + return createFoo("none1"); + } + + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("none2") + public Foo getFooTest2() { + return createFoo("none2"); + } + }).start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.getName(), equalTo("none1")); + } + + @Test(expected=ProvisionException.class) + public void failOnMultipleDefaults() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @ProvidesConditionally(isDefault=true) + @ConditionalOnProfile("none1") + public Foo getFooTest1() { + return createFoo("none1"); + } + + @Singleton + @ProvidesConditionally(isDefault=true) + @ConditionalOnProfile("none2") + public Foo getFooTest2() { + return createFoo("none2"); + } + }).start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.getName(), equalTo("none1")); + } + + @Test + public void successOnMultipleQualifiedBindings() { + Injector injector = Karyon.newBuilder().addModules(new TestModule() { + @Singleton + @Named("foo") + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getFooTest() { + return new Foo() { + @Override + public String getName() { + return "getFooTest"; + } + + @Override + public String toString() { + return "Foo[getFooTest]"; + } + }; + } + + @Singleton + @Named("foo") + @ProvidesConditionally + @ConditionalOnProfile("prod") + public Foo getFooProd() { + return new Foo() { + @Override + public String getName() { + return "getFooProd"; + } + }; + } + + @Singleton + @Named("bar") + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getFooTestBar() { + return new Foo() { + @Override + public String getName() { + return "getFooTestBar"; + } + + @Override + public String toString() { + return "Foo[getFooTestBar]"; + } + }; + } + + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("test") + public Foo getUnannotatedFoo() { + return new Foo() { + @Override + public String getName() { + return "getUnannotatedFoo"; + } + + @Override + public String toString() { + return "Foo[getUnannotatedFoo]"; + } + }; + } + }).start(); + + Assert.assertEquals("getFooTest", injector.getInstance(Key.get(Foo.class, Names.named("foo"))).getName()); + Assert.assertEquals("getFooTestBar", injector.getInstance(Key.get(Foo.class, Names.named("bar"))).getName()); + Assert.assertEquals("getUnannotatedFoo", injector.getInstance(Key.get(Foo.class)).getName()); + } + + @Test + public void conditionalOnProperty() { + PropertySource property = mock(PropertySource.class); + when(property.get("foo.type", (String)null)).thenReturn("foo1"); + when(property.get("karyon.features.shutdownOnError", Boolean.class, true)).thenReturn(false); + + Injector injector = Karyon.newBuilder().addModules(new TestModule(), new AbstractModule() { + @Singleton + @ProvidesConditionally + @ConditionalOnProperty(key="foo.type", value="foo1") + public Foo getFooTest1() { + return createFoo("foo1"); + } + + @Singleton + @ProvidesConditionally + @ConditionalOnProperty(key="foo.type", value="foo2") + public Foo getFooTest2() { + return createFoo("foo2"); + } + + @Override + protected void configure() { + bind(PropertySource.class).toInstance(property); + } + }).start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.getName(), equalTo("foo1")); + } + + @Test + public void conditionalOnPropertyAndProfile() { + PropertySource property = mock(PropertySource.class); + when(property.get("foo.type", (String)null)).thenReturn("foo1"); + when(property.get("karyon.features.shutdownOnError", Boolean.class, true)).thenReturn(false); + + Injector injector = Karyon.newBuilder().addModules(new TestModule(), new AbstractModule() { + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("prod") + @ConditionalOnProperty(key="foo.type", value="foo1") + public Foo getFooTest1() { + return createFoo("foo1"); + } + + @Singleton + @ProvidesConditionally + @ConditionalOnProfile("test") + @ConditionalOnProperty(key="foo.type", value="foo1") + public Foo getFooTest2() { + return createFoo("foo2"); + } + + @Override + protected void configure() { + bind(PropertySource.class).toInstance(property); + } + }).start(); + + Foo foo = injector.getInstance(Foo.class); + assertThat(foo.getName(), equalTo("foo2")); + } +} diff --git a/karyon3-core/src/test/java/com/netflix/karyon/KaryonTest.java b/karyon3-core/src/test/java/com/netflix/karyon/KaryonTest.java index c82852fe..b6f610e7 100644 --- a/karyon3-core/src/test/java/com/netflix/karyon/KaryonTest.java +++ b/karyon3-core/src/test/java/com/netflix/karyon/KaryonTest.java @@ -3,16 +3,10 @@ import org.junit.Test; public class KaryonTest { - @Test(expected=RuntimeException.class) - public void testDefault() { - Karyon.create().start(); - } - @Test - public void testDefaultWithoutArchaius() { + public void defaultKaryonSucceeds() { Karyon .create() - .disableFeature(KaryonFeatures.USE_ARCHAIUS) .start(); } } diff --git a/karyon3-core/src/test/java/com/netflix/karyon/KaryonTypeBindingFactoryTest.java b/karyon3-core/src/test/java/com/netflix/karyon/KaryonTypeBindingFactoryTest.java new file mode 100644 index 00000000..a5e2545f --- /dev/null +++ b/karyon3-core/src/test/java/com/netflix/karyon/KaryonTypeBindingFactoryTest.java @@ -0,0 +1,103 @@ +package com.netflix.karyon; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.junit.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.CreationException; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.netflix.karyon.spi.AutoBinder; + +public class KaryonTypeBindingFactoryTest { + public static interface Baz { + String getName(); + } + + public static interface Bar { + String getName(); + } + + public static class Foo { + private Bar bar; + private Baz baz; + private Bar namedBar; + + @Inject + public Foo(Bar bar, Baz baz, @Named("bar") Bar namedBar) { + this.bar = bar; + this.baz = baz; + this.namedBar = namedBar; + } + } + + @Test(expected=CreationException.class) + public void confirmCreateFailsWithMissingBinding() { + Karyon.newBuilder() + .addModules(new AbstractModule() { + @Override + protected void configure() { + bind(Foo.class).asEagerSingleton(); + } + }) + .start(); + } + + @Test + public void testAddBinding() { + Injector injector = Karyon.newBuilder() + .addModules(new AbstractModule() { + @Override + protected void configure() { + bind(Foo.class).asEagerSingleton(); + } + }) + .addAutoBinder( + TypeLiteralMatchers.subclassOf(Bar.class), new BarAutoBinder() + ) + .addAutoBinder( + TypeLiteralMatchers.subclassOf(Baz.class), new BazAutoBinder() + ) + .start(); + + Foo foo = injector.getInstance(Foo.class); + + assertThat(foo.bar.getName(), equalTo("")); + assertThat(foo.baz.getName(), equalTo("")); + assertThat(foo.namedBar.getName(), equalTo("@com.google.inject.name.Named(value=bar)")); + } + + static class BarAutoBinder implements AutoBinder { + @SuppressWarnings("unchecked") + @Override + public boolean configure(Binder binder, final Key key) { + binder.bind(key).toInstance((T)new Bar() { + @Override + public String getName() { + return null == key.getAnnotation() ? "" : key.getAnnotation().toString(); + } + }); + return true; + } + } + + static class BazAutoBinder implements AutoBinder { + @SuppressWarnings("unchecked") + @Override + public boolean configure(Binder binder, Key key) { + binder.bind(key).toInstance((T)new Baz() { + @Override + public String getName() { + return null == key.getAnnotation() ? "" : key.getAnnotation().toString(); + } + }); + return true; + } + } +} diff --git a/karyon3-core/src/test/java/com/netflix/karyon/admin/HealthCheckBindingTest.java b/karyon3-core/src/test/java/com/netflix/karyon/admin/HealthCheckBindingTest.java index c4b58f47..a136c5e6 100644 --- a/karyon3-core/src/test/java/com/netflix/karyon/admin/HealthCheckBindingTest.java +++ b/karyon3-core/src/test/java/com/netflix/karyon/admin/HealthCheckBindingTest.java @@ -16,6 +16,7 @@ import com.google.inject.multibindings.Multibinder; import com.netflix.governator.Governator; import com.netflix.governator.LifecycleInjector; +import com.netflix.karyon.admin.HealthCheckResource; import com.netflix.karyon.health.HealthCheck; import com.netflix.karyon.health.HealthCheckStatus; import com.netflix.karyon.health.HealthIndicator; diff --git a/karyon3-core/src/test/java/com/netflix/karyon/conditional/ConditionalOnLocalDevTest.java b/karyon3-core/src/test/java/com/netflix/karyon/conditional/ConditionalOnLocalDevTest.java deleted file mode 100644 index f6078156..00000000 --- a/karyon3-core/src/test/java/com/netflix/karyon/conditional/ConditionalOnLocalDevTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.netflix.karyon.conditional; - -import junit.framework.Assert; - -import org.junit.Test; - -import com.google.inject.Injector; -import com.netflix.karyon.Karyon; -import com.netflix.karyon.KaryonFeatures; -import com.netflix.karyon.conditional.impl.OnLocalDevCondition; - -public class ConditionalOnLocalDevTest { - @Test - public void test() throws Exception { - Injector injector = Karyon.create().disableFeature(KaryonFeatures.USE_ARCHAIUS).start(); - OnLocalDevCondition condition = injector.getInstance(OnLocalDevCondition.class); - Assert.assertTrue(condition.check(null)); - } -} diff --git a/karyon3-core/src/test/java/com/netflix/karyon/conditional/ConditionalOnMissingBindingTest.java b/karyon3-core/src/test/java/com/netflix/karyon/conditional/ConditionalOnMissingBindingTest.java deleted file mode 100644 index f9dfdd55..00000000 --- a/karyon3-core/src/test/java/com/netflix/karyon/conditional/ConditionalOnMissingBindingTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.netflix.karyon.conditional; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.junit.Test; - -import com.google.inject.AbstractModule; -import com.google.inject.CreationException; -import com.google.inject.Guice; -import com.netflix.karyon.Karyon; -import com.netflix.karyon.ModuleListProviders; - -public class ConditionalOnMissingBindingTest { - - @ConditionalOnMissingBinding("com.netflix.karyon.conditional.ConditionalOnMissingBindingTest.DepA") - public static class DepAImplModule extends AbstractModule { - @Override - protected void configure() { - } - } - - public static interface DepA { - } - - public static class DepAImpl implements DepA { - } - - @Singleton - public static class Foo { - @Inject - Foo(DepA a) { - } - } - - @Test(expected=CreationException.class) - public void testNoConditional() { - Guice.createInjector(new AbstractModule() { - @Override - protected void configure() { - bind(Foo.class).asEagerSingleton(); - } - }); - } - - @Test - public void testWithConditional() { - Karyon.forApplication("test") - .addModules(new AbstractModule() { - @Override - protected void configure() { - bind(Foo.class).asEagerSingleton(); - } - }) - .addAutoModuleListProvider(ModuleListProviders.forModules(new DepAImplModule())); - } -} diff --git a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaAdminModule.java b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaAdminModule.java deleted file mode 100644 index 7e3e6c9b..00000000 --- a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaAdminModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.netflix.karyon.eureka; - -import com.google.inject.AbstractModule; -import com.netflix.discovery.guice.EurekaModule; -import com.netflix.karyon.admin.AdminModule; -import com.netflix.karyon.conditional.ConditionalOnModule; - -@ConditionalOnModule({AdminModule.class, EurekaModule.class}) -public final class EurekaAdminModule extends AbstractModule { - @Override - protected void configure() { - bind(EurekaApplicationAdminResource.class); - bind(EurekaStatusAdminResource.class); - } -} diff --git a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckModule.java b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckModule.java index 974abe32..5bc47337 100644 --- a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckModule.java +++ b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckModule.java @@ -25,14 +25,14 @@ protected void configure() { public HealthCheckConfiguration getConfiguration(ConfigProxyFactory factory) { return factory.newProxy(HealthCheckConfiguration.class); } - + @Override public boolean equals(Object obj) { - return EurekaHealthCheckModule.class.equals(obj.getClass()); + return getClass().equals(obj.getClass()); } @Override public int hashCode() { - return EurekaHealthCheckModule.class.hashCode(); + return getClass().hashCode(); } } diff --git a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4jAdminModule.java b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaAdminModule.java similarity index 50% rename from karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4jAdminModule.java rename to karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaAdminModule.java index 85ce0bf2..3ab9b092 100644 --- a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4jAdminModule.java +++ b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaAdminModule.java @@ -1,14 +1,15 @@ -package com.netflix.karyon.log4j.admin; +package com.netflix.karyon.eureka.admin; import com.google.inject.AbstractModule; -import com.netflix.karyon.admin.AdminModule; -import com.netflix.karyon.conditional.ConditionalOnModule; +import com.netflix.discovery.EurekaClient; -@ConditionalOnModule(AdminModule.class) -public final class Log4jAdminModule extends AbstractModule { +public class EurekaAdminModule extends AbstractModule { @Override protected void configure() { - bind(Log4JResource.class); + bind(EurekaApplicationAdminResource.class); + bind(EurekaStatusAdminResource.class); + + requireBinding(EurekaClient.class); } @Override diff --git a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaApplicationAdminResource.java b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaApplicationAdminResource.java similarity index 97% rename from karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaApplicationAdminResource.java rename to karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaApplicationAdminResource.java index c4dc006d..acaf4a49 100644 --- a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaApplicationAdminResource.java +++ b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaApplicationAdminResource.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.eureka; +package com.netflix.karyon.eureka.admin; import java.util.HashMap; import java.util.List; @@ -15,7 +15,7 @@ @Singleton @AdminService(name="eureka-apps", index="list") -public class EurekaApplicationAdminResource { +final class EurekaApplicationAdminResource { private DiscoveryClient client; @Inject diff --git a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaStatusAdminResource.java b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaStatusAdminResource.java similarity index 66% rename from karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaStatusAdminResource.java rename to karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaStatusAdminResource.java index 3bfbaaa0..67295961 100644 --- a/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/EurekaStatusAdminResource.java +++ b/karyon3-eureka/src/main/java/com/netflix/karyon/eureka/admin/EurekaStatusAdminResource.java @@ -1,37 +1,33 @@ -package com.netflix.karyon.eureka; +package com.netflix.karyon.eureka.admin; import javax.inject.Inject; import javax.inject.Singleton; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo.InstanceStatus; -import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.EurekaClient; import com.netflix.karyon.admin.AdminService; @Singleton @AdminService(name="eureka-status", index="current") -public class EurekaStatusAdminResource { - private final DiscoveryClient client; +final class EurekaStatusAdminResource { + private final EurekaClient client; private final InstanceInfo instanceInfo; @Inject - public EurekaStatusAdminResource(DiscoveryClient client, InstanceInfo instanceInfo) { + public EurekaStatusAdminResource(EurekaClient client, InstanceInfo instanceInfo) { this.client = client; this.instanceInfo = instanceInfo; } public static interface DiscoveryStatus { - public InstanceStatus getHealth(); - public String getRegion(); + InstanceStatus getHealth(); + InstanceInfo getInstanceInfo(); } public DiscoveryStatus current() { return new DiscoveryStatus() { @Override - public String getRegion() { - return client.getRegion(); - } - public InstanceInfo getInstanceInfo() { return instanceInfo; } diff --git a/karyon3-examples/src/main/java/com/netflix/karyon/example/jetty/HelloWorldApp.java b/karyon3-examples/src/main/java/com/netflix/karyon/example/jetty/HelloWorldApp.java index 3c5d3ed5..9c17c199 100644 --- a/karyon3-examples/src/main/java/com/netflix/karyon/example/jetty/HelloWorldApp.java +++ b/karyon3-examples/src/main/java/com/netflix/karyon/example/jetty/HelloWorldApp.java @@ -11,6 +11,7 @@ import com.netflix.governator.guice.jetty.JettyModule; import com.netflix.karyon.Karyon; import com.netflix.karyon.KaryonFeatures; +import com.netflix.karyon.admin.CoreAdminModule; import com.netflix.karyon.admin.rest.AdminServerModule; import com.netflix.karyon.admin.ui.AdminUIServerModule; import com.netflix.karyon.archaius.ArchaiusKaryonModule; @@ -25,13 +26,13 @@ public class HelloWorldApp extends DefaultLifecycleListener { private static final Logger LOG = LoggerFactory.getLogger(HelloWorldApp.class); public static void main(String[] args) throws Exception { - Karyon.forApplication("helloworld") - .apply(new ArchaiusKaryonModule() - .withConfigName("helloworld") - ) - .addProfile("local") + Karyon.newBuilder() + .addProfile("local") // Setting the profile should normally done using system/env property: karyon.profiles=local .addModules( + new ArchaiusKaryonModule() + .withConfigName("helloworld"), new ArchaiusLog4J2ConfigurationModule(), + new CoreAdminModule(), new ProvisionDebugModule(), new JettyModule(), new AdminServerModule(), diff --git a/karyon3-examples/src/main/java/com/netflix/karyon/example/rxnetty/RxNettyHelloWorldApp.java b/karyon3-examples/src/main/java/com/netflix/karyon/example/rxnetty/RxNettyHelloWorldApp.java index 83d54573..4b1bf819 100644 --- a/karyon3-examples/src/main/java/com/netflix/karyon/example/rxnetty/RxNettyHelloWorldApp.java +++ b/karyon3-examples/src/main/java/com/netflix/karyon/example/rxnetty/RxNettyHelloWorldApp.java @@ -4,6 +4,7 @@ import com.google.inject.AbstractModule; import com.netflix.karyon.Karyon; +import com.netflix.karyon.admin.CoreAdminModule; import com.netflix.karyon.admin.rest.AdminServerModule; import com.netflix.karyon.admin.ui.AdminUIServerModule; import com.netflix.karyon.archaius.ArchaiusKaryonModule; @@ -15,12 +16,12 @@ @Singleton public class RxNettyHelloWorldApp { public static void main(String[] args) throws Exception { - Karyon.forApplication("rxnetty-helloworld") - .apply(new ArchaiusKaryonModule() - .withConfigName("rxnetty-helloworld") - ) - .addProfile("local") + Karyon.newBuilder() + .addProfile("local") // Setting the profile should normally done using system/env property: karyon.profiles=local .addModules( + new ArchaiusKaryonModule() + .withConfigName("rxnetty-helloworld"), + new CoreAdminModule(), new AdminServerModule(), new AdminUIServerModule(), // These bindings will go on the 'default' server diff --git a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyAdminModule.java b/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyAdminModule.java deleted file mode 100644 index e8c2aba6..00000000 --- a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyAdminModule.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.netflix.karyon.jetty; - -import com.google.inject.AbstractModule; -import com.netflix.governator.guice.jetty.JettyModule; -import com.netflix.karyon.admin.AdminModule; -import com.netflix.karyon.conditional.ConditionalOnModule; - -/** - * Admin module for exposing Jetty's configuration information - * - * @author elandau - * - */ -@ConditionalOnModule({AdminModule.class, JettyModule.class}) -public final class JettyAdminModule extends AbstractModule { - @Override - protected void configure() { - bind(JettyAdminResource.class); - } - - @Override - public boolean equals(Object obj) { - return getClass().equals(obj.getClass()); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } -} diff --git a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyOverrideModule.java b/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyOverrideModule.java deleted file mode 100644 index 7806308c..00000000 --- a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyOverrideModule.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.netflix.karyon.jetty; - -import javax.inject.Singleton; - -import com.google.inject.Provides; -import com.netflix.archaius.ConfigProxyFactory; -import com.netflix.governator.DefaultModule; -import com.netflix.governator.guice.jetty.JettyConfig; -import com.netflix.governator.guice.jetty.JettyModule; -import com.netflix.karyon.conditional.ConditionalOnModule; -import com.netflix.karyon.conditional.OverrideModule; - -@OverrideModule -@ConditionalOnModule(JettyModule.class) -public final class JettyOverrideModule extends DefaultModule { - @Provides - @Singleton - private JettyConfig getDefaultConfig(ConfigProxyFactory factory) { - return factory.newProxy(AnnotatedJettyConfig.class); - } -} diff --git a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/admin/JettyAdminModule.java b/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/admin/JettyAdminModule.java new file mode 100644 index 00000000..e96a59a0 --- /dev/null +++ b/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/admin/JettyAdminModule.java @@ -0,0 +1,16 @@ +package com.netflix.karyon.jetty.admin; + +import com.google.inject.AbstractModule; + +/** + * Admin module for exposing Jetty's configuration information + * + * @author elandau + * + */ +public final class JettyAdminModule extends AbstractModule { + @Override + protected void configure() { + bind(JettyAdminResource.class); + } +} diff --git a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyAdminResource.java b/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/admin/JettyAdminResource.java similarity index 86% rename from karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyAdminResource.java rename to karyon3-jetty/src/main/java/com/netflix/karyon/jetty/admin/JettyAdminResource.java index 7cc0f4d6..89f6ab80 100644 --- a/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/JettyAdminResource.java +++ b/karyon3-jetty/src/main/java/com/netflix/karyon/jetty/admin/JettyAdminResource.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.jetty; +package com.netflix.karyon.jetty.admin; import javax.inject.Inject; import javax.inject.Singleton; @@ -8,7 +8,7 @@ @Singleton @AdminService(name="jetty", index="config") -public class JettyAdminResource { +final class JettyAdminResource { private final JettyConfig jettyConfig; @Inject diff --git a/karyon3-junit/src/main/java/com/netflix/karyon/junit/KaryonRule.java b/karyon3-junit/src/main/java/com/netflix/karyon/junit/KaryonRule.java index f944b0b7..ea0ed159 100644 --- a/karyon3-junit/src/main/java/com/netflix/karyon/junit/KaryonRule.java +++ b/karyon3-junit/src/main/java/com/netflix/karyon/junit/KaryonRule.java @@ -9,7 +9,6 @@ import com.netflix.governator.DefaultLifecycleListener; import com.netflix.governator.LifecycleInjector; import com.netflix.karyon.Karyon; -import com.netflix.karyon.KaryonModule; /** * JUnit rule to simplify testing with Karyon. KaryonRule extends Karyon and as such @@ -83,21 +82,14 @@ public void onStartFailed(Throwable t) { } public KaryonRule(Object obj) { - this(obj, null); - } - - public KaryonRule(Object obj, KaryonModule module) { - this(obj, "unittest", module); + this(obj, "unittest"); } - public KaryonRule(Object obj, String applicationName, KaryonModule module) { + public KaryonRule(Object obj, String applicationName) { super(applicationName); this.obj = obj; - this.injector = null; - - if (module != null) - this.apply(module); + this.injector = null; } public LifecycleInjector getInjector() { diff --git a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestActiveLifecycleListener.java b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestActiveLifecycleListener.java index c11147bb..4d22f48b 100644 --- a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestActiveLifecycleListener.java +++ b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestActiveLifecycleListener.java @@ -4,13 +4,10 @@ import org.junit.Test; import com.netflix.governator.LifecycleInjector; -import com.netflix.karyon.KaryonFeatures; public class TestActiveLifecycleListener { @Rule - public KaryonRule rule = new KaryonRule(this, (karyon) -> { - karyon.disableFeature(KaryonFeatures.USE_ARCHAIUS); - }); + public KaryonRule rule = new KaryonRule(this); @Test public void testWithStart() { diff --git a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestDefaultRule.java b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestDefaultRule.java index 30e4569f..abd0da61 100644 --- a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestDefaultRule.java +++ b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestDefaultRule.java @@ -5,13 +5,10 @@ import com.google.inject.AbstractModule; import com.netflix.governator.LifecycleInjector; -import com.netflix.karyon.KaryonFeatures; public class TestDefaultRule { @Rule - public KaryonRule rule = new KaryonRule(this, (karyon) -> { - karyon.disableFeature(KaryonFeatures.USE_ARCHAIUS); - }); + public KaryonRule rule = new KaryonRule(this); @Test public void testWithStart() { diff --git a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestInjection.java b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestInjection.java index fd0f5844..bb6b4e00 100644 --- a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestInjection.java +++ b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestInjection.java @@ -8,13 +8,10 @@ import com.google.inject.AbstractModule; import com.netflix.governator.LifecycleInjector; -import com.netflix.karyon.KaryonFeatures; public class TestInjection { @Rule - public KaryonRule karyon = new KaryonRule(this, (karyon) -> { - karyon.disableFeature(KaryonFeatures.USE_ARCHAIUS); - }); + public KaryonRule karyon = new KaryonRule(this); @Inject public String foo; diff --git a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestRule.java b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestRule.java index fbedcc12..71a59665 100644 --- a/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestRule.java +++ b/karyon3-junit/src/test/java/com/netflix/karyon/junit/TestRule.java @@ -4,13 +4,10 @@ import org.junit.Test; import com.netflix.governator.LifecycleInjector; -import com.netflix.karyon.KaryonFeatures; public class TestRule { @Rule - public KaryonRule rule = new KaryonRule(this, (karyon) -> { - karyon.disableFeature(KaryonFeatures.USE_ARCHAIUS); - }); + public KaryonRule rule = new KaryonRule(this); @Test public void testWithStart() throws Exception { diff --git a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/ArchaiusLog4J2ConfigurationModule.java b/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/ArchaiusLog4J2ConfigurationModule.java index 6d43a630..cddd5ed3 100644 --- a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/ArchaiusLog4J2ConfigurationModule.java +++ b/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/ArchaiusLog4J2ConfigurationModule.java @@ -5,6 +5,7 @@ import com.google.inject.multibindings.Multibinder; import com.netflix.governator.DefaultModule; +import com.netflix.karyon.log4j.admin.Log4j2AdminModule; /** * Add this to the list of Guice modules to enable reconfiguration of log4j2 @@ -23,5 +24,6 @@ protected void configure() { Multibinder appenderBinder = Multibinder.newSetBinder(binder(), Log4jConfigurator.class); appenderBinder.addBinding().to(ConsoleAppenderConfigurator.class); + install(new Log4j2AdminModule()); } } diff --git a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4JResource.java b/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4JResource.java index 0818e372..31f1a436 100644 --- a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4JResource.java +++ b/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4JResource.java @@ -15,7 +15,7 @@ @Singleton @AdminService(name="log4j", index="current") -public class Log4JResource { +final class Log4JResource { public static class Result { Map loggers = new TreeMap<>(); diff --git a/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4j2AdminModule.java b/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4j2AdminModule.java new file mode 100644 index 00000000..583f21fd --- /dev/null +++ b/karyon3-log4j2/src/main/java/com/netflix/karyon/log4j/admin/Log4j2AdminModule.java @@ -0,0 +1,10 @@ +package com.netflix.karyon.log4j.admin; + +import com.google.inject.AbstractModule; + +public class Log4j2AdminModule extends AbstractModule { + @Override + protected void configure() { + bind(Log4JResource.class); + } +} diff --git a/karyon3-log4j2/src/test/java/com/netflix/karyon/TestDynamicLogLevels.java b/karyon3-log4j2/src/test/java/com/netflix/karyon/TestDynamicLogLevels.java index 5fd123e2..95042ac1 100644 --- a/karyon3-log4j2/src/test/java/com/netflix/karyon/TestDynamicLogLevels.java +++ b/karyon3-log4j2/src/test/java/com/netflix/karyon/TestDynamicLogLevels.java @@ -67,12 +67,12 @@ public void doConfigure(AbstractConfiguration config) { @Test public void testDynamicConfigurationChange() throws ConfigException, InterruptedException { - Injector injector = Karyon.forApplication("test") - .apply(new ArchaiusKaryonModule() - .withRuntimeOverrides(MapConfig.builder() - .put("log4j.logger.com.netflix.karyon", "WARN") - .build())) + Injector injector = Karyon.newBuilder() .addModules( + new ArchaiusKaryonModule() + .withRuntimeOverrides(MapConfig.builder() + .put("log4j.logger.com.netflix.karyon", "WARN") + .build()), new ArchaiusLog4J2ConfigurationModule(), new AbstractModule() { @Override diff --git a/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminModule.java b/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminModule.java index 97a10b05..004039a4 100644 --- a/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminModule.java +++ b/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminModule.java @@ -1,11 +1,7 @@ package com.netflix.karyon.rxnetty.admin; import com.google.inject.AbstractModule; -import com.netflix.karyon.admin.AdminModule; -import com.netflix.karyon.conditional.ConditionalOnModule; -import com.netflix.karyon.rxnetty.server.RxNettyModule; -@ConditionalOnModule(value = {AdminModule.class, RxNettyModule.class}) public final class RxNettyAdminModule extends AbstractModule { @Override protected void configure() { diff --git a/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminResource.java b/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminResource.java index e2149d2b..8251fe8c 100644 --- a/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminResource.java +++ b/karyon3-rxnetty/src/main/java/com/netflix/karyon/rxnetty/admin/RxNettyAdminResource.java @@ -16,7 +16,7 @@ @Singleton @AdminService(name="netty", index="current") -public class RxNettyAdminResource { +class RxNettyAdminResource { private final RxNettyHttpServerRegistry registry; public static interface ServerInfo { diff --git a/karyon3-servlet/src/main/java/com/google/inject/servlet/HealthCheckServlet.java b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/HealthCheckServlet.java similarity index 98% rename from karyon3-servlet/src/main/java/com/google/inject/servlet/HealthCheckServlet.java rename to karyon3-servlet/src/main/java/com/netflix/karyon/servlet/HealthCheckServlet.java index 46dd746e..ec5c4cc3 100644 --- a/karyon3-servlet/src/main/java/com/google/inject/servlet/HealthCheckServlet.java +++ b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/HealthCheckServlet.java @@ -1,4 +1,4 @@ -package com.google.inject.servlet; +package com.netflix.karyon.servlet; import java.io.IOException; import java.io.PrintWriter; diff --git a/karyon3-servlet/src/main/java/com/google/inject/servlet/ServletHealthCheckModule.java b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletHealthCheckModule.java similarity index 73% rename from karyon3-servlet/src/main/java/com/google/inject/servlet/ServletHealthCheckModule.java rename to karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletHealthCheckModule.java index 2d0d743a..9707fba6 100644 --- a/karyon3-servlet/src/main/java/com/google/inject/servlet/ServletHealthCheckModule.java +++ b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletHealthCheckModule.java @@ -1,9 +1,8 @@ -package com.google.inject.servlet; +package com.netflix.karyon.servlet; import com.google.inject.AbstractModule; -import com.netflix.karyon.conditional.ConditionalOnModule; +import com.google.inject.servlet.ServletModule; -@ConditionalOnModule(InternalServletModule.class) public class ServletHealthCheckModule extends AbstractModule { @Override protected void configure() { diff --git a/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletAdminModule.java b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/admin/ServletAdminModule.java similarity index 63% rename from karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletAdminModule.java rename to karyon3-servlet/src/main/java/com/netflix/karyon/servlet/admin/ServletAdminModule.java index 13dc1ae6..f2f8c71d 100644 --- a/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletAdminModule.java +++ b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/admin/ServletAdminModule.java @@ -1,8 +1,6 @@ -package com.netflix.karyon.servlet; +package com.netflix.karyon.servlet.admin; import com.google.inject.AbstractModule; -import com.netflix.karyon.admin.AdminModule; -import com.netflix.karyon.conditional.ConditionalOnModule; /** * Admin module for exposing servlets and filters configured via ServletModule @@ -10,7 +8,6 @@ * @author elandau * */ -@ConditionalOnModule({AdminModule.class}) public final class ServletAdminModule extends AbstractModule { @Override protected void configure() { diff --git a/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletAdminResource.java b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/admin/ServletAdminResource.java similarity index 91% rename from karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletAdminResource.java rename to karyon3-servlet/src/main/java/com/netflix/karyon/servlet/admin/ServletAdminResource.java index 328edf0f..5102f17f 100644 --- a/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/ServletAdminResource.java +++ b/karyon3-servlet/src/main/java/com/netflix/karyon/servlet/admin/ServletAdminResource.java @@ -1,4 +1,4 @@ -package com.netflix.karyon.servlet; +package com.netflix.karyon.servlet.admin; import java.util.Map; @@ -10,7 +10,7 @@ @Singleton @AdminService(name="servlet", index="get") -public class ServletAdminResource { +final class ServletAdminResource { private ServletInfo info; static interface Info {