Commit 5bb2aaf1 by Yiming Liu

Merge pull request #79 from nobodyiam/client-refresh-merge

Add client side auto refresh capability
parents b1c4b7d8 df474afd
...@@ -13,8 +13,7 @@ import com.ctrip.apollo.biz.entity.Release; ...@@ -13,8 +13,7 @@ import com.ctrip.apollo.biz.entity.Release;
*/ */
public interface ReleaseRepository extends PagingAndSortingRepository<Release, Long> { public interface ReleaseRepository extends PagingAndSortingRepository<Release, Long> {
@Query("SELECT r FROM Release r WHERE r.appId = :appId AND r.clusterName = :clusterName AND r.namespaceName = :namespaceName order by r.id desc") Release findFirstByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName,
Release findLatest(@Param("appId") String appId, @Param("clusterName") String clusterName,
@Param("namespaceName") String namespaceName); @Param("namespaceName") String namespaceName);
List<Release> findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName, List<Release> findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName,
......
...@@ -30,7 +30,7 @@ public class ConfigService { ...@@ -30,7 +30,7 @@ public class ConfigService {
private Type configurationTypeReference = new TypeToken<Map<String, String>>(){}.getType(); private Type configurationTypeReference = new TypeToken<Map<String, String>>(){}.getType();
public Release findRelease(String appId, String clusterName, String namespaceName) { public Release findRelease(String appId, String clusterName, String namespaceName) {
Release release = releaseRepository.findLatest(appId, clusterName, namespaceName); Release release = releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(appId, clusterName, namespaceName);
return release; return release;
} }
......
...@@ -12,4 +12,6 @@ public interface Config { ...@@ -12,4 +12,6 @@ public interface Config {
* @return the property value * @return the property value
*/ */
public String getProperty(String key, String defaultValue); public String getProperty(String key, String defaultValue);
public void addChangeListener(ConfigChangeListener listener);
} }
package com.ctrip.apollo;
import com.ctrip.apollo.model.ConfigChangeEvent;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ConfigChangeListener {
public void onChange(ConfigChangeEvent changeEvent);
}
package com.ctrip.apollo.internals;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.ctrip.apollo.Config;
import com.ctrip.apollo.ConfigChangeListener;
import com.ctrip.apollo.enums.PropertyChangeType;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractConfig implements Config {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfig.class);
private List<ConfigChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
@Override
public void addChangeListener(ConfigChangeListener listener) {
if (!m_listeners.contains(listener)) {
m_listeners.add(listener);
}
}
protected void fireConfigChange(ConfigChangeEvent changeEvent) {
for (ConfigChangeListener listener : m_listeners) {
try {
listener.onChange(changeEvent);
} catch (Throwable t) {
logger.error("Failed to invoke config change listener {}", listener.getClass(), t);
}
}
}
List<ConfigChange> calcPropertyChanges(Properties previous,
Properties current) {
if (previous == null) {
previous = new Properties();
}
if (current == null) {
current = new Properties();
}
Set<String> previousKeys = previous.stringPropertyNames();
Set<String> currentKeys = current.stringPropertyNames();
Set<String> commonKeys = Sets.intersection(previousKeys, currentKeys);
Set<String> newKeys = Sets.difference(currentKeys, commonKeys);
Set<String> removedKeys = Sets.difference(previousKeys, commonKeys);
List<ConfigChange> changes = Lists.newArrayList();
for (String newKey : newKeys) {
changes.add(new ConfigChange(newKey, null, current.getProperty(newKey), PropertyChangeType.NEW));
}
for (String removedKey : removedKeys) {
changes.add(new ConfigChange(removedKey, previous.getProperty(removedKey), null,
PropertyChangeType.DELETED));
}
for (String commonKey : commonKeys) {
String previousValue = previous.getProperty(commonKey);
String currentValue = current.getProperty(commonKey);
if (Objects.equal(previousValue, currentValue)) {
continue;
}
changes.add(new ConfigChange(commonKey, previousValue,
currentValue, PropertyChangeType.MODIFIED));
}
return changes;
}
}
package com.ctrip.apollo.internals;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Properties;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractConfigRepository implements ConfigRepository {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class);
private List<RepositoryChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
@Override
public void addChangeListener(RepositoryChangeListener listener) {
if (!m_listeners.contains(listener)) {
m_listeners.add(listener);
}
}
@Override
public void removeChangeListener(RepositoryChangeListener listener) {
m_listeners.remove(listener);
}
protected void fireRepositoryChange(String namespace, Properties newProperties) {
for (RepositoryChangeListener listener : m_listeners) {
try {
listener.onRepositoryChange(namespace, newProperties);
} catch (Throwable t) {
logger.error("Failed to invoke repository change listener {}", listener.getClass(), t);
}
}
}
}
...@@ -6,7 +6,19 @@ import java.util.Properties; ...@@ -6,7 +6,19 @@ import java.util.Properties;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public interface ConfigRepository { public interface ConfigRepository {
public Properties loadConfig(); /**
* Get the config from this repository
* @return
*/
public Properties getConfig();
/**
* Set the fallback repo for this repository
* @param fallbackConfigRepository
*/
public void setFallback(ConfigRepository fallbackConfigRepository); public void setFallback(ConfigRepository fallbackConfigRepository);
public void addChangeListener(RepositoryChangeListener listener);
public void removeChangeListener(RepositoryChangeListener listener);
} }
package com.ctrip.apollo.internals; package com.ctrip.apollo.internals;
import com.ctrip.apollo.Config; import com.google.common.collect.ImmutableMap;
import com.ctrip.apollo.core.utils.ClassLoaderUtil; import com.ctrip.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.apollo.enums.PropertyChangeType;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import com.dianping.cat.Cat; import com.dianping.cat.Cat;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -9,12 +13,15 @@ import org.slf4j.LoggerFactory; ...@@ -9,12 +13,15 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties; import java.util.Properties;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class DefaultConfig implements Config { public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {
private static final Logger logger = LoggerFactory.getLogger(DefaultConfig.class); private static final Logger logger = LoggerFactory.getLogger(DefaultConfig.class);
private final String m_namespace; private final String m_namespace;
private Properties m_resourceProperties; private Properties m_resourceProperties;
...@@ -30,7 +37,8 @@ public class DefaultConfig implements Config { ...@@ -30,7 +37,8 @@ public class DefaultConfig implements Config {
private void initialize() { private void initialize() {
try { try {
m_configProperties = m_configRepository.loadConfig(); m_configProperties = m_configRepository.getConfig();
m_configRepository.addChangeListener(this);
} catch (Throwable ex) { } catch (Throwable ex) {
String message = String.format("Init Apollo Local Config failed - namespace: %s", String message = String.format("Init Apollo Local Config failed - namespace: %s",
m_namespace); m_namespace);
...@@ -68,6 +76,69 @@ public class DefaultConfig implements Config { ...@@ -68,6 +76,69 @@ public class DefaultConfig implements Config {
return value == null ? defaultValue : value; return value == null ? defaultValue : value;
} }
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_configProperties)) {
return;
}
Properties newConfigProperties = new Properties();
newConfigProperties.putAll(newProperties);
Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties);
this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
}
private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties) {
List<ConfigChange> configChanges =
calcPropertyChanges(m_configProperties, newConfigProperties);
// List<ConfigChange> actualChanges = Lists.newArrayListWithCapacity(configChanges.size());
ImmutableMap.Builder<String, ConfigChange> actualChanges =
new ImmutableMap.Builder<>();
/** === Double check since DefaultConfig has multiple config sources ==== **/
//1. use getProperty to update configChanges's old value
for (ConfigChange change : configChanges) {
change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
}
//2. update m_configProperties
m_configProperties = newConfigProperties;
//3. use getProperty to update configChange's new value and calc the final changes
for (ConfigChange change : configChanges) {
change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
switch (change.getChangeType()) {
case NEW:
if (Objects.equals(change.getOldValue(), change.getNewValue())) {
break;
}
if (!Objects.isNull(change.getOldValue())) {
change.setChangeType(PropertyChangeType.MODIFIED);
}
actualChanges.put(change.getPropertyName(), change);
break;
case MODIFIED:
if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
actualChanges.put(change.getPropertyName(), change);
}
break;
case DELETED:
if (Objects.equals(change.getOldValue(), change.getNewValue())) {
break;
}
if (!Objects.isNull(change.getNewValue())) {
change.setChangeType(PropertyChangeType.MODIFIED);
}
actualChanges.put(change.getPropertyName(), change);
break;
}
}
return actualChanges.build();
}
private Properties loadFromResource(String namespace) { private Properties loadFromResource(String namespace) {
String name = String.format("META-INF/config/%s.properties", namespace); String name = String.format("META-INF/config/%s.properties", namespace);
InputStream in = ClassLoaderUtil.getLoader().getResourceAsStream(name); InputStream in = ClassLoaderUtil.getLoader().getResourceAsStream(name);
...@@ -92,6 +163,4 @@ public class DefaultConfig implements Config { ...@@ -92,6 +163,4 @@ public class DefaultConfig implements Config {
return properties; return properties;
} }
} }
...@@ -19,7 +19,7 @@ public class DefaultConfigManager implements ConfigManager { ...@@ -19,7 +19,7 @@ public class DefaultConfigManager implements ConfigManager {
@Inject @Inject
private ConfigFactoryManager m_factoryManager; private ConfigFactoryManager m_factoryManager;
private Map<String, Config> m_configs = Maps.newHashMap(); private Map<String, Config> m_configs = Maps.newConcurrentMap();
@Override @Override
public Config getConfig(String namespace) { public Config getConfig(String namespace) {
......
...@@ -22,14 +22,15 @@ import java.util.Properties; ...@@ -22,14 +22,15 @@ import java.util.Properties;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class LocalFileConfigRepository implements ConfigRepository { public class LocalFileConfigRepository extends AbstractConfigRepository
implements RepositoryChangeListener {
private static final Logger logger = LoggerFactory.getLogger(LocalFileConfigRepository.class); private static final Logger logger = LoggerFactory.getLogger(LocalFileConfigRepository.class);
private PlexusContainer m_container; private final PlexusContainer m_container;
private final String m_namespace; private final String m_namespace;
private final File m_baseDir; private final File m_baseDir;
private final ConfigUtil m_configUtil; private final ConfigUtil m_configUtil;
private Properties m_fileProperties; private volatile Properties m_fileProperties;
private ConfigRepository m_fallback; private volatile ConfigRepository m_fallback;
public LocalFileConfigRepository(File baseDir, String namespace) { public LocalFileConfigRepository(File baseDir, String namespace) {
m_baseDir = baseDir; m_baseDir = baseDir;
...@@ -43,7 +44,7 @@ public class LocalFileConfigRepository implements ConfigRepository { ...@@ -43,7 +44,7 @@ public class LocalFileConfigRepository implements ConfigRepository {
} }
@Override @Override
public Properties loadConfig() { public Properties getConfig() {
if (m_fileProperties == null) { if (m_fileProperties == null) {
initLocalConfig(); initLocalConfig();
} }
...@@ -54,26 +55,51 @@ public class LocalFileConfigRepository implements ConfigRepository { ...@@ -54,26 +55,51 @@ public class LocalFileConfigRepository implements ConfigRepository {
@Override @Override
public void setFallback(ConfigRepository fallbackConfigRepository) { public void setFallback(ConfigRepository fallbackConfigRepository) {
//clear previous listener
if (m_fallback != null) {
m_fallback.removeChangeListener(this);
}
m_fallback = fallbackConfigRepository; m_fallback = fallbackConfigRepository;
fallbackConfigRepository.addChangeListener(this);
} }
void initLocalConfig() { @Override
if (m_fallback != null) { public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
try { if (newProperties.equals(m_fileProperties)) {
m_fileProperties = m_fallback.loadConfig(); return;
//TODO register change listener
persistLocalCacheFile(m_baseDir, m_namespace);
return;
} catch (Throwable ex) {
logger.error("Load config from fallback loader failed", ex);
Cat.logError(ex);
}
} }
Properties newFileProperties = new Properties();
newFileProperties.putAll(newProperties);
this.m_fileProperties = newFileProperties;
persistLocalCacheFile(m_baseDir, m_namespace);
this.fireRepositoryChange(namespace, newProperties);
}
void initLocalConfig() {
try { try {
m_fileProperties = this.loadFromLocalCacheFile(m_baseDir, m_namespace); m_fileProperties = this.loadFromLocalCacheFile(m_baseDir, m_namespace);
} catch (IOException ex) { } catch (Throwable ex) {
throw new RuntimeException("Loading config from local cache file failed", ex); logger.error("Load config from local config cache file failed", ex);
}
//TODO check whether properties is expired or should we return after it's synced with fallback?
if (m_fileProperties != null) {
return;
}
if (m_fallback == null) {
throw new RuntimeException(
"Load config from local config cache failed and there is no fallback repository!");
}
try {
m_fileProperties = m_fallback.getConfig();
persistLocalCacheFile(m_baseDir, m_namespace);
} catch (Throwable ex) {
String message =
String.format("Load config from fallback repository %s failed", m_fallback.getClass());
logger.error(message, ex);
throw new RuntimeException(message, ex);
} }
} }
......
...@@ -5,6 +5,7 @@ import com.google.common.collect.Lists; ...@@ -5,6 +5,7 @@ import com.google.common.collect.Lists;
import com.ctrip.apollo.core.dto.ApolloConfig; import com.ctrip.apollo.core.dto.ApolloConfig;
import com.ctrip.apollo.core.dto.ServiceDTO; import com.ctrip.apollo.core.dto.ServiceDTO;
import com.ctrip.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.apollo.util.ConfigUtil; import com.ctrip.apollo.util.ConfigUtil;
import com.ctrip.apollo.util.http.HttpRequest; import com.ctrip.apollo.util.http.HttpRequest;
import com.ctrip.apollo.util.http.HttpResponse; import com.ctrip.apollo.util.http.HttpResponse;
...@@ -18,19 +19,22 @@ import org.unidal.lookup.ContainerLoader; ...@@ -18,19 +19,22 @@ import org.unidal.lookup.ContainerLoader;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class RemoteConfigRepository implements ConfigRepository { public class RemoteConfigRepository extends AbstractConfigRepository{
private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class); private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class);
private PlexusContainer m_container; private PlexusContainer m_container;
private ConfigServiceLocator m_serviceLocator; private final ConfigServiceLocator m_serviceLocator;
private HttpUtil m_httpUtil; private final HttpUtil m_httpUtil;
private ConfigUtil m_configUtil; private final ConfigUtil m_configUtil;
private AtomicReference<ApolloConfig> m_configCache; private volatile AtomicReference<ApolloConfig> m_configCache;
private String m_namespace; private final String m_namespace;
private final ScheduledExecutorService m_executorService;
public RemoteConfigRepository(String namespace) { public RemoteConfigRepository(String namespace) {
m_namespace = namespace; m_namespace = namespace;
...@@ -43,12 +47,15 @@ public class RemoteConfigRepository implements ConfigRepository { ...@@ -43,12 +47,15 @@ public class RemoteConfigRepository implements ConfigRepository {
} catch (ComponentLookupException e) { } catch (ComponentLookupException e) {
throw new IllegalStateException("Unable to load component!", e); throw new IllegalStateException("Unable to load component!", e);
} }
this.m_executorService = Executors.newScheduledThreadPool(1,
ApolloThreadFactory.create("RemoteConfigRepository", true));
this.schedulePeriodicRefresh();
} }
@Override @Override
public Properties loadConfig() { public Properties getConfig() {
if (m_configCache.get() == null) { if (m_configCache.get() == null) {
initRemoteConfig(); this.loadRemoteConfig();
} }
return transformApolloConfigToProperties(m_configCache.get()); return transformApolloConfigToProperties(m_configCache.get());
} }
...@@ -58,8 +65,37 @@ public class RemoteConfigRepository implements ConfigRepository { ...@@ -58,8 +65,37 @@ public class RemoteConfigRepository implements ConfigRepository {
//remote config doesn't need fallback //remote config doesn't need fallback
} }
private void initRemoteConfig() { private void schedulePeriodicRefresh() {
m_configCache.set(this.loadApolloConfig()); logger.info("Schedule periodic refresh with interval: {} {}",
m_configUtil.getRefreshInterval(), m_configUtil.getRefreshTimeUnit());
this.m_executorService.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
try {
loadRemoteConfig();
} catch (Throwable ex) {
logger.error("Refreshing config failed", ex);
}
}
}, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
m_configUtil.getRefreshTimeUnit());
}
synchronized void loadRemoteConfig() {
ApolloConfig previous = m_configCache.get();
ApolloConfig current = loadApolloConfig();
//HTTP 304, nothing changed
if (previous == current) {
return;
}
logger.info("Remote Config changes!");
m_configCache.set(current);
this.fireRepositoryChange(m_namespace, this.getConfig());
} }
private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) { private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) {
...@@ -72,16 +108,15 @@ public class RemoteConfigRepository implements ConfigRepository { ...@@ -72,16 +108,15 @@ public class RemoteConfigRepository implements ConfigRepository {
private ApolloConfig loadApolloConfig() { private ApolloConfig loadApolloConfig() {
String appId = m_configUtil.getAppId(); String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster(); String cluster = m_configUtil.getCluster();
String uri = getConfigServiceUrl(); String url = assembleUrl(getConfigServiceUrl(), appId, cluster, m_namespace, m_configCache.get());
logger.info("Loading config from {}, appId={}, cluster={}, namespace={}", uri, appId, cluster, logger.info("Loading config from {}", url);
m_namespace); HttpRequest request = new HttpRequest(url);
HttpRequest request =
new HttpRequest(assembleUrl(uri, appId, cluster, m_namespace, m_configCache.get()));
try { try {
HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class); HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class);
if (response.getStatusCode() == 304) { if (response.getStatusCode() == 304) {
logger.info("Config server responds with 304 HTTP status code.");
return m_configCache.get(); return m_configCache.get();
} }
......
package com.ctrip.apollo.internals;
import java.util.Properties;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface RepositoryChangeListener {
public void onRepositoryChange(String namespace, Properties newProperties);
}
package com.ctrip.apollo.internals; package com.ctrip.apollo.internals;
import com.ctrip.apollo.Config; import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class SimpleConfig implements Config { public class SimpleConfig extends AbstractConfig implements RepositoryChangeListener {
private static final Logger logger = LoggerFactory.getLogger(SimpleConfig.class); private static final Logger logger = LoggerFactory.getLogger(SimpleConfig.class);
private String m_namespace; private final String m_namespace;
private ConfigRepository m_configRepository; private final ConfigRepository m_configRepository;
private Properties m_configProperties; private volatile Properties m_configProperties;
public SimpleConfig(String namespace, ConfigRepository configRepository) { public SimpleConfig(String namespace, ConfigRepository configRepository) {
m_namespace = namespace; m_namespace = namespace;
...@@ -24,9 +30,10 @@ public class SimpleConfig implements Config { ...@@ -24,9 +30,10 @@ public class SimpleConfig implements Config {
private void initialize() { private void initialize() {
try { try {
m_configProperties = m_configRepository.loadConfig(); m_configProperties = m_configRepository.getConfig();
m_configRepository.addChangeListener(this);
} catch (Throwable ex) { } catch (Throwable ex) {
String message = String.format("Init Apollo Remote Config failed - namespace: %s", String message = String.format("Init Apollo Simple Config failed - namespace: %s",
m_namespace); m_namespace);
logger.error(message, ex); logger.error(message, ex);
throw new RuntimeException(message, ex); throw new RuntimeException(message, ex);
...@@ -38,4 +45,25 @@ public class SimpleConfig implements Config { ...@@ -38,4 +45,25 @@ public class SimpleConfig implements Config {
return this.m_configProperties.getProperty(key, defaultValue); return this.m_configProperties.getProperty(key, defaultValue);
} }
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_configProperties)) {
return;
}
Properties newConfigProperties = new Properties();
newConfigProperties.putAll(newProperties);
List<ConfigChange> changes = calcPropertyChanges(m_configProperties, newConfigProperties);
Map<String, ConfigChange> changeMap = Maps.uniqueIndex(changes,
new Function<ConfigChange, String>() {
@Override
public String apply(ConfigChange input) {
return input.getPropertyName();
}
});
m_configProperties = newConfigProperties;
this.fireConfigChange(new ConfigChangeEvent(m_namespace, changeMap));
}
} }
package com.ctrip.apollo.model; package com.ctrip.apollo.model;
import com.google.common.base.MoreObjects;
import com.ctrip.apollo.enums.PropertyChangeType; import com.ctrip.apollo.enums.PropertyChangeType;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class PropertyChange { public class ConfigChange {
private String propertyName; private final String propertyName;
private Object oldValue; private String oldValue;
private Object newValue; private String newValue;
private PropertyChangeType changeType; private PropertyChangeType changeType;
public PropertyChange(String propertyName, Object oldValue, Object newValue, public ConfigChange(String propertyName, String oldValue, String newValue,
PropertyChangeType changeType) { PropertyChangeType changeType) {
this.propertyName = propertyName; this.propertyName = propertyName;
this.oldValue = oldValue; this.oldValue = oldValue;
this.newValue = newValue; this.newValue = newValue;
...@@ -24,31 +26,38 @@ public class PropertyChange { ...@@ -24,31 +26,38 @@ public class PropertyChange {
return propertyName; return propertyName;
} }
public void setPropertyName(String propertyName) { public String getOldValue() {
this.propertyName = propertyName;
}
public Object getOldValue() {
return oldValue; return oldValue;
} }
public void setOldValue(Object oldValue) { public String getNewValue() {
this.oldValue = oldValue; return newValue;
} }
public Object getNewValue() { public PropertyChangeType getChangeType() {
return newValue; return changeType;
} }
public void setNewValue(Object newValue) { public void setOldValue(String oldValue) {
this.newValue = newValue; this.oldValue = oldValue;
} }
public PropertyChangeType getChangeType() { public void setNewValue(String newValue) {
return changeType; this.newValue = newValue;
} }
public void setChangeType(PropertyChangeType changeType) { public void setChangeType(PropertyChangeType changeType) {
this.changeType = changeType; this.changeType = changeType;
} }
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("propertyName", propertyName)
.add("oldValue", oldValue)
.add("newValue", newValue)
.add("changeType", changeType)
.toString();
}
} }
package com.ctrip.apollo.model;
import java.util.Map;
import java.util.Set;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigChangeEvent {
private final String m_namespace;
private final Map<String, ConfigChange> changes;
public ConfigChangeEvent(String m_namespace,
Map<String, ConfigChange> changes) {
this.m_namespace = m_namespace;
this.changes = changes;
}
public Set<String> changedKeys() {
return changes.keySet();
}
public ConfigChange getChange(String key) {
return changes.get(key);
}
/**
* Please note that the returned Map is immutable
* @return changes
*/
public Map<String, ConfigChange> getChanges() {
return changes;
}
public String getNamespace() {
return m_namespace;
}
}
package com.ctrip.apollo.model;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Properties;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigRefreshResult {
private String m_namespace;
private Properties m_properties;
private List<PropertyChange> m_changes;
public ConfigRefreshResult(String namespace, Properties properties) {
this.m_namespace = namespace;
this.m_properties = properties;
m_changes = Lists.newArrayList();
}
public Properties getProperties() {
return m_properties;
}
public String getNamespace() {
return m_namespace;
}
public List<PropertyChange> getChanges() {
return m_changes;
}
public void setChanges(List<PropertyChange> changes) {
this.m_changes = changes;
}
public boolean hasChanges() {
return !m_changes.isEmpty();
}
}
...@@ -33,8 +33,7 @@ public class DefaultConfigFactory implements ConfigFactory { ...@@ -33,8 +33,7 @@ public class DefaultConfigFactory implements ConfigFactory {
} }
LocalFileConfigRepository createLocalConfigRepository(String namespace) { LocalFileConfigRepository createLocalConfigRepository(String namespace) {
LocalFileConfigRepository LocalFileConfigRepository localFileConfigLoader =
localFileConfigLoader =
new LocalFileConfigRepository(m_baseDir, namespace); new LocalFileConfigRepository(m_baseDir, namespace);
localFileConfigLoader.setFallback(createRemoteConfigRepository(namespace)); localFileConfigLoader.setFallback(createRemoteConfigRepository(namespace));
return localFileConfigLoader; return localFileConfigLoader;
......
...@@ -39,18 +39,19 @@ public class HttpUtil { ...@@ -39,18 +39,19 @@ public class HttpUtil {
conn.setRequestMethod("GET"); conn.setRequestMethod("GET");
if (httpRequest.getConnectTimeout() < 0) { int connectTimeout = httpRequest.getConnectTimeout();
conn.setConnectTimeout(m_configUtil.getConnectTimeout()); if (connectTimeout < 0) {
} else { connectTimeout = m_configUtil.getConnectTimeout();
conn.setConnectTimeout(httpRequest.getConnectTimeout());
} }
if (httpRequest.getReadTimeout() < 0) { int readTimeout = httpRequest.getReadTimeout();
conn.setReadTimeout(m_configUtil.getReadTimeout()); if (readTimeout < 0) {
} else { readTimeout = m_configUtil.getReadTimeout();
conn.setReadTimeout(httpRequest.getReadTimeout());
} }
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
conn.connect(); conn.connect();
int statusCode = conn.getResponseCode(); int statusCode = conn.getResponseCode();
...@@ -66,7 +67,7 @@ public class HttpUtil { ...@@ -66,7 +67,7 @@ public class HttpUtil {
} }
throw new RuntimeException( throw new RuntimeException(
String.format("Get operation failed, status code - %d", statusCode)); String.format("Get operation failed for %s, status code - %d", httpRequest.getUrl(), statusCode));
} catch (Throwable ex) { } catch (Throwable ex) {
throw new RuntimeException("Could not complete get operation", ex); throw new RuntimeException("Could not complete get operation", ex);
......
...@@ -71,6 +71,11 @@ public class ConfigServiceTest extends ComponentTestCase { ...@@ -71,6 +71,11 @@ public class ConfigServiceTest extends ComponentTestCase {
return m_namespace + ":" + key; return m_namespace + ":" + key;
} }
@Override
public void addChangeListener(ConfigChangeListener listener) {
}
} }
public static class MockConfigFactory implements ConfigFactory { public static class MockConfigFactory implements ConfigFactory {
......
package com.ctrip.apollo.internals; package com.ctrip.apollo.internals;
import com.ctrip.apollo.Config; import com.ctrip.apollo.Config;
import com.ctrip.apollo.ConfigChangeListener;
import com.ctrip.apollo.spi.ConfigFactory; import com.ctrip.apollo.spi.ConfigFactory;
import com.ctrip.apollo.spi.ConfigFactoryManager; import com.ctrip.apollo.spi.ConfigFactoryManager;
...@@ -60,6 +61,12 @@ public class DefaultConfigManagerTest extends ComponentTestCase { ...@@ -60,6 +61,12 @@ public class DefaultConfigManagerTest extends ComponentTestCase {
public String getProperty(String key, String defaultValue) { public String getProperty(String key, String defaultValue) {
return namespace + ":" + key; return namespace + ":" + key;
} }
@Override
public void addChangeListener(ConfigChangeListener listener) {
}
}; };
} }
}; };
......
package com.ctrip.apollo.internals; package com.ctrip.apollo.internals;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.ctrip.apollo.ConfigChangeListener;
import com.ctrip.apollo.core.utils.ClassLoaderUtil; import com.ctrip.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.apollo.enums.PropertyChangeType;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.File; import java.io.File;
import java.util.Properties; import java.util.Properties;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
...@@ -69,7 +77,7 @@ public class DefaultConfigTest { ...@@ -69,7 +77,7 @@ public class DefaultConfigTest {
someProperties = new Properties(); someProperties = new Properties();
someProperties.setProperty(someKey, someLocalFileValue); someProperties.setProperty(someKey, someLocalFileValue);
someProperties.setProperty(anotherKey, someLocalFileValue); someProperties.setProperty(anotherKey, someLocalFileValue);
when(configRepository.loadConfig()).thenReturn(someProperties); when(configRepository.getConfig()).thenReturn(someProperties);
//set up resource file //set up resource file
File resourceFile = new File(someResourceDir, someNamespace + ".properties"); File resourceFile = new File(someResourceDir, someNamespace + ".properties");
...@@ -79,9 +87,8 @@ public class DefaultConfigTest { ...@@ -79,9 +87,8 @@ public class DefaultConfigTest {
Files.append(System.getProperty("line.separator"), resourceFile, Charsets.UTF_8); Files.append(System.getProperty("line.separator"), resourceFile, Charsets.UTF_8);
Files.append(lastKey + "=" + someResourceValue, resourceFile, Charsets.UTF_8); Files.append(lastKey + "=" + someResourceValue, resourceFile, Charsets.UTF_8);
DefaultConfig DefaultConfig defaultConfig =
defaultConfig = new DefaultConfig(someNamespace, configRepository);
new DefaultConfig(someNamespace, configRepository);
String someKeyValue = defaultConfig.getProperty(someKey, null); String someKeyValue = defaultConfig.getProperty(someKey, null);
String anotherKeyValue = defaultConfig.getProperty(anotherKey, null); String anotherKeyValue = defaultConfig.getProperty(anotherKey, null);
...@@ -95,4 +102,80 @@ public class DefaultConfigTest { ...@@ -95,4 +102,80 @@ public class DefaultConfigTest {
assertEquals(someResourceValue, lastKeyValue); assertEquals(someResourceValue, lastKeyValue);
} }
@Test
public void testOnRepositoryChange() throws Exception {
String someKey = "someKey";
String someSystemPropertyValue = "system-property-value";
String anotherKey = "anotherKey";
String someLocalFileValue = "local-file-value";
String keyToBeDeleted = "keyToBeDeleted";
String keyToBeDeletedValue = "keyToBeDeletedValue";
String yetAnotherKey = "yetAnotherKey";
String yetAnotherValue = "yetAnotherValue";
String yetAnotherResourceValue = "yetAnotherResourceValue";
//set up system property
System.setProperty(someKey, someSystemPropertyValue);
//set up config repo
someProperties = new Properties();
someProperties.putAll(ImmutableMap
.of(someKey, someLocalFileValue, anotherKey, someLocalFileValue, keyToBeDeleted,
keyToBeDeletedValue, yetAnotherKey, yetAnotherValue));
when(configRepository.getConfig()).thenReturn(someProperties);
//set up resource file
File resourceFile = new File(someResourceDir, someNamespace + ".properties");
Files.append(yetAnotherKey + "=" + yetAnotherResourceValue, resourceFile, Charsets.UTF_8);
DefaultConfig defaultConfig =
new DefaultConfig(someNamespace, configRepository);
ConfigChangeListener someListener = mock(ConfigChangeListener.class);
defaultConfig.addChangeListener(someListener);
Properties newProperties = new Properties();
String someKeyNewValue = "new-some-value";
String anotherKeyNewValue = "another-new-value";
String newKey = "newKey";
String newValue = "newValue";
newProperties.putAll(ImmutableMap
.of(someKey, someKeyNewValue, anotherKey, anotherKeyNewValue, newKey, newValue));
final ArgumentCaptor<ConfigChangeEvent> captor =
ArgumentCaptor.forClass(ConfigChangeEvent.class);
defaultConfig.onRepositoryChange(someNamespace, newProperties);
verify(someListener, times(1)).onChange(captor.capture());
ConfigChangeEvent changeEvent = captor.getValue();
assertEquals(someNamespace, changeEvent.getNamespace());
assertEquals(4, changeEvent.getChanges().size());
ConfigChange anotherKeyChange = changeEvent.getChange(anotherKey);
assertEquals(someLocalFileValue, anotherKeyChange.getOldValue());
assertEquals(anotherKeyNewValue, anotherKeyChange.getNewValue());
assertEquals(PropertyChangeType.MODIFIED, anotherKeyChange.getChangeType());
ConfigChange yetAnotherKeyChange = changeEvent.getChange(yetAnotherKey);
assertEquals(yetAnotherValue, yetAnotherKeyChange.getOldValue());
assertEquals(yetAnotherResourceValue, yetAnotherKeyChange.getNewValue());
assertEquals(PropertyChangeType.MODIFIED, yetAnotherKeyChange.getChangeType());
ConfigChange keyToBeDeletedChange = changeEvent.getChange(keyToBeDeleted);
assertEquals(keyToBeDeletedValue, keyToBeDeletedChange.getOldValue());
assertEquals(null, keyToBeDeletedChange.getNewValue());
assertEquals(PropertyChangeType.DELETED, keyToBeDeletedChange.getChangeType());
ConfigChange newKeyChange = changeEvent.getChange(newKey);
assertEquals(null, newKeyChange.getOldValue());
assertEquals(newValue, newKeyChange.getNewValue());
assertEquals(PropertyChangeType.NEW, newKeyChange.getChangeType());
}
} }
...@@ -8,6 +8,7 @@ import com.ctrip.apollo.util.ConfigUtil; ...@@ -8,6 +8,7 @@ import com.ctrip.apollo.util.ConfigUtil;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.unidal.lookup.ComponentTestCase; import org.unidal.lookup.ComponentTestCase;
import java.io.File; import java.io.File;
...@@ -16,7 +17,11 @@ import java.util.Properties; ...@@ -16,7 +17,11 @@ import java.util.Properties;
import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
...@@ -40,7 +45,7 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase { ...@@ -40,7 +45,7 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase {
someProperties = new Properties(); someProperties = new Properties();
someProperties.setProperty("defaultKey", "defaultValue"); someProperties.setProperty("defaultKey", "defaultValue");
fallbackRepo = mock(ConfigRepository.class); fallbackRepo = mock(ConfigRepository.class);
when(fallbackRepo.loadConfig()).thenReturn(someProperties); when(fallbackRepo.getConfig()).thenReturn(someProperties);
defineComponent(ConfigUtil.class, MockConfigUtil.class); defineComponent(ConfigUtil.class, MockConfigUtil.class);
} }
...@@ -83,7 +88,7 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase { ...@@ -83,7 +88,7 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase {
Files.write(someKey + "=" + someValue, file, Charsets.UTF_8); Files.write(someKey + "=" + someValue, file, Charsets.UTF_8);
LocalFileConfigRepository localRepo = new LocalFileConfigRepository(someBaseDir, someNamespace); LocalFileConfigRepository localRepo = new LocalFileConfigRepository(someBaseDir, someNamespace);
Properties properties = localRepo.loadConfig(); Properties properties = localRepo.getConfig();
assertEquals(someValue, properties.getProperty(someKey)); assertEquals(someValue, properties.getProperty(someKey));
...@@ -97,7 +102,7 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase { ...@@ -97,7 +102,7 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase {
localFileConfigRepository.setFallback(fallbackRepo); localFileConfigRepository.setFallback(fallbackRepo);
Properties result = localFileConfigRepository.loadConfig(); Properties result = localFileConfigRepository.getConfig();
assertThat( assertThat(
"LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache",
...@@ -111,13 +116,13 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase { ...@@ -111,13 +116,13 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase {
localRepo.setFallback(fallbackRepo); localRepo.setFallback(fallbackRepo);
Properties someProperties = localRepo.loadConfig(); Properties someProperties = localRepo.getConfig();
LocalFileConfigRepository LocalFileConfigRepository
anotherLocalRepoWithNoFallback = anotherLocalRepoWithNoFallback =
new LocalFileConfigRepository(someBaseDir, someNamespace); new LocalFileConfigRepository(someBaseDir, someNamespace);
Properties anotherProperties = anotherLocalRepoWithNoFallback.loadConfig(); Properties anotherProperties = anotherLocalRepoWithNoFallback.getConfig();
assertThat( assertThat(
"LocalFileConfigRepository should persist local cache files and return that afterwards", "LocalFileConfigRepository should persist local cache files and return that afterwards",
...@@ -125,6 +130,30 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase { ...@@ -125,6 +130,30 @@ public class LocalFileConfigRepositoryTest extends ComponentTestCase {
} }
@Test
public void testOnRepositoryChange() throws Exception {
RepositoryChangeListener someListener = mock(RepositoryChangeListener.class);
LocalFileConfigRepository localFileConfigRepository =
new LocalFileConfigRepository(someBaseDir, someNamespace);
localFileConfigRepository.setFallback(fallbackRepo);
localFileConfigRepository.addChangeListener(someListener);
localFileConfigRepository.getConfig();
Properties anotherProperties = new Properties();
anotherProperties.put("anotherKey", "anotherValue");
localFileConfigRepository.onRepositoryChange(someNamespace, anotherProperties);
final ArgumentCaptor<Properties> captor = ArgumentCaptor.forClass(Properties.class);
verify(someListener, times(1)).onRepositoryChange(eq(someNamespace), captor.capture());
assertEquals(anotherProperties, captor.getValue());
}
public static class MockConfigUtil extends ConfigUtil { public static class MockConfigUtil extends ConfigUtil {
@Override @Override
public String getAppId() { public String getAppId() {
......
package com.ctrip.apollo.internals; package com.ctrip.apollo.internals;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
...@@ -13,6 +14,7 @@ import com.ctrip.apollo.util.http.HttpUtil; ...@@ -13,6 +14,7 @@ import com.ctrip.apollo.util.http.HttpUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.unidal.lookup.ComponentTestCase; import org.unidal.lookup.ComponentTestCase;
...@@ -22,7 +24,10 @@ import java.util.Map; ...@@ -22,7 +24,10 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
...@@ -60,7 +65,7 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase { ...@@ -60,7 +65,7 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase {
when(someResponse.getBody()).thenReturn(someApolloConfig); when(someResponse.getBody()).thenReturn(someApolloConfig);
RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace);
Properties config = remoteConfigRepository.loadConfig(); Properties config = remoteConfigRepository.getConfig();
assertEquals(configurations, config); assertEquals(configurations, config);
} }
...@@ -71,7 +76,27 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase { ...@@ -71,7 +76,27 @@ public class RemoteConfigRepositoryTest extends ComponentTestCase {
when(someResponse.getStatusCode()).thenReturn(500); when(someResponse.getStatusCode()).thenReturn(500);
RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace);
remoteConfigRepository.loadConfig(); remoteConfigRepository.getConfig();
}
@Test
public void testRepositoryChangeListener() throws Exception {
Map<String, String> configurations = ImmutableMap.of("someKey", "someValue");
ApolloConfig someApolloConfig = assembleApolloConfig(configurations);
when(someResponse.getStatusCode()).thenReturn(200);
when(someResponse.getBody()).thenReturn(someApolloConfig);
RepositoryChangeListener someListener = mock(RepositoryChangeListener.class);
RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace);
remoteConfigRepository.addChangeListener(someListener);
final ArgumentCaptor<Properties> captor = ArgumentCaptor.forClass(Properties.class);
remoteConfigRepository.loadRemoteConfig();
verify(someListener, times(1)).onRepositoryChange(eq(someNamespace), captor.capture());
assertEquals(configurations, captor.getValue());
} }
private ApolloConfig assembleApolloConfig(Map<String, String> configurations) { private ApolloConfig assembleApolloConfig(Map<String, String> configurations) {
......
package com.ctrip.apollo.internals; package com.ctrip.apollo.internals;
import com.google.common.collect.ImmutableMap;
import com.ctrip.apollo.ConfigChangeListener;
import com.ctrip.apollo.enums.PropertyChangeType;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import java.util.Properties; import java.util.Properties;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
...@@ -32,7 +43,7 @@ public class SimpleConfigTest { ...@@ -32,7 +43,7 @@ public class SimpleConfigTest {
String someValue = "someValue"; String someValue = "someValue";
someProperties.setProperty(someKey, someValue); someProperties.setProperty(someKey, someValue);
when(configRepository.loadConfig()).thenReturn(someProperties); when(configRepository.getConfig()).thenReturn(someProperties);
SimpleConfig config = new SimpleConfig(someNamespace, configRepository); SimpleConfig config = new SimpleConfig(someNamespace, configRepository);
...@@ -41,8 +52,57 @@ public class SimpleConfigTest { ...@@ -41,8 +52,57 @@ public class SimpleConfigTest {
@Test(expected = RuntimeException.class) @Test(expected = RuntimeException.class)
public void testLoadConfigFromConfigRepositoryError() throws Exception { public void testLoadConfigFromConfigRepositoryError() throws Exception {
when(configRepository.loadConfig()).thenThrow(Throwable.class); when(configRepository.getConfig()).thenThrow(Throwable.class);
new SimpleConfig(someNamespace, configRepository); new SimpleConfig(someNamespace, configRepository);
} }
@Test
public void testOnRepositoryChange() throws Exception {
Properties someProperties = new Properties();
String someKey = "someKey";
String someValue = "someValue";
String anotherKey = "anotherKey";
String anotherValue = "anotherValue";
someProperties.putAll(ImmutableMap.of(someKey, someValue, anotherKey, anotherValue));
Properties anotherProperties = new Properties();
String newKey = "newKey";
String newValue = "newValue";
String someValueNew = "someValueNew";
anotherProperties.putAll(ImmutableMap.of(someKey, someValueNew, newKey, newValue));
when(configRepository.getConfig()).thenReturn(someProperties);
ConfigChangeListener someListener = mock(ConfigChangeListener.class);
SimpleConfig config = new SimpleConfig(someNamespace, configRepository);
config.addChangeListener(someListener);
config.onRepositoryChange(someNamespace, anotherProperties);
ArgumentCaptor<ConfigChangeEvent> captor = ArgumentCaptor.forClass(ConfigChangeEvent.class);
verify(someListener, times(1)).onChange(captor.capture());
ConfigChangeEvent changeEvent = captor.getValue();
assertEquals(someNamespace, changeEvent.getNamespace());
assertEquals(3, changeEvent.getChanges().size());
ConfigChange someKeyChange = changeEvent.getChange(someKey);
assertEquals(someValue, someKeyChange.getOldValue());
assertEquals(someValueNew, someKeyChange.getNewValue());
assertEquals(PropertyChangeType.MODIFIED, someKeyChange.getChangeType());
ConfigChange anotherKeyChange = changeEvent.getChange(anotherKey);
assertEquals(anotherValue, anotherKeyChange.getOldValue());
assertEquals(null, anotherKeyChange.getNewValue());
assertEquals(PropertyChangeType.DELETED, anotherKeyChange.getChangeType());
ConfigChange newKeyChange = changeEvent.getChange(newKey);
assertEquals(null, newKeyChange.getOldValue());
assertEquals(newValue, newKeyChange.getNewValue());
assertEquals(PropertyChangeType.NEW, newKeyChange.getChangeType());
}
} }
...@@ -40,7 +40,7 @@ public class DefaultConfigFactoryTest extends ComponentTestCase { ...@@ -40,7 +40,7 @@ public class DefaultConfigFactoryTest extends ComponentTestCase {
someProperties.setProperty(someKey, someValue); someProperties.setProperty(someKey, someValue);
LocalFileConfigRepository someLocalConfigRepo = mock(LocalFileConfigRepository.class); LocalFileConfigRepository someLocalConfigRepo = mock(LocalFileConfigRepository.class);
when(someLocalConfigRepo.loadConfig()).thenReturn(someProperties); when(someLocalConfigRepo.getConfig()).thenReturn(someProperties);
doReturn(someLocalConfigRepo).when(defaultConfigFactory).createLocalConfigRepository(someNamespace); doReturn(someLocalConfigRepo).when(defaultConfigFactory).createLocalConfigRepository(someNamespace);
......
import com.ctrip.apollo.Config; import com.ctrip.apollo.Config;
import com.ctrip.apollo.ConfigChangeListener;
import com.ctrip.apollo.ConfigService; import com.ctrip.apollo.ConfigService;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
...@@ -9,16 +15,18 @@ import java.io.InputStreamReader; ...@@ -9,16 +15,18 @@ import java.io.InputStreamReader;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ApolloConfigDemo { public class ApolloConfigDemo implements ConfigChangeListener {
private static final Logger logger = LoggerFactory.getLogger(ApolloConfigDemo.class);
private Config config; private Config config;
public ApolloConfigDemo() { public ApolloConfigDemo() {
config = ConfigService.getConfig(); config = ConfigService.getConfig();
config.addChangeListener(this);
} }
private String getConfig(String key) { private String getConfig(String key) {
String result = config.getProperty(key, "undefined"); String result = config.getProperty(key, "undefined");
System.out.println(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;
} }
...@@ -35,4 +43,14 @@ public class ApolloConfigDemo { ...@@ -35,4 +43,14 @@ public class ApolloConfigDemo {
apolloConfigDemo.getConfig(input); apolloConfigDemo.getConfig(input);
} }
} }
@Override
public void onChange(ConfigChangeEvent changeEvent) {
logger.info("Changes for namespace {}", changeEvent.getNamespace());
for (ConfigChange change : changeEvent.getChanges().values()) {
logger.info("Change - key: {}, oldValue: {}, newValue: {}, changeType: {}",
change.getPropertyName(), change.getOldValue(), change.getNewValue(),
change.getChangeType());
}
}
} }
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