Skip to content

Commit

Permalink
Load property source from remote config server
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Song committed Mar 17, 2016
1 parent 46baa01 commit a74c713
Show file tree
Hide file tree
Showing 28 changed files with 712 additions and 15 deletions.
2 changes: 1 addition & 1 deletion apollo-assembly/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.ctrip.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.1</version>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
31 changes: 30 additions & 1 deletion apollo-client/pom.xml
Expand Up @@ -4,12 +4,15 @@
<parent>
<groupId>com.ctrip.apollo</groupId>
<artifactId>apollo</artifactId>
<version>0.0.1</version>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apollo-client</artifactId>
<name>Apollo Client</name>
<properties>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- apollo -->
<dependency>
Expand All @@ -22,19 +25,45 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- end of spring -->
<!-- util -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- end of util -->
<!-- log -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- end of log -->
<!-- test -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<!-- end of test -->
</dependencies>
</project>
Expand Up @@ -2,6 +2,7 @@

import com.ctrip.apollo.client.loader.ConfigLoader;
import com.ctrip.apollo.client.loader.ConfigLoaderFactory;
import com.ctrip.apollo.client.util.ConfigUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
Expand All @@ -27,7 +28,7 @@ public class ApolloConfig implements BeanDefinitionRegistryPostProcessor, Priori
private ConfigurableApplicationContext applicationContext;

public ApolloConfig() {
this.configLoader = ConfigLoaderFactory.getInstance().getMockConfigLoader();
this.configLoader = ConfigLoaderFactory.getInstance().getRemoteConfigLoader();
}

@Override
Expand All @@ -37,10 +38,13 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
String.format("ApplicationContext must implement ConfigurableApplicationContext, but found: %s", applicationContext.getClass().getName()));
}
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
ConfigUtil.getInstance().setApplicationContext(applicationContext);
}

/**
* This is the first method invoked, so we could prepare the property sources here
* This is the first method invoked, so we could prepare the property sources here.
* Specifically we need to finish preparing property source before PropertySourcesPlaceholderConfigurer
* so that configurations could be injected correctly
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Expand Down
@@ -0,0 +1,10 @@
package com.ctrip.apollo.client.constants;

/**
* @author Jason Song(song_s@ctrip.com)
*/
public class Constants {
public static final String APP_ID = "appId";
public static final String VERSION = "version";
public static final String DEFAULT_VERSION_NAME = "latest-release";
}
@@ -1,6 +1,7 @@
package com.ctrip.apollo.client.loader;

import com.ctrip.apollo.client.loader.impl.MockConfigLoader;
import com.ctrip.apollo.client.loader.impl.RemoteConfigLoader;

/**
* @author Jason Song(song_s@ctrip.com)
Expand All @@ -18,4 +19,8 @@ public static ConfigLoaderFactory getInstance() {
public ConfigLoader getMockConfigLoader() {
return new MockConfigLoader();
}

public ConfigLoader getRemoteConfigLoader() {
return new RemoteConfigLoader();
}
}
@@ -1,17 +1,139 @@
package com.ctrip.apollo.client.loader.impl;

import com.ctrip.apollo.client.loader.ConfigLoader;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.ctrip.apollo.client.util.ConfigUtil;
import com.ctrip.apollo.core.environment.Environment;
import com.ctrip.apollo.core.environment.PropertySource;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
* Load config from remote config server
*
* @author Jason Song(song_s@ctrip.com)
*/
public class RemoteConfigLoader implements ConfigLoader {
private static final String PROPERTY_SOURCE_NAME = "ApolloRemoteConfigProperties";
private static final Logger logger = LoggerFactory.getLogger(RemoteConfigLoader.class);
private final RestTemplate restTemplate;
private final ConfigUtil configUtil;
private final ExecutorService executorService;
private final AtomicLong counter;

public RemoteConfigLoader() {
this(new RestTemplate(), ConfigUtil.getInstance());
}

public RemoteConfigLoader(RestTemplate restTemplate, ConfigUtil configUtil) {
this.restTemplate = restTemplate;
this.configUtil = configUtil;
this.counter = new AtomicLong();
this.executorService = Executors.newFixedThreadPool(5, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "RemoteConfigLoader-" + counter.incrementAndGet());
return thread;
}
});
}

@Override
public CompositePropertySource loadPropertySource() {
return null;
CompositePropertySource composite = new CompositePropertySource(PROPERTY_SOURCE_NAME);
List<ApolloRegistry> apolloRegistries;
try {
apolloRegistries = configUtil.loadApolloRegistries();
} catch (IOException e) {
throw new RuntimeException("Load apollo config registry failed", e);
}

if (apolloRegistries == null || apolloRegistries.isEmpty()) {
logger.warn("No Apollo Registry found!");
return composite;
}

try {
doLoadRemoteApolloConfig(apolloRegistries, composite);
} catch (Throwable throwable) {
throw new RuntimeException("Load remote property source failed", throwable);
}
return composite;
}

void doLoadRemoteApolloConfig(List<ApolloRegistry> apolloRegistries, CompositePropertySource compositePropertySource) throws Throwable {
List<Future<CompositePropertySource>> futures = Lists.newArrayList();
for (final ApolloRegistry apolloRegistry : apolloRegistries) {
futures.add(executorService.submit(new Callable<CompositePropertySource>() {
@Override
public CompositePropertySource call() throws Exception {
return loadSingleApolloConfig(apolloRegistry.getAppId(), apolloRegistry.getVersion());
}
}));
}
for (Future<CompositePropertySource> future : futures) {
try {
compositePropertySource.addPropertySource(future.get());
} catch (ExecutionException e) {
throw e.getCause();
}
}
}

CompositePropertySource loadSingleApolloConfig(String appId, String version) {
CompositePropertySource composite = new CompositePropertySource(appId + "-" + version);
Environment result =
this.getRemoteEnvironment(restTemplate, configUtil.getConfigServerUrl(), appId, configUtil.getCluster(), version);
if (result == null) {
logger.error("Loaded environment as null...");
return composite;
}
logger.info("Loaded environment: name={}, cluster={}, label={}, version={}", result.getName(), result.getProfiles(), result.getLabel(), result.getVersion());

for (PropertySource source : result.getPropertySources()) {
composite.addPropertySource(new MapPropertySource(source.getName(), source.getSource()));
}
return composite;
}

Environment getRemoteEnvironment(RestTemplate restTemplate, String uri, String name, String cluster, String release) {
logger.info("Loading environment from {}, name={}, cluster={}, release={}", uri, name, cluster, release);
String path = "/{name}/{cluster}";
Object[] args = new String[] {name, cluster};
if (StringUtils.hasText(release)) {
args = new String[] {name, cluster, release};
path = path + "/{release}";
}
ResponseEntity<Environment> response = null;

try {
// TODO retry
response = restTemplate.exchange(uri
+ path, HttpMethod.GET, new HttpEntity<Void>((Void) null), Environment.class, args);
} catch (Exception e) {
throw e;
}

if (response == null || response.getStatusCode() != HttpStatus.OK) {
return null;
}
Environment result = response.getBody();
return result;
}

}
@@ -0,0 +1,25 @@
package com.ctrip.apollo.client.model;

/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloRegistry {
private String appId;
private String version;

public String getAppId() {
return appId;
}

public String getVersion() {
return version;
}

public void setAppId(String appId) {
this.appId = appId;
}

public void setVersion(String version) {
this.version = version;
}
}
@@ -0,0 +1,80 @@
package com.ctrip.apollo.client.util;

import com.ctrip.apollo.client.constants.Constants;
import com.ctrip.apollo.client.model.ApolloRegistry;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigUtil {
public static final String APOLLO_PROPERTY = "apollo.properties";
private static ConfigUtil configUtil = new ConfigUtil();
private ApplicationContext applicationContext;

private ConfigUtil() {
}

public static ConfigUtil getInstance() {
return configUtil;
}

public String getConfigServerUrl() {
// TODO return the meta server url based on different environments
return "http://localhost:8888";
}

public String getCluster() {
// TODO return the actual cluster
return "default";
}

public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

public List<ApolloRegistry> loadApolloRegistries() throws IOException {
List<URL> resourceUrls =
Collections.list(Thread.currentThread().getContextClassLoader().getResources(APOLLO_PROPERTY));
List<ApolloRegistry> registries =
FluentIterable.from(resourceUrls).transform(new Function<URL, ApolloRegistry>() {
@Override
public ApolloRegistry apply(URL input) {
Properties properties = loadPropertiesFromResourceURL(input);
if (properties == null || !properties.containsKey(Constants.APP_ID)) {
return null;
}
ApolloRegistry registry = new ApolloRegistry();
registry.setAppId(properties.getProperty(Constants.APP_ID));
registry.setVersion(properties.getProperty(Constants.VERSION, Constants.DEFAULT_VERSION_NAME));
return registry;
}
}).filter(Predicates.notNull()).toList();
return registries;
}

Properties loadPropertiesFromResourceURL(URL resourceUrl) {
Resource resource = applicationContext.getResource(resourceUrl.toExternalForm());
if (resource == null || !resource.exists()) {
return null;
}
try {
return PropertiesLoaderUtils.loadProperties(new EncodedResource(resource, "UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
2 changes: 2 additions & 0 deletions apollo-client/src/main/resources/apollo.properties
@@ -0,0 +1,2 @@
appId=apollo
version=master
15 changes: 15 additions & 0 deletions apollo-client/src/test/java/com/ctrip/apollo/client/AllTests.java
@@ -0,0 +1,15 @@
package com.ctrip.apollo.client;

import com.ctrip.apollo.client.loader.impl.RemoteConfigLoaderTest;
import com.ctrip.apollo.client.util.ConfigUtilTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({
ApolloConfigTest.class, RemoteConfigLoaderTest.class, ConfigUtilTest.class
})
public class AllTests {

}

0 comments on commit a74c713

Please sign in to comment.