Browse Source

Load property source from remote config server

Jason Song 9 years ago
parent
commit
a74c713b93
28 changed files with 712 additions and 15 deletions
  1. 1 1
      apollo-assembly/pom.xml
  2. 30 1
      apollo-client/pom.xml
  3. 6 2
      apollo-client/src/main/java/com/ctrip/apollo/client/ApolloConfig.java
  4. 10 0
      apollo-client/src/main/java/com/ctrip/apollo/client/constants/Constants.java
  5. 5 0
      apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java
  6. 123 1
      apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java
  7. 25 0
      apollo-client/src/main/java/com/ctrip/apollo/client/model/ApolloRegistry.java
  8. 80 0
      apollo-client/src/main/java/com/ctrip/apollo/client/util/ConfigUtil.java
  9. 2 0
      apollo-client/src/main/resources/apollo.properties
  10. 15 0
      apollo-client/src/test/java/com/ctrip/apollo/client/AllTests.java
  11. 1 1
      apollo-client/src/test/java/com/ctrip/apollo/client/ApolloConfigTest.java
  12. 116 0
      apollo-client/src/test/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoaderTest.java
  13. 77 0
      apollo-client/src/test/java/com/ctrip/apollo/client/util/ConfigUtilTest.java
  14. 19 0
      apollo-client/src/test/resources/log4j2.xml
  15. 1 1
      apollo-configserver/pom.xml
  16. 2 0
      apollo-configserver/src/main/java/com/ctrip/apollo/server/ConfigServerApplication.java
  17. 8 3
      apollo-configserver/src/main/resources/application.yml
  18. 15 1
      apollo-core/pom.xml
  19. 90 0
      apollo-core/src/main/java/com/ctrip/apollo/core/environment/Environment.java
  20. 36 0
      apollo-core/src/main/java/com/ctrip/apollo/core/environment/PropertySource.java
  21. 17 1
      apollo-demo/pom.xml
  22. 2 0
      apollo-demo/src/main/java/com/ctrip/apollo/demo/DemoController.java
  23. 2 0
      apollo-demo/src/main/resources/apollo.properties
  24. 2 0
      apollo-demo/src/main/resources/application.properties
  25. 19 0
      apollo-demo/src/main/resources/log4j2.xml
  26. 1 1
      apollo-metaserver/pom.xml
  27. 1 1
      apollo-portal/pom.xml
  28. 6 1
      pom.xml

+ 1 - 1
apollo-assembly/pom.xml

@@ -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>

+ 30 - 1
apollo-client/pom.xml

@@ -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>
@@ -22,6 +25,10 @@
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
         <!-- end of spring -->
         <!-- util -->
         <dependency>
@@ -29,12 +36,34 @@
             <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>

+ 6 - 2
apollo-client/src/main/java/com/ctrip/apollo/client/ApolloConfig.java

@@ -2,6 +2,7 @@ package com.ctrip.apollo.client;
 
 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;
@@ -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
@@ -37,10 +38,13 @@ public class ApolloConfig implements BeanDefinitionRegistryPostProcessor, Priori
                     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 {

+ 10 - 0
apollo-client/src/main/java/com/ctrip/apollo/client/constants/Constants.java

@@ -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";
+}

+ 5 - 0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java

@@ -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)
@@ -18,4 +19,8 @@ public class ConfigLoaderFactory {
     public ConfigLoader getMockConfigLoader() {
         return new MockConfigLoader();
     }
+
+    public ConfigLoader getRemoteConfigLoader() {
+        return new RemoteConfigLoader();
+    }
 }

+ 123 - 1
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java

@@ -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;
+    }
+
 }

+ 25 - 0
apollo-client/src/main/java/com/ctrip/apollo/client/model/ApolloRegistry.java

@@ -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;
+    }
+}

+ 80 - 0
apollo-client/src/main/java/com/ctrip/apollo/client/util/ConfigUtil.java

@@ -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 - 0
apollo-client/src/main/resources/apollo.properties

@@ -0,0 +1,2 @@
+appId=apollo
+version=master

+ 15 - 0
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 {
+
+}

+ 1 - 1
apollo-client/src/test/java/com/ctrip/apollo/client/ApolloConfigTest.java

@@ -50,7 +50,7 @@ public class ApolloConfigTest {
     }
 
     @Test
-    public void testPreparePropertySourceSuccessful() {
+    public void testPreparePropertySourceSuccessfully() {
         CompositePropertySource somePropertySource = mock(CompositePropertySource.class);
         final ArgumentCaptor<CompositePropertySource> captor = ArgumentCaptor.forClass(CompositePropertySource.class);
 

+ 116 - 0
apollo-client/src/test/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoaderTest.java

@@ -0,0 +1,116 @@
+package com.ctrip.apollo.client.loader.impl;
+
+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.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class RemoteConfigLoaderTest {
+    private RemoteConfigLoader remoteConfigLoader;
+    @Mock
+    private RestTemplate restTemplate;
+    private ConfigUtil configUtil;
+
+    @Before
+    public void setUp() {
+        configUtil = spy(ConfigUtil.getInstance());
+        remoteConfigLoader = spy(new RemoteConfigLoader(restTemplate, configUtil));
+    }
+
+    @Test
+    public void testLoadPropertySource() throws Exception {
+        ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry("someAppId", "someVersion");
+        ApolloRegistry anotherApolloRegistry = assembleSomeApolloRegistry("anotherAppId", "anotherVersion");
+        CompositePropertySource somePropertySource = mock(CompositePropertySource.class);
+        CompositePropertySource anotherPropertySource = mock(CompositePropertySource.class);
+
+        doReturn(Lists.newArrayList(someApolloRegistry, anotherApolloRegistry)).when(configUtil).loadApolloRegistries();
+        doReturn(somePropertySource).when(remoteConfigLoader).loadSingleApolloConfig(someApolloRegistry.getAppId(), someApolloRegistry.getVersion());
+        doReturn(anotherPropertySource).when(remoteConfigLoader).loadSingleApolloConfig(anotherApolloRegistry.getAppId(), anotherApolloRegistry.getVersion());
+
+        CompositePropertySource result = remoteConfigLoader.loadPropertySource();
+
+        assertEquals(2, result.getPropertySources().size());
+        assertTrue(result.getPropertySources().containsAll(Lists.newArrayList(somePropertySource, anotherPropertySource)));
+    }
+
+    @Test
+    public void testLoadPropertySourceWithNoApolloRegistry() throws Exception {
+        doReturn(null).when(configUtil).loadApolloRegistries();
+
+        CompositePropertySource result = remoteConfigLoader.loadPropertySource();
+
+        assertTrue(result.getPropertySources().isEmpty());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testLoadPropertySourceWithError() throws Exception {
+        Exception someException = mock(Exception.class);
+        ApolloRegistry someApolloRegistry = assembleSomeApolloRegistry("someAppId", "someVersion");
+        doReturn(Lists.newArrayList(someApolloRegistry)).when(configUtil).loadApolloRegistries();
+
+        doThrow(someException).when(remoteConfigLoader).loadSingleApolloConfig(someApolloRegistry.getAppId(), someApolloRegistry.getVersion());
+
+        remoteConfigLoader.loadPropertySource();
+    }
+
+    @Test
+    public void testLoadSingleApolloConfig() throws Exception {
+        Environment someRemoteEnv = mock(Environment.class);
+        String someSourceName = "someSource";
+        String anotherSourceName = "anotherSource";
+        Map<String, Object> someMap = Maps.newHashMap();
+        PropertySource somePropertySource = mock(PropertySource.class);
+        PropertySource anotherPropertySource = mock(PropertySource.class);
+
+        when(somePropertySource.getSource()).thenReturn(someMap);
+        when(somePropertySource.getName()).thenReturn(someSourceName);
+        when(anotherPropertySource.getSource()).thenReturn(someMap);
+        when(anotherPropertySource.getName()).thenReturn(anotherSourceName);
+        when(someRemoteEnv.getPropertySources()).thenReturn(Lists.newArrayList(somePropertySource, anotherPropertySource));
+        doReturn(someRemoteEnv).when(remoteConfigLoader).getRemoteEnvironment(any(RestTemplate.class), anyString(), anyString(), anyString(), anyString());
+
+        CompositePropertySource result = remoteConfigLoader.loadSingleApolloConfig("someAppId", "someVersion");
+
+        assertEquals(2, result.getPropertySources().size());
+
+        List<String> resultPropertySourceNames = FluentIterable.from(result.getPropertySources()).transform(new Function<org.springframework.core.env.PropertySource<?>, String>() {
+            @Override
+            public String apply(org.springframework.core.env.PropertySource<?> input) {
+                return input.getName();
+            }
+        }).toList();
+
+        assertTrue(resultPropertySourceNames.containsAll(Lists.newArrayList(someSourceName, anotherSourceName)));
+    }
+
+    private ApolloRegistry assembleSomeApolloRegistry(String someAppId, String someVersion) {
+        ApolloRegistry someApolloRegistry = new ApolloRegistry();
+        someApolloRegistry.setAppId(someAppId);
+        someApolloRegistry.setVersion(someVersion);
+
+        return someApolloRegistry;
+    }
+}

+ 77 - 0
apollo-client/src/test/java/com/ctrip/apollo/client/util/ConfigUtilTest.java

@@ -0,0 +1,77 @@
+package com.ctrip.apollo.client.util;
+
+import com.ctrip.apollo.client.constants.Constants;
+import com.ctrip.apollo.client.model.ApolloRegistry;
+import com.google.common.collect.Lists;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ConfigUtilTest {
+    private ConfigUtil configUtil;
+    @Mock
+    private ConfigurableApplicationContext applicationContext;
+
+    @Before
+    public void setUp() throws Exception {
+        configUtil = spy(ConfigUtil.getInstance());
+
+        configUtil.setApplicationContext(applicationContext);
+    }
+
+    @Test
+    public void testLoadApolloRegistriesSuccessfully() throws Exception {
+        Properties someProperties = mock(Properties.class);
+        preparePropertiesFromLocalResource(someProperties);
+
+        String someAppId = "someApp";
+        String someVersionId = "someVersion";
+
+        when(someProperties.containsKey(Constants.APP_ID)).thenReturn(true);
+        when(someProperties.getProperty(Constants.APP_ID)).thenReturn(someAppId);
+        when(someProperties.getProperty(eq(Constants.VERSION), anyString())).thenReturn(someVersionId);
+
+        List<ApolloRegistry> apolloRegistries =  configUtil.loadApolloRegistries();
+
+        ApolloRegistry apolloRegistry = apolloRegistries.get(0);
+        assertEquals(1, apolloRegistries.size());
+        assertEquals(someAppId, apolloRegistry.getAppId());
+        assertEquals(someVersionId, apolloRegistry.getVersion());
+    }
+
+    @Test
+    public void testLoadApolloRegistriesError() throws Exception {
+        preparePropertiesFromLocalResource(null);
+
+        List<ApolloRegistry> apolloRegistries =  configUtil.loadApolloRegistries();
+
+        assertTrue(apolloRegistries.isEmpty());
+    }
+
+    private void preparePropertiesFromLocalResource(Properties someProperties) throws IOException {
+        ClassLoader someClassLoader = mock(ClassLoader.class);
+        Thread.currentThread().setContextClassLoader(someClassLoader);
+        URL someUrl = new URL("http", "somepath/", "someFile");
+        Enumeration<URL> someResourceUrls = Collections.enumeration(Lists.newArrayList(someUrl));
+
+        when(someClassLoader.getResources(anyString())).thenReturn(someResourceUrls);
+        doReturn(someProperties).when(configUtil).loadPropertiesFromResourceURL(someUrl);
+    }
+}

+ 19 - 0
apollo-client/src/test/resources/log4j2.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration monitorInterval="60">
+    <appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="[apollo-client][%t]%d %-5p [%c] %m%n"/>
+        </Console>
+        <Async name="Async" includeLocation="true">
+            <AppenderRef ref="Console" />
+        </Async>
+    </appenders>
+    <loggers>
+        <logger name="com.ctrip.apollo" additivity="false" level="trace">
+            <AppenderRef ref="Async" level="DEBUG"/>
+        </logger>
+        <root level="INFO">
+            <AppenderRef ref="Async"/>
+        </root>
+    </loggers>
+</configuration>

+ 1 - 1
apollo-configserver/pom.xml

@@ -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>

+ 2 - 0
apollo-configserver/src/main/java/com/ctrip/apollo/server/ConfigServerApplication.java

@@ -1,5 +1,6 @@
 package com.ctrip.apollo.server;
 
+import com.jcraft.jsch.JSch;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.config.server.EnableConfigServer;
@@ -9,6 +10,7 @@ import org.springframework.cloud.config.server.EnableConfigServer;
 public class ConfigServerApplication {
 
   public static void main(String[] args) {
+    JSch.setConfig("StrictHostKeyChecking", "no");//for git server key
     SpringApplication.run(ConfigServerApplication.class, args);
   }
 

+ 8 - 3
apollo-configserver/src/main/resources/application.yml

@@ -1,10 +1,15 @@
+spring:
+  cloud:
+    config:
+      server:
+        git:
+          uri: ssh://git@10.3.2.56:1022/spring-cloud-config-repo.git
+
 server:
   port: 8888
   
 logging:
   level:
     org.springframework.cloud: 'DEBUG'
+  file: /opt/logs/apollo-configserver.log
 
-spring:
-  profiles:
-    active: native

+ 15 - 1
apollo-core/pom.xml

@@ -4,10 +4,24 @@
 	<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-core</artifactId>
 	<name>Apollo Core</name>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<!-- json -->
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-annotations</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		<!-- end of json -->
+	</dependencies>
 </project>

+ 90 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/environment/Environment.java

@@ -0,0 +1,90 @@
+package com.ctrip.apollo.core.environment;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class Environment {
+
+    private String name;
+
+    private String[] profiles = new String[0];
+
+    private String label;
+
+    private List<PropertySource> propertySources = new ArrayList<PropertySource>();
+
+    private String version;
+
+    public Environment(String name, String... profiles) {
+        this(name, profiles, "master", null);
+    }
+
+    @JsonCreator
+    public Environment(@JsonProperty("name") String name,
+                       @JsonProperty("profiles") String[] profiles,
+                       @JsonProperty("label") String label,
+                       @JsonProperty("version") String version) {
+        super();
+        this.name = name;
+        this.profiles = profiles;
+        this.label = label;
+        this.version = version;
+    }
+
+    public void add(PropertySource propertySource) {
+        this.propertySources.add(propertySource);
+    }
+
+    public void addFirst(PropertySource propertySource) {
+        this.propertySources.add(0, propertySource);
+    }
+
+    public List<PropertySource> getPropertySources() {
+        return propertySources;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public String[] getProfiles() {
+        return profiles;
+    }
+
+    public void setProfiles(String[] profiles) {
+        this.profiles = profiles;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    @Override
+    public String toString() {
+        return "Environment [name=" + name + ", profiles=" + Arrays.asList(profiles) + ", label="
+            + label + ", propertySources=" + propertySources + ", version=" + version + "]";
+    }
+}

+ 36 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/environment/PropertySource.java

@@ -0,0 +1,36 @@
+package com.ctrip.apollo.core.environment;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class PropertySource {
+
+    private String name;
+
+    private Map<String, Object> source;
+
+    @JsonCreator
+    public PropertySource(@JsonProperty("name") String name, @JsonProperty("source") Map<String, Object> source) {
+        this.name = name;
+        this.source = source;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Map<String, Object>  getSource() {
+        return source;
+    }
+
+    @Override
+    public String toString() {
+        return "PropertySource [name=" + name + "]";
+    }
+
+}

+ 17 - 1
apollo-demo/pom.xml

@@ -3,7 +3,7 @@
     <parent>
         <artifactId>apollo</artifactId>
         <groupId>com.ctrip.apollo</groupId>
-        <version>0.0.1</version>
+        <version>0.0.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>apollo-demo</artifactId>
@@ -29,6 +29,22 @@
             <groupId>javax.servlet</groupId>
             <artifactId>jstl</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+        </dependency>
         <dependency>
             <groupId>commons-logging</groupId>
             <artifactId>commons-logging</artifactId>

+ 2 - 0
apollo-demo/src/main/java/com/ctrip/apollo/demo/DemoController.java

@@ -2,6 +2,7 @@ package com.ctrip.apollo.demo;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.PropertySource;
 import org.springframework.core.env.Environment;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
  */
 @RestController
 @RequestMapping("/demo")
+@PropertySource("classpath:application.properties")
 public class DemoController {
     @Autowired
     private Environment env;

+ 2 - 0
apollo-demo/src/main/resources/apollo.properties

@@ -0,0 +1,2 @@
+appId=hermes
+version=master

+ 2 - 0
apollo-demo/src/main/resources/application.properties

@@ -0,0 +1,2 @@
+apollo.foo=xxx
+apollo.bar=yyy

+ 19 - 0
apollo-demo/src/main/resources/log4j2.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration monitorInterval="60">
+    <appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="[apollo-demo][%t]%d %-5p [%c] %m%n"/>
+        </Console>
+        <Async name="Async" includeLocation="true">
+            <AppenderRef ref="Console" />
+        </Async>
+    </appenders>
+    <loggers>
+        <logger name="com.ctrip.apollo" additivity="false" level="trace">
+            <AppenderRef ref="Async" level="DEBUG"/>
+        </logger>
+        <root level="INFO">
+            <AppenderRef ref="Async"/>
+        </root>
+    </loggers>
+</configuration>

+ 1 - 1
apollo-metaserver/pom.xml

@@ -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>

+ 1 - 1
apollo-portal/pom.xml

@@ -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>

+ 6 - 1
pom.xml

@@ -4,7 +4,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>com.ctrip.apollo</groupId>
 	<artifactId>apollo</artifactId>
-	<version>0.0.1</version>
+	<version>0.0.1-SNAPSHOT</version>
 	<name>Apollo</name>
 	<packaging>pom</packaging>
 	<description>Ctrip Configuration Center</description>
@@ -90,6 +90,11 @@
 				<artifactId>mysql-connector-java</artifactId>
 				<version>5.1.38</version>
 			</dependency>
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>19.0</version>
+            </dependency>
 			<!--for test -->
 			<dependency>
 				<groupId>com.h2database</groupId>