com.ctrip.framework.apollo.internals.AbstractConfig Maven / Gradle / Ivy
The newest version!
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.enums.PropertyChangeType;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.function.Functions;
import com.ctrip.framework.apollo.util.parser.Parsers;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Jason Song([email protected])
*/
public abstract class AbstractConfig implements Config {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfig.class);
private static final ExecutorService m_executorService;
private final List m_listeners = Lists.newCopyOnWriteArrayList();
private final Map> m_interestedKeys = Maps.newConcurrentMap();
private final Map> m_interestedKeyPrefixes = Maps.newConcurrentMap();
private final ConfigUtil m_configUtil;
private volatile Cache m_integerCache;
private volatile Cache m_longCache;
private volatile Cache m_shortCache;
private volatile Cache m_floatCache;
private volatile Cache m_doubleCache;
private volatile Cache m_byteCache;
private volatile Cache m_booleanCache;
private volatile Cache m_dateCache;
private volatile Cache m_durationCache;
private final Map> m_arrayCache;
private final List allCaches;
private final AtomicLong m_configVersion; //indicate config version
static {
m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory
.create("Config", true));
}
public AbstractConfig() {
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
m_configVersion = new AtomicLong();
m_arrayCache = Maps.newConcurrentMap();
allCaches = Lists.newArrayList();
}
@Override
public void addChangeListener(ConfigChangeListener listener) {
addChangeListener(listener, null);
}
@Override
public void addChangeListener(ConfigChangeListener listener, Set interestedKeys) {
addChangeListener(listener, interestedKeys, null);
}
@Override
public void addChangeListener(ConfigChangeListener listener, Set interestedKeys, Set interestedKeyPrefixes) {
if (!m_listeners.contains(listener)) {
m_listeners.add(listener);
if (interestedKeys != null && !interestedKeys.isEmpty()) {
m_interestedKeys.put(listener, Sets.newHashSet(interestedKeys));
}
if (interestedKeyPrefixes != null && !interestedKeyPrefixes.isEmpty()) {
m_interestedKeyPrefixes.put(listener, Sets.newHashSet(interestedKeyPrefixes));
}
}
}
@Override
public boolean removeChangeListener(ConfigChangeListener listener) {
m_interestedKeys.remove(listener);
m_interestedKeyPrefixes.remove(listener);
return m_listeners.remove(listener);
}
@Override
public Integer getIntProperty(String key, Integer defaultValue) {
try {
if (m_integerCache == null) {
synchronized (this) {
if (m_integerCache == null) {
m_integerCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_INT_FUNCTION, m_integerCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getIntProperty for %s failed, return default value %d", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Long getLongProperty(String key, Long defaultValue) {
try {
if (m_longCache == null) {
synchronized (this) {
if (m_longCache == null) {
m_longCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_LONG_FUNCTION, m_longCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getLongProperty for %s failed, return default value %d", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Short getShortProperty(String key, Short defaultValue) {
try {
if (m_shortCache == null) {
synchronized (this) {
if (m_shortCache == null) {
m_shortCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_SHORT_FUNCTION, m_shortCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getShortProperty for %s failed, return default value %d", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Float getFloatProperty(String key, Float defaultValue) {
try {
if (m_floatCache == null) {
synchronized (this) {
if (m_floatCache == null) {
m_floatCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_FLOAT_FUNCTION, m_floatCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getFloatProperty for %s failed, return default value %f", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Double getDoubleProperty(String key, Double defaultValue) {
try {
if (m_doubleCache == null) {
synchronized (this) {
if (m_doubleCache == null) {
m_doubleCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_DOUBLE_FUNCTION, m_doubleCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getDoubleProperty for %s failed, return default value %f", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Byte getByteProperty(String key, Byte defaultValue) {
try {
if (m_byteCache == null) {
synchronized (this) {
if (m_byteCache == null) {
m_byteCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_BYTE_FUNCTION, m_byteCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getByteProperty for %s failed, return default value %d", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Boolean getBooleanProperty(String key, Boolean defaultValue) {
try {
if (m_booleanCache == null) {
synchronized (this) {
if (m_booleanCache == null) {
m_booleanCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_BOOLEAN_FUNCTION, m_booleanCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getBooleanProperty for %s failed, return default value %b", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public String[] getArrayProperty(String key, final String delimiter, String[] defaultValue) {
try {
if (!m_arrayCache.containsKey(delimiter)) {
synchronized (this) {
if (!m_arrayCache.containsKey(delimiter)) {
m_arrayCache.put(delimiter, this.newCache());
}
}
}
Cache cache = m_arrayCache.get(delimiter);
String[] result = cache.getIfPresent(key);
if (result != null) {
return result;
}
return getValueAndStoreToCache(key, new Function() {
@Override
public String[] apply(String input) {
return input.split(delimiter);
}
}, cache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getArrayProperty for %s failed, return default value", key), ex));
}
return defaultValue;
}
@Override
public > T getEnumProperty(String key, Class enumType, T defaultValue) {
try {
String value = getProperty(key, null);
if (value != null) {
return Enum.valueOf(enumType, value);
}
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getEnumProperty for %s failed, return default value %s", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Date getDateProperty(String key, Date defaultValue) {
try {
if (m_dateCache == null) {
synchronized (this) {
if (m_dateCache == null) {
m_dateCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_DATE_FUNCTION, m_dateCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getDateProperty for %s failed, return default value %s", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Date getDateProperty(String key, String format, Date defaultValue) {
try {
String value = getProperty(key, null);
if (value != null) {
return Parsers.forDate().parse(value, format);
}
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getDateProperty for %s failed, return default value %s", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public Date getDateProperty(String key, String format, Locale locale, Date defaultValue) {
try {
String value = getProperty(key, null);
if (value != null) {
return Parsers.forDate().parse(value, format, locale);
}
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getDateProperty for %s failed, return default value %s", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public long getDurationProperty(String key, long defaultValue) {
try {
if (m_durationCache == null) {
synchronized (this) {
if (m_durationCache == null) {
m_durationCache = newCache();
}
}
}
return getValueFromCache(key, Functions.TO_DURATION_FUNCTION, m_durationCache, defaultValue);
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getDurationProperty for %s failed, return default value %d", key,
defaultValue), ex));
}
return defaultValue;
}
@Override
public T getProperty(String key, Function function, T defaultValue) {
try {
String value = getProperty(key, null);
if (value != null) {
return function.apply(value);
}
} catch (Throwable ex) {
Tracer.logError(new ApolloConfigException(
String.format("getProperty for %s failed, return default value %s", key,
defaultValue), ex));
}
return defaultValue;
}
private T getValueFromCache(String key, Function parser, Cache cache, T defaultValue) {
T result = cache.getIfPresent(key);
if (result != null) {
return result;
}
return getValueAndStoreToCache(key, parser, cache, defaultValue);
}
private T getValueAndStoreToCache(String key, Function parser, Cache cache, T defaultValue) {
long currentConfigVersion = m_configVersion.get();
String value = getProperty(key, null);
if (value != null) {
T result = parser.apply(value);
if (result != null) {
synchronized (this) {
if (m_configVersion.get() == currentConfigVersion) {
cache.put(key, result);
}
}
return result;
}
}
return defaultValue;
}
private Cache newCache() {
Cache cache = CacheBuilder.newBuilder()
.maximumSize(m_configUtil.getMaxConfigCacheSize())
.expireAfterAccess(m_configUtil.getConfigCacheExpireTime(), m_configUtil.getConfigCacheExpireTimeUnit())
.build();
allCaches.add(cache);
return cache;
}
/**
* Clear config cache
*/
protected void clearConfigCache() {
synchronized (this) {
for (Cache c : allCaches) {
if (c != null) {
c.invalidateAll();
}
}
m_configVersion.incrementAndGet();
}
}
protected void fireConfigChange(final ConfigChangeEvent changeEvent) {
for (final ConfigChangeListener listener : m_listeners) {
// check whether the listener is interested in this change event
if (!isConfigChangeListenerInterested(listener, changeEvent)) {
continue;
}
m_executorService.submit(new Runnable() {
@Override
public void run() {
String listenerName = listener.getClass().getName();
Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName);
try {
listener.onChange(changeEvent);
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
Tracer.logError(ex);
logger.error("Failed to invoke config change listener {}", listenerName, ex);
} finally {
transaction.complete();
}
}
});
}
}
private boolean isConfigChangeListenerInterested(ConfigChangeListener configChangeListener, ConfigChangeEvent configChangeEvent) {
Set interestedKeys = m_interestedKeys.get(configChangeListener);
Set interestedKeyPrefixes = m_interestedKeyPrefixes.get(configChangeListener);
if ((interestedKeys == null || interestedKeys.isEmpty())
&& (interestedKeyPrefixes == null || interestedKeyPrefixes.isEmpty())) {
return true; // no interested keys means interested in all keys
}
if (interestedKeys != null) {
for (String interestedKey : interestedKeys) {
if (configChangeEvent.isChanged(interestedKey)) {
return true;
}
}
}
if (interestedKeyPrefixes != null) {
for (String prefix : interestedKeyPrefixes) {
for (final String changedKey : configChangeEvent.changedKeys()) {
if (changedKey.startsWith(prefix)) {
return true;
}
}
}
}
return false;
}
List calcPropertyChanges(String namespace, Properties previous,
Properties current) {
if (previous == null) {
previous = new Properties();
}
if (current == null) {
current = new Properties();
}
Set previousKeys = previous.stringPropertyNames();
Set currentKeys = current.stringPropertyNames();
Set commonKeys = Sets.intersection(previousKeys, currentKeys);
Set newKeys = Sets.difference(currentKeys, commonKeys);
Set removedKeys = Sets.difference(previousKeys, commonKeys);
List changes = Lists.newArrayList();
for (String newKey : newKeys) {
changes.add(new ConfigChange(namespace, newKey, null, current.getProperty(newKey),
PropertyChangeType.ADDED));
}
for (String removedKey : removedKeys) {
changes.add(new ConfigChange(namespace, 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(namespace, commonKey, previousValue, currentValue,
PropertyChangeType.MODIFIED));
}
return changes;
}
}