package com.netflix.fabricator.component; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.netflix.config.ConfigurationManager; import com.netflix.fabricator.ComponentType; import com.netflix.fabricator.annotations.Type; import com.netflix.fabricator.annotations.TypeImplementation; import com.netflix.fabricator.archaius.ArchaiusConfigurationModule; import com.netflix.fabricator.component.exception.ComponentAlreadyExistsException; import com.netflix.fabricator.component.exception.ComponentCreationException; import com.netflix.fabricator.guice.ComponentModuleBuilder; import com.netflix.fabricator.properties.PropertiesConfigurationModule; import com.netflix.fabricator.supplier.ListenableSupplier; import com.netflix.fabricator.supplier.SupplierWithDefault; /** * Test Driven development for Beaver library. * Prerequisites: * Environment: * 1. Guice injector environment to inject anything * 2. Archaius * 3. Jackson * 4. Properties files * Code: * 1. Classes with builder pattern built in. * 1.1. Different builder classes with focuses on different patterns. * 1.1.1. Simple properties * 1.1.1.1. Supplier argument type. * 1.1.1.2. Simple property type. * 1.1.1.3. Composite properties type. * 1.1.2. Supplier properties. * 1.1.2.1. With default values. * 1.1.2.2. Without default values. * 1.1.3. Compostite properties * 1.1.3.1. Argument type has to be composite. * 2. Prebuilt objects can be directly retrieved without construction * 3. Negative cases */ public class ComponentManagerTest { private static final Logger logger = LoggerFactory.getLogger(ComponentManagerTest.class); /** * Example of a subentity * */ public static class SubEntity { public static class Builder { private String str; public Builder withStr(String str) { this.str = str; return this; } public SubEntity build() { return new SubEntity(this); } } private final String str; private SubEntity(Builder init) { this.str = init.str; } public String getString() { return str; } @Override public String toString() { return "SubEntity [str=" + str + "]"; } } /** * Interface for a policy * @author elandau * */ @Type("policy") public static interface Policy { } /** * Implementation of a policy with one String arg * * @author elandau */ @TypeImplementation("pa") public static class PolicyA implements Policy { private final String s; private final Long l; private final Boolean b; private final Double d; private final Integer i; public static class Builder { private String s; private Long l; private Boolean b; private Double d; private Integer i; public Builder withString(String s) { this.s = s; return this; } public Builder withLong(Long l) { this.l = l; return this; } public Builder withInteger(Integer i) { this.i = i; return this; } public Builder withDouble(Double d) { this.d = d; return this; } public Builder withBoolean(Boolean b) { this.b = b; return this; } public PolicyA build() { return new PolicyA(this); } } private PolicyA(Builder init) { this.s = init.s; this.l = init.l; this.b = init.b; this.i = init.i; this.d = init.d; } public String getString() { return this.s; } @Override public String toString() { return "PolicyA [s=" + s + ", l=" + l + ", b=" + b + ", d=" + d + ", i=" + i + "]"; } } /** * A plicy with one string arg and a supplier * @author elandau * */ @TypeImplementation("pb") public static class PolicyB implements Policy { private final Supplier<String> arg1; public static class Builder { private final SupplierWithDefault<String> arg1 = SupplierWithDefault.from("abc"); public Builder withArg1(String arg1) { this.arg1.setValue(arg1); return this; } public Builder withArg1(Supplier<String> arg1) { this.arg1.addOverride(arg1); return this; } public PolicyB build() { return new PolicyB(this); } } private PolicyB(Builder init) { this.arg1 = init.arg1; } @Override public String toString() { return "PolicyB [arg1=" + arg1 + "]"; } } /** * * @author elandau * */ @Type("some") public static abstract class SomeInterface { } /** * * @author elandau * */ @TypeImplementation("a") public static class BaseA extends SomeInterface { public static class Builder { private String id; private String prop1; private Policy policy; private final SupplierWithDefault<String> dyn1 = new SupplierWithDefault<String>("dyn1_default"); private final SupplierWithDefault<String> dyn2 = new SupplierWithDefault<String>("dyn2_default"); public Builder withId(String id) { this.id = id; return this; } public Builder withProp1(String prop1) { this.prop1 = prop1; return this; } public Builder withPolicy(Policy policy) { this.policy = policy; return this; } public Builder withDyn1(Supplier<String> supplier) { logger.info("dyn1=" + supplier.get()); this.dyn1.addOverride(supplier); return this; } public Builder withDyn2(ListenableSupplier<String> supplier) { logger.info("dyn2=" + supplier.get()); this.dyn2.setSource(supplier); return this; } public BaseA build() { return new BaseA(this); } } private final String id; private final String prop1; private final Policy policy; private final Supplier<String> dyn1; private final ListenableSupplier<String> dyn2; private BaseA(Builder init) { this.id = init.id; this.prop1 = init.prop1; this.policy = init.policy; this.dyn1 = init.dyn1; this.dyn2 = init.dyn2; this.dyn2.onChange(new Function<String, Void>() { public Void apply(@Nullable String input) { logger.info("Value has changed to : " + input); return null; } }); } public Policy getPolicy() { return policy; } @Override public String toString() { return "BaseA [id=" + id + ", prop1=" + prop1 + ", policy=" + policy + ", dyn1=" + dyn1.get() + ", dyn2=" + dyn2.get() + "]"; } } /** * * @author elandau * */ @TypeImplementation("b") public static class BaseB extends SomeInterface { public static class Builder { private String id; public Builder withId(String id) { this.id = id; return this; } public BaseB build() { return new BaseB(this); } } private final String id; private BaseB(Builder init) { id = init.id; } @Override public String toString() { return "BaseB [id=" + id + "]"; } } @TypeImplementation("c") public static class BaseC extends SomeInterface { public static class Builder { private SubEntity entity; private String id; public Builder withSubEntity(SubEntity entity) { this.entity = entity; return this; } public Builder withId(String id) { this.id = id; return this; } public BaseC build() { return new BaseC(this); } } private final SubEntity entity; private final String id; private BaseC(Builder init) { this.entity = init.entity; this.id = init.id; } @Override public String toString() { return "BaseC [entity=" + entity + ", id=" + id + "]"; } } @TypeImplementation("d") public static class BaseD extends SomeInterface { private final String s; private final Long l; private final Boolean b; private final Double d; private final Integer i; private final Properties p; private final String id; public static class Builder { private String s; private Long l; private Boolean b; private Double d; private Integer i; private Properties p; private String id; public Builder() { } public Builder withId(String id) { this.id = id; return this; } public Builder withString(String s) { this.s = s; return this; } public Builder withLong(long l) { this.l = l; return this; } public Builder withInteger(int i) { this.i = i; return this; } public Builder withDouble(double d) { this.d = d; return this; } public Builder withBoolean(boolean b) { this.b = b; return this; } public Builder withProperties(Properties props) { this.p = props; return this; } public BaseD build() { return new BaseD(this); } } private BaseD(Builder init) { this.s = init.s; this.l = init.l; this.b = init.b; this.i = init.i; this.d = init.d; this.p = init.p; this.id = init.id; } public String getString() { return this.s; } public Properties getProperties() { return this.p; } @Override public String toString() { return "BaseD [id=" + id + ", s=" + s + ", l=" + l + ", b=" + b + ", d=" + d + ", i=" + i + ", p=" + p + "]"; } } public static class MyService { private SomeInterface if1; private SomeInterface if2; @Inject public MyService(final ComponentManager<SomeInterface> manager) throws ComponentCreationException, ComponentAlreadyExistsException { if1 = manager.get("id1"); if2 = manager.get("id2"); } } public static class MyServiceWithNamedComponent { private SomeInterface if1; private SomeInterface if2; @Inject public MyServiceWithNamedComponent(@Named("id1") SomeInterface if1, @Named("id2") SomeInterface if2) { this.if1 = if1; this.if2 = if2; } } @Before public void setUp() throws Exception { } public static class SomeInterfaceModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral<ComponentManager<SomeInterface>>(){}) .to(new TypeLiteral<SynchronizedComponentManager<SomeInterface>>(){}) .in(Scopes.SINGLETON); bind(new TypeLiteral<ComponentType<SomeInterface>>(){}) .toInstance(new ComponentType<SomeInterface>("some")); install(new ComponentModuleBuilder<SomeInterface>() .build(SomeInterface.class)); } } @Test public void testEmbeddedEntity() throws Exception { final Properties props = new Properties(); props.put("id1.some.type", "a"); props.put("id1.some.string", "str"); props.put("id1.some.dyn1", "dyn1_value"); props.put("id1.some.dyn2", "dyn2_value"); props.put("id1.some.policy.type", "pb"); props.put("id1.some.policy.arg1", "pb_arg1"); Injector injector = Guice.createInjector( new PropertiesConfigurationModule(props), new SomeInterfaceModule(), new AbstractModule() { @Override protected void configure() { install(new ComponentModuleBuilder<SomeInterface>() .implementation(BaseA.class) .build(SomeInterface.class) ); install(new ComponentModuleBuilder<Policy>() .implementation(PolicyA.class) .implementation(PolicyB.class) .build(Policy.class)); } }, new AbstractModule() { @Override protected void configure() { bind(MyService.class); } } ); Assert.assertNotNull(injector.getBinding(Key.get(new TypeLiteral<Map<String, Provider<ComponentFactory<Policy>>>>() {}))); Assert.assertNotNull(injector.getBinding(Key.get(new TypeLiteral<Map<String, Provider<ComponentFactory<Policy>>>>() {}))); ComponentManager<SomeInterface> ifmanager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {})); BaseA if1 = (BaseA)ifmanager.get("id1"); logger.info("get id4 from manager: " + if1.toString()); Assert.assertNotNull(if1.getPolicy()); } @Test public void testComponentManager() throws ComponentCreationException, ComponentAlreadyExistsException, InterruptedException { final Properties props = new Properties(); props.setProperty("id1.some.type", "d"); props.setProperty("id1.some.string", "str"); props.setProperty("id1.some.long", "1"); props.setProperty("id1.some.boolean", "true"); props.setProperty("id1.some.integer", "2"); props.setProperty("id1.some.double", "2.1"); props.setProperty("id1.some.properties.a", "_a"); props.setProperty("id1.some.properties.b", "_b"); props.setProperty("id2.some.type", "c"); props.setProperty("id2.some.subEntity.str", "id2_subEntity_str"); props.setProperty("id3.some.type", "b"); props.setProperty("id4.some.type", "a"); props.setProperty("id4.some.string", "str"); props.setProperty("id4.some.dyn1", "dyn1_value"); props.setProperty("id4.some.dyn2", "dyn2_value"); props.setProperty("id4.some.policy.type", "pb"); props.setProperty("id4.some.policy.arg1", "pb_arg1"); ConfigurationManager.getConfigInstance(); ConfigurationManager.loadProperties(props); String value = ConfigurationManager.getConfigInstance().getString("id1.some.type"); Injector injector = Guice.createInjector( new ArchaiusConfigurationModule(), new SomeInterfaceModule(), new AbstractModule() { @Override protected void configure() { install(new ComponentModuleBuilder<SomeInterface>() .implementation(BaseA.class) .implementation(BaseB.class) .implementation(BaseC.class) .implementation(BaseD.class) .build(SomeInterface.class) ); install(new ComponentModuleBuilder<Policy>() .implementation(PolicyA.class) .implementation("pb", PolicyB.class) .build(Policy.class)); } }, new AbstractModule() { @Override protected void configure() { install(new ComponentModuleBuilder<SomeInterface>() .named("id1") .named("id2") .build(SomeInterface.class) ); } } ); TimeUnit.SECONDS.sleep(1); SomeInterface si1 = injector.getInstance(Key.get(SomeInterface.class, Names.named("id1"))); Assert.assertNotNull(si1); ComponentManager<SomeInterface> ifmanager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {})); BaseD if1 = (BaseD)ifmanager.get("id1"); logger.info("get id1 from manager: " + if1.toString()); Assert.assertEquals(BaseD.class, if1.getClass()); Assert.assertNotNull(if1.getProperties()); Assert.assertEquals(2, if1.getProperties().size()); SomeInterface if2 = ifmanager.get("id2"); logger.info("get id2 from manager: " + if2.toString()); Assert.assertEquals(BaseC.class, if2.getClass()); SomeInterface if3 = ifmanager.get("id3"); logger.info("get id3 from manager: " + if3.toString()); Assert.assertEquals(BaseB.class, if3.getClass()); SomeInterface if4 = ifmanager.get("id4"); BaseA a = (BaseA)if4; Assert.assertEquals(PolicyB.class, a.getPolicy().getClass()); Assert.assertEquals("pb_arg1", ((PolicyB)a.getPolicy()).arg1.get()); logger.info("get id4 from manager: " + if4.toString()); Assert.assertEquals(BaseA.class, if4.getClass()); ConfigurationManager.getConfigInstance().setProperty("id1.some.dyn1", "dyn1_value_new"); ConfigurationManager.getConfigInstance().setProperty("id1.some.dyn2", "dyn2_value_new"); logger.info("if4 after setProperty: " + if4.toString()); MyService service = injector.getInstance(MyService.class); Assert.assertEquals(if1, service.if1); Assert.assertEquals(if2, service.if2); MyServiceWithNamedComponent serviceWithNamedComponent = injector.getInstance(MyServiceWithNamedComponent.class); Assert.assertEquals(if1, serviceWithNamedComponent.if1); Assert.assertEquals(if2, serviceWithNamedComponent.if2); } }