package restx.factory; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import org.junit.After; import org.junit.Test; import java.util.Set; import static org.assertj.core.api.Assertions.*; import static restx.factory.Factory.LocalMachines.threadLocal; /** * User: xavierhanin * Date: 1/31/13 * Time: 7:11 PM */ public class FactoryTest { @Test public void should_compare_named_component() throws Exception { assertThat(Factory.NAMED_COMPONENT_COMPARATOR.compare( NamedComponent.of(String.class, "test1", 0, "A"), NamedComponent.of(String.class, "test2", 0, "A") )).isLessThan(0); assertThat(Factory.NAMED_COMPONENT_COMPARATOR.compare( NamedComponent.of(String.class, "test", 0, "A"), NamedComponent.of(String.class, "test2", 0, "A") )).isLessThan(0); assertThat(Factory.NAMED_COMPONENT_COMPARATOR.compare( NamedComponent.of(String.class, "test2", -10, "A"), NamedComponent.of(String.class, "test1", 0, "A") )).isLessThan(0); } @Test public void should_build_new_component_from_single_machine() throws Exception { Factory factory = Factory.builder().addMachine(testMachine()).build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test")).findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getName()).isEqualTo(Name.of(String.class, "test")); assertThat(component.get().getComponent()).isEqualTo("value1"); assertThat(factory.queryByName(Name.of(String.class, "test")).findOne().get()).isEqualTo(component.get()); } @Test public void should_concat_new_single_machine() throws Exception { Factory factory = Factory.builder().addMachine(testMachine()).build(); factory = factory.concat(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "c2", "v2"))); assertThat(factory.getComponent(Name.of(String.class, "test"))).isEqualTo("value1"); assertThat(factory.getComponent(Name.of(String.class, "c2"))).isEqualTo("v2"); } @Test public void should_build_with_warehouse() throws Exception { Factory factory = Factory.builder().addMachine(testMachine()).addMachine(testMachine("test3")).build(); factory.getComponent(Name.of(String.class, "test")); // this will build the component and put it into the warehouse factory = Factory.builder() .addMachine(testMachine("test2")) .addWarehouseProvider(factory.getWarehouse()) .build(); assertThat(factory.getComponent(Name.of(String.class, "test"))).isEqualTo("value1"); assertThat(factory.getComponent(Name.of(String.class, "test2"))).isEqualTo("value1"); assertThat(factory.queryByName(Name.of(String.class, "test3")).optional().findOne().isPresent()).isFalse(); } @Test public void should_start_auto_startable() throws Exception { LifecycleComponent lifecycleComponent = new LifecycleComponent(); Factory factory = Factory.builder().addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( AutoStartable.class, "t", lifecycleComponent))).build(); factory.start(); assertThat(lifecycleComponent.started).isTrue(); } @Test public void should_start_auto_startable_in_order() throws Exception { final LifecycleComponent lifecycleComponent1 = new LifecycleComponent(); final boolean[] l2Started = new boolean[1]; final boolean[] wasL1Started = new boolean[1]; Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(10, NamedComponent.of( AutoStartable.class, "t0", new AutoStartable() { @Override public void start() { wasL1Started[0] = lifecycleComponent1.started; l2Started[0] = true; } } ))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( AutoStartable.class, "t1", lifecycleComponent1))) .build(); factory.start(); assertThat(lifecycleComponent1.started).isTrue(); assertThat(l2Started[0]).isTrue(); assertThat(wasL1Started[0]).isTrue(); } @Test public void should_prepare_auto_preparable() throws Exception { LifecycleComponent lifecycleComponent = new LifecycleComponent(); Factory factory = Factory.builder().addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( AutoPreparable.class, "t", lifecycleComponent))).build(); factory.prepare(); assertThat(lifecycleComponent.prepared).isTrue(); } @Test public void should_not_close_closeable_from_other_warehouse() throws Exception { LifecycleComponent lifecycleComponent = new LifecycleComponent(); Factory factory = Factory.builder().addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( AutoStartable.class, "t", lifecycleComponent))).build(); factory.start(); assertThat(lifecycleComponent.started).isTrue(); Factory otherFactory = Factory.builder() .addMachine(testMachine()) .addWarehouseProvider(factory.getWarehouse()) .build(); assertThat(otherFactory.getComponents(AutoStartable.class)).containsOnly(lifecycleComponent); otherFactory.close(); assertThat(lifecycleComponent.closed).isFalse(); factory.close(); assertThat(lifecycleComponent.closed).isTrue(); } @Test public void should_build_new_component_with_deps() throws Exception { Factory factory = Factory.builder() .addMachine(testMachine()) .addMachine(new SingleNameFactoryMachine<>( 0, new StdMachineEngine<String>(Name.of(String.class, "test2"), BoundlessComponentBox.FACTORY) { private Factory.Query<String> stringQuery = Factory.Query.byName(Name.of(String.class, "test")); @Override public BillOfMaterials getBillOfMaterial() { return BillOfMaterials.of(stringQuery); } @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return satisfiedBOM.getOne(stringQuery).get().getComponent() + " value2"; } })) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test2")).findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getName()).isEqualTo(Name.of(String.class, "test2")); assertThat(component.get().getComponent()).isEqualTo("value1 value2"); assertThat(factory.getComponent(Name.of(String.class, "test2"))).isEqualTo("value1 value2"); assertThat(factory.queryByName(Name.of(String.class, "test2")).findOne().get()).isEqualTo(component.get()); } @Test public void should_fail_with_missing_deps() throws Exception { SingleNameFactoryMachine<String> machine = machineWithMissingDependency(); Factory factory = Factory.builder().addMachine(machine).build(); try { factory.queryByName(Name.of(String.class, "test")).findOne(); fail("should raise exception when asking for a component with missing dependency"); } catch (IllegalStateException e) { assertThat(e) .hasMessageStartingWith( "\n" + " QueryByName{name=Name{name='test', clazz=java.lang.String[]}}\n" + " | \\__=> Name{name='test', clazz=java.lang.String[]}\n" + " |\n" + " +-> QueryByName{name=Name{name='missing', clazz=java.lang.String[]}}\n" + " |\n" + " +--: Name{name='missing', clazz=java.lang.String[]} can't be satisfied") ; } } @Test public void should_fail_with_similar_components() throws Exception { SingleNameFactoryMachine<String> machine = machineWithMissingDependency(); Factory factory = Factory.builder().addMachine(machine).addMachine(testMachine("ASIMILARCOMPONENT")).build(); try { factory.queryByName(Name.of(String.class, "test")).findOne(); fail("should raise exception when asking for a component with missing dependency"); } catch (IllegalStateException e) { assertThat(e) .hasMessageContaining("ASIMILARCOMPONENT") ; } } @Test public void should_find_one_when_multiple_available_fail() throws Exception { Factory factory = Factory.builder().addMachine(testMachine("test")).addMachine(testMachine("test2")).build(); try { factory.queryByClass(String.class).findOne(); fail("should raise exception when asking for one component with multiple available"); } catch (IllegalStateException e) { assertThat(e) .hasMessage("more than one component is available for query QueryByClass{componentClass=java.lang.String[]}.\n" + " Please select which one you want with a more specific query,\n" + " or by deactivating one of the available components.\n" + " Available components:\n" + " - NamedComponent{name=Name{name='test', clazz=java.lang.String[]}, priority=0, component=value1}\n" + " [Activation key: 'restx.activation::java.lang.String::test']\n" + " - NamedComponent{name=Name{name='test2', clazz=java.lang.String[]}, priority=0, component=value1}\n" + " [Activation key: 'restx.activation::java.lang.String::test2']\n") ; } } @Test public void should_warn_about_missing_annotated_machine() throws Exception { Factory factory = Factory.builder().addFromServiceLoader().build(); assertThat(factory.dump()).contains(TestMissingAnnotatedMachine.class.getName()); } @Test public void should_dump_list_overrider() throws Exception { SingleNameFactoryMachine<String> machine1 = testMachine(); SingleNameFactoryMachine<String> machine2 = overridingMachine(); Factory factory = Factory.builder() .addMachine(machine1) .addMachine(machine2) .build(); assertThat(factory.dump()).contains("OVERRIDING:\n " + machine1); } @Test public void should_customize_component() throws Exception { Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( String.class, "test", "hello"))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(ComponentCustomizerEngine.class, "cutomizerTest", new ComponentCustomizerEngine() { @Override public <T> boolean canCustomize(Name<T> name) { return name.getClazz() == String.class; } @Override public <T> ComponentCustomizer<T> getCustomizer(Name<T> name) { return new ComponentCustomizer<T>() { @Override public int priority() { return 0; } @Override @SuppressWarnings("unchecked") public NamedComponent<T> customize(NamedComponent<T> namedComponent) { return new NamedComponent<>( namedComponent.getName(), (T) (namedComponent.getComponent() + " world")); } }; } }))) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test")).findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getName()).isEqualTo(Name.of(String.class, "test")); assertThat(component.get().getComponent()).isEqualTo("hello world"); assertThat(factory.queryByName(Name.of(String.class, "test")).findOne().get()).isEqualTo(component.get()); } @Test public void should_customize_component_with_simple_customizer() throws Exception { Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( String.class, "test", "hello"))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(ComponentCustomizerEngine.class, "cutomizerTest", new SingleComponentClassCustomizerEngine<String>(0, String.class) { @Override public NamedComponent<String> customize(NamedComponent<String> namedComponent) { return new NamedComponent<>( namedComponent.getName(), namedComponent.getComponent() + " world"); } }))) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test")).findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getComponent()).isEqualTo("hello world"); } @Test public void should_customize_component_with_customizer_with_deps() throws Exception { Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( String.class, "dep", "world"))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of( String.class, "test", "hello"))) .addMachine(new SingleNameFactoryMachine<>(0, new StdMachineEngine<ComponentCustomizerEngine>( Name.of(ComponentCustomizerEngine.class, "cutomizerTest"), BoundlessComponentBox.FACTORY) { private Factory.Query<String> query = Factory.Query.byName(Name.of(String.class, "dep")); @Override public BillOfMaterials getBillOfMaterial() { return BillOfMaterials.of(query); } @Override protected ComponentCustomizerEngine doNewComponent(final SatisfiedBOM satisfiedBOM) { return new SingleComponentClassCustomizerEngine<String>(0, String.class) { @Override public NamedComponent<String> customize(NamedComponent<String> namedComponent) { return new NamedComponent<>( namedComponent.getName(), namedComponent.getComponent() + " " + satisfiedBOM.getOne(query).get().getComponent()); } }; } })) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test")).findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getComponent()).isEqualTo("hello world"); } @Test public void should_handle_machine_factory_to_build_conditional_component() throws Exception { SingleNameFactoryMachine<FactoryMachine> alternativeMachine = alternativeMachine(); Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "mode", "dev"))) .addMachine(alternativeMachine) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test")).optional().findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getComponent()).isEqualTo("hello"); factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "mode", "prod"))) .addMachine(alternativeMachine) .build(); component = factory.queryByName(Name.of(String.class, "test")).optional().findOne(); assertThat(component.isPresent()).isFalse(); } @Test public void should_handle_machine_factory_to_build_alternative() throws Exception { SingleNameFactoryMachine<FactoryMachine> alternativeMachine = alternativeMachine(); Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "mode", "dev"))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "test", "default"))) .addMachine(alternativeMachine) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test")).optional().findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getComponent()).isEqualTo("hello"); factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "mode", "prod"))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "test", "default"))) .addMachine(alternativeMachine) .build(); component = factory.queryByName(Name.of(String.class, "test")).optional().findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getComponent()).isEqualTo("default"); } @Test public void should_handle_machine_factory_with_dependencies_on_other_machine_factory() throws Exception { SingleNameFactoryMachine<FactoryMachine> alternativeMachine = alternativeMachine(); SingleNameFactoryMachine<FactoryMachine> dependentMachine = new SingleNameFactoryMachine<>(0, new StdMachineEngine<FactoryMachine>( Name.of(FactoryMachine.class, "machineFactoryTest2"), BoundlessComponentBox.FACTORY) { private Factory.Query<String> query = Factory.Query.byName(Name.of(String.class, "test")); @Override public BillOfMaterials getBillOfMaterial() { return BillOfMaterials.of(query); } @Override protected FactoryMachine doNewComponent(final SatisfiedBOM satisfiedBOM) { return new SingletonFactoryMachine<>(0, NamedComponent.of( String.class, "test2", satisfiedBOM.getOne(query).get().getComponent() + " world")); } }); Factory factory = Factory.builder() .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, "mode", "dev"))) .addMachine(alternativeMachine) .addMachine(dependentMachine) .build(); Optional<NamedComponent<String>> component = factory.queryByName(Name.of(String.class, "test2")).optional().findOne(); assertThat(component.isPresent()).isTrue(); assertThat(component.get().getComponent()).isEqualTo("hello world"); } private SingleNameFactoryMachine<FactoryMachine> alternativeMachine() { return new AlternativesFactoryMachine<>(0, Name.of(String.class, "mode"), ImmutableMap.of("dev", new SingletonFactoryMachine<>(-100, NamedComponent.of( String.class, "test", "hello"))), BoundlessComponentBox.FACTORY); } @Test @SuppressWarnings("unchecked") public void should_build_component_lists_from_multiple_machines() throws Exception { Factory factory = Factory.builder() .addMachine(new SingleNameFactoryMachine<>( 0, new NoDepsMachineEngine<String>(Name.of(String.class, "test 1"), BoundlessComponentBox.FACTORY) { @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return "value 1"; } })) .addMachine(new SingleNameFactoryMachine<>( 0, new NoDepsMachineEngine<String>(Name.of(String.class, "test 2"), BoundlessComponentBox.FACTORY) { @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return "value 2"; } })) .build(); Set<NamedComponent<String>> components = factory.queryByClass(String.class).find(); assertThat(components).containsExactly( NamedComponent.of(String.class, "test 1", "value 1"), NamedComponent.of(String.class, "test 2", "value 2")); } @Test @SuppressWarnings("unchecked") public void should_respect_component_priorities() throws Exception { Factory factory = Factory.builder() .addMachine(new SingleNameFactoryMachine<>( 0, new NoDepsMachineEngine<String>(Name.of(String.class, "test 1"), 1, BoundlessComponentBox.FACTORY) { @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return "value 1"; } })) .addMachine(new SingleNameFactoryMachine<>( 0, new NoDepsMachineEngine<String>(Name.of(String.class, "test 2"), 0, BoundlessComponentBox.FACTORY) { @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return "value 2"; } })) .build(); Set<NamedComponent<String>> components = factory.queryByClass(String.class).find(); assertThat(components).containsExactly( NamedComponent.of(String.class, "test 2", "value 2"), NamedComponent.of(String.class, "test 1", "value 1")); } @Test public void should_factory_be_queryable() throws Exception { Factory factory = Factory.builder().build(); assertThat(factory.queryByClass(Factory.class).findAsComponents()).containsExactly(factory); assertThat(factory.getComponent(Factory.class)).isEqualTo(factory); } @Test public void should_allow_to_close_with_factory_queried() throws Exception { // check that we don't get a stack overflow error due to box closing the factory Factory factory = Factory.builder().build(); assertThat(factory.queryByClass(Factory.class).findAsComponents()).containsExactly(factory); factory.close(); } @Test public void should_register_and_unregister() throws Exception { Factory factory = Factory.builder().build(); assertThat(Factory.getFactory(factory.getId())).isEqualTo(Optional.absent()); Factory.register(factory.getId(), factory); assertThat(Factory.getFactory(factory.getId())).isEqualTo(Optional.of(factory)); Factory.unregister(factory.getId(), factory); assertThat(Factory.getFactory(factory.getId())).isEqualTo(Optional.absent()); } @Test public void should_get_default_instance() throws Exception { Factory factory = Factory.getInstance(); assertThat(factory).isNotNull().isSameAs(Factory.getInstance()); } @Test public void should_use_local_machines() throws Exception { threadLocal().set("test", "myvalue"); Factory factory = Factory.newInstance(); assertThat(factory.getComponent(Name.of(String.class, "test"))).isEqualTo("myvalue"); threadLocal().clear(); factory = Factory.newInstance(); assertThat(factory.queryByName(Name.of(String.class, "test")).optional().findOne().isPresent()).isFalse(); } @Test public void should_deactivate_component() throws Exception { threadLocal().set("name1", new A("a1")); threadLocal().set("name2", new A("a2")); Factory factory = Factory.newInstance(); assertThat(factory.getComponents(A.class)).extracting("a").containsExactly("a1", "a2"); assertThat(factory.queryByName(Name.of(A.class, "name2")).findOne().isPresent()).isTrue(); threadLocal().set(Factory.activationKey(A.class, "name2"), "false"); factory = Factory.newInstance(); assertThat(factory.getComponents(A.class)).extracting("a").containsExactly("a1"); assertThat(factory.queryByName(Name.of(A.class, "name2")).findOne().isPresent()).isFalse(); } @Test public void should_deactivate_component2() throws Exception { threadLocal().set("name1", new A("a1")); threadLocal().set("name2", new A("a2")); Factory factory = Factory.newInstance(); assertThat(factory.queryByName(Name.of(Object.class, "name2")).findOne().isPresent()).isTrue(); threadLocal().set(Factory.activationKey(A.class, "name2"), "false"); factory = Factory.newInstance(); assertThat(factory.queryByName(Name.of(Object.class, "name2")).findOne().isPresent()).isFalse(); } @Test public void should_allow_to_deactivate_components_from_provided_warehouse() throws Exception { Factory factory = Factory.builder().addMachine( new SingletonFactoryMachine<>(0, NamedComponent.of(A.class, "a", new A("v1")))).build(); Set<A> components = factory.getComponents(A.class); assertThat(components).hasSize(1).extracting("a").containsExactly("v1"); Factory newFactory = Factory.builder().addWarehouseProvider(factory.getWarehouse()) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(String.class, Factory.activationKey(A.class, "a"), "false"))) .addMachine(new SingletonFactoryMachine<>(0, NamedComponent.of(A.class, "b", new A("v2")))).build(); components = newFactory.getComponents(A.class); assertThat(components).hasSize(1).extracting("a").containsExactly("v2"); } @After public void teardown() { threadLocal().clear(); } private SingleNameFactoryMachine<String> testMachine() { return testMachine("test"); } private SingleNameFactoryMachine<String> testMachine(String name) { return new SingleNameFactoryMachine<>( 0, new NoDepsMachineEngine<String>(Name.of(String.class, name), BoundlessComponentBox.FACTORY) { @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return "value1"; } }); } private SingleNameFactoryMachine<String> machineWithMissingDependency() { return new SingleNameFactoryMachine<>( 0, new StdMachineEngine<String>(Name.of(String.class, "test"), BoundlessComponentBox.FACTORY) { @Override public BillOfMaterials getBillOfMaterial() { return BillOfMaterials.of(Factory.Query.byName(Name.of(String.class, "missing"))); } @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { throw new RuntimeException("shouldn't be called"); } }); } private SingleNameFactoryMachine<String> overridingMachine() { return new SingleNameFactoryMachine<>( -10, new NoDepsMachineEngine<String>(Name.of(String.class, "test"), BoundlessComponentBox.FACTORY) { @Override protected String doNewComponent(SatisfiedBOM satisfiedBOM) { return "value1"; } }); } private static class LifecycleComponent implements AutoStartable, AutoCloseable, AutoPreparable { private boolean closed; private boolean started; private boolean prepared; @Override public void close() throws Exception { closed = true; } @Override public void start() { started = true; } @Override public void prepare() { prepared = true; } } private static class A { String a; private A(String a) { this.a = a; } public String getA() { return a; } } }