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