Commit 4c0a4889 by Yiming Liu

Merge pull request #70 from nobodyiam/client-local-cache

Add local cache file persistence
parents 761dd307 b73ecacc
......@@ -2,12 +2,16 @@ package com.ctrip.apollo.internals;
import com.ctrip.apollo.Config;
import com.ctrip.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.apollo.util.ConfigUtil;
import com.dianping.cat.Cat;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
/**
......@@ -19,12 +23,14 @@ public class DefaultConfig implements Config, ConfigLoader {
private Properties m_resourceProperties;
private Properties m_fileProperties;
private ConfigLoader m_fallbackLoader;
private ConfigUtil m_configUtil;
public DefaultConfig(File baseDir, String namespace, ConfigLoader fallbackLoader) {
public DefaultConfig(File baseDir, String namespace, ConfigLoader fallbackLoader, ConfigUtil configUtil) {
m_namespace = namespace;
m_baseDir = baseDir;
m_resourceProperties = loadFromResource(m_namespace);
m_fallbackLoader = fallbackLoader;
m_configUtil = configUtil;
this.initLocalConfig();
}
......@@ -81,12 +87,29 @@ public class DefaultConfig implements Config, ConfigLoader {
return properties;
}
private Properties loadFromFile(File baseDir, String namespace) {
void initLocalConfig() {
m_fileProperties = this.loadFromLocalCacheFile(m_baseDir, m_namespace);
//TODO check if local file is expired
if (m_fileProperties != null) {
return;
}
if (m_fallbackLoader != null) {
m_fileProperties = m_fallbackLoader.loadConfig();
}
if (m_fileProperties == null) {
throw new RuntimeException(
String.format("Init Apollo Local Config failed - namespace: %s",
m_namespace));
}
persistLocalCacheFile(m_baseDir, m_namespace);
}
private Properties loadFromLocalCacheFile(File baseDir, String namespace) {
if (baseDir == null) {
return null;
}
File file = new File(baseDir, namespace + ".properties");
File file = assembleLocalCacheFile(baseDir, namespace);
Properties properties = null;
if (file.isFile() && file.canRead()) {
......@@ -108,23 +131,43 @@ public class DefaultConfig implements Config, ConfigLoader {
// ignore
}
}
} else {
//TODO error handling
}
return properties;
}
void initLocalConfig() {
m_fileProperties = this.loadFromFile(m_baseDir, m_namespace);
//TODO check if local file is expired
if (m_fileProperties == null && m_fallbackLoader != null) {
m_fileProperties = m_fallbackLoader.loadConfig();
void persistLocalCacheFile(File baseDir, String namespace) {
if (baseDir == null) {
return;
}
File file = assembleLocalCacheFile(baseDir, namespace);
OutputStream out = null;
try {
out = new FileOutputStream(file);
m_fileProperties.store(out, "Persisted by DefaultConfig");
} catch (FileNotFoundException ex) {
Cat.logError(ex);
} catch (IOException ex) {
Cat.logError(ex);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
//ignore
}
if (m_fileProperties == null) {
throw new RuntimeException(
String.format("Init Apollo Local Config failed - namespace: %s",
m_namespace));
}
//TODO persist file
}
}
File assembleLocalCacheFile(File baseDir, String namespace) {
String fileName = String.format("%s-%s-%s.properties", m_configUtil.getAppId(),
m_configUtil.getCluster(), namespace);
return new File(baseDir, fileName);
}
@Override
......
......@@ -28,14 +28,15 @@ public class RemoteConfig implements Config, ConfigLoader {
private RestTemplate m_restTemplate;
private ConfigServiceLocator m_serviceLocator;
private String m_namespace;
private ConfigUtil m_configUtil;
private Properties m_remoteProperties;
public RemoteConfig(RestTemplate restTemplate,
ConfigServiceLocator locator, String namespace) {
ConfigServiceLocator locator, String namespace, ConfigUtil configUtil) {
this.m_restTemplate = restTemplate;
this.m_serviceLocator = locator;
this.m_namespace = namespace;
this.m_configUtil = configUtil;
this.initialize();
}
......@@ -61,8 +62,8 @@ public class RemoteConfig implements Config, ConfigLoader {
}
private ApolloConfig loadApolloConfig() {
String appId = ConfigUtil.getInstance().getAppId();
String cluster = ConfigUtil.getInstance().getCluster();
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
try {
ApolloConfig result =
this.getRemoteConfig(m_restTemplate, getConfigServiceUrl(),
......
......@@ -5,6 +5,7 @@ import com.ctrip.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.apollo.internals.ConfigServiceLocator;
import com.ctrip.apollo.internals.DefaultConfig;
import com.ctrip.apollo.internals.RemoteConfig;
import com.ctrip.apollo.util.ConfigUtil;
import org.springframework.web.client.RestTemplate;
import org.unidal.lookup.annotation.Named;
......@@ -21,16 +22,21 @@ public class DefaultConfigFactory implements ConfigFactory {
public DefaultConfigFactory() {
m_baseDir = new File(ClassLoaderUtil.getClassPath() + CONFIG_DIR);
if (!m_baseDir.exists()) {
m_baseDir.mkdir();
}
}
@Override
public Config create(String namespace) {
RemoteConfig remoteConfig = this.createRemoteConfig(namespace);
DefaultConfig defaultConfig = new DefaultConfig(m_baseDir, namespace, remoteConfig);
DefaultConfig defaultConfig =
new DefaultConfig(m_baseDir, namespace, remoteConfig, ConfigUtil.getInstance());
return defaultConfig;
}
public RemoteConfig createRemoteConfig(String namespace) {
return new RemoteConfig(new RestTemplate(), new ConfigServiceLocator(), namespace);
return new RemoteConfig(new RestTemplate(), new ConfigServiceLocator(), namespace,
ConfigUtil.getInstance());
}
}
......@@ -4,6 +4,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.ctrip.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.apollo.util.ConfigUtil;
import org.junit.After;
import org.junit.Before;
......@@ -28,16 +29,15 @@ public class DefaultConfigTest extends ComponentTestCase {
private String someNamespace;
private ConfigLoader fallbackLoader;
private Properties someProperties;
private ConfigUtil someConfigUtil;
@Before
public void setUp() throws Exception {
super.setUp();
someBaseDir = new File("src/test/resources/config-cache");
someBaseDir.deleteOnExit();
someBaseDir.mkdir();
someResourceDir = new File(ClassLoaderUtil.getClassPath() + "/META-INF/config");
someResourceDir.deleteOnExit();
someResourceDir.mkdir();
someNamespace = "someName";
......@@ -45,23 +45,49 @@ public class DefaultConfigTest extends ComponentTestCase {
someProperties.setProperty("defaultKey", "defaultValue");
fallbackLoader = mock(RemoteConfig.class);
when(fallbackLoader.loadConfig()).thenReturn(someProperties);
String someAppId = "someApp";
String someCluster = "someCluster";
someConfigUtil = mock(ConfigUtil.class);
when(someConfigUtil.getAppId()).thenReturn(someAppId);
when(someConfigUtil.getCluster()).thenReturn(someCluster);
}
@Override
@After
public void tearDown() throws Exception {
recursiveDelete(someBaseDir);
recursiveDelete(someResourceDir);
}
//helper method to clean created files
private void recursiveDelete(File file) {
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
for (File f : file.listFiles()) {
recursiveDelete(f);
}
}
file.delete();
}
private String assembleLocalCacheFileName() {
return String.format("%s-%s-%s.properties", someConfigUtil.getAppId(),
someConfigUtil.getCluster(), someNamespace);
}
@Test
public void testGetPropertyWithLocalFile() throws Exception {
File file = new File(someBaseDir, someNamespace + ".properties");
File file = new File(someBaseDir, assembleLocalCacheFileName());
String someKey = "someKey";
String someValue = "someValue";
Files.write(someKey + "=" + someValue, file, Charsets.UTF_8);
DefaultConfig defaultConfig = new DefaultConfig(someBaseDir, someNamespace, fallbackLoader);
DefaultConfig defaultConfig = new DefaultConfig(someBaseDir, someNamespace, fallbackLoader, someConfigUtil);
file.delete();
......@@ -80,7 +106,9 @@ public class DefaultConfigTest extends ComponentTestCase {
Files.write(someKey + "=" + someValue, file, Charsets.UTF_8);
DefaultConfig defaultConfig = new DefaultConfig(someBaseDir, someNamespace, fallbackLoader);
DefaultConfig
defaultConfig =
new DefaultConfig(someBaseDir, someNamespace, fallbackLoader, someConfigUtil);
file.delete();
assertEquals(someValue, defaultConfig.getProperty(someKey, null));
......@@ -101,7 +129,7 @@ public class DefaultConfigTest extends ComponentTestCase {
System.setProperty(someKey, someSystemPropertyValue);
//set up local file
File localCacheFile = new File(someBaseDir, someNamespace + ".properties");
File localCacheFile = new File(someBaseDir, assembleLocalCacheFileName());
Files.write(someKey + "=" + someLocalFileValue, localCacheFile, Charsets.UTF_8);
Files.append(System.getProperty("line.separator"), localCacheFile, Charsets.UTF_8);
Files.append(anotherKey + "=" + someLocalFileValue, localCacheFile, Charsets.UTF_8);
......@@ -114,7 +142,9 @@ public class DefaultConfigTest extends ComponentTestCase {
Files.append(System.getProperty("line.separator"), resourceFile, Charsets.UTF_8);
Files.append(lastKey + "=" + someResourceValue, resourceFile, Charsets.UTF_8);
DefaultConfig defaultConfig = new DefaultConfig(someBaseDir, someNamespace, fallbackLoader);
DefaultConfig
defaultConfig =
new DefaultConfig(someBaseDir, someNamespace, fallbackLoader, someConfigUtil);
String someKeyValue = defaultConfig.getProperty(someKey, null);
String anotherKeyValue = defaultConfig.getProperty(anotherKey, null);
......@@ -131,7 +161,9 @@ public class DefaultConfigTest extends ComponentTestCase {
@Test
public void testInitLocalConfigWithNoLocalFile() throws Exception {
DefaultConfig defaultConfig = new DefaultConfig(someBaseDir, someNamespace, fallbackLoader);
DefaultConfig
defaultConfig =
new DefaultConfig(someBaseDir, someNamespace, fallbackLoader, someConfigUtil);
Properties result = defaultConfig.loadConfig();
......@@ -139,4 +171,24 @@ public class DefaultConfigTest extends ComponentTestCase {
"Default config's properties should be the same as fallback loader's when there is no local cache",
result.entrySet(), equalTo(someProperties.entrySet()));
}
@Test
public void testInitLocalConfigWithNoLocalFileMultipleTimes() throws Exception {
Properties anotherProperties = new Properties();
anotherProperties.setProperty("anotherKey", "anotherValue");
ConfigLoader anotherLoader = mock(RemoteConfig.class);
when(anotherLoader.loadConfig()).thenReturn(anotherProperties);
DefaultConfig
defaultConfig =
new DefaultConfig(someBaseDir, someNamespace, fallbackLoader, someConfigUtil);
DefaultConfig
anotherConfig =
new DefaultConfig(someBaseDir, someNamespace, anotherLoader, someConfigUtil);
assertThat(
"Default config should persist local cache files and return that afterwards",
defaultConfig.loadConfig().entrySet(), equalTo(anotherConfig.loadConfig().entrySet()));
}
}
......@@ -5,6 +5,7 @@ import com.google.common.collect.Maps;
import com.ctrip.apollo.core.dto.ApolloConfig;
import com.ctrip.apollo.core.dto.ServiceDTO;
import com.ctrip.apollo.util.ConfigUtil;
import org.junit.Before;
import org.junit.Test;
......@@ -40,6 +41,8 @@ public class RemoteConfigTest {
private String someNamespace;
@Mock
private ResponseEntity<ApolloConfig> someResponse;
@Mock
private ConfigUtil someConfigUtil;
@Before
public void setUp() throws Exception {
......@@ -47,6 +50,11 @@ public class RemoteConfigTest {
String someServerUrl = "http://someServer";
mockConfigServiceLocator(someServerUrl);
String someAppId = "someApp";
String someCluster = "someCluster";
when(someConfigUtil.getAppId()).thenReturn(someAppId);
when(someConfigUtil.getCluster()).thenReturn(someCluster);
}
@Test
......@@ -64,7 +72,7 @@ public class RemoteConfigTest {
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class),
eq(ApolloConfig.class), anyMap())).thenReturn(someResponse);
RemoteConfig remoteConfig = new RemoteConfig(restTemplate, configServiceLocator, someNamespace);
RemoteConfig remoteConfig = new RemoteConfig(restTemplate, configServiceLocator, someNamespace, someConfigUtil);
assertEquals(someValue, remoteConfig.getProperty(someKey, null));
assertEquals(someDefaultValue, remoteConfig.getProperty(someKeyNotExisted, someDefaultValue));
......@@ -77,7 +85,7 @@ public class RemoteConfigTest {
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class),
eq(ApolloConfig.class), anyMap())).thenReturn(someResponse);
RemoteConfig remoteConfig = new RemoteConfig(restTemplate, configServiceLocator, someNamespace);
new RemoteConfig(restTemplate, configServiceLocator, someNamespace, someConfigUtil);
}
@Test
......@@ -94,7 +102,7 @@ public class RemoteConfigTest {
eq(ApolloConfig.class), anyMap())).thenReturn(someResponse);
RemoteConfig remoteConfig = new RemoteConfig(restTemplate, configServiceLocator, someNamespace);
RemoteConfig remoteConfig = new RemoteConfig(restTemplate, configServiceLocator, someNamespace, someConfigUtil);
Properties config = remoteConfig.loadConfig();
......
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