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; m_namespace = namespace; m_configProperties = new AtomicReference<>(); initialize(); } private void initialize() { try { m_configProperties.set(m_configRepository.getConfig()); } catch (Throwable ex) { Tracer.logError(ex); logger.warn("Init Apollo Config File failed - namespace: {}, reason: {}.", m_namespace, ExceptionUtil.getDetailMessage(ex)); } finally { //register the change listener no matter config repository is working or not //so that whenever config repository is recovered, config could get changed m_configRepository.addChangeListener(this); } } @Override public String getNamespace() { return m_namespace; } protected abstract void update(Properties newProperties); @Override public synchronized void onRepositoryChange(String namespace, Properties newProperties) { if (newProperties.equals(m_configProperties.get())) { return; } Properties newConfigProperties = new Properties(); newConfigProperties.putAll(newProperties); 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(); } } }); } } }