package com.netflix.fabricator;
import java.util.Properties;
import javax.annotation.Nullable;
import com.netflix.fabricator.properties.PropertiesConfigurationModule;
import com.netflix.fabricator.supplier.SupplierWithDefault;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.netflix.config.ConfigurationManager;
import com.netflix.fabricator.archaius.ArchaiusConfigurationModule;
import com.netflix.fabricator.annotations.TypeImplementation;
import com.netflix.fabricator.annotations.Type;
import com.netflix.fabricator.component.ComponentManager;
import com.netflix.fabricator.component.SynchronizedComponentManager;
import com.netflix.fabricator.component.exception.ComponentAlreadyExistsException;
import com.netflix.fabricator.component.exception.ComponentCreationException;
import com.netflix.fabricator.guice.ComponentModuleBuilder;
import com.netflix.fabricator.supplier.ListenableSupplier;
public class ComponentFactoryModuleBuilderTest {
private static final Logger LOG = LoggerFactory.getLogger(ComponentFactoryModuleBuilderTest.class);
/**
* Example of a subentity
*
* @author elandau
*/
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 {
}
public static class SomeInterfaceModule extends AbstractModule {
@Override
protected void configure() {
install(new ComponentModuleBuilder<SomeInterface>()
.manager(SynchronizedComponentManager.class)
.build(SomeInterface.class));
}
}
/**
*
* @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) {
LOG.info("dyn1=" + supplier.get());
this.dyn1.addOverride(supplier);
return this;
}
public Builder withDyn2(ListenableSupplier<String> supplier) {
LOG.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) {
LOG.info("Value has changed to : " + input);
return null;
}
});
}
@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 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 {
@Inject
public MyService(final ComponentManager<SomeInterface> manager) throws ComponentCreationException, ComponentAlreadyExistsException {
SomeInterface if1 = manager.get("id1");
LOG.info(if1.toString());
SomeInterface if2 = manager.get("id2");
LOG.info(if2.toString());
Properties prop2 = new Properties();
prop2.put("type", "b");
prop2.put("prop1", "v1");
prop2.put("prop2", "v2");
// SomeInterface base3 = manager.get(new PropertyMapper("id2", prop2.getProperty("type"), prop2));
// LOG.info(base3.toString());
}
}
public static class MyServiceWithNamedComponent {
@Inject
public MyServiceWithNamedComponent(@Named("id1") SomeInterface iface) {
}
}
@Test
public void testNamed() {
// 1. Bootstrap on startup
// 2. Load by 'id'
// 3. Map with prefix
final Properties props = new Properties();
props.put("id1.some.type", "a");
props.put("id1.some.prop1", "v1");
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(props),
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(PolicyB.class)
.build(Policy.class));
}
},
new AbstractModule() {
@Override
protected void configure() {
install(new ComponentModuleBuilder<SomeInterface>()
.named("id1")
.build(SomeInterface.class)
);
bind(MyServiceWithNamedComponent.class);
}
}
);
MyServiceWithNamedComponent service = injector.getInstance(MyServiceWithNamedComponent.class);
}
@Test
public void testProperies() throws Exception {
// 1. Bootstrap on startup
// 2. Load by 'id'
// 3. Map with prefix
final Properties props = new Properties();
// Properties is NOT a map
props.put("id1.some.type", "d");
props.put("id1.some.properties", "simplevalue");
// Properties not provided
props.put("id2.some.type", "d");
// Properties is a map
props.put("id3.some.type", "d");
props.put("id3.some.properties.a", "a");
props.put("id3.some.properties.b.c","bc");
props.put("id3.some.properties.d", "true");
props.put("id3.some.properties.e", "123");
ConfigurationManager.loadProperties(props);
Injector injector = Guice.createInjector(
new ArchaiusConfigurationModule(),
new SomeInterfaceModule(),
new AbstractModule() {
@Override
protected void configure() {
install(new ComponentModuleBuilder<SomeInterface>()
.implementation(BaseD.class)
.build(SomeInterface.class)
);
}
}
);
ComponentManager<SomeInterface> ifmanager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
BaseD iface2 = (BaseD)ifmanager.get("id2");
BaseD iface3 = (BaseD)ifmanager.get("id3");
LOG.info(iface2.toString());
LOG.info(iface3.toString());
try {
// TODO: Need to fix this unit test
BaseD iface1 = (BaseD)ifmanager.get("id1");
LOG.info(iface1.toString());
// Assert.fail();
}
catch (Exception e) {
}
}
public static class SomeInterfaceProvider implements Provider<SomeInterface> {
@Override
public SomeInterface get() {
LOG.info("hi");
return null;
}
}
@Test
public void testProperties() throws Exception {
// 1. Bootstrap on startup
// 2. Load by 'id'
// 3. Map with prefix
final Properties props = new Properties();
props.put("id1.some.type", "d");
props.put("id1.some.string", "str");
props.put("id1.some.long", "1");
props.put("id1.some.boolean", "true");
props.put("id1.some.integer", "2");
props.put("id1.some.double", "2.1");
props.put("id1.some.dyn1", "dyn1_value");
props.put("id1.some.dyn2", "dyn2_value");
props.put("id1.some.policy.type", "pa");
props.put("id1.some.policy.arg2", "pa_arg2");
props.put("id2.some.type", "c");
props.put("id2.some.subEntity.str", "id2_subEntity_str");
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(props),
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(PolicyB.class)
.build(Policy.class));
bind(SomeInterface.class)
.annotatedWith(Names.named("id1"))
.toProvider(SomeInterfaceProvider.class);
}
},
new AbstractModule() {
@Override
protected void configure() {
bind(MyService.class);
}
}
);
MyService service = injector.getInstance(MyService.class);
ComponentManager<SomeInterface> ifmanager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
SomeInterface if1 = ifmanager.get("id1");
LOG.info(if1.toString());
ConfigurationManager.getConfigInstance().setProperty("id1.some.dyn1", "dyn1_value_new");
ConfigurationManager.getConfigInstance().setProperty("id1.some.dyn2", "dyn2_value_new");
// Map<Object, Object> props2 = ConfigurationConverter.getMap(ConfigurationManager.getConfigInstance());
// for (Entry<Object, Object> prop : props2.entrySet()) {
// LOG.info(prop.getKey() + " = " + prop.getValue());
// }
LOG.info(if1.toString());
}
@Test
@Ignore
public void testJson() throws Exception {
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(new Properties()),
new SomeInterfaceModule(),
new AbstractModule() {
@Override
protected void configure() {
install(new ComponentModuleBuilder<SomeInterface>()
.implementation(BaseA.class)
.implementation(BaseB.class)
.implementation(BaseC.class)
.build(SomeInterface.class)
);
install(new ComponentModuleBuilder<Policy>()
.implementation(PolicyA.class)
.implementation(PolicyB.class)
.build(Policy.class));
bind(SomeInterface.class)
.annotatedWith(Names.named("id1"))
.toProvider(SomeInterfaceProvider.class);
}
},
new AbstractModule() {
@Override
protected void configure() {
bind(MyService.class);
}
}
);
// MyService service = injector.getInstance(MyService.class);
ComponentManager<SomeInterface> ifmanager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
// 1. Bootstrap on startup
// 2. Load by 'id'
// 3. Map with prefix
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.createObjectNode();
node.put("type", "a");
node.put("prop1", "v1");
node.put("prop2", "v2");
node.put("dyn1", "dyn1_value");
node.put("dyn2", "dyn2_value");
ObjectNode policy = mapper.createObjectNode();
policy.put("type", "pa");
policy.put("arg1", "pa_arg2");
node.put("policy", policy);
SomeInterface if1 = ifmanager.get("id1");
LOG.info(if1.toString());
ConfigurationManager.getConfigInstance().setProperty("id1.some.dyn1", "dyn1_value_new");
ConfigurationManager.getConfigInstance().setProperty("id1.some.dyn2", "dyn2_value_new");
// Map<Object, Object> props2 = ConfigurationConverter.getMap(ConfigurationManager.getConfigInstance());
// for (Entry<Object, Object> prop : props2.entrySet()) {
// LOG.info(prop.getKey() + " = " + prop.getValue());
// }
LOG.info(if1.toString());
}
@Test
@Ignore
public void testRegisterBeforeGet() throws Exception {
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(new Properties()),
new SomeInterfaceModule()
);
ComponentManager<SomeInterface> manager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
manager.add("a", new SomeInterface() {});
// final AtomicInteger changed = new AtomicInteger();
// ListenableReference<SomeInterface> ref = manager.get("a");
// ref.change(new ReferenceChangeListener<SomeInterface>() {
// @Override
// public void removed() {
// }
//
// @Override
// public void changed(SomeInterface newComponent) {
// changed.incrementAndGet();
// }
// });
//
// ref.get();
// Assert.assertEquals(1, changed.get());
}
@Test
@Ignore
public void testRegisterAfterGet() throws Exception {
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(new Properties()),
new SomeInterfaceModule()
);
ComponentManager<SomeInterface> manager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
manager.add("a", new SomeInterface() {});
// final AtomicInteger changed = new AtomicInteger();
// ListenableReference<SomeInterface> ref = manager.get("a");
// ref.get();
// ref.change(new ReferenceChangeListener<SomeInterface>() {
// @Override
// public void removed() {
// }
//
// @Override
// public void changed(SomeInterface newComponent) {
// changed.incrementAndGet();
// }
// });
// Assert.assertEquals(0, changed.get());
}
@Test
@Ignore
public void testChangedComponent() throws Exception {
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(new Properties()),
new SomeInterfaceModule()
);
ComponentManager<SomeInterface> manager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
manager.add("a", new SomeInterface() {});
// final AtomicInteger changed = new AtomicInteger();
// ListenableReference<SomeInterface> ref = manager.get("a");
// ref.change(new ReferenceChangeListener<SomeInterface>() {
// @Override
// public void removed() {
// }
//
// @Override
// public void changed(SomeInterface newComponent) {
// changed.incrementAndGet();
// }
// });
// manager.replace("a", new SomeInterface() {});
// Assert.assertEquals(2, changed.get());
}
@Test
@Ignore
public void testRemovedComponent() throws Exception {
Injector injector = Guice.createInjector(
new PropertiesConfigurationModule(new Properties()),
new SomeInterfaceModule()
);
ComponentManager<SomeInterface> manager = injector.getInstance(Key.get(new TypeLiteral<ComponentManager<SomeInterface>>() {}));
manager.add("a", new SomeInterface() {});
// final AtomicBoolean changed = new AtomicBoolean(false);
// ListenableReference<SomeInterface> ref = manager.get("a");
// ref.change(new ReferenceChangeListener<SomeInterface>() {
// @Override
// public void removed() {
// changed.set(true);
// }
//
// @Override
// public void changed(SomeInterface newComponent) {
// }
// });
// ref.get();
// manager.remove("a");
// Assert.assertTrue(changed.get());
}
}