Commit b9fca2d9 by 张乐 Committed by GitHub

Merge pull request #606 from nobodyiam/configfile-change-listener

add config file change listener
parents bd1832bf 942fffdf
......@@ -29,4 +29,11 @@ public interface ConfigFile {
* @return the config file format enum
*/
ConfigFileFormat getConfigFileFormat();
/**
* Add change listener to this config file instance.
*
* @param listener the config file change listener
*/
void addChangeListener(ConfigFileChangeListener listener);
}
package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ConfigFileChangeListener {
/**
* Invoked when there is any config change for the namespace.
* @param changeEvent the event for this change
*/
void onChange(ConfigFileChangeEvent changeEvent);
}
package com.ctrip.framework.apollo.internals;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigFileChangeListener;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.enums.PropertyChangeType;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.google.common.collect.Lists;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractConfigFile implements ConfigFile, RepositoryChangeListener {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigFile.class);
private static ExecutorService m_executorService;
protected ConfigRepository m_configRepository;
protected String m_namespace;
protected AtomicReference<Properties> m_configProperties;
private List<ConfigFileChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
static {
m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory
.create("ConfigFile", true));
}
public AbstractConfigFile(String namespace, ConfigRepository configRepository) {
m_configRepository = configRepository;
......@@ -45,6 +62,8 @@ public abstract class AbstractConfigFile implements ConfigFile, RepositoryChange
return m_namespace;
}
protected abstract void update(Properties newProperties);
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_configProperties.get())) {
......@@ -53,9 +72,51 @@ public abstract class AbstractConfigFile implements ConfigFile, RepositoryChange
Properties newConfigProperties = new Properties();
newConfigProperties.putAll(newProperties);
m_configProperties.set(newConfigProperties);
String oldValue = getContent();
update(newProperties);
String newValue = getContent();
PropertyChangeType changeType = PropertyChangeType.MODIFIED;
if (oldValue == null) {
changeType = PropertyChangeType.ADDED;
} else if (newValue == null) {
changeType = PropertyChangeType.DELETED;
}
this.fireConfigChange(new ConfigFileChangeEvent(m_namespace, oldValue, newValue, changeType));
Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}
@Override
public void addChangeListener(ConfigFileChangeListener listener) {
if (!m_listeners.contains(listener)) {
m_listeners.add(listener);
}
}
private void fireConfigChange(final ConfigFileChangeEvent changeEvent) {
for (final ConfigFileChangeListener listener : m_listeners) {
m_executorService.submit(new Runnable() {
@Override
public void run() {
String listenerName = listener.getClass().getName();
Transaction transaction = Tracer.newTransaction("Apollo.ConfigFileChangeListener", listenerName);
try {
listener.onChange(changeEvent);
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
Tracer.logError(ex);
logger.error("Failed to invoke config file change listener {}", listenerName, ex);
} finally {
transaction.complete();
}
}
});
}
}
}
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.core.ConfigConsts;
import java.util.Properties;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class PlainTextConfigFile extends AbstractConfigFile {
public PlainTextConfigFile(String namespace, ConfigRepository configRepository) {
super(namespace, configRepository);
}
......@@ -25,4 +27,9 @@ public abstract class PlainTextConfigFile extends AbstractConfigFile {
}
return m_configProperties.get().containsKey(ConfigConsts.CONFIG_FILE_CONTENT_KEY);
}
@Override
protected void update(Properties newProperties) {
m_configProperties.set(newProperties);
}
}
......@@ -26,6 +26,12 @@ public class PropertiesConfigFile extends AbstractConfigFile {
}
@Override
protected void update(Properties newProperties) {
m_configProperties.set(newProperties);
m_contentCache.set(null);
}
@Override
public String getContent() {
if (m_contentCache.get() == null) {
m_contentCache.set(doGetContent());
......@@ -60,9 +66,4 @@ public class PropertiesConfigFile extends AbstractConfigFile {
return ConfigFileFormat.Properties;
}
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
super.onRepositoryChange(namespace, newProperties);
m_contentCache.set(null);
}
}
package com.ctrip.framework.apollo.model;
import com.ctrip.framework.apollo.enums.PropertyChangeType;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigFileChangeEvent {
private final String namespace;
private final String oldValue;
private final String newValue;
private final PropertyChangeType changeType;
/**
* Constructor.
*
* @param namespace the namespace of the config file change event
* @param oldValue the value before change
* @param newValue the value after change
* @param changeType the change type
*/
public ConfigFileChangeEvent(String namespace, String oldValue, String newValue,
PropertyChangeType changeType) {
this.namespace = namespace;
this.oldValue = oldValue;
this.newValue = newValue;
this.changeType = changeType;
}
public String getNamespace() {
return namespace;
}
public String getOldValue() {
return oldValue;
}
public String getNewValue() {
return newValue;
}
public PropertyChangeType getChangeType() {
return changeType;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConfigFileChangeEvent{");
sb.append("namespace='").append(namespace).append('\'');
sb.append(", oldValue='").append(oldValue).append('\'');
sb.append(", newValue='").append(newValue).append('\'');
sb.append(", changeType=").append(changeType);
sb.append('}');
return sb.toString();
}
}
......@@ -132,6 +132,11 @@ public class ConfigServiceTest {
public ConfigFileFormat getConfigFileFormat() {
return m_configFileFormat;
}
@Override
public void addChangeListener(ConfigFileChangeListener listener) {
}
}
public static class MockConfigFactory implements ConfigFactory {
......
......@@ -5,6 +5,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import java.util.Properties;
import java.util.Set;
import org.junit.Before;
......@@ -111,6 +112,11 @@ public class DefaultConfigManagerTest {
return new AbstractConfigFile(namespace, someConfigRepository) {
@Override
protected void update(Properties newProperties) {
}
@Override
public String getContent() {
return someConfigContent;
}
......
......@@ -6,8 +6,14 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigFileChangeListener;
import com.ctrip.framework.apollo.enums.PropertyChangeType;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -84,10 +90,27 @@ public class PropertiesConfigFileTest {
Properties anotherProperties = new Properties();
anotherProperties.setProperty(someKey, anotherValue);
final SettableFuture<ConfigFileChangeEvent> configFileChangeFuture = SettableFuture.create();
ConfigFileChangeListener someListener = new ConfigFileChangeListener() {
@Override
public void onChange(ConfigFileChangeEvent changeEvent) {
configFileChangeFuture.set(changeEvent);
}
};
configFile.addChangeListener(someListener);
configFile.onRepositoryChange(someNamespace, anotherProperties);
ConfigFileChangeEvent changeEvent = configFileChangeFuture.get(500, TimeUnit.MILLISECONDS);
assertFalse(configFile.getContent().contains(String.format("%s=%s", someKey, someValue)));
assertTrue(configFile.getContent().contains(String.format("%s=%s", someKey, anotherValue)));
assertEquals(someNamespace, changeEvent.getNamespace());
assertTrue(changeEvent.getOldValue().contains(String.format("%s=%s", someKey, someValue)));
assertTrue(changeEvent.getNewValue().contains(String.format("%s=%s", someKey, anotherValue)));
assertEquals(PropertyChangeType.MODIFIED, changeEvent.getChangeType());
}
@Test
......
......@@ -6,8 +6,13 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.ConfigFileChangeListener;
import com.ctrip.framework.apollo.enums.PropertyChangeType;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -85,9 +90,97 @@ public class XmlConfigFileTest {
Properties anotherProperties = new Properties();
anotherProperties.setProperty(key, anotherValue);
final SettableFuture<ConfigFileChangeEvent> configFileChangeFuture = SettableFuture.create();
ConfigFileChangeListener someListener = new ConfigFileChangeListener() {
@Override
public void onChange(ConfigFileChangeEvent changeEvent) {
configFileChangeFuture.set(changeEvent);
}
};
configFile.addChangeListener(someListener);
configFile.onRepositoryChange(someNamespace, anotherProperties);
ConfigFileChangeEvent changeEvent = configFileChangeFuture.get(500, TimeUnit.MILLISECONDS);
assertEquals(anotherValue, configFile.getContent());
assertEquals(someNamespace, changeEvent.getNamespace());
assertEquals(someValue, changeEvent.getOldValue());
assertEquals(anotherValue, changeEvent.getNewValue());
assertEquals(PropertyChangeType.MODIFIED, changeEvent.getChangeType());
}
@Test
public void testOnRepositoryChangeWithContentAdded() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someValue";
when(configRepository.getConfig()).thenReturn(someProperties);
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertEquals(null, configFile.getContent());
Properties anotherProperties = new Properties();
anotherProperties.setProperty(key, someValue);
final SettableFuture<ConfigFileChangeEvent> configFileChangeFuture = SettableFuture.create();
ConfigFileChangeListener someListener = new ConfigFileChangeListener() {
@Override
public void onChange(ConfigFileChangeEvent changeEvent) {
configFileChangeFuture.set(changeEvent);
}
};
configFile.addChangeListener(someListener);
configFile.onRepositoryChange(someNamespace, anotherProperties);
ConfigFileChangeEvent changeEvent = configFileChangeFuture.get(500, TimeUnit.MILLISECONDS);
assertEquals(someValue, configFile.getContent());
assertEquals(someNamespace, changeEvent.getNamespace());
assertEquals(null, changeEvent.getOldValue());
assertEquals(someValue, changeEvent.getNewValue());
assertEquals(PropertyChangeType.ADDED, changeEvent.getChangeType());
}
@Test
public void testOnRepositoryChangeWithContentDeleted() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someValue";
someProperties.setProperty(key, someValue);
when(configRepository.getConfig()).thenReturn(someProperties);
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertEquals(someValue, configFile.getContent());
Properties anotherProperties = new Properties();
final SettableFuture<ConfigFileChangeEvent> configFileChangeFuture = SettableFuture.create();
ConfigFileChangeListener someListener = new ConfigFileChangeListener() {
@Override
public void onChange(ConfigFileChangeEvent changeEvent) {
configFileChangeFuture.set(changeEvent);
}
};
configFile.addChangeListener(someListener);
configFile.onRepositoryChange(someNamespace, anotherProperties);
ConfigFileChangeEvent changeEvent = configFileChangeFuture.get(500, TimeUnit.MILLISECONDS);
assertEquals(null, configFile.getContent());
assertEquals(someNamespace, changeEvent.getNamespace());
assertEquals(someValue, changeEvent.getOldValue());
assertEquals(null, changeEvent.getNewValue());
assertEquals(PropertyChangeType.DELETED, changeEvent.getChangeType());
}
@Test
......
......@@ -5,10 +5,12 @@ import com.google.common.base.Charsets;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigFileChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.model.ConfigFileChangeEvent;
import com.ctrip.framework.foundation.Foundation;
import org.slf4j.Logger;
......@@ -48,6 +50,12 @@ public class ApolloConfigDemo {
publicConfig.addChangeListener(changeListener);
applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties);
xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML);
xmlConfigFile.addChangeListener(new ConfigFileChangeListener() {
@Override
public void onChange(ConfigFileChangeEvent changeEvent) {
logger.info(changeEvent.toString());
}
});
}
private String getConfig(String key) {
......
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