Skip to content

Commit

Permalink
Add config file controller to support get config as plain properties …
Browse files Browse the repository at this point in the history
…file
  • Loading branch information
nobodyiam committed Jul 13, 2016
1 parent f5bc9f8 commit f435f27
Show file tree
Hide file tree
Showing 24 changed files with 691 additions and 111 deletions.
2 changes: 1 addition & 1 deletion apollo-adminservice/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion apollo-assembly/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion apollo-biz/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>apollo</artifactId>
<groupId>com.ctrip.framework.apollo</groupId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apollo-biz</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion apollo-buildtools/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion apollo-client/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
@@ -1,15 +1,14 @@
package com.ctrip.framework.apollo.internals;

import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.utils.PropertiesUtil;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.dianping.cat.Cat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

Expand Down Expand Up @@ -38,13 +37,10 @@ String doGetContent() {
if (m_configProperties.get() == null) {
return null;
}
StringWriter writer = new StringWriter();

try {
m_configProperties.get().store(writer, null);
StringBuffer stringBuffer = writer.getBuffer();
filterPropertiesComment(stringBuffer);
return stringBuffer.toString();
} catch (IOException ex) {
return PropertiesUtil.toString(m_configProperties.get());
} catch (Throwable ex) {
ApolloConfigException exception =
new ApolloConfigException(String
.format("Parse properties file content failed for namespace: %s, cause: %s",
Expand All @@ -54,25 +50,6 @@ String doGetContent() {
}
}

/**
* filter out the first comment line
* @param stringBuffer the string buffer
* @return true if filtered successfully, false otherwise
*/
boolean filterPropertiesComment(StringBuffer stringBuffer) {
//check whether has comment in the first line
if (stringBuffer.charAt(0) != '#') {
return false;
}
int commentLineIndex = stringBuffer.indexOf("\n");
if (commentLineIndex == -1) {
return false;
}
stringBuffer.delete(0, commentLineIndex + 1);
return true;
}


@Override
public boolean hasContent() {
return m_configProperties.get() != null && !m_configProperties.get().isEmpty();
Expand Down
2 changes: 1 addition & 1 deletion apollo-common/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion apollo-configservice/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.5</version>
<version>0.0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
@@ -1,6 +1,7 @@
package com.ctrip.framework.apollo.configservice;

import com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner;
import com.ctrip.framework.apollo.configservice.controller.ConfigFileController;
import com.ctrip.framework.apollo.configservice.controller.NotificationController;

import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -14,10 +15,14 @@
public class ConfigServiceAutoConfiguration {
@Autowired
private NotificationController notificationController;
@Autowired
private ConfigFileController configFileController;

@Bean
public ReleaseMessageScanner releaseMessageScanner() {
ReleaseMessageScanner releaseMessageScanner = new ReleaseMessageScanner();
//handle server cache first
releaseMessageScanner.addMessageListener(configFileController);
releaseMessageScanner.addMessageListener(notificationController);
return releaseMessageScanner;
}
Expand Down
@@ -0,0 +1,187 @@
package com.ctrip.framework.apollo.configservice.controller;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.Weigher;
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.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener;
import com.ctrip.framework.apollo.biz.message.Topics;
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;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
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;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletResponse;

/**
* @author Jason Song(song_s@ctrip.com)
*/
@RestController
@RequestMapping("/configfiles")
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 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());

@Autowired
private ConfigController configController;

@Autowired
private NamespaceUtil namespaceUtil;

@Autowired
private WatchKeysUtil watchKeysUtil;

public ConfigFileController() {
localCache = CacheBuilder.newBuilder()
.expireAfterWrite(EXPIRE_AFTER_WRITE, TimeUnit.MINUTES)
.weigher(new Weigher<String, String>() {
@Override
public int weigh(String key, String value) {
return value == null ? 0 : value.length();
}
})
.maximumWeight(MAX_CACHE_SIZE)
.removalListener(new RemovalListener<String, String>() {
@Override
public void onRemoval(RemovalNotification<String, String> notification) {
String cacheKey = notification.getKey();
logger.debug("removing cache key: {}", cacheKey);
if (!cacheKey2WatchedKeys.containsKey(cacheKey)) {
return;
}
//create a new list to avoid ConcurrentModificationException
List<String> watchedKeys = new ArrayList<>(cacheKey2WatchedKeys.get(cacheKey));
for (String watchedKey : watchedKeys) {
watchedKeys2CacheKey.remove(watchedKey, cacheKey);
}
cacheKey2WatchedKeys.removeAll(cacheKey);
logger.debug("removed cache key: {}", cacheKey);
}
})
.build();
responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "text/plain;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,
@PathVariable String clusterName,
@PathVariable String namespace,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) 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 result = localCache.getIfPresent(cacheKey);

if (Strings.isNullOrEmpty(result)) {
ApolloConfig apolloConfig =
configController
.queryConfig(appId, clusterName, namespace, dataCenter, "-1", clientIp,
response);

if (apolloConfig == null || apolloConfig.getConfigurations() == null) {
return NOT_FOUND_RESPONSE;
}
Properties properties = new Properties();
properties.putAll(apolloConfig.getConfigurations());
result = PropertiesUtil.toString(properties);

localCache.put(cacheKey, result);
logger.debug("adding cache for key: {}", cacheKey);

Set<String> watchedKeys =
watchKeysUtil.assembleAllWatchKeys(appId, clusterName, namespace, dataCenter);

for (String watchedKey : watchedKeys) {
watchedKeys2CacheKey.put(watchedKey, cacheKey);
}

cacheKey2WatchedKeys.putAll(cacheKey, watchedKeys);
logger.debug("added cache for key: {}", cacheKey);
}

return new ResponseEntity<>(result, responseHeaders,
HttpStatus.OK);
}

String assembleCacheKey(String appId, String clusterName, String namespace,
String dataCenter) {
List<String> keyParts = Lists.newArrayList(appId, clusterName, namespace);
if (!Strings.isNullOrEmpty(dataCenter)) {
keyParts.add(dataCenter);
}
return STRING_JOINER.join(keyParts);
}

@Override
public void handleMessage(ReleaseMessage message, String channel) {
logger.info("message received - channel: {}, message: {}", channel, message);

String content = message.getMessage();
if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(content)) {
return;
}

if (!watchedKeys2CacheKey.containsKey(content)) {
return;
}

//create a new list to avoid ConcurrentModificationException
List<String> cacheKeys = new ArrayList<>(watchedKeys2CacheKey.get(content));

for (String cacheKey : cacheKeys) {
logger.debug("invalidate cache key: {}", cacheKey);
localCache.invalidate(cacheKey);
}
}
}

0 comments on commit f435f27

Please sign in to comment.