Commit b1ca961b authored by Jason Song's avatar Jason Song Committed by nobodyiam

add yaml support to apollo-client

1. support yaml config file transform to properties
2. support yaml config file injection to Spring
parent c8f84a63
...@@ -45,6 +45,12 @@ ...@@ -45,6 +45,12 @@
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
</dependency> </dependency>
<!-- yml processing -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- end of yml processing -->
<!-- optional spring dependency --> <!-- optional spring dependency -->
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
......
package com.ctrip.framework.apollo;
import java.util.Properties;
/**
* Config files that are properties compatible, e.g. yaml
*
* @since 1.3.0
*/
public interface PropertiesCompatibleConfigFile extends ConfigFile {
/**
* @return the properties form of the config file
*
* @throws RuntimeException if the content could not be transformed to properties
*/
Properties asProperties();
}
...@@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.tracer.Tracer; ...@@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpUtil; import com.ctrip.framework.apollo.util.http.HttpUtil;
import com.ctrip.framework.apollo.util.yaml.YamlParser;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Singleton; import com.google.inject.Singleton;
...@@ -60,6 +61,7 @@ public class DefaultInjector implements Injector { ...@@ -60,6 +61,7 @@ public class DefaultInjector implements Injector {
bind(HttpUtil.class).in(Singleton.class); bind(HttpUtil.class).in(Singleton.class);
bind(ConfigServiceLocator.class).in(Singleton.class); bind(ConfigServiceLocator.class).in(Singleton.class);
bind(RemoteConfigLongPollService.class).in(Singleton.class); bind(RemoteConfigLongPollService.class).in(Singleton.class);
bind(YamlParser.class).in(Singleton.class);
} }
} }
} }
package com.ctrip.framework.apollo.internals;
import java.util.Properties;
import com.ctrip.framework.apollo.ConfigFileChangeListener;
import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
import com.google.common.base.Preconditions;
public class PropertiesCompatibleFileConfigRepository extends AbstractConfigRepository implements
ConfigFileChangeListener {
private final PropertiesCompatibleConfigFile configFile;
private volatile Properties cachedProperties;
public PropertiesCompatibleFileConfigRepository(PropertiesCompatibleConfigFile configFile) {
this.configFile = configFile;
this.configFile.addChangeListener(this);
this.trySync();
}
@Override
protected synchronized void sync() {
Properties current = configFile.asProperties();
Preconditions.checkState(current != null, "PropertiesCompatibleConfigFile.asProperties should never return null");
if (cachedProperties != current) {
cachedProperties = current;
this.fireRepositoryChange(configFile.getNamespace(), cachedProperties);
}
}
@Override
public Properties getConfig() {
if (cachedProperties == null) {
sync();
}
return cachedProperties;
}
@Override
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
//config file is the upstream, so no need to set up extra upstream
}
@Override
public ConfigSourceType getSourceType() {
return configFile.getSourceType();
}
@Override
public void onChange(ConfigFileChangeEvent changeEvent) {
this.trySync();
}
}
...@@ -16,7 +16,6 @@ import com.ctrip.framework.apollo.util.ExceptionUtil; ...@@ -16,7 +16,6 @@ import com.ctrip.framework.apollo.util.ExceptionUtil;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class PropertiesConfigFile extends AbstractConfigFile { public class PropertiesConfigFile extends AbstractConfigFile {
private static final Logger logger = LoggerFactory.getLogger(PropertiesConfigFile.class);
protected AtomicReference<String> m_contentCache; protected AtomicReference<String> m_contentCache;
public PropertiesConfigFile(String namespace, public PropertiesConfigFile(String namespace,
......
package com.ctrip.framework.apollo.internals; package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.util.yaml.YamlParser;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class YamlConfigFile extends PlainTextConfigFile { public class YamlConfigFile extends PlainTextConfigFile implements PropertiesCompatibleConfigFile {
private static final Logger logger = LoggerFactory.getLogger(YamlConfigFile.class);
private volatile Properties cachedProperties;
public YamlConfigFile(String namespace, ConfigRepository configRepository) { public YamlConfigFile(String namespace, ConfigRepository configRepository) {
super(namespace, configRepository); super(namespace, configRepository);
tryTransformToProperties();
} }
@Override @Override
public ConfigFileFormat getConfigFileFormat() { public ConfigFileFormat getConfigFileFormat() {
return ConfigFileFormat.YAML; return ConfigFileFormat.YAML;
} }
@Override
protected void update(Properties newProperties) {
super.update(newProperties);
tryTransformToProperties();
}
@Override
public Properties asProperties() {
if (cachedProperties == null) {
transformToProperties();
}
return cachedProperties;
}
private boolean tryTransformToProperties() {
try {
transformToProperties();
return true;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
logger.warn("yaml to properties failed, reason: {}", ExceptionUtil.getDetailMessage(ex));
}
return false;
}
private synchronized void transformToProperties() {
cachedProperties = toProperties();
}
private Properties toProperties() {
if (!this.hasContent()) {
return new Properties();
}
try {
return ApolloInjector.getInstance(YamlParser.class).yamlToProperties(getContent());
} catch (Throwable ex) {
ApolloConfigException exception = new ApolloConfigException(
"Parse yaml file content failed for namespace: " + m_namespace, ex);
Tracer.logError(exception);
throw exception;
}
}
} }
...@@ -5,7 +5,7 @@ import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; ...@@ -5,7 +5,7 @@ import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class YmlConfigFile extends PlainTextConfigFile { public class YmlConfigFile extends YamlConfigFile {
public YmlConfigFile(String namespace, ConfigRepository configRepository) { public YmlConfigFile(String namespace, ConfigRepository configRepository) {
super(namespace, configRepository); super(namespace, configRepository);
} }
......
package com.ctrip.framework.apollo.spi; package com.ctrip.framework.apollo.spi;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile;
import com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -31,9 +34,11 @@ public class DefaultConfigFactory implements ConfigFactory { ...@@ -31,9 +34,11 @@ public class DefaultConfigFactory implements ConfigFactory {
@Override @Override
public Config create(String namespace) { public Config create(String namespace) {
DefaultConfig defaultConfig = ConfigFileFormat format = determineFileFormat(namespace);
new DefaultConfig(namespace, createLocalConfigRepository(namespace)); if (ConfigFileFormat.isPropertiesCompatible(format)) {
return defaultConfig; return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
}
return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
} }
@Override @Override
...@@ -68,4 +73,35 @@ public class DefaultConfigFactory implements ConfigFactory { ...@@ -68,4 +73,35 @@ public class DefaultConfigFactory implements ConfigFactory {
RemoteConfigRepository createRemoteConfigRepository(String namespace) { RemoteConfigRepository createRemoteConfigRepository(String namespace) {
return new RemoteConfigRepository(namespace); return new RemoteConfigRepository(namespace);
} }
PropertiesCompatibleFileConfigRepository createPropertiesCompatibleFileConfigRepository(String namespace,
ConfigFileFormat format) {
String actualNamespaceName = trimNamespaceFormat(namespace, format);
PropertiesCompatibleConfigFile configFile = (PropertiesCompatibleConfigFile) ConfigService
.getConfigFile(actualNamespaceName, format);
return new PropertiesCompatibleFileConfigRepository(configFile);
}
// for namespaces whose format are not properties, the file extension must be present, e.g. application.yaml
ConfigFileFormat determineFileFormat(String namespaceName) {
String lowerCase = namespaceName.toLowerCase();
for (ConfigFileFormat format : ConfigFileFormat.values()) {
if (lowerCase.endsWith("." + format.getValue())) {
return format;
}
}
return ConfigFileFormat.Properties;
}
String trimNamespaceFormat(String namespaceName, ConfigFileFormat format) {
String extension = "." + format.getValue();
if (!namespaceName.toLowerCase().endsWith(extension)) {
return namespaceName;
}
return namespaceName.substring(0, namespaceName.length() - extension.length());
}
} }
...@@ -137,4 +137,10 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -137,4 +137,10 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
//make it as early as possible //make it as early as possible
return Ordered.HIGHEST_PRECEDENCE; return Ordered.HIGHEST_PRECEDENCE;
} }
// for test only
static void reset() {
NAMESPACE_NAMES.clear();
AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.clear();
}
} }
package com.ctrip.framework.apollo.util.yaml;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.parser.ParserException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
/**
* Transplanted from org.springframework.beans.factory.config.YamlProcessor since apollo can't depend on Spring directly
*
* @since 1.3.0
*/
public class YamlParser {
private static final Logger logger = LoggerFactory.getLogger(YamlParser.class);
/**
* Transform yaml content to properties
*/
public Properties yamlToProperties(String yamlContent) {
Yaml yaml = createYaml();
final Properties result = new Properties();
process(new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
result.putAll(properties);
}
}, yaml, yamlContent);
return result;
}
/**
* Create the {@link Yaml} instance to use.
*/
private Yaml createYaml() {
return new Yaml(new StrictMapAppenderConstructor());
}
private boolean process(MatchCallback callback, Yaml yaml, String content) {
int count = 0;
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + content);
}
for (Object object : yaml.loadAll(content)) {
if (object != null && process(asMap(object), callback)) {
count++;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "") + " from YAML resource: " + content);
}
return (count > 0);
}
@SuppressWarnings("unchecked")
private Map<String, Object> asMap(Object object) {
// YAML can have numbers as keys
Map<String, Object> result = new LinkedHashMap<String, Object>();
if (!(object instanceof Map)) {
// A document can be a text literal
result.put("document", object);
return result;
}
Map<Object, Object> map = (Map<Object, Object>) object;
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
value = asMap(value);
}
Object key = entry.getKey();
if (key instanceof CharSequence) {
result.put(key.toString(), value);
} else {
// It has to be a map key in this case
result.put("[" + key.toString() + "]", value);
}
}
return result;
}
private boolean process(Map<String, Object> map, MatchCallback callback) {
Properties properties = new Properties();
properties.putAll(getFlattenedMap(map));
if (logger.isDebugEnabled()) {
logger.debug("Merging document (no matchers set): " + map);
}
callback.process(properties, map);
return true;
}
private Map<String, Object> getFlattenedMap(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<String, Object>();
buildFlattenedMap(result, source, null);
return result;
}
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
for (Map.Entry<String, Object> entry : source.entrySet()) {
String key = entry.getKey();
if (!StringUtils.isBlank(path)) {
if (key.startsWith("[")) {
key = path + key;
} else {
key = path + '.' + key;
}
}
Object value = entry.getValue();
if (value instanceof String) {
result.put(key, value);
} else if (value instanceof Map) {
// Need a compound key
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
buildFlattenedMap(result, map, key);
} else if (value instanceof Collection) {
// Need a compound key
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) value;
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key);
}
} else {
result.put(key, (value != null ? value.toString() : ""));
}
}
}
private interface MatchCallback {
void process(Properties properties, Map<String, Object> map);
}
private static class StrictMapAppenderConstructor extends Constructor {
// Declared as public for use in subclasses
StrictMapAppenderConstructor() {
super();
}
@Override
protected Map<Object, Object> constructMapping(MappingNode node) {
try {
return super.constructMapping(node);
} catch (IllegalStateException ex) {
throw new ParserException("while parsing MappingNode", node.getStartMark(), ex.getMessage(), node.getEndMark());
}
}
@Override
protected Map<Object, Object> createDefaultMap() {
final Map<Object, Object> delegate = super.createDefaultMap();
return new AbstractMap<Object, Object>() {
@Override
public Object put(Object key, Object value) {
if (delegate.containsKey(key)) {
throw new IllegalStateException("Duplicate key: " + key);
}
return delegate.put(key, value);
}
@Override
public Set<Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
};
}
}
}
package com.ctrip.framework.apollo.internals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PropertiesCompatibleFileConfigRepositoryTest {
@Mock
private PropertiesCompatibleConfigFile configFile;
private String someNamespaceName;
@Mock
private Properties someProperties;
@Before
public void setUp() throws Exception {
someNamespaceName = "someNamespaceName";
when(configFile.getNamespace()).thenReturn(someNamespaceName);
when(configFile.asProperties()).thenReturn(someProperties);
}
@Test
public void testGetConfig() throws Exception {
PropertiesCompatibleFileConfigRepository configFileRepository = new PropertiesCompatibleFileConfigRepository(
configFile);
assertSame(someProperties, configFileRepository.getConfig());
verify(configFile, times(1)).addChangeListener(configFileRepository);
}
@Test
public void testGetConfigFailedAndThenRecovered() throws Exception {
RuntimeException someException = new RuntimeException("some exception");
when(configFile.asProperties()).thenThrow(someException);
PropertiesCompatibleFileConfigRepository configFileRepository = new PropertiesCompatibleFileConfigRepository(
configFile);
Throwable exceptionThrown = null;
try {
configFileRepository.getConfig();
} catch (Throwable ex) {
exceptionThrown = ex;
}
assertSame(someException, exceptionThrown);
// recovered
reset(configFile);
Properties someProperties = mock(Properties.class);
when(configFile.asProperties()).thenReturn(someProperties);
assertSame(someProperties, configFileRepository.getConfig());
}
@Test(expected = IllegalStateException.class)
public void testGetConfigWithConfigFileReturnNullProperties() throws Exception {
when(configFile.asProperties()).thenReturn(null);
PropertiesCompatibleFileConfigRepository configFileRepository = new PropertiesCompatibleFileConfigRepository(
configFile);
configFileRepository.getConfig();
}
@Test
public void testGetSourceType() throws Exception {
ConfigSourceType someType = ConfigSourceType.REMOTE;
when(configFile.getSourceType()).thenReturn(someType);
PropertiesCompatibleFileConfigRepository configFileRepository = new PropertiesCompatibleFileConfigRepository(
configFile);
assertSame(someType, configFileRepository.getSourceType());
}
@Test
public void testOnChange() throws Exception {
Properties anotherProperties = mock(Properties.class);
ConfigFileChangeEvent someChangeEvent = mock(ConfigFileChangeEvent.class);
RepositoryChangeListener someListener = mock(RepositoryChangeListener.class);
PropertiesCompatibleFileConfigRepository configFileRepository = new PropertiesCompatibleFileConfigRepository(
configFile);
configFileRepository.addChangeListener(someListener);
assertSame(someProperties, configFileRepository.getConfig());
when(configFile.asProperties()).thenReturn(anotherProperties);
configFileRepository.onChange(someChangeEvent);
assertSame(anotherProperties, configFileRepository.getConfig());
verify(someListener, times(1)).onRepositoryChange(someNamespaceName, anotherProperties);
}
}
package com.ctrip.framework.apollo.internals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.util.yaml.YamlParser;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class YamlConfigFileTest {
private String someNamespace;
@Mock
private ConfigRepository configRepository;
@Mock
private YamlParser yamlParser;
private ConfigSourceType someSourceType;
@Before
public void setUp() throws Exception {
someNamespace = "someName";
MockInjector.reset();
MockInjector.setInstance(YamlParser.class, yamlParser);
}
@Test
public void testWhenHasContent() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someContent = "someKey: 'someValue'";
someProperties.setProperty(key, someContent);
someSourceType = ConfigSourceType.LOCAL;
Properties yamlProperties = new Properties();
yamlProperties.setProperty("someKey", "someValue");
when(configRepository.getConfig()).thenReturn(someProperties);
when(configRepository.getSourceType()).thenReturn(someSourceType);
when(yamlParser.yamlToProperties(someContent)).thenReturn(yamlProperties);
YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository);
assertSame(someContent, configFile.getContent());
assertSame(yamlProperties, configFile.asProperties());
}
@Test
public void testWhenHasNoContent() throws Exception {
when(configRepository.getConfig()).thenReturn(null);
YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
Properties properties = configFile.asProperties();
assertTrue(properties.isEmpty());
}
@Test
public void testWhenInvalidYamlContent() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someInvalidContent = ",";
someProperties.setProperty(key, someInvalidContent);
someSourceType = ConfigSourceType.LOCAL;
when(configRepository.getConfig()).thenReturn(someProperties);
when(configRepository.getSourceType()).thenReturn(someSourceType);
when(yamlParser.yamlToProperties(someInvalidContent)).thenThrow(new RuntimeException("some exception"));
YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository);
assertSame(someInvalidContent, configFile.getContent());
Throwable exceptionThrown = null;
try {
configFile.asProperties();
} catch (Throwable ex) {
exceptionThrown = ex;
}
assertTrue(exceptionThrown instanceof ApolloConfigException);
assertNotNull(exceptionThrown.getCause());
}
@Test
public void testWhenConfigRepositoryHasError() throws Exception {
when(configRepository.getConfig()).thenThrow(new RuntimeException("someError"));
YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
assertEquals(ConfigSourceType.NONE, configFile.getSourceType());
Properties properties = configFile.asProperties();
assertTrue(properties.isEmpty());
}
@Test
public void testOnRepositoryChange() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someKey: 'someValue'";
String anotherValue = "anotherKey: 'anotherValue'";
someProperties.setProperty(key, someValue);
someSourceType = ConfigSourceType.LOCAL;
Properties someYamlProperties = new Properties();
someYamlProperties.setProperty("someKey", "someValue");
Properties anotherYamlProperties = new Properties();
anotherYamlProperties.setProperty("anotherKey", "anotherValue");
when(configRepository.getConfig()).thenReturn(someProperties);
when(configRepository.getSourceType()).thenReturn(someSourceType);
when(yamlParser.yamlToProperties(someValue)).thenReturn(someYamlProperties);
when(yamlParser.yamlToProperties(anotherValue)).thenReturn(anotherYamlProperties);
YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository);
assertEquals(someValue, configFile.getContent());
assertEquals(someSourceType, configFile.getSourceType());
assertSame(someYamlProperties, configFile.asProperties());
Properties anotherProperties = new Properties();
anotherProperties.setProperty(key, anotherValue);
ConfigSourceType anotherSourceType = ConfigSourceType.REMOTE;
when(configRepository.getSourceType()).thenReturn(anotherSourceType);
configFile.onRepositoryChange(someNamespace, anotherProperties);
assertEquals(anotherValue, configFile.getContent());
assertEquals(anotherSourceType, configFile.getSourceType());
assertSame(anotherYamlProperties, configFile.asProperties());
}
@Test
public void testWhenConfigRepositoryHasErrorAndThenRecovered() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someKey: 'someValue'";
someProperties.setProperty(key, someValue);
someSourceType = ConfigSourceType.LOCAL;
Properties someYamlProperties = new Properties();
someYamlProperties.setProperty("someKey", "someValue");
when(configRepository.getConfig()).thenThrow(new RuntimeException("someError"));
when(configRepository.getSourceType()).thenReturn(someSourceType);
when(yamlParser.yamlToProperties(someValue)).thenReturn(someYamlProperties);
YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
assertEquals(ConfigSourceType.NONE, configFile.getSourceType());
assertTrue(configFile.asProperties().isEmpty());
configFile.onRepositoryChange(someNamespace, someProperties);
assertTrue(configFile.hasContent());
assertEquals(someValue, configFile.getContent());
assertEquals(someSourceType, configFile.getSourceType());
assertSame(someYamlProperties, configFile.asProperties());
}
}
...@@ -10,6 +10,7 @@ import static org.mockito.Mockito.mock; ...@@ -10,6 +10,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository;
import java.util.Properties; import java.util.Properties;
import org.junit.Before; import org.junit.Before;
...@@ -78,6 +79,28 @@ public class DefaultConfigFactoryTest { ...@@ -78,6 +79,28 @@ public class DefaultConfigFactoryTest {
assertNull(ReflectionTestUtils.getField(localFileConfigRepository, "m_upstream")); assertNull(ReflectionTestUtils.getField(localFileConfigRepository, "m_upstream"));
} }
@Test
public void testCreatePropertiesCompatibleFileConfigRepository() throws Exception {
ConfigFileFormat somePropertiesCompatibleFormat = ConfigFileFormat.YML;
String someNamespace = "someName" + "." + somePropertiesCompatibleFormat;
Properties someProperties = new Properties();
String someKey = "someKey";
String someValue = "someValue";
someProperties.setProperty(someKey, someValue);
PropertiesCompatibleFileConfigRepository someRepository = mock(PropertiesCompatibleFileConfigRepository.class);
when(someRepository.getConfig()).thenReturn(someProperties);
doReturn(someRepository).when(defaultConfigFactory)
.createPropertiesCompatibleFileConfigRepository(someNamespace, somePropertiesCompatibleFormat);
Config result = defaultConfigFactory.create(someNamespace);
assertThat("DefaultConfigFactory should create DefaultConfig", result,
is(instanceOf(DefaultConfig.class)));
assertEquals(someValue, result.getProperty(someKey, null));
}
@Test @Test
public void testCreateConfigFile() throws Exception { public void testCreateConfigFile() throws Exception {
String someNamespace = "someName"; String someNamespace = "someName";
...@@ -125,6 +148,47 @@ public class DefaultConfigFactoryTest { ...@@ -125,6 +148,47 @@ public class DefaultConfigFactoryTest {
} }
@Test
public void testDetermineFileFormat() throws Exception {
checkFileFormat("abc", ConfigFileFormat.Properties);
checkFileFormat("abc.properties", ConfigFileFormat.Properties);
checkFileFormat("abc.pRopErties", ConfigFileFormat.Properties);
checkFileFormat("abc.xml", ConfigFileFormat.XML);
checkFileFormat("abc.xmL", ConfigFileFormat.XML);
checkFileFormat("abc.json", ConfigFileFormat.JSON);
checkFileFormat("abc.jsOn", ConfigFileFormat.JSON);
checkFileFormat("abc.yaml", ConfigFileFormat.YAML);
checkFileFormat("abc.yAml", ConfigFileFormat.YAML);
checkFileFormat("abc.yml", ConfigFileFormat.YML);
checkFileFormat("abc.yMl", ConfigFileFormat.YML);
checkFileFormat("abc.properties.yml", ConfigFileFormat.YML);
}
@Test
public void testTrimNamespaceFormat() throws Exception {
checkNamespaceName("abc", ConfigFileFormat.Properties, "abc");
checkNamespaceName("abc.properties", ConfigFileFormat.Properties, "abc");
checkNamespaceName("abcproperties", ConfigFileFormat.Properties, "abcproperties");
checkNamespaceName("abc.pRopErties", ConfigFileFormat.Properties, "abc");
checkNamespaceName("abc.xml", ConfigFileFormat.XML, "abc");
checkNamespaceName("abc.xmL", ConfigFileFormat.XML, "abc");
checkNamespaceName("abc.json", ConfigFileFormat.JSON, "abc");
checkNamespaceName("abc.jsOn", ConfigFileFormat.JSON, "abc");
checkNamespaceName("abc.yaml", ConfigFileFormat.YAML, "abc");
checkNamespaceName("abc.yAml", ConfigFileFormat.YAML, "abc");
checkNamespaceName("abc.yml", ConfigFileFormat.YML, "abc");
checkNamespaceName("abc.yMl", ConfigFileFormat.YML, "abc");
checkNamespaceName("abc.proPerties.yml", ConfigFileFormat.YML, "abc.proPerties");
}
private void checkFileFormat(String namespaceName, ConfigFileFormat expectedFormat) {
assertEquals(expectedFormat, defaultConfigFactory.determineFileFormat(namespaceName));
}
private void checkNamespaceName(String namespaceName, ConfigFileFormat format, String expectedNamespaceName) {
assertEquals(expectedNamespaceName, defaultConfigFactory.trimNamespaceFormat(namespaceName, format));
}
public static class MockConfigUtil extends ConfigUtil { public static class MockConfigUtil extends ConfigUtil {
@Override @Override
public String getAppId() { public String getAppId() {
......
...@@ -7,8 +7,13 @@ import com.ctrip.framework.apollo.core.ConfigConsts; ...@@ -7,8 +7,13 @@ import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.ConfigRepository; import com.ctrip.framework.apollo.internals.ConfigRepository;
import com.ctrip.framework.apollo.internals.DefaultInjector; import com.ctrip.framework.apollo.internals.DefaultInjector;
import com.ctrip.framework.apollo.internals.SimpleConfig; import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import com.ctrip.framework.apollo.internals.YamlConfigFile;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
...@@ -25,7 +30,6 @@ import com.ctrip.framework.apollo.ConfigService; ...@@ -25,7 +30,6 @@ import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.internals.ConfigManager; import com.ctrip.framework.apollo.internals.ConfigManager;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
/** /**
...@@ -33,12 +37,16 @@ import com.google.common.collect.Maps; ...@@ -33,12 +37,16 @@ import com.google.common.collect.Maps;
*/ */
public abstract class AbstractSpringIntegrationTest { public abstract class AbstractSpringIntegrationTest {
private static final Map<String, Config> CONFIG_REGISTRY = Maps.newHashMap(); private static final Map<String, Config> CONFIG_REGISTRY = Maps.newHashMap();
private static final Map<String, ConfigFile> CONFIG_FILE_REGISTRY = Maps.newHashMap();
private static Method CONFIG_SERVICE_RESET; private static Method CONFIG_SERVICE_RESET;
private static Method PROPERTY_SOURCES_PROCESSOR_RESET;
static { static {
try { try {
CONFIG_SERVICE_RESET = ConfigService.class.getDeclaredMethod("reset"); CONFIG_SERVICE_RESET = ConfigService.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(CONFIG_SERVICE_RESET); ReflectionUtils.makeAccessible(CONFIG_SERVICE_RESET);
PROPERTY_SOURCES_PROCESSOR_RESET = PropertySourcesProcessor.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(PROPERTY_SOURCES_PROCESSOR_RESET);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
e.printStackTrace(); e.printStackTrace();
} }
...@@ -66,6 +74,29 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -66,6 +74,29 @@ public abstract class AbstractSpringIntegrationTest {
return config; return config;
} }
protected static Properties readYamlContentAsConfigFileProperties(String caseName) throws IOException {
File file = new File("src/test/resources/spring/yaml/" + caseName);
String yamlContent = Files.toString(file, Charsets.UTF_8);
Properties properties = new Properties();
properties.setProperty(ConfigConsts.CONFIG_FILE_CONTENT_KEY, yamlContent);
return properties;
}
protected static YamlConfigFile prepareYamlConfigFile(String namespaceNameWithFormat, Properties properties) {
ConfigRepository configRepository = mock(ConfigRepository.class);
when(configRepository.getConfig()).thenReturn(properties);
YamlConfigFile configFile = new YamlConfigFile(namespaceNameWithFormat, configRepository);
mockConfigFile(namespaceNameWithFormat, configFile);
return configFile;
}
protected Properties assembleProperties(String key, String value) { protected Properties assembleProperties(String key, String value) {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty(key, value); properties.setProperty(key, value);
...@@ -105,12 +136,20 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -105,12 +136,20 @@ public abstract class AbstractSpringIntegrationTest {
CONFIG_REGISTRY.put(namespace, config); CONFIG_REGISTRY.put(namespace, config);
} }
protected static void mockConfigFile(String namespaceNameWithFormat, ConfigFile configFile) {
CONFIG_FILE_REGISTRY.put(namespaceNameWithFormat, configFile);
}
protected static void doSetUp() { protected static void doSetUp() {
//as ConfigService is singleton, so we must manually clear its container //as ConfigService is singleton, so we must manually clear its container
ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null); ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
//as PropertySourcesProcessor has some static variables, so we must manually clear them
ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_RESET, null);
DefaultInjector defaultInjector = new DefaultInjector();
ConfigManager defaultConfigManager = defaultInjector.getInstance(ConfigManager.class);
MockInjector.reset(); MockInjector.reset();
MockInjector.setInstance(ConfigManager.class, new MockConfigManager()); MockInjector.setInstance(ConfigManager.class, new MockConfigManager(defaultConfigManager));
MockInjector.setDelegate(new DefaultInjector()); MockInjector.setDelegate(defaultInjector);
} }
protected static void doTearDown() { protected static void doTearDown() {
...@@ -119,14 +158,28 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -119,14 +158,28 @@ public abstract class AbstractSpringIntegrationTest {
private static class MockConfigManager implements ConfigManager { private static class MockConfigManager implements ConfigManager {
private final ConfigManager delegate;
public MockConfigManager(ConfigManager delegate) {
this.delegate = delegate;
}
@Override @Override
public Config getConfig(String namespace) { public Config getConfig(String namespace) {
return CONFIG_REGISTRY.get(namespace); Config config = CONFIG_REGISTRY.get(namespace);
if (config != null) {
return config;
}
return delegate.getConfig(namespace);
} }
@Override @Override
public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) { public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null; ConfigFile configFile = CONFIG_FILE_REGISTRY.get(String.format("%s.%s", namespace, configFileFormat.getValue()));
if (configFile != null) {
return configFile;
}
return delegate.getConfigFile(namespace, configFileFormat);
} }
} }
......
...@@ -138,6 +138,45 @@ public class BootstrapConfigTest { ...@@ -138,6 +138,45 @@ public class BootstrapConfigTest {
} }
} }
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndNamespacesAndConditionalOnWithYamlFile extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES,
String.format("%s, %s", "application.yml", FX_APOLLO_NAMESPACE));
prepareYamlConfigFile("application.yml", readYamlContentAsConfigFileProperties("case6.yml"));
Config anotherConfig = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, anotherConfig);
mockConfig(FX_APOLLO_NAMESPACE, anotherConfig);
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNotNull(testBean);
Assert.assertTrue(testBean.execute());
}
}
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithConditionalOnProperty.class) @SpringBootTest(classes = ConfigurationWithConditionalOnProperty.class)
@DirtiesContext @DirtiesContext
...@@ -174,6 +213,39 @@ public class BootstrapConfigTest { ...@@ -174,6 +213,39 @@ public class BootstrapConfigTest {
} }
} }
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndDefaultNamespacesAndConditionalOnFailedWithYamlFile extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, "application.yml");
prepareYamlConfigFile("application.yml", readYamlContentAsConfigFileProperties("case7.yml"));
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNull(testBean);
}
}
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithoutConditionalOnProperty.class) @SpringBootTest(classes = ConfigurationWithoutConditionalOnProperty.class)
@DirtiesContext @DirtiesContext
...@@ -208,6 +280,40 @@ public class BootstrapConfigTest { ...@@ -208,6 +280,40 @@ public class BootstrapConfigTest {
} }
} }
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithoutConditionalOnProperty.class)
@DirtiesContext
public static class TestWithBootstrapEnabledAndDefaultNamespacesAndConditionalOffWithYamlFile extends
AbstractSpringIntegrationTest {
@Autowired(required = false)
private TestBean testBean;
@BeforeClass
public static void beforeClass() throws Exception {
doSetUp();
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "true");
System.setProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, "application.yml");
prepareYamlConfigFile("application.yml", readYamlContentAsConfigFileProperties("case8.yml"));
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
System.clearProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES);
doTearDown();
}
@Test
public void test() throws Exception {
Assert.assertNotNull(testBean);
Assert.assertTrue(testBean.execute());
}
}
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithConditionalOnProperty.class) @SpringBootTest(classes = ConfigurationWithConditionalOnProperty.class)
@DirtiesContext @DirtiesContext
...@@ -272,7 +378,6 @@ public class BootstrapConfigTest { ...@@ -272,7 +378,6 @@ public class BootstrapConfigTest {
} }
} }
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ConfigurationWithoutConditionalOnProperty.class) @SpringBootTest(classes = ConfigurationWithoutConditionalOnProperty.class)
@DirtiesContext @DirtiesContext
...@@ -306,14 +411,13 @@ public class BootstrapConfigTest { ...@@ -306,14 +411,13 @@ public class BootstrapConfigTest {
Boolean containsApollo = !Collections2.filter(processorList, new Predicate<EnvironmentPostProcessor>() { Boolean containsApollo = !Collections2.filter(processorList, new Predicate<EnvironmentPostProcessor>() {
@Override @Override
public boolean apply(EnvironmentPostProcessor input) { public boolean apply(EnvironmentPostProcessor input) {
return input instanceof ApolloApplicationContextInitializer; return input instanceof ApolloApplicationContextInitializer;
} }
}).isEmpty(); }).isEmpty();
Assert.assertTrue(containsApollo); Assert.assertTrue(containsApollo);
} }
} }
@EnableAutoConfiguration @EnableAutoConfiguration
@Configuration @Configuration
static class ConfigurationWithoutConditionalOnProperty { static class ConfigurationWithoutConditionalOnProperty {
......
...@@ -3,12 +3,16 @@ package com.ctrip.framework.apollo.spring; ...@@ -3,12 +3,16 @@ package com.ctrip.framework.apollo.spring;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.YamlConfigFile;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
...@@ -23,6 +27,7 @@ import java.util.Set; ...@@ -23,6 +27,7 @@ import java.util.Set;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anySetOf; import static org.mockito.Matchers.anySetOf;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
...@@ -35,20 +40,28 @@ import static org.mockito.Mockito.verify; ...@@ -35,20 +40,28 @@ import static org.mockito.Mockito.verify;
*/ */
public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
private static final String FX_APOLLO_NAMESPACE = "FX.apollo"; private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
private static final String APPLICATION_YAML_NAMESPACE = "application.yaml";
@Test @Test
public void testApolloConfig() throws Exception { public void testApolloConfig() throws Exception {
Config applicationConfig = mock(Config.class); Config applicationConfig = mock(Config.class);
Config fxApolloConfig = mock(Config.class); Config fxApolloConfig = mock(Config.class);
String someKey = "someKey";
String someValue = "someValue";
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig); mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
prepareYamlConfigFile(APPLICATION_YAML_NAMESPACE, readYamlContentAsConfigFileProperties("case9.yml"));
TestApolloConfigBean1 bean = getBean(TestApolloConfigBean1.class, AppConfig1.class); TestApolloConfigBean1 bean = getBean(TestApolloConfigBean1.class, AppConfig1.class);
assertEquals(applicationConfig, bean.getConfig()); assertEquals(applicationConfig, bean.getConfig());
assertEquals(applicationConfig, bean.getAnotherConfig()); assertEquals(applicationConfig, bean.getAnotherConfig());
assertEquals(fxApolloConfig, bean.getYetAnotherConfig()); assertEquals(fxApolloConfig, bean.getYetAnotherConfig());
Config yamlConfig = bean.getYamlConfig();
assertEquals(someValue, yamlConfig.getProperty(someKey, null));
} }
@Test(expected = BeanCreationException.class) @Test(expected = BeanCreationException.class)
...@@ -239,6 +252,33 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { ...@@ -239,6 +252,33 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
assertEquals(asList(Sets.newHashSet("anotherKey")), fxApolloConfigInterestedKeys.getAllValues()); assertEquals(asList(Sets.newHashSet("anotherKey")), fxApolloConfigInterestedKeys.getAllValues());
} }
@Test
public void testApolloConfigChangeListenerWithYamlFile() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
String anotherValue = "anotherValue";
YamlConfigFile configFile = prepareYamlConfigFile(APPLICATION_YAML_NAMESPACE,
readYamlContentAsConfigFileProperties("case9.yml"));
TestApolloConfigChangeListenerWithYamlFile bean = getBean(TestApolloConfigChangeListenerWithYamlFile.class, AppConfig9.class);
Config yamlConfig = bean.getYamlConfig();
SettableFuture<ConfigChangeEvent> future = bean.getConfigChangeEventFuture();
assertEquals(someValue, yamlConfig.getProperty(someKey, null));
assertFalse(future.isDone());
configFile.onRepositoryChange(APPLICATION_YAML_NAMESPACE, readYamlContentAsConfigFileProperties("case9-new.yml"));
ConfigChangeEvent configChangeEvent = future.get(100, TimeUnit.MILLISECONDS);
ConfigChange change = configChangeEvent.getChange(someKey);
assertEquals(someValue, change.getOldValue());
assertEquals(anotherValue, change.getNewValue());
assertEquals(anotherValue, yamlConfig.getProperty(someKey, null));
}
private <T> T getBean(Class<T> beanClass, Class<?>... annotatedClasses) { private <T> T getBean(Class<T> beanClass, Class<?>... annotatedClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
...@@ -317,6 +357,15 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { ...@@ -317,6 +357,15 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
} }
} }
@Configuration
@EnableApolloConfig(APPLICATION_YAML_NAMESPACE)
static class AppConfig9 {
@Bean
public TestApolloConfigChangeListenerWithYamlFile bean() {
return new TestApolloConfigChangeListenerWithYamlFile();
}
}
static class TestApolloConfigBean1 { static class TestApolloConfigBean1 {
@ApolloConfig @ApolloConfig
private Config config; private Config config;
...@@ -324,6 +373,8 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { ...@@ -324,6 +373,8 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
private Config anotherConfig; private Config anotherConfig;
@ApolloConfig(FX_APOLLO_NAMESPACE) @ApolloConfig(FX_APOLLO_NAMESPACE)
private Config yetAnotherConfig; private Config yetAnotherConfig;
@ApolloConfig(APPLICATION_YAML_NAMESPACE)
private Config yamlConfig;
public Config getConfig() { public Config getConfig() {
return config; return config;
...@@ -336,6 +387,10 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { ...@@ -336,6 +387,10 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
public Config getYetAnotherConfig() { public Config getYetAnotherConfig() {
return yetAnotherConfig; return yetAnotherConfig;
} }
public Config getYamlConfig() {
return yamlConfig;
}
} }
static class TestApolloConfigBean2 { static class TestApolloConfigBean2 {
...@@ -425,4 +480,25 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { ...@@ -425,4 +480,25 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
} }
} }
static class TestApolloConfigChangeListenerWithYamlFile {
private SettableFuture<ConfigChangeEvent> configChangeEventFuture = SettableFuture.create();
@ApolloConfig(APPLICATION_YAML_NAMESPACE)
private Config yamlConfig;
@ApolloConfigChangeListener(APPLICATION_YAML_NAMESPACE)
private void onChange(ConfigChangeEvent event) {
configChangeEventFuture.set(event);
}
public SettableFuture<ConfigChangeEvent> getConfigChangeEventFuture() {
return configChangeEventFuture;
}
public Config getYamlConfig() {
return yamlConfig;
}
}
} }
...@@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue; ...@@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.SimpleConfig; import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.internals.YamlConfigFile;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest.JsonBean; import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest.JsonBean;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean; import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue; import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
...@@ -71,6 +72,31 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -71,6 +72,31 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(newBatch, bean.getBatch()); assertEquals(newBatch, bean.getBatch());
} }
@Test
public void testAutoUpdateWithOneYamlFile() throws Exception {
int initialTimeout = 1000;
int initialBatch = 2000;
int newTimeout = 1001;
int newBatch = 2001;
YamlConfigFile configFile = prepareYamlConfigFile("application.yaml",
readYamlContentAsConfigFileProperties("case1.yaml"));
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig12.class);
TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class);
assertEquals(initialTimeout, bean.getTimeout());
assertEquals(initialBatch, bean.getBatch());
configFile.onRepositoryChange("application.yaml", readYamlContentAsConfigFileProperties("case1-new.yaml"));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(newTimeout, bean.getTimeout());
assertEquals(newBatch, bean.getBatch());
}
@Test @Test
public void testAutoUpdateWithValueAndXmlProperty() throws Exception { public void testAutoUpdateWithValueAndXmlProperty() throws Exception {
int initialTimeout = 1000; int initialTimeout = 1000;
...@@ -106,6 +132,36 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -106,6 +132,36 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(newBatch, xmlBean.getBatch()); assertEquals(newBatch, xmlBean.getBatch());
} }
@Test
public void testAutoUpdateWithYamlFileWithValueAndXmlProperty() throws Exception {
int initialTimeout = 1000;
int initialBatch = 2000;
int newTimeout = 1001;
int newBatch = 2001;
YamlConfigFile configFile = prepareYamlConfigFile("application.yaml",
readYamlContentAsConfigFileProperties("case1.yaml"));
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig13.class);
TestJavaConfigBean javaConfigBean = context.getBean(TestJavaConfigBean.class);
TestXmlBean xmlBean = context.getBean(TestXmlBean.class);
assertEquals(initialTimeout, javaConfigBean.getTimeout());
assertEquals(initialBatch, javaConfigBean.getBatch());
assertEquals(initialTimeout, xmlBean.getTimeout());
assertEquals(initialBatch, xmlBean.getBatch());
configFile.onRepositoryChange("application.yaml", readYamlContentAsConfigFileProperties("case1-new.yaml"));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(newTimeout, javaConfigBean.getTimeout());
assertEquals(newBatch, javaConfigBean.getBatch());
assertEquals(newTimeout, xmlBean.getTimeout());
assertEquals(newBatch, xmlBean.getBatch());
}
@Test @Test
public void testAutoUpdateDisabled() throws Exception { public void testAutoUpdateDisabled() throws Exception {
int initialTimeout = 1000; int initialTimeout = 1000;
...@@ -213,6 +269,35 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -213,6 +269,35 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someBatch, bean.getBatch()); assertEquals(someBatch, bean.getBatch());
} }
@Test
public void testAutoUpdateWithMultipleNamespacesWithSamePropertiesWithYamlFile() throws Exception {
int someTimeout = 1000;
int someBatch = 2000;
int anotherBatch = 3000;
int someNewBatch = 2001;
YamlConfigFile configFile = prepareYamlConfigFile("application.yml",
readYamlContentAsConfigFileProperties("case2.yml"));
Properties fxApolloProperties =
assembleProperties(TIMEOUT_PROPERTY, String.valueOf(someTimeout), BATCH_PROPERTY, String.valueOf(anotherBatch));
prepareConfig(FX_APOLLO_NAMESPACE, fxApolloProperties);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig14.class);
TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class);
assertEquals(someTimeout, bean.getTimeout());
assertEquals(someBatch, bean.getBatch());
configFile.onRepositoryChange("application.yml", readYamlContentAsConfigFileProperties("case2-new.yml"));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(someTimeout, bean.getTimeout());
assertEquals(someNewBatch, bean.getBatch());
}
@Test @Test
public void testAutoUpdateWithNewProperties() throws Exception { public void testAutoUpdateWithNewProperties() throws Exception {
int initialTimeout = 1000; int initialTimeout = 1000;
...@@ -241,6 +326,30 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -241,6 +326,30 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(newBatch, bean.getBatch()); assertEquals(newBatch, bean.getBatch());
} }
@Test
public void testAutoUpdateWithNewPropertiesWithYamlFile() throws Exception {
int initialTimeout = 1000;
int newTimeout = 1001;
int newBatch = 2001;
YamlConfigFile configFile = prepareYamlConfigFile("application.yaml",
readYamlContentAsConfigFileProperties("case3.yaml"));
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig12.class);
TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class);
assertEquals(initialTimeout, bean.getTimeout());
assertEquals(DEFAULT_BATCH, bean.getBatch());
configFile.onRepositoryChange("application.yaml", readYamlContentAsConfigFileProperties("case3-new.yaml"));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(newTimeout, bean.getTimeout());
assertEquals(newBatch, bean.getBatch());
}
@Test @Test
public void testAutoUpdateWithIrrelevantProperties() throws Exception { public void testAutoUpdateWithIrrelevantProperties() throws Exception {
int initialTimeout = 1000; int initialTimeout = 1000;
...@@ -301,6 +410,29 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -301,6 +410,29 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(DEFAULT_BATCH, bean.getBatch()); assertEquals(DEFAULT_BATCH, bean.getBatch());
} }
@Test
public void testAutoUpdateWithDeletedPropertiesWithYamlFile() throws Exception {
int initialTimeout = 1000;
int initialBatch = 2000;
YamlConfigFile configFile = prepareYamlConfigFile("application.yaml",
readYamlContentAsConfigFileProperties("case4.yaml"));
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig12.class);
TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class);
assertEquals(initialTimeout, bean.getTimeout());
assertEquals(initialBatch, bean.getBatch());
configFile.onRepositoryChange("application.yaml", readYamlContentAsConfigFileProperties("case4-new.yaml"));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(DEFAULT_TIMEOUT, bean.getTimeout());
assertEquals(DEFAULT_BATCH, bean.getBatch());
}
@Test @Test
public void testAutoUpdateWithMultipleNamespacesWithSamePropertiesDeleted() throws Exception { public void testAutoUpdateWithMultipleNamespacesWithSamePropertiesDeleted() throws Exception {
int someTimeout = 1000; int someTimeout = 1000;
...@@ -389,6 +521,30 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -389,6 +521,30 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(initialBatch, bean.getBatch()); assertEquals(initialBatch, bean.getBatch());
} }
@Test
public void testAutoUpdateWithTypeMismatchWithYamlFile() throws Exception {
int initialTimeout = 1000;
int initialBatch = 2000;
int newTimeout = 1001;
YamlConfigFile configFile = prepareYamlConfigFile("application.yaml",
readYamlContentAsConfigFileProperties("case5.yaml"));
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig12.class);
TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class);
assertEquals(initialTimeout, bean.getTimeout());
assertEquals(initialBatch, bean.getBatch());
configFile.onRepositoryChange("application.yaml", readYamlContentAsConfigFileProperties("case5-new.yaml"));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(newTimeout, bean.getTimeout());
assertEquals(initialBatch, bean.getBatch());
}
@Test @Test
public void testAutoUpdateWithValueInjectedAsParameter() throws Exception { public void testAutoUpdateWithValueInjectedAsParameter() throws Exception {
int initialTimeout = 1000; int initialTimeout = 1000;
...@@ -949,6 +1105,34 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -949,6 +1105,34 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
} }
} }
@Configuration
@EnableApolloConfig("application.yaMl")
static class AppConfig12 {
@Bean
TestJavaConfigBean testJavaConfigBean() {
return new TestJavaConfigBean();
}
}
@Configuration
@EnableApolloConfig("application.yaml")
@ImportResource("spring/XmlConfigPlaceholderTest11.xml")
static class AppConfig13 {
@Bean
TestJavaConfigBean testJavaConfigBean() {
return new TestJavaConfigBean();
}
}
@Configuration
@EnableApolloConfig({"application.yml", "FX.apollo"})
static class AppConfig14 {
@Bean
TestJavaConfigBean testJavaConfigBean() {
return new TestJavaConfigBean();
}
}
static class TestJavaConfigBean { static class TestJavaConfigBean {
@Value("${timeout:100}") @Value("${timeout:100}")
......
...@@ -7,10 +7,12 @@ import static org.mockito.Mockito.mock; ...@@ -7,10 +7,12 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue; import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import java.util.List; import java.util.List;
import java.util.Properties;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -70,6 +72,38 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -70,6 +72,38 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
check(someTimeout, someBatch, AppConfig2.class); 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 @Test
public void testMultiplePropertySources() throws Exception { public void testMultiplePropertySources() throws Exception {
int someTimeout = 1000; int someTimeout = 1000;
...@@ -87,24 +121,27 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -87,24 +121,27 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
@Test @Test
public void testMultiplePropertySourcesWithSameProperties() throws Exception { public void testMultiplePropertiesCompatiblePropertySourcesWithSameProperties() throws Exception {
int someTimeout = 1000; int someTimeout = 1000;
int anotherTimeout = someTimeout + 1; int anotherTimeout = someTimeout + 1;
int someBatch = 2000; int someBatch = 2000;
Config application = mock(Config.class); Properties properties = mock(Properties.class);
when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); when(properties.getProperty(TIMEOUT_PROPERTY)).thenReturn(String.valueOf(someTimeout));
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application); 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); Config fxApollo = mock(Config.class);
when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout)); when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
mockConfig(FX_APOLLO_NAMESPACE, fxApollo); mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
check(someTimeout, someBatch, AppConfig3.class); check(someTimeout, someBatch, AppConfig11.class);
} }
@Test @Test
public void testMultiplePropertySourcesCoverWithSameProperties() throws Exception { public void testMultiplePropertySourcesCoverWithSameProperties() throws Exception {
//Multimap does not maintain the strict input order of namespace. //Multimap does not maintain the strict input order of namespace.
...@@ -124,6 +161,25 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -124,6 +161,25 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
check(someTimeout, someBatch, AppConfig6.class); 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 @Test
public void testMultiplePropertySourcesWithSamePropertiesWithWeight() throws Exception { public void testMultiplePropertySourcesWithSamePropertiesWithWeight() throws Exception {
int someTimeout = 1000; int someTimeout = 1000;
...@@ -424,6 +480,33 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -424,6 +480,33 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
} }
@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 @Component
static class TestJavaConfigBean { static class TestJavaConfigBean {
@Value("${timeout:100}") @Value("${timeout:100}")
......
package com.ctrip.framework.apollo.util.yaml;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.ByteArrayResource;
import org.yaml.snakeyaml.parser.ParserException;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
public class YamlParserTest {
private YamlParser parser = new YamlParser();
@Test
public void testValidCases() throws Exception {
test("case1.yaml");
test("case3.yaml");
test("case4.yaml");
test("case5.yaml");
test("case6.yaml");
test("case7.yaml");
}
@Test(expected = ParserException.class)
public void testcase2() throws Exception {
testInvalid("case2.yaml");
}
@Test(expected = ParserException.class)
public void testcase8() throws Exception {
testInvalid("case8.yaml");
}
private void test(String caseName) throws Exception {
File file = new File("src/test/resources/yaml/" + caseName);
String yamlContent = Files.toString(file, Charsets.UTF_8);
check(yamlContent);
}
private void testInvalid(String caseName) throws Exception {
File file = new File("src/test/resources/yaml/" + caseName);
String yamlContent = Files.toString(file, Charsets.UTF_8);
parser.yamlToProperties(yamlContent);
}
private void check(String yamlContent) {
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ByteArrayResource(yamlContent.getBytes()));
Properties expected = yamlPropertiesFactoryBean.getObject();
Properties actual = parser.yamlToProperties(yamlContent);
assertTrue("expected: " + expected + " actual: " + actual, checkPropertiesEquals(expected, actual));
}
private boolean checkPropertiesEquals(Properties expected, Properties actual) {
if (expected == actual)
return true;
if (expected.size() != actual.size())
return false;
for (Object key : expected.keySet()) {
if (!expected.getProperty((String) key).equals(actual.getProperty((String) key))) {
return false;
}
}
return true;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout:100}"/>
<property name="batch" value="${batch:200}"/>
</bean>
</beans>
root:
key1: "someValue"
key2: 100
key3:
key4:
key5: '(%sender%) %message%'
key6: '* %sender% %message%'
# commented: "xxx"
list:
- 'item 1'
- 'item 2'
intList:
- 100
- 200
listOfMap:
- key: '#mychannel'
value: ''
- key: '#myprivatechannel'
value: 'mypassword'
listOfList:
- - 'a1'
- 'a2'
- - 'b1'
- 'b2'
listOfList2: [ ['a1', 'a2'], ['b1', 'b2'] ]
root:
key1: "someValue"
key2: 100
key1: "anotherValue"
--- # document start
# Comments in YAML look like this.
################
# SCALAR TYPES #
################
# Our root object (which continues for the entire document) will be a map,
# which is equivalent to a dictionary, hash or object in other languages.
key: value
another_key: Another value goes here.
a_number_value: 100
scientific_notation: 1e+12
# The number 1 will be interpreted as a number, not a boolean. if you want
# it to be interpreted as a boolean, use true
boolean: true
null_value: null
key with spaces: value
# Notice that strings don't need to be quoted. However, they can be.
however: 'A string, enclosed in quotes.'
'Keys can be quoted too.': "Useful if you want to put a ':' in your key."
single quotes: 'have ''one'' escape pattern'
double quotes: "have many: \", \0, \t, \u263A, \x0d\x0a == \r\n, and more."
# Multiple-line strings can be written either as a 'literal block' (using |),
# or a 'folded block' (using '>').
literal_block: |
This entire block of text will be the value of the 'literal_block' key,
with line breaks being preserved.
The literal continues until de-dented, and the leading indentation is
stripped.
Any lines that are 'more-indented' keep the rest of their indentation -
these lines will be indented by 4 spaces.
folded_style: >
This entire block of text will be the value of 'folded_style', but this
time, all newlines will be replaced with a single space.
Blank lines, like above, are converted to a newline character.
'More-indented' lines keep their newlines, too -
this text will appear over two lines.
####################
# COLLECTION TYPES #
####################
# Nesting uses indentation. 2 space indent is preferred (but not required).
a_nested_map:
key: value
another_key: Another Value
another_nested_map:
hello: hello
# Maps don't have to have string keys.
0.25: a float key
# Keys can also be complex, like multi-line objects
# We use ? followed by a space to indicate the start of a complex key.
? |
This is a key
that has multiple lines
: and this is its value
# YAML also allows mapping between sequences with the complex key syntax
# Some language parsers might complain
# An example
? - Manchester United
- Real Madrid
: [2001-01-01, 2002-02-02]
# Sequences (equivalent to lists or arrays) look like this
# (note that the '-' counts as indentation):
a_sequence:
- Item 1
- Item 2
- 0.5 # sequences can contain disparate types.
- Item 4
- key: value
another_key: another_value
-
- This is a sequence
- inside another sequence
- - - Nested sequence indicators
- can be collapsed
# Since YAML is a superset of JSON, you can also write JSON-style maps and
# sequences:
json_map: {"key": "value"}
json_seq: [3, 2, 1, "takeoff"]
and quotes are optional: {key: [3, 2, 1, takeoff]}
#######################
# EXTRA YAML FEATURES #
#######################
# YAML also has a handy feature called 'anchors', which let you easily duplicate
# content across your document. Both of these keys will have the same value:
anchored_content: &anchor_name This string will appear as the value of two keys.
other_anchor: *anchor_name
# Anchors can be used to duplicate/inherit properties
base: &base
name: Everyone has same name
# The regexp << is called Merge Key Language-Independent Type. It is used to
# indicate that all the keys of one or more specified maps should be inserted
# into the current map.
foo: &foo
<<: *base
age: 10
bar: &bar
<<: *base
age: 20
# foo and bar would also have name: Everyone has same name
# YAML also has tags, which you can use to explicitly declare types.
explicit_string: !!str 0.5
####################
# EXTRA YAML TYPES #
####################
# Strings and numbers aren't the only scalars that YAML can understand.
# ISO-formatted date and datetime literals are also parsed.
datetime: 2001-12-15T02:59:43.1Z
datetime_with_spaces: 2001-12-14 21:59:43.10 -5
date: 2002-12-14
# YAML also has a set type, which looks like this:
set:
? item1
? item2
? item3
or: {item1, item2, item3}
# Sets are just maps with null values; the above is equivalent to:
set2:
item1: null
item2: null
item3: null
... # document end
---
- Ada
- APL
- ASP
- Assembly
- Awk
---
- Basic
---
- C
- C# # Note that comments are denoted with ' #' (space and #).
- C++
- Cold Fusion
-
- HTML
- LaTeX
- SGML
- VRML
- XML
- YAML
-
- BSD
- GNU Hurd
- Linux
- 1.1
- - 2.1
- 2.2
- - - 3.1
- 3.2
- 3.3
- name: PyYAML
status: 4
license: MIT
language: Python
- name: PySyck
status: 5
license: BSD
language: Python
left hand:
- Ring of Teleportation
- Ring of Speed
right hand:
- Ring of Resist Fire
- Ring of Resist Cold
- Ring of Resist Poison
base armor class: 0
base damage: [4,4]
plus to-hit: 12
plus to-dam: 16
plus to-ac: 0
hero:
hp: 34
sp: 8
level: 4
orc:
hp: 12
sp: 0
level: 2
plain: Scroll of Remove Curse
single-quoted: 'EASY_KNOW'
double-quoted: "?"
literal: | # Borrowed from http://www.kersbergen.com/flump/religion.html
by hjw ___
__ /.-.\
/ )_____________\\ Y
/_ /=== == === === =\ _\_
( /)=== == === === == Y \
`-------------------( o )
\___/
folded: >
It removes all ordinary curses from all equipped items.
Heavy or permanent curses are unaffected.
...@@ -45,4 +45,8 @@ public enum ConfigFileFormat { ...@@ -45,4 +45,8 @@ public enum ConfigFileFormat {
return false; return false;
} }
} }
public static boolean isPropertiesCompatible(ConfigFileFormat format) {
return format == YAML || format == YML;
}
} }
package com.ctrip.framework.apollo.demo.api; package com.ctrip.framework.apollo.demo.api;
import com.ctrip.framework.apollo.internals.YamlConfigFile;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
...@@ -27,9 +28,11 @@ public class ApolloConfigDemo { ...@@ -27,9 +28,11 @@ public class ApolloConfigDemo {
private static final Logger logger = LoggerFactory.getLogger(ApolloConfigDemo.class); private static final Logger logger = LoggerFactory.getLogger(ApolloConfigDemo.class);
private String DEFAULT_VALUE = "undefined"; private String DEFAULT_VALUE = "undefined";
private Config config; private Config config;
private Config yamlConfig;
private Config publicConfig; private Config publicConfig;
private ConfigFile applicationConfigFile; private ConfigFile applicationConfigFile;
private ConfigFile xmlConfigFile; private ConfigFile xmlConfigFile;
private YamlConfigFile yamlConfigFile;
public ApolloConfigDemo() { public ApolloConfigDemo() {
ConfigChangeListener changeListener = new ConfigChangeListener() { ConfigChangeListener changeListener = new ConfigChangeListener() {
...@@ -46,9 +49,12 @@ public class ApolloConfigDemo { ...@@ -46,9 +49,12 @@ public class ApolloConfigDemo {
}; };
config = ConfigService.getAppConfig(); config = ConfigService.getAppConfig();
config.addChangeListener(changeListener); config.addChangeListener(changeListener);
yamlConfig = ConfigService.getConfig("application.yaml");
yamlConfig.addChangeListener(changeListener);
publicConfig = ConfigService.getConfig("TEST1.apollo"); publicConfig = ConfigService.getConfig("TEST1.apollo");
publicConfig.addChangeListener(changeListener); publicConfig.addChangeListener(changeListener);
applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties); applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties);
// datasources.xml
xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML); xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML);
xmlConfigFile.addChangeListener(new ConfigFileChangeListener() { xmlConfigFile.addChangeListener(new ConfigFileChangeListener() {
@Override @Override
...@@ -56,6 +62,8 @@ public class ApolloConfigDemo { ...@@ -56,6 +62,8 @@ public class ApolloConfigDemo {
logger.info(changeEvent.toString()); logger.info(changeEvent.toString());
} }
}); });
// application.yaml
yamlConfigFile = (YamlConfigFile) ConfigService.getConfigFile("application", ConfigFileFormat.YAML);
} }
private String getConfig(String key) { private String getConfig(String key) {
...@@ -63,6 +71,9 @@ public class ApolloConfigDemo { ...@@ -63,6 +71,9 @@ public class ApolloConfigDemo {
if (DEFAULT_VALUE.equals(result)) { if (DEFAULT_VALUE.equals(result)) {
result = publicConfig.getProperty(key, DEFAULT_VALUE); result = publicConfig.getProperty(key, DEFAULT_VALUE);
} }
if (DEFAULT_VALUE.equals(result)) {
result = yamlConfig.getProperty(key, DEFAULT_VALUE);
}
logger.info(String.format("Loading key : %s with value: %s", key, result)); logger.info(String.format("Loading key : %s with value: %s", key, result));
return result; return result;
} }
...@@ -75,6 +86,9 @@ public class ApolloConfigDemo { ...@@ -75,6 +86,9 @@ public class ApolloConfigDemo {
case "xml": case "xml":
print(xmlConfigFile); print(xmlConfigFile);
return; return;
case "yaml":
printYaml(yamlConfigFile);
return;
} }
} }
...@@ -87,6 +101,11 @@ public class ApolloConfigDemo { ...@@ -87,6 +101,11 @@ public class ApolloConfigDemo {
System.out.println(configFile.getContent()); System.out.println(configFile.getContent());
} }
private void printYaml(YamlConfigFile configFile) {
System.out.println("=== Properties for " + configFile.getNamespace() + " is as follows: ");
System.out.println(configFile.asProperties());
}
private void printEnvInfo() { private void printEnvInfo() {
String message = String.format("AppId: %s, Env: %s, DC: %s, IP: %s", Foundation.app() String message = String.format("AppId: %s, Env: %s, DC: %s, IP: %s", Foundation.app()
.getAppId(), Foundation.server().getEnvType(), Foundation.server().getDataCenter(), .getAppId(), Foundation.server().getEnvType(), Foundation.server().getDataCenter(),
...@@ -106,18 +125,26 @@ public class ApolloConfigDemo { ...@@ -106,18 +125,26 @@ public class ApolloConfigDemo {
continue; continue;
} }
input = input.trim(); input = input.trim();
if (input.equalsIgnoreCase("application")) { try {
apolloConfigDemo.print("application"); if (input.equalsIgnoreCase("application")) {
continue; apolloConfigDemo.print("application");
} continue;
if (input.equalsIgnoreCase("xml")) { }
apolloConfigDemo.print("xml"); if (input.equalsIgnoreCase("xml")) {
continue; apolloConfigDemo.print("xml");
} continue;
if (input.equalsIgnoreCase("quit")) { }
System.exit(0); if (input.equalsIgnoreCase("yaml") || input.equalsIgnoreCase("yml")) {
apolloConfigDemo.print("yaml");
continue;
}
if (input.equalsIgnoreCase("quit")) {
System.exit(0);
}
apolloConfigDemo.getConfig(input);
} catch (Throwable ex) {
logger.error("some error occurred", ex);
} }
apolloConfigDemo.getConfig(input);
} }
} }
} }
...@@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration; ...@@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
@Configuration @Configuration
@EnableApolloConfig(value = "TEST1.apollo", order = 11) @EnableApolloConfig(value = {"TEST1.apollo", "application.yaml"}, order = 11)
public class AnotherAppConfig { public class AnotherAppConfig {
} }
...@@ -15,6 +15,8 @@ import javax.annotation.PostConstruct; ...@@ -15,6 +15,8 @@ import javax.annotation.PostConstruct;
/** /**
* You may set up data like the following in Apollo: * You may set up data like the following in Apollo:
* <br /><br />
* Properties Sample: application.properties
* <pre> * <pre>
* redis.cache.enabled = true * redis.cache.enabled = true
* redis.cache.expireSeconds = 100 * redis.cache.expireSeconds = 100
...@@ -26,6 +28,22 @@ import javax.annotation.PostConstruct; ...@@ -26,6 +28,22 @@ import javax.annotation.PostConstruct;
* redis.cache.someList[1] = d * redis.cache.someList[1] = d
* </pre> * </pre>
* *
* Yaml Sample: application.yaml
* <pre>
* redis:
* cache:
* enabled: true
* expireSeconds: 100
* clusterNodes: 1,2
* commandTimeout: 50
* someMap:
* key1: a
* key2: b
* someList:
* - c
* - d
* </pre>
*
* To make <code>@ConditionalOnProperty</code> work properly, <code>apollo.bootstrap.enabled</code> should be set to true * To make <code>@ConditionalOnProperty</code> work properly, <code>apollo.bootstrap.enabled</code> should be set to true
* and <code>redis.cache.enabled</code> should also be set to true. Check 'src/main/resources/application.yml' for more information. * and <code>redis.cache.enabled</code> should also be set to true. Check 'src/main/resources/application.yml' for more information.
* *
......
package com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh; package com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig; import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig;
import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
...@@ -27,7 +28,7 @@ public class SpringBootApolloRefreshConfig { ...@@ -27,7 +28,7 @@ public class SpringBootApolloRefreshConfig {
this.refreshScope = refreshScope; this.refreshScope = refreshScope;
} }
@ApolloConfigChangeListener @ApolloConfigChangeListener({ConfigConsts.NAMESPACE_APPLICATION, "TEST1.apollo", "application.yaml"})
public void onChange(ConfigChangeEvent changeEvent) { public void onChange(ConfigChangeEvent changeEvent) {
boolean redisCacheKeysChanged = false; boolean redisCacheKeysChanged = false;
for (String changedKey : changeEvent.changedKeys()) { for (String changedKey : changeEvent.changedKeys()) {
......
...@@ -2,4 +2,4 @@ apollo: ...@@ -2,4 +2,4 @@ apollo:
bootstrap: bootstrap:
enabled: true enabled: true
# will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase # will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase
namespaces: application,TEST1.apollo namespaces: application,TEST1.apollo,application.yaml
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config order="10"/> <apollo:config order="10"/>
<apollo:config namespaces="TEST1.apollo" order="11"/> <apollo:config namespaces="TEST1.apollo,application.yaml" order="11"/>
<bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean"> <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
<property name="timeout" value="${timeout:200}"/> <property name="timeout" value="${timeout:200}"/>
......
...@@ -57,7 +57,8 @@ ...@@ -57,7 +57,8 @@
<li> <li>
通过创建一个私有的Namespace可以实现分组管理配置 通过创建一个私有的Namespace可以实现分组管理配置
</li> </li>
<li>私有Namespace的格式可以是xml、yml、yaml、json. 您可以通过Apollo-client中ConfigFile接口来获取非properties格式Namespace的内容</li> <li>私有Namespace的格式可以是xml、yml、yaml、json. 您可以通过apollo-client中ConfigFile接口来获取非properties格式Namespace的内容</li>
<li>1.3.0及以上版本的apollo-client针对yaml/yml提供了更好的支持,可以通过ConfigService.getConfig("someNamespace.yaml")直接获取Config对象,也可以通过@EnableApolloConfig("someNamespace.yaml")注入yaml配置到Spring中去</li>
</ul> </ul>
</div> </div>
<div class="row text-right" style="padding-right: 20px;"> <div class="row text-right" style="padding-right: 20px;">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment