package com.ctrip.framework.apollo.internals; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.utils.ClassLoaderUtil; import com.ctrip.framework.apollo.exceptions.ApolloConfigException; 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.ExceptionUtil; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; /** * @author Jason Song(song_s@ctrip.com) */ public class LocalFileConfigRepository extends AbstractConfigRepository implements RepositoryChangeListener { private static final Logger logger = LoggerFactory.getLogger(LocalFileConfigRepository.class); private static final String CONFIG_DIR = "/config-cache"; private final String m_namespace; private File m_baseDir; private final ConfigUtil m_configUtil; private volatile Properties m_fileProperties; private volatile ConfigRepository m_upstream; /** * Constructor. * * @param namespace the namespace */ public LocalFileConfigRepository(String namespace) { this(namespace, null); } public LocalFileConfigRepository(String namespace, ConfigRepository upstream) { m_namespace = namespace; m_configUtil = ApolloInjector.getInstance(ConfigUtil.class); this.setLocalCacheDir(findLocalCacheDir(), false); this.setUpstreamRepository(upstream); this.trySync(); } void setLocalCacheDir(File baseDir, boolean syncImmediately) { m_baseDir = baseDir; this.checkLocalConfigCacheDir(m_baseDir); if (syncImmediately) { this.trySync(); } } private File findLocalCacheDir() { try { String defaultCacheDir = m_configUtil.getDefaultLocalCacheDir(); Path path = Paths.get(defaultCacheDir); if (!Files.exists(path)) { Files.createDirectories(path); } if (Files.exists(path) && Files.isWritable(path)) { return new File(defaultCacheDir, CONFIG_DIR); } } catch (Throwable ex) { //ignore } return new File(ClassLoaderUtil.getClassPath(), CONFIG_DIR); } @Override public Properties getConfig() { if (m_fileProperties == null) { sync(); } Properties result = new Properties(); result.putAll(m_fileProperties); return result; } @Override public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) { if (upstreamConfigRepository == null) { return; } //clear previous listener if (m_upstream != null) { m_upstream.removeChangeListener(this); } m_upstream = upstreamConfigRepository; trySyncFromUpstream(); upstreamConfigRepository.addChangeListener(this); } @Override public void onRepositoryChange(String namespace, Properties newProperties) { if (newProperties.equals(m_fileProperties)) { return; } Properties newFileProperties = new Properties(); newFileProperties.putAll(newProperties); updateFileProperties(newFileProperties); this.fireRepositoryChange(namespace, newProperties); } @Override protected void sync() { //sync with upstream immediately boolean syncFromUpstreamResultSuccess = trySyncFromUpstream(); if (syncFromUpstreamResultSuccess) { return; } Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncLocalConfig"); Throwable exception = null; try { transaction.addData("Basedir", m_baseDir.getAbsolutePath()); m_fileProperties = this.loadFromLocalCacheFile(m_baseDir, m_namespace); transaction.setStatus(Transaction.SUCCESS); } catch (Throwable ex) { Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); transaction.setStatus(ex); exception = ex; //ignore } finally { transaction.complete(); } if (m_fileProperties == null) { throw new ApolloConfigException( "Load config from local config failed!", exception); } } private boolean trySyncFromUpstream() { if (m_upstream == null) { return false; } try { Properties properties = m_upstream.getConfig(); updateFileProperties(properties); return true; } catch (Throwable ex) { Tracer.logError(ex); logger .warn("Sync config from upstream repository {} failed, reason: {}", m_upstream.getClass(), ExceptionUtil.getDetailMessage(ex)); } return false; } private synchronized void updateFileProperties(Properties newProperties) { if (newProperties.equals(m_fileProperties)) { return; } this.m_fileProperties = newProperties; persistLocalCacheFile(m_baseDir, m_namespace); } private Properties loadFromLocalCacheFile(File baseDir, String namespace) throws IOException { Preconditions.checkNotNull(baseDir, "Basedir cannot be null"); File file = assembleLocalCacheFile(baseDir, namespace); Properties properties = null; if (file.isFile() && file.canRead()) { InputStream in = null; try { in = new FileInputStream(file); properties = new Properties(); properties.load(in); logger.debug("Loading local config file {} successfully!", file.getAbsolutePath()); } catch (IOException ex) { Tracer.logError(ex); throw new ApolloConfigException(String .format("Loading config from local cache file %s failed", file.getAbsolutePath()), ex); } finally { try { if (in != null) { in.close(); } } catch (IOException ex) { // ignore } } } else { throw new ApolloConfigException( String.format("Cannot read from local cache file %s", file.getAbsolutePath())); } return properties; } void persistLocalCacheFile(File baseDir, String namespace) { if (baseDir == null) { return; } File file = assembleLocalCacheFile(baseDir, namespace); OutputStream out = null; Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistLocalConfigFile"); transaction.addData("LocalConfigFile", file.getAbsolutePath()); try { out = new FileOutputStream(file); m_fileProperties.store(out, "Persisted by DefaultConfig"); transaction.setStatus(Transaction.SUCCESS); } catch (IOException ex) { ApolloConfigException exception = new ApolloConfigException( String.format("Persist local cache file %s failed", file.getAbsolutePath()), ex); Tracer.logError(exception); transaction.setStatus(exception); logger.warn("Persist local cache file {} failed, reason: {}.", file.getAbsolutePath(), ExceptionUtil.getDetailMessage(ex)); } finally { if (out != null) { try { out.close(); } catch (IOException ex) { //ignore } } transaction.complete(); } } private void checkLocalConfigCacheDir(File baseDir) { if (baseDir.exists()) { return; } Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createLocalConfigDir"); transaction.addData("BaseDir", baseDir.getAbsolutePath()); try { Files.createDirectory(baseDir.toPath()); transaction.setStatus(Transaction.SUCCESS); } catch (IOException ex) { ApolloConfigException exception = new ApolloConfigException( String.format("Create local config directory %s failed", baseDir.getAbsolutePath()), ex); Tracer.logError(exception); transaction.setStatus(exception); logger.warn( "Unable to create local config cache directory {}, reason: {}. Will not able to cache config file.", baseDir.getAbsolutePath(), ExceptionUtil.getDetailMessage(ex)); } finally { transaction.complete(); } } File assembleLocalCacheFile(File baseDir, String namespace) { String fileName = String.format("%s.properties", Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR) .join(m_configUtil.getAppId(), m_configUtil.getCluster(), namespace)); return new File(baseDir, fileName); } }