Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add config file controller to support get config as plain properties …
…file
- Loading branch information
Showing
24 changed files
with
691 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
...c/main/java/com/ctrip/framework/apollo/configservice/controller/ConfigFileController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
Oops, something went wrong.