package com.ctrip.framework.apollo.spring; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile; import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import java.util.List; import java.util.Properties; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Component; /** * @author Jason Song([email protected]) */ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { private static final String TIMEOUT_PROPERTY = "timeout"; private static final int DEFAULT_TIMEOUT = 100; private static final String BATCH_PROPERTY = "batch"; private static final int DEFAULT_BATCH = 200; private static final String FX_APOLLO_NAMESPACE = "FX.apollo"; private static final String JSON_PROPERTY = "jsonProperty"; private static final String OTHER_JSON_PROPERTY = "otherJsonProperty"; @Test public void testPropertySourceWithNoNamespace() throws Exception { int someTimeout = 1000; int someBatch = 2000; Config config = mock(Config.class); when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); check(someTimeout, someBatch, AppConfig1.class); } @Test public void testPropertySourceWithNoConfig() throws Exception { Config config = mock(Config.class); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); check(DEFAULT_TIMEOUT, DEFAULT_BATCH, AppConfig1.class); } @Test public void testApplicationPropertySource() throws Exception { int someTimeout = 1000; int someBatch = 2000; Config config = mock(Config.class); when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); check(someTimeout, someBatch, AppConfig2.class); } @Test public void testPropertiesCompatiblePropertySource() throws Exception { int someTimeout = 1000; int someBatch = 2000; Properties properties = mock(Properties.class); when(properties.getProperty(TIMEOUT_PROPERTY)).thenReturn(String.valueOf(someTimeout)); when(properties.getProperty(BATCH_PROPERTY)).thenReturn(String.valueOf(someBatch)); PropertiesCompatibleConfigFile configFile = mock(PropertiesCompatibleConfigFile.class); when(configFile.asProperties()).thenReturn(properties); mockConfigFile("application.yaml", configFile); check(someTimeout, someBatch, AppConfig9.class); } @Test public void testPropertiesCompatiblePropertySourceWithNonNormalizedCase() throws Exception { int someTimeout = 1000; int someBatch = 2000; Properties properties = mock(Properties.class); when(properties.getProperty(TIMEOUT_PROPERTY)).thenReturn(String.valueOf(someTimeout)); when(properties.getProperty(BATCH_PROPERTY)).thenReturn(String.valueOf(someBatch)); PropertiesCompatibleConfigFile configFile = mock(PropertiesCompatibleConfigFile.class); when(configFile.asProperties()).thenReturn(properties); mockConfigFile("application.yaml", configFile); check(someTimeout, someBatch, AppConfig10.class); } @Test public void testMultiplePropertySources() throws Exception { int someTimeout = 1000; int someBatch = 2000; Config application = mock(Config.class); when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application); Config fxApollo = mock(Config.class); when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(FX_APOLLO_NAMESPACE, fxApollo); check(someTimeout, someBatch, AppConfig3.class); } @Test public void testMultiplePropertiesCompatiblePropertySourcesWithSameProperties() throws Exception { int someTimeout = 1000; int anotherTimeout = someTimeout + 1; int someBatch = 2000; Properties properties = mock(Properties.class); when(properties.getProperty(TIMEOUT_PROPERTY)).thenReturn(String.valueOf(someTimeout)); when(properties.getProperty(BATCH_PROPERTY)).thenReturn(String.valueOf(someBatch)); PropertiesCompatibleConfigFile configFile = mock(PropertiesCompatibleConfigFile.class); when(configFile.asProperties()).thenReturn(properties); mockConfigFile("application.yml", configFile); Config fxApollo = mock(Config.class); when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout)); mockConfig(FX_APOLLO_NAMESPACE, fxApollo); check(someTimeout, someBatch, AppConfig11.class); } @Test public void testMultiplePropertySourcesCoverWithSameProperties() throws Exception { //Multimap does not maintain the strict input order of namespace. int someTimeout = 1000; int anotherTimeout = someTimeout + 1; int someBatch = 2000; Config fxApollo = mock(Config.class); when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(fxApollo.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(FX_APOLLO_NAMESPACE, fxApollo); Config application = mock(Config.class); when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application); check(someTimeout, someBatch, AppConfig6.class); } @Test public void testMultiplePropertySourcesCoverWithSamePropertiesWithPropertiesCompatiblePropertySource() throws Exception { //Multimap does not maintain the strict input order of namespace. int someTimeout = 1000; int anotherTimeout = someTimeout + 1; int someBatch = 2000; Config fxApollo = mock(Config.class); when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(fxApollo.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(FX_APOLLO_NAMESPACE, fxApollo); Config application = mock(Config.class); when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application); check(someTimeout, someBatch, AppConfig6.class); } @Test public void testMultiplePropertySourcesWithSamePropertiesWithWeight() throws Exception { int someTimeout = 1000; int anotherTimeout = someTimeout + 1; int someBatch = 2000; Config application = mock(Config.class); when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application); Config fxApollo = mock(Config.class); when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout)); mockConfig(FX_APOLLO_NAMESPACE, fxApollo); check(anotherTimeout, someBatch, AppConfig2.class, AppConfig4.class); } @Test public void testApplicationPropertySourceWithValueInjectedAsParameter() throws Exception { int someTimeout = 1000; int someBatch = 2000; Config config = mock(Config.class); when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig5.class); TestJavaConfigBean2 bean = context.getBean(TestJavaConfigBean2.class); assertEquals(someTimeout, bean.getTimeout()); assertEquals(someBatch, bean.getBatch()); } @Test public void testApplicationPropertySourceWithValueInjectedAsConstructorArgs() throws Exception { int someTimeout = 1000; int someBatch = 2000; Config config = mock(Config.class); when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout)); when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig7.class); TestJavaConfigBean3 bean = context.getBean(TestJavaConfigBean3.class); assertEquals(someTimeout, bean.getTimeout()); assertEquals(someBatch, bean.getBatch()); } @Test public void testNestedProperty() throws Exception { String a = "a"; String b = "b"; int someValue = 1234; Config config = mock(Config.class); when(config.getProperty(eq(a), anyString())).thenReturn(a); when(config.getProperty(eq(b), anyString())).thenReturn(b); when(config.getProperty(eq(String.format("%s.%s", a, b)), anyString())) .thenReturn(String.valueOf(someValue)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); assertEquals(someValue, bean.getNestedProperty()); } @Test public void testNestedPropertyWithDefaultValue() throws Exception { String a = "a"; String b = "b"; String c = "c"; int someValue = 1234; Config config = mock(Config.class); when(config.getProperty(eq(a), anyString())).thenReturn(a); when(config.getProperty(eq(b), anyString())).thenReturn(b); when(config.getProperty(eq(c), anyString())).thenReturn(String.valueOf(someValue)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); assertEquals(someValue, bean.getNestedProperty()); } @Test public void testNestedPropertyWithNestedDefaultValue() throws Exception { String a = "a"; String b = "b"; Config config = mock(Config.class); when(config.getProperty(eq(a), anyString())).thenReturn(a); when(config.getProperty(eq(b), anyString())).thenReturn(b); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); assertEquals(100, bean.getNestedProperty()); } @Test public void testMultipleNestedProperty() throws Exception { String a = "a"; String b = "b"; String nestedKey = "c.d"; String nestedProperty = String.format("${%s}", nestedKey); int someValue = 1234; Config config = mock(Config.class); when(config.getProperty(eq(a), anyString())).thenReturn(a); when(config.getProperty(eq(b), anyString())).thenReturn(b); when(config.getProperty(eq(String.format("%s.%s", a, b)), anyString())).thenReturn(nestedProperty); when(config.getProperty(eq(nestedKey), anyString())).thenReturn(String.valueOf(someValue)); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); assertEquals(someValue, bean.getNestedProperty()); } @Test public void testMultipleNestedPropertyWithDefaultValue() throws Exception { String a = "a"; String b = "b"; String nestedKey = "c.d"; int someValue = 1234; String nestedProperty = String.format("${%s:%d}", nestedKey, someValue); Config config = mock(Config.class); when(config.getProperty(eq(a), anyString())).thenReturn(a); when(config.getProperty(eq(b), anyString())).thenReturn(b); when(config.getProperty(eq(String.format("%s.%s", a, b)), anyString())).thenReturn(nestedProperty); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); assertEquals(someValue, bean.getNestedProperty()); } @Test public void testApolloJsonValue() { String someJson = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]"; String otherJson = "[{\"a\":\"otherString\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]"; Config config = mock(Config.class); when(config.getProperty(eq(JSON_PROPERTY), anyString())).thenReturn(someJson); when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(otherJson); when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( AppConfig8.class); TestJsonPropertyBean testJsonPropertyBean = context.getBean(TestJsonPropertyBean.class); assertEquals(2, testJsonPropertyBean.getJsonBeanList().size()); assertEquals("astring", testJsonPropertyBean.getJsonBeanList().get(0).getA()); assertEquals(10, testJsonPropertyBean.getJsonBeanList().get(0).getB()); assertEquals("astring2", testJsonPropertyBean.getJsonBeanList().get(1).getA()); assertEquals(20, testJsonPropertyBean.getJsonBeanList().get(1).getB()); assertEquals(testJsonPropertyBean.getJsonBeanList(), testJsonPropertyBean.getEmbeddedJsonBeanList()); assertEquals("otherString", testJsonPropertyBean.getOtherJsonBeanList().get(0).getA()); assertEquals(10, testJsonPropertyBean.getOtherJsonBeanList().get(0).getB()); assertEquals("astring2", testJsonPropertyBean.getOtherJsonBeanList().get(1).getA()); assertEquals(20, testJsonPropertyBean.getOtherJsonBeanList().get(1).getB()); } @Test(expected = BeanCreationException.class) public void testApolloJsonValueWithInvalidJson() throws Exception { String someInvalidJson = "someInvalidJson"; Config config = mock(Config.class); when(config.getProperty(eq(JSON_PROPERTY), anyString())).thenReturn(someInvalidJson); when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(someInvalidJson); when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); new AnnotationConfigApplicationContext(AppConfig8.class).getBean(TestJsonPropertyBean.class); } @Test(expected = BeanCreationException.class) public void testApolloJsonValueWithNoPropertyValue() throws Exception { Config config = mock(Config.class); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); new AnnotationConfigApplicationContext(AppConfig8.class); } private void check(int expectedTimeout, int expectedBatch, Class<?>... annotatedClasses) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); assertEquals(expectedTimeout, bean.getTimeout()); assertEquals(expectedBatch, bean.getBatch()); } @Configuration @EnableApolloConfig static class AppConfig1 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig("application") static class AppConfig2 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig({"application", "FX.apollo"}) static class AppConfig3 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig(value = "FX.apollo", order = 10) static class AppConfig4 { } @Configuration @EnableApolloConfig static class AppConfig5 { @Bean TestJavaConfigBean2 testJavaConfigBean2(@Value("${timeout:100}") int timeout, @Value("${batch:200}") int batch) { TestJavaConfigBean2 bean = new TestJavaConfigBean2(); bean.setTimeout(timeout); bean.setBatch(batch); return bean; } } @Configuration @EnableApolloConfig({"FX.apollo", "application"}) static class AppConfig6 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @ComponentScan( includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Component.class})}, excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Configuration.class})}) @EnableApolloConfig static class AppConfig7 { } @Configuration @EnableApolloConfig static class NestedPropertyConfig1 { @Bean TestNestedPropertyBean testNestedPropertyBean() { return new TestNestedPropertyBean(); } } @Configuration @EnableApolloConfig static class AppConfig8 { @Bean TestJsonPropertyBean testJavaConfigBean() { return new TestJsonPropertyBean(); } } @Configuration @EnableApolloConfig("application.yaml") static class AppConfig9 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig("application.yaMl") static class AppConfig10 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig({"application.yml", "FX.apollo"}) static class AppConfig11 { @Bean TestJavaConfigBean testJavaConfigBean() { return new TestJavaConfigBean(); } } @Component static class TestJavaConfigBean { @Value("${timeout:100}") private int timeout; private int batch; @Value("${batch:200}") public void setBatch(int batch) { this.batch = batch; } public int getTimeout() { return timeout; } public int getBatch() { return batch; } } static class TestJavaConfigBean2 { private int timeout; private int batch; public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public int getBatch() { return batch; } public void setBatch(int batch) { this.batch = batch; } } @Component static class TestJavaConfigBean3 { private final int timeout; private final int batch; @Autowired public TestJavaConfigBean3(@Value("${timeout:100}") int timeout, @Value("${batch:200}") int batch) { this.timeout = timeout; this.batch = batch; } public int getTimeout() { return timeout; } public int getBatch() { return batch; } } static class TestNestedPropertyBean { @Value("${${a}.${b}:${c:100}}") private int nestedProperty; public int getNestedProperty() { return nestedProperty; } } static class TestJsonPropertyBean { @ApolloJsonValue("${jsonProperty}") private List<JsonBean> jsonBeanList; private List<JsonBean> otherJsonBeanList; @ApolloJsonValue("${${a}}") private List<JsonBean> embeddedJsonBeanList; public List<JsonBean> getJsonBeanList() { return jsonBeanList; } @ApolloJsonValue("${otherJsonProperty}") public void setOtherJsonBeanList(List<JsonBean> otherJsonBeanList) { this.otherJsonBeanList = otherJsonBeanList; } public List<JsonBean> getOtherJsonBeanList() { return otherJsonBeanList; } public List<JsonBean> getEmbeddedJsonBeanList() { return embeddedJsonBeanList; } } static class JsonBean { private String a; private int b; String getA() { return a; } public void setA(String a) { this.a = a; } int getB() { return b; } public void setB(int b) { this.b = b; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } JsonBean jsonBean = (JsonBean) o; if (b != jsonBean.b) { return false; } return a != null ? a.equals(jsonBean.a) : jsonBean.a == null; } @Override public int hashCode() { int result = a != null ? a.hashCode() : 0; result = 31 * result + b; return result; } } }