Skip to content

Commit

Permalink
Add client side auto refresh capability
Browse files Browse the repository at this point in the history
  • Loading branch information
nobodyiam committed Apr 12, 2016
1 parent b1c4b7d commit df474af
Show file tree
Hide file tree
Showing 27 changed files with 719 additions and 172 deletions.
Expand Up @@ -13,8 +13,7 @@
*/
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 findLatest(@Param("appId") String appId, @Param("clusterName") String clusterName,
Release findFirstByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName,
@Param("namespaceName") String namespaceName);

List<Release> findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName,
Expand Down
Expand Up @@ -30,7 +30,7 @@ public class ConfigService {
private Type configurationTypeReference = new TypeToken<Map<String, String>>(){}.getType();

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;
}

Expand Down
2 changes: 2 additions & 0 deletions apollo-client/src/main/java/com/ctrip/apollo/Config.java
Expand Up @@ -12,4 +12,6 @@ public interface Config {
* @return the property value
*/
public String getProperty(String key, String defaultValue);

public void addChangeListener(ConfigChangeListener listener);
}
@@ -0,0 +1,10 @@
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);
}
@@ -0,0 +1,84 @@
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;
}
}
@@ -0,0 +1,39 @@
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);
}
}
}
}
Expand Up @@ -6,7 +6,19 @@
* @author Jason Song(song_s@ctrip.com)
*/
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 addChangeListener(RepositoryChangeListener listener);

public void removeChangeListener(RepositoryChangeListener listener);
}
@@ -1,20 +1,27 @@
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.enums.PropertyChangeType;
import com.ctrip.apollo.model.ConfigChange;
import com.ctrip.apollo.model.ConfigChangeEvent;
import com.dianping.cat.Cat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
* @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 final String m_namespace;
private Properties m_resourceProperties;
Expand All @@ -30,7 +37,8 @@ public DefaultConfig(String namespace, ConfigRepository configRepository) {

private void initialize() {
try {
m_configProperties = m_configRepository.loadConfig();
m_configProperties = m_configRepository.getConfig();
m_configRepository.addChangeListener(this);
} catch (Throwable ex) {
String message = String.format("Init Apollo Local Config failed - namespace: %s",
m_namespace);
Expand Down Expand Up @@ -68,6 +76,69 @@ public String getProperty(String key, String defaultValue) {
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) {
String name = String.format("META-INF/config/%s.properties", namespace);
InputStream in = ClassLoaderUtil.getLoader().getResourceAsStream(name);
Expand All @@ -92,6 +163,4 @@ private Properties loadFromResource(String namespace) {

return properties;
}


}
Expand Up @@ -19,7 +19,7 @@ public class DefaultConfigManager implements ConfigManager {
@Inject
private ConfigFactoryManager m_factoryManager;

private Map<String, Config> m_configs = Maps.newHashMap();
private Map<String, Config> m_configs = Maps.newConcurrentMap();

@Override
public Config getConfig(String namespace) {
Expand Down

0 comments on commit df474af

Please sign in to comment.