Commit 90513a19 by Jason Song

refactor code and add json config file controller

parent a1e4690d
......@@ -11,6 +11,7 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener;
......@@ -19,11 +20,9 @@ import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtil;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.utils.PropertiesUtil;
import com.dianping.cat.Cat;
import org.hibernate.cache.spi.CacheKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -35,12 +34,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;
......@@ -53,18 +49,20 @@ import javax.servlet.http.HttpServletResponse;
*/
@RestController
@RequestMapping("/configfiles")
public class ConfigFileController implements ReleaseMessageListener{
public class ConfigFileController implements ReleaseMessageListener {
private static final Logger logger = LoggerFactory.getLogger(ConfigFileController.class);
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private static final long MAX_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
private static final long EXPIRE_AFTER_WRITE = 10;
private final HttpHeaders responseHeaders;
private static final long EXPIRE_AFTER_WRITE = 30;
private final HttpHeaders propertiesResponseHeaders;
private final HttpHeaders jsonResponseHeaders;
private final ResponseEntity<String> NOT_FOUND_RESPONSE;
private Cache<String, String> localCache;
private final Multimap<String, String>
watchedKeys2CacheKey = Multimaps.synchronizedSetMultimap(HashMultimap.create());
private final Multimap<String, String>
cacheKey2WatchedKeys = Multimaps.synchronizedSetMultimap(HashMultimap.create());
private static final Gson gson = new Gson();
@Autowired
private ConfigController configController;
......@@ -103,23 +101,60 @@ public class ConfigFileController implements ReleaseMessageListener{
}
})
.build();
responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "text/plain;charset=UTF-8");
propertiesResponseHeaders = new HttpHeaders();
propertiesResponseHeaders.add("Content-Type", "text/plain;charset=UTF-8");
jsonResponseHeaders = new HttpHeaders();
jsonResponseHeaders.add("Content-Type", "application/json;charset=UTF-8");
NOT_FOUND_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@RequestMapping(value = "/{appId}/{clusterName}/{namespace:.+}", method = RequestMethod.GET)
public ResponseEntity<String> queryConfigAsFile(@PathVariable String appId,
public ResponseEntity<String> queryConfigAsProperties(@PathVariable String appId,
@PathVariable String clusterName,
@PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) String clientIp,
HttpServletResponse response)
throws IOException {
String result =
queryConfig(ConfigFileOutputFormat.PROPERTIES, appId, clusterName, namespace, dataCenter,
clientIp, response);
if (result == null) {
return NOT_FOUND_RESPONSE;
}
return new ResponseEntity<>(result, propertiesResponseHeaders, HttpStatus.OK);
}
@RequestMapping(value = "/json/{appId}/{clusterName}/{namespace:.+}", method = RequestMethod.GET)
public ResponseEntity<String> queryConfigAsJson(@PathVariable String appId,
@PathVariable String clusterName,
@PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) String clientIp,
HttpServletResponse response) throws IOException {
String result =
queryConfig(ConfigFileOutputFormat.JSON, appId, clusterName, namespace, dataCenter,
clientIp, response);
if (result == null) {
return NOT_FOUND_RESPONSE;
}
return new ResponseEntity<>(result, jsonResponseHeaders, HttpStatus.OK);
}
String queryConfig(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
String namespace, String dataCenter, String clientIp,
HttpServletResponse response) throws IOException {
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
//TODO add clientIp as key parts?
String cacheKey = assembleCacheKey(appId, clusterName, namespace, dataCenter);
String cacheKey = assembleCacheKey(outputFormat, appId, clusterName, namespace, dataCenter);
String result = localCache.getIfPresent(cacheKey);
......@@ -131,11 +166,19 @@ public class ConfigFileController implements ReleaseMessageListener{
response);
if (apolloConfig == null || apolloConfig.getConfigurations() == null) {
return NOT_FOUND_RESPONSE;
return null;
}
switch (outputFormat) {
case PROPERTIES:
Properties properties = new Properties();
properties.putAll(apolloConfig.getConfigurations());
result = PropertiesUtil.toString(properties);
break;
case JSON:
result = gson.toJson(apolloConfig.getConfigurations());
break;
}
Properties properties = new Properties();
properties.putAll(apolloConfig.getConfigurations());
result = PropertiesUtil.toString(properties);
localCache.put(cacheKey, result);
logger.debug("adding cache for key: {}", cacheKey);
......@@ -153,13 +196,14 @@ public class ConfigFileController implements ReleaseMessageListener{
Cat.logEvent("ConfigFile-Cache-Hit", cacheKey);
}
return new ResponseEntity<>(result, responseHeaders,
HttpStatus.OK);
return result;
}
String assembleCacheKey(String appId, String clusterName, String namespace,
String dataCenter) {
List<String> keyParts = Lists.newArrayList(appId, clusterName, namespace);
String assembleCacheKey(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
String namespace,
String dataCenter) {
List<String> keyParts =
Lists.newArrayList(outputFormat.getValue(), appId, clusterName, namespace);
if (!Strings.isNullOrEmpty(dataCenter)) {
keyParts.add(dataCenter);
}
......@@ -187,4 +231,18 @@ public class ConfigFileController implements ReleaseMessageListener{
localCache.invalidate(cacheKey);
}
}
enum ConfigFileOutputFormat {
PROPERTIES("properties"), JSON("json");
private String value;
ConfigFileOutputFormat(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
......@@ -5,6 +5,8 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.Topics;
......@@ -21,7 +23,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
......@@ -81,7 +83,7 @@ public class ConfigFileControllerTest {
}
@Test
public void testQueryConfigAsFile() throws Exception {
public void testQueryConfigAsProperties() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
String anotherKey = "anotherKey";
......@@ -93,7 +95,7 @@ public class ConfigFileControllerTest {
String cacheKey =
configFileController
.assembleCacheKey(someAppId, someClusterName, someNamespace, someDataCenter);
.assembleCacheKey(ConfigFileController.ConfigFileOutputFormat.PROPERTIES, someAppId, someClusterName, someNamespace, someDataCenter);
Map<String, String> configurations =
ImmutableMap.of(someKey, someValue, anotherKey, anotherValue);
......@@ -108,7 +110,7 @@ public class ConfigFileControllerTest {
ResponseEntity<String> response =
configFileController
.queryConfigAsFile(someAppId, someClusterName, someNamespace, someDataCenter,
.queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someResponse);
assertEquals(2, watchedKeys2CacheKey.size());
......@@ -124,7 +126,7 @@ public class ConfigFileControllerTest {
ResponseEntity<String> anotherResponse =
configFileController
.queryConfigAsFile(someAppId, someClusterName, someNamespace, someDataCenter,
.queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someResponse);
assertEquals(response, anotherResponse);
......@@ -135,6 +137,36 @@ public class ConfigFileControllerTest {
}
@Test
public void testQueryConfigAsJson() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
Gson gson = new Gson();
Type responseType = new TypeToken<Map<String, String>>(){}.getType();
String someWatchKey = "someWatchKey";
Set<String> watchKeys = Sets.newHashSet(someWatchKey);
Map<String, String> configurations =
ImmutableMap.of(someKey, someValue);
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
someResponse)).thenReturn(someApolloConfig);
when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(watchKeysUtil
.assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
.thenReturn(watchKeys);
ResponseEntity<String> response =
configFileController
.queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someResponse);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(configurations, gson.fromJson(response.getBody(), responseType));
}
@Test
public void testHandleMessage() throws Exception {
String someWatchKey = "someWatchKey";
String anotherWatchKey = "anotherWatchKey";
......
......@@ -2,6 +2,8 @@ package com.ctrip.framework.apollo.configservice.integration;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.core.ConfigConsts;
......@@ -13,6 +15,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
......@@ -30,6 +33,8 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
private String somePublicNamespace;
private String someDC;
private String someDefaultCluster;
private Gson gson = new Gson();
private Type mapResponseType = new TypeToken<Map<String, String>>(){}.getType();
@Before
public void setUp() throws Exception {
......@@ -44,7 +49,7 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigAsFile() throws Exception {
public void testQueryConfigAsProperties() throws Exception {
ResponseEntity<String> response =
restTemplate
.getForEntity("{baseurl}/configfiles/{appId}/{clusterName}/{namespace}", String.class,
......@@ -60,7 +65,7 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicConfigAsFile() throws Exception {
public void testQueryPublicConfigAsProperties() throws Exception {
ResponseEntity<String> response =
restTemplate
.getForEntity(
......@@ -78,6 +83,42 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigAsJson() throws Exception {
ResponseEntity<String> response =
restTemplate
.getForEntity("{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}", String.class,
getHostUrl(), someAppId, someCluster, someNamespace);
String result = response.getBody();
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("v2", configs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicConfigAsJson() throws Exception {
ResponseEntity<String> response =
restTemplate
.getForEntity(
"{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDC);
String result = response.getBody();
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("override-someDC-v1", configs.get("k1"));
assertEquals("someDC-v2", configs.get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testConfigChanged() throws Exception {
ResponseEntity<String> response =
restTemplate
......
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