diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java new file mode 100644 index 00000000..84457dde --- /dev/null +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java @@ -0,0 +1,131 @@ +package org.springframework.cloud.context.named; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.env.MapPropertySource; + +/** + * Creates a set of child contexts that allows a set of Specifications to define the + * beans in each child context. + * + * Ported from spring-cloud-netflix FeignClientFactory and SpringClientFactory + * + * @author Spencer Gibb + * @author Dave Syer + */ +public abstract class NamedContextFactory implements DisposableBean, ApplicationContextAware { + + public interface Specification { + String getName(); + Class[] getConfiguration(); + } + + private Map contexts = new ConcurrentHashMap<>(); + + private Map configurations = new ConcurrentHashMap<>(); + + private ApplicationContext parent; + + private Class defaultConfigType; + private final String propertySourceName; + private final String propertyName; + + + public NamedContextFactory(Class defaultConfigType, String propertySourceName, String propertyName) { + this.defaultConfigType = defaultConfigType; + this.propertySourceName = propertySourceName; + this.propertyName = propertyName; + } + + @Override + public void setApplicationContext(ApplicationContext parent) throws BeansException { + this.parent = parent; + } + + public void setConfigurations(List configurations) { + for (C client : configurations) { + this.configurations.put(client.getName(), client); + } + } + + @Override + public void destroy() { + Collection values = this.contexts.values(); + this.contexts.clear(); + for (AnnotationConfigApplicationContext context : values) { + context.close(); + } + } + + protected AnnotationConfigApplicationContext getContext(String name) { + if (!this.contexts.containsKey(name)) { + synchronized (this.contexts) { + if (!this.contexts.containsKey(name)) { + this.contexts.put(name, createContext(name)); + } + } + } + return this.contexts.get(name); + } + + protected AnnotationConfigApplicationContext createContext(String name) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + if (this.configurations.containsKey(name)) { + for (Class configuration : this.configurations.get(name) + .getConfiguration()) { + context.register(configuration); + } + } + for (Map.Entry entry : this.configurations + .entrySet()) { + if (entry.getKey().startsWith("default.")) { + for (Class configuration : entry.getValue().getConfiguration()) { + context.register(configuration); + } + } + } + context.register(PropertyPlaceholderAutoConfiguration.class, + this.defaultConfigType); + context.getEnvironment() + .getPropertySources() + .addFirst(new MapPropertySource( + propertySourceName, + Collections. singletonMap(propertyName, name))); + if (this.parent != null) { + // Uses Environment from parent as well as beans + context.setParent(this.parent); + } + context.refresh(); + return context; + } + + public T getInstance(String name, Class type) { + AnnotationConfigApplicationContext context = getContext(name); + if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, + type).length > 0) { + return context.getBean(type); + } + return null; + } + + public Map getInstances(String name, Class type) { + AnnotationConfigApplicationContext context = getContext(name); + if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, + type).length > 0) { + return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type); + } + return null; + } + +} diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/named/NamedContextFactoryTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/named/NamedContextFactoryTests.java new file mode 100644 index 00000000..0c6da498 --- /dev/null +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/named/NamedContextFactoryTests.java @@ -0,0 +1,101 @@ +package org.springframework.cloud.context.named; + +import java.util.Arrays; +import java.util.Map; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * @author Spencer Gibb + */ +public class NamedContextFactoryTests { + + @Test + public void testChildContexts() { + AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); + parent.register(BaseConfig.class); + parent.refresh(); + TestClientFactory factory = new TestClientFactory(); + factory.setApplicationContext(parent); + factory.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class), + getSpec("bar", BarConfig.class))); + + Foo foo = factory.getInstance("foo", Foo.class); + assertThat("foo was null", foo, is(notNullValue())); + + Bar bar = factory.getInstance("bar", Bar.class); + assertThat("bar was null", bar, is(notNullValue())); + + Bar foobar = factory.getInstance("foo", Bar.class); + assertThat("bar was not null", foobar, is(nullValue())); + + Map fooBazes = factory.getInstances("foo", Baz.class); + assertThat("fooBazes was null", fooBazes, is(notNullValue())); + assertThat("fooBazes size was wrong", fooBazes.size(), is(1)); + + Map barBazes = factory.getInstances("bar", Baz.class); + assertThat("barBazes was null", barBazes, is(notNullValue())); + assertThat("barBazes size was wrong", barBazes.size(), is(2)); + } + + private TestSpec getSpec(String name, Class configClass) { + return new TestSpec(name, new Class[]{configClass}); + } + + static class TestClientFactory extends NamedContextFactory { + + public TestClientFactory() { + super(TestSpec.class, "testfactory", "test.client.name"); + } + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + static class TestSpec implements NamedContextFactory.Specification { + private String name; + + private Class[] configuration; + } + + static class BaseConfig { + @Bean + Baz baz1() { + return new Baz(); + } + } + static class Baz{} + + static class FooConfig { + @Bean + Foo foo() { + return new Foo(); + } + } + static class Foo{} + + static class BarConfig { + @Bean + Bar bar() { + return new Bar(); + } + + @Bean + Baz baz2() { + return new Baz(); + } + } + static class Bar{} + +}