From a865a3ca7d5d853a8765752cfbdf69a5453a5106 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 16 Jul 2014 19:48:27 -0500 Subject: [PATCH] Rewrite local connector to eliminate static direct API calls for configuration. --- .../cloud/util/EnvironmentAccessor.java | 23 ++- spring-cloud-localconfig-connector/README.md | 68 ++++---- .../build.gradle | 2 +- .../localconfig/LocalConfigConnector.java | 78 ++++----- .../localconfig/PropertiesFileResolver.java | 150 ++++++++++++++++++ .../AbstractLocalConfigConnectorTest.java | 19 +-- ...tractLocalConfigConnectorWithUrisTest.java | 20 +++ .../LocalConfigConnectorAmqpServiceTest.java | 2 +- .../LocalConfigConnectorMongoServiceTest.java | 2 +- .../LocalConfigConnectorMysqlServiceTest.java | 2 +- ...lConfigConnectorPostgresqlServiceTest.java | 2 +- .../LocalConfigConnectorRedisServiceTest.java | 2 +- .../localconfig/LocalConfigConnectorTest.java | 135 ++-------------- .../LocalConfigServiceOverrideTest.java | 13 +- .../PassthroughEnvironmentAccessor.java | 27 ++++ .../PropertiesFileResolverTest.java | 96 +++++++++++ .../StubbedOpenFileLocalConfigConnector.java | 65 ++++++++ .../resources/spring-cloud-literal.properties | 1 + .../spring-cloud-template.properties | 1 + 19 files changed, 479 insertions(+), 229 deletions(-) create mode 100644 spring-cloud-localconfig-connector/src/main/java/org/springframework/cloud/localconfig/PropertiesFileResolver.java create mode 100644 spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorWithUrisTest.java create mode 100644 spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PassthroughEnvironmentAccessor.java create mode 100644 spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PropertiesFileResolverTest.java create mode 100644 spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/StubbedOpenFileLocalConfigConnector.java create mode 100644 spring-cloud-localconfig-connector/src/test/resources/spring-cloud-literal.properties create mode 100644 spring-cloud-localconfig-connector/src/test/resources/spring-cloud-template.properties diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/util/EnvironmentAccessor.java b/spring-cloud-core/src/main/java/org/springframework/cloud/util/EnvironmentAccessor.java index d38a539..9f60343 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/util/EnvironmentAccessor.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/util/EnvironmentAccessor.java @@ -3,30 +3,39 @@ package org.springframework.cloud.util; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; +import java.util.Properties; import org.springframework.cloud.CloudConnector; import org.springframework.cloud.CloudException; /** * Environment available to the deployed app. - * + * * The main purpose of this class is to allow unit-testing of {@link CloudConnector} implementations * that rely on environment - * + * * @author Ramnivas Laddad */ public class EnvironmentAccessor { - + public Map getEnv() { return System.getenv(); } - + public String getEnvValue(String key) { return System.getenv(key); } - - public String getPropertyValue(String key) { - return System.getProperty(key); + + public Properties getSystemProperties() { + return System.getProperties(); + } + + public String getSystemProperty(String key) { + return getSystemProperty(key, null); + } + + public String getSystemProperty(String key, String def) { + return System.getProperty(key, def); } public String getHost() { diff --git a/spring-cloud-localconfig-connector/README.md b/spring-cloud-localconfig-connector/README.md index 9efeb89..6ee9fcf 100644 --- a/spring-cloud-localconfig-connector/README.md +++ b/spring-cloud-localconfig-connector/README.md @@ -1,29 +1,36 @@ #Spring Cloud local-configuration connector This connector provides the ability to configure Spring Cloud services locally for development -or testing. The current implementation reads from Java properties only; in order to prevent -dependencies on the Spring Framework, the placeholder functionality is unavailable in the -connector. Pull requests for also inspecting environment variables are welcome. +or testing. The current implementation reads from Java properties only. Pull requests for also +inspecting environment variables are welcome. ##Quick start Since service URIs contain passwords and should not be stored in code, this connector does not -attempt to read properties out of the classpath. You can provide a filename with service definitions -by setting the `spring.cloud.propertiesFile` system property: +attempt to read service definitions out of the classpath. You can provide service definitions +as system properties + +```` +java -Dspring.cloud.database='mysql://user:pass@host:1234/dbname' -jar my-app.jar +```` + +and from a configuration properties file either by setting the `spring.cloud.propertiesFile` system property ```` java -Dspring.cloud.propertiesFile=/path/to/spring-cloud.properties -jar my-app.jar ```` - or by passing in an open `InputStream`: +or by providing a *bootstrap* properties file on the runtime classpath named +`spring-cloud-bootstrap.properties`. This file will be inspected for only +the property named `spring.cloud.propertiesFile`, and its value will be interpolated +from the system properties. -````java -InputStream propertyStream = new FileInputStream("/path/to/spring-cloud.properties"); -LocalConfigConnector.supplyProperties(propertyStream); -Cloud cloud = new CloudFactory().getCloud(); +````properties +spring.cloud.propertiesFile: ${user.home}/.config/myApp/spring-cloud.properties ```` -The property file should contain an application ID and the desired services in this format: +The system properties or the configuration properties file should contain an application ID +and the desired services in this format: ````properties spring.cloud.appId: myApp @@ -32,8 +39,7 @@ spring.cloud.database: mysql://user:pass@host:1234/dbname ```` Service type is determined by the URI scheme. The connector will activate if it finds a property -(in the system properties, supplied properties, or the file provided in `spring.cloud.propertiesFile`) -named `spring.cloud.appId`. +(in the system properties or the configuration properties file) named `spring.cloud.appId`. ##Property sources @@ -43,31 +49,33 @@ This connector first attempts to read the system properties generally and a syst If a system property named `spring.cloud.propertiesFile` is found, that file will be loaded as a property list. -###Programmatically supplying properties -You can programmatically supply a property source by calling the static method -`LocalConfigConnector.supplyProperties(InputStream)` before invoking `getCloud()`. -Calling this method will cause the connector to read the stream as a property list -and then close the stream. Calling this method after invoking `getCloud()` will -still read the stream, but the properties will have no effect on the connector -service configuration. Calling this method multiple times will load the supplied -streams onto the same `Properties` object, overwriting duplicates. +###Providing a bootstrap properties file + +To avoid having to manually configure run configurations or test runners with the path to the +configuration properties file, the connector supports reading a templated filename out of the +runtime classpath. This file must be named `spring-cloud-bootstrap.properties` and located +at the classpath root, and for security the connector will not attempt to read any service URIs +out of it. If the connector does find the file, it will read the property +`spring.cloud.propertiesFile` and [substitute the pattern +`${system.property}`](http://commons.apache.org/proper/commons-lang/javadocs/api-release/index.html?org/apache/commons/lang3/text/StrSubstitutor.html) +with the appropriate value from the system properties. The most useful option is generally +`${user.home}`. + +A configuration properties file specified in the system properties will override any bootstrap +file that may be available on the classpath. ###Property precedence -To provide the maximum configuration flexibility, the connector will scan the available -property sources in this order: - -- programmatically-supplied properties -- properties read from `spring.cloud.propertiesFile` -- system properties - -The last definition of a specific service ID wins. The connector will log a message at +To provide the maximum configuration flexibility, the connector will override any properties +(both application ID and service definitions) specified in the file at `spring.cloud.propertiesFile` +with system properties defined at runtime. The connector will log a message at `WARN` if you override a service ID. ##Activating the connector The Spring Cloud core expects exactly one cloud connector match the runtime environment. This connector identifies the "local cloud" by the presence of a property named -`spring.cloud.appId`, which will be used in the `ApplicationInstanceInfo`. +`spring.cloud.appId` in a configuration properties file or the system properties, +which will be used in the `ApplicationInstanceInfo`. ##Service definitions diff --git a/spring-cloud-localconfig-connector/build.gradle b/spring-cloud-localconfig-connector/build.gradle index d020bde..8c3a467 100644 --- a/spring-cloud-localconfig-connector/build.gradle +++ b/spring-cloud-localconfig-connector/build.gradle @@ -2,5 +2,5 @@ description = 'Spring Cloud local-configuration connector' dependencies { compile project(':spring-cloud-core') - testCompile 'com.github.stefanbirkner:system-rules:1.5.0' + compile 'org.apache.commons:commons-lang3:3.3.2' } diff --git a/spring-cloud-localconfig-connector/src/main/java/org/springframework/cloud/localconfig/LocalConfigConnector.java b/spring-cloud-localconfig-connector/src/main/java/org/springframework/cloud/localconfig/LocalConfigConnector.java index 3a8251a..7555e30 100644 --- a/spring-cloud-localconfig-connector/src/main/java/org/springframework/cloud/localconfig/LocalConfigConnector.java +++ b/spring-cloud-localconfig-connector/src/main/java/org/springframework/cloud/localconfig/LocalConfigConnector.java @@ -1,5 +1,6 @@ package org.springframework.cloud.localconfig; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -20,6 +21,7 @@ import org.springframework.cloud.app.BasicApplicationInstanceInfo; import org.springframework.cloud.service.BaseServiceInfo; import org.springframework.cloud.service.FallbackBaseServiceInfoCreator; import org.springframework.cloud.service.UriBasedServiceData; +import org.springframework.cloud.util.EnvironmentAccessor; /** * @@ -46,11 +48,13 @@ public class LocalConfigConnector extends AbstractCloudConnector META_PROPERTIES = Collections.unmodifiableList( Arrays.asList(new String[] { APP_ID_PROPERTY, PROPERTIES_FILE_PROPERTY })); - /*--------------- sources for service-definition properties ---------------*/ + /*--------------- inject system property access for testing ---------------*/ - static Properties programmaticProperties = new Properties(); + private EnvironmentAccessor env = new EnvironmentAccessor(); - private Properties fileProperties = null; + void setEnvironmentAccessor(EnvironmentAccessor env) { + this.env = env; + } /*--------------- API implementation ---------------*/ @@ -59,6 +63,10 @@ public class LocalConfigConnector extends AbstractCloudConnector getServicesData() { - if(fileProperties == null) + if (fileProperties == null) throw new IllegalStateException("isInMatchingCloud() must be called first to initialize connector"); LinkedHashMap propertySources = new LinkedHashMap(); - propertySources.put("programmatic properties", programmaticProperties); propertySources.put("properties from file", fileProperties); try { - propertySources.put("system properties", System.getProperties()); + propertySources.put("system properties", env.getSystemProperties()); } catch (SecurityException e) { logger.log(Level.WARNING, "couldn't read system properties; no service definitions from system properties will be applied", e); @@ -103,20 +110,6 @@ public class LocalConfigConnector extends AbstractCloudConnector 1) + logger.warning("the properties file " + classpathPropertiesFilename + " contained properties besides " + + LocalConfigConnector.PROPERTIES_FILE_PROPERTY + "; ignoring"); + + logger.fine("substituting system properties into '" + template + "'"); + File configFile = new File(new StrSubstitutor(systemPropertiesLookup(env)).replace(template)); + logger.info("derived configuration file name: " + configFile); + + return configFile; + } + + File findCloudPropertiesFile() { + File file = findCloudPropertiesFileFromSystem(); + + if (file != null) { + logger.info("using configuration file from system properties"); + return file; + } + + file = findCloudPropertiesFileFromClasspath(); + + if (file != null) + logger.info("using configuration file derived from " + classpathPropertiesFilename); + else + logger.info("did not find any Spring Cloud configuration file"); + + return file; + } + + /** + * Adapter from the {@link EnvironmentAccessor}'s system-property resolution to the {@code StrLookup} interface. + * + * @param env + * the {@code EnvironmentAccessor} to use for the lookups + * @return a {@code StrLookup} view of the accessor's system properties + */ + private StrLookup systemPropertiesLookup(final EnvironmentAccessor env) { + return new StrLookup() { + @Override + public String lookup(String key) { + return env.getSystemProperty(key); + } + }; + } +} diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorTest.java index 9313717..0874985 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorTest.java @@ -1,14 +1,10 @@ package org.springframework.cloud.localconfig; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.io.InputStream; import java.util.List; -import java.util.Properties; -import org.junit.After; import org.junit.Before; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.UriBasedServiceInfo; @@ -17,7 +13,9 @@ public class AbstractLocalConfigConnectorTest { public static final String PROPERTIES_FILE = "localconfig.testuris.properties"; - protected LocalConfigConnector connector = new LocalConfigConnector(); + protected StubbedOpenFileLocalConfigConnector connector = new StubbedOpenFileLocalConfigConnector(); + + protected PassthroughEnvironmentAccessor env; protected static final String HOSTNAME = "10.20.30.40"; protected static final int PORT = 1234; @@ -26,14 +24,9 @@ public class AbstractLocalConfigConnectorTest { @Before public void init() throws IOException { - InputStream propertiesFile = getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE); - LocalConfigConnector.supplyProperties(propertiesFile); - assertTrue(connector.isInMatchingCloud()); - } - - @After - public void clearProperties() { - LocalConfigConnector.programmaticProperties = new Properties(); + env = new PassthroughEnvironmentAccessor(); + env.setSystemProperty("spring.cloud.baz", "inline!"); + connector.setEnvironmentAccessor(env); } protected static ServiceInfo getServiceInfo(List serviceInfos, String serviceId) { diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorWithUrisTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorWithUrisTest.java new file mode 100644 index 0000000..f87ec1b --- /dev/null +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/AbstractLocalConfigConnectorWithUrisTest.java @@ -0,0 +1,20 @@ +package org.springframework.cloud.localconfig; + +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; + +import org.junit.Before; + +public class AbstractLocalConfigConnectorWithUrisTest extends AbstractLocalConfigConnectorTest { + + public static String PROPERTY_FILE_WITH_URIS = "localconfig.testuris.properties"; + + @Before + public void useTestUris() { + InputStream testUrisProperties = getClass().getClassLoader().getResourceAsStream(PROPERTY_FILE_WITH_URIS); + env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_WITH_URIS); + connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromStream(PROPERTY_FILE_WITH_URIS, testUrisProperties)); + assertTrue(connector.isInMatchingCloud()); + } +} diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorAmqpServiceTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorAmqpServiceTest.java index a515741..59c48e7 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorAmqpServiceTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorAmqpServiceTest.java @@ -9,7 +9,7 @@ import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.AmqpServiceInfo; -public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorTest { +public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorWithUrisTest { @Test public void serviceCreation() { diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMongoServiceTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMongoServiceTest.java index 5fec86f..0acf6b3 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMongoServiceTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMongoServiceTest.java @@ -9,7 +9,7 @@ import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.MongoServiceInfo; -public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorTest { +public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorWithUrisTest { @Test public void serviceCreation() { diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMysqlServiceTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMysqlServiceTest.java index 4e305dd..44aad44 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMysqlServiceTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorMysqlServiceTest.java @@ -9,7 +9,7 @@ import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.MysqlServiceInfo; -public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorTest { +public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorWithUrisTest { @Test public void serviceCreation() { diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorPostgresqlServiceTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorPostgresqlServiceTest.java index 13123a5..9083a71 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorPostgresqlServiceTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorPostgresqlServiceTest.java @@ -9,7 +9,7 @@ import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.PostgresqlServiceInfo; -public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorTest { +public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorWithUrisTest { @Test public void serviceCreation() { diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorRedisServiceTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorRedisServiceTest.java index 29dd550..f199dc2 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorRedisServiceTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorRedisServiceTest.java @@ -9,7 +9,7 @@ import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.RedisServiceInfo; -public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorTest { +public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorWithUrisTest { @Test public void serviceCreation() { diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorTest.java index 9bf3f1d..e0cc3c8 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigConnectorTest.java @@ -4,142 +4,41 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Properties; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.contrib.java.lang.system.ClearSystemProperties; -import org.junit.contrib.java.lang.system.ProvideSystemProperty; -import org.springframework.cloud.service.UriBasedServiceData; -public class LocalConfigConnectorTest { +public class LocalConfigConnectorTest extends AbstractLocalConfigConnectorTest { - static final Charset UTF_8 = Charset.forName("UTF-8"); - - public static final String APP_ID_1 = "appId1"; - public static final String APP_ID_1_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID_1; - - public static final String APP_ID_2 = "appId2"; - public static final String APP_ID_2_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID_2; + public static final String APP_ID = "appId2"; + public static final String APP_ID_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID; public static final String PROPERTY_FILE_NAME = "localconfig.nonsense.properties"; public static final String PROPERTY_FILE_PROPERTY = LocalConfigConnector.PROPERTIES_FILE_PROPERTY + ": " + PROPERTY_FILE_NAME; - public static class DetectAppIdTest { - - private LocalConfigConnector connector; - - @Before - public void setup() { - connector = new LocalConfigConnector(); - } - - @After - public void clearProperties() { - LocalConfigConnector.programmaticProperties = new Properties(); - } - - @Rule - public final ClearSystemProperties NO_APP_ID_PROPERTY = new ClearSystemProperties(LocalConfigConnector.APP_ID_PROPERTY); - - @Test - public void testNoAppIdAnywhere() { - assertFalse(connector.isInMatchingCloud()); - } - - @Test - public void testProgrammaticAppId() throws IOException { - LocalConfigConnector.supplyProperties(new ByteArrayInputStream(APP_ID_1_PROPERTY.getBytes(UTF_8))); - assertTrue(connector.isInMatchingCloud()); - assertEquals(APP_ID_1, connector.getApplicationInstanceInfo().getAppId()); - } - - @Test - public void testProgrammaticAndFileAppIds() throws IOException { - LocalConfigConnector.supplyProperties(new ByteArrayInputStream(APP_ID_1_PROPERTY.getBytes(UTF_8))); - LocalConfigConnector.supplyProperties(new ByteArrayInputStream(PROPERTY_FILE_PROPERTY.getBytes(UTF_8))); - - LocalConfigConnector stubConnector = new LocalConfigConnector() { - @Override - InputStream openFile(String filename) throws IOException { - assertEquals(PROPERTY_FILE_NAME, filename); - return new ByteArrayInputStream(APP_ID_2_PROPERTY.getBytes(UTF_8)); - }; - }; - - assertTrue(stubConnector.isInMatchingCloud()); - assertEquals(APP_ID_2, stubConnector.getApplicationInstanceInfo().getAppId()); - } - - @Test - public void testProgrammaticFilenamePlusSystemAppId() throws IOException { - LocalConfigConnector.supplyProperties(new ByteArrayInputStream(PROPERTY_FILE_PROPERTY.getBytes(UTF_8))); - - LocalConfigConnector stubConnector = new LocalConfigConnector() { - @Override - InputStream openFile(String filename) throws IOException { - assertEquals(PROPERTY_FILE_NAME, filename); - return new ByteArrayInputStream(APP_ID_2_PROPERTY.getBytes(UTF_8)); - }; - }; - - System.setProperty(LocalConfigConnector.APP_ID_PROPERTY, "helloApp"); - assertTrue(stubConnector.isInMatchingCloud()); - assertEquals("helloApp", stubConnector.getApplicationInstanceInfo().getAppId()); - } - } - - private LocalConfigConnector connector; - - InputStream propertiesFile; - - @Before - public void setup() { - connector = new LocalConfigConnector(); - propertiesFile = LocalConfigConnectorTest.class.getClassLoader().getResourceAsStream(PROPERTY_FILE_NAME); - } - - @After - public void cleanup() throws IOException { - LocalConfigConnector.programmaticProperties = new Properties(); - propertiesFile.close(); + @Test + public void testNoAppIdAnywhere() { + assertFalse(connector.isInMatchingCloud()); } @Test - public void testLoadFromFile() throws IOException { - LocalConfigConnector.supplyProperties(propertiesFile); + public void testAppIdInConfigFile() throws IOException { + env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_NAME); + + connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromString(PROPERTY_FILE_NAME, APP_ID_PROPERTY)); assertTrue(connector.isInMatchingCloud()); - assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId()); - - List services = connector.getServicesData(); - assertEquals(2, services.size()); - for (UriBasedServiceData service : services) - if ("foo".equals(service.getKey())) - assertEquals("bar", service.getUri()); + assertEquals(APP_ID, connector.getApplicationInstanceInfo().getAppId()); } - @Rule - public ProvideSystemProperty BAZ_PROPERTY = new ProvideSystemProperty("spring.cloud.baz", "inline!"); - @Test - public void testLoadFromInputStreamWithOverride() throws IOException { - LocalConfigConnector.supplyProperties(propertiesFile); + public void testAppIdInFileAndSystem() throws IOException { + env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_NAME); + env.setSystemProperty(LocalConfigConnector.APP_ID_PROPERTY, APP_ID); + + connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromString(PROPERTY_FILE_NAME, APP_ID_PROPERTY)); assertTrue(connector.isInMatchingCloud()); - assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId()); - - List services = connector.getServicesData(); - assertEquals(2, services.size()); - for(UriBasedServiceData service: services) - if("baz".equals(service.getKey())) - assertEquals("inline!", service.getUri()); + assertEquals(APP_ID, connector.getApplicationInstanceInfo().getAppId()); } } diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigServiceOverrideTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigServiceOverrideTest.java index 980067a..6bef1dc 100644 --- a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigServiceOverrideTest.java +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/LocalConfigServiceOverrideTest.java @@ -6,22 +6,16 @@ import static org.junit.Assert.assertTrue; import java.util.List; -import org.junit.Rule; import org.junit.Test; -import org.junit.contrib.java.lang.system.ProvideSystemProperty; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.MongoServiceInfo; -public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnectorTest { - - @Rule - public final ProvideSystemProperty OVERRIDE_MYSQL = - new ProvideSystemProperty( - "spring.cloud.candygram", - "mongodb://youruser:yourpass@40.30.20.10:4321/dbname"); +public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnectorWithUrisTest { @Test public void serviceOverride() { + env.setSystemProperty("spring.cloud.candygram", "mongodb://youruser:yourpass@40.30.20.10:4321/dbname"); + List services = connector.getServiceInfos(); ServiceInfo service = getServiceInfo(services, "candygram"); assertNotNull(service); @@ -30,5 +24,4 @@ public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnector assertEquals("youruser", mongo.getUserName()); assertEquals(4321, mongo.getPort()); } - } diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PassthroughEnvironmentAccessor.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PassthroughEnvironmentAccessor.java new file mode 100644 index 0000000..37403e8 --- /dev/null +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PassthroughEnvironmentAccessor.java @@ -0,0 +1,27 @@ +package org.springframework.cloud.localconfig; + +import java.util.Properties; + +import org.springframework.cloud.util.EnvironmentAccessor; + +class PassthroughEnvironmentAccessor extends EnvironmentAccessor { + private Properties systemProperties = new Properties(System.getProperties()); + + void clear() { + systemProperties.clear(); + } + + void setSystemProperty(String key, String value) { + systemProperties.setProperty(key, value); + } + + @Override + public String getSystemProperty(String key, String defaultValue) { + return systemProperties.getProperty(key, defaultValue); + } + + @Override + public Properties getSystemProperties() { + return systemProperties; + } +} diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PropertiesFileResolverTest.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PropertiesFileResolverTest.java new file mode 100644 index 0000000..1069346 --- /dev/null +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/PropertiesFileResolverTest.java @@ -0,0 +1,96 @@ +package org.springframework.cloud.localconfig; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; + +public class PropertiesFileResolverTest { + + private PassthroughEnvironmentAccessor env; + + private PropertiesFileResolver resolver; + + private String PROPERTIES_FILE_NAME = "/foo/bar.properties"; + + @Before + public void setDefaults() { + env = new PassthroughEnvironmentAccessor(); + resolver = new PropertiesFileResolver(env); + } + + @Test + public void testSecurityExceptionHandling() { + env = mock(PassthroughEnvironmentAccessor.class); + resolver = new PropertiesFileResolver(env); + + when(env.getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY)).thenThrow(new SecurityException()); + assertNull(resolver.findCloudPropertiesFileFromSystem()); + verify(env).getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY); + } + + @Test + public void testMissingSystemProperty() { + assertNull(resolver.findCloudPropertiesFileFromSystem()); + } + + @Test + public void testSystemProperty() { + env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME); + assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFileFromSystem().getPath()); + } + + @Test + public void testNoClasspathFile() { + resolver = new PropertiesFileResolver(env, "bazquux.properties"); + assertNull(resolver.findCloudPropertiesFileFromClasspath()); + } + + @Test + public void testClasspathFileWithoutKey() { + resolver = new PropertiesFileResolver(env, "localconfig.testuris.properties"); + assertNull(resolver.findCloudPropertiesFileFromClasspath()); + } + + @Test + public void testLiteral() { + resolver = new PropertiesFileResolver(env, "spring-cloud-literal.properties"); + assertEquals(PROPERTIES_FILE_NAME, + resolver.findCloudPropertiesFileFromClasspath().getPath()); + } + + @Test + public void testTemplate() { + resolver = new PropertiesFileResolver(env, "spring-cloud-template.properties"); + env.setSystemProperty("user.home", "/foo"); + assertEquals(PROPERTIES_FILE_NAME, + resolver.findCloudPropertiesFileFromClasspath().getPath()); + } + + @Test + public void testFromSystem() { + env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME); + assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFile().getPath()); + } + + @Test + public void testFromClasspath() { + resolver = new PropertiesFileResolver(env, "spring-cloud-template.properties"); + env.setSystemProperty("user.home", "/foo"); + assertEquals(PROPERTIES_FILE_NAME, + resolver.findCloudPropertiesFile().getPath()); + } + + @Test + public void testNowhere() { + assertNull(resolver.findCloudPropertiesFile()); + } + + @Test + public void testPrecedence() { + env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME); + resolver = new PropertiesFileResolver(env, "spring-cloud-literal.properties"); + assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFile().getPath()); + } +} diff --git a/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/StubbedOpenFileLocalConfigConnector.java b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/StubbedOpenFileLocalConfigConnector.java new file mode 100644 index 0000000..88a0467 --- /dev/null +++ b/spring-cloud-localconfig-connector/src/test/java/org/springframework/cloud/localconfig/StubbedOpenFileLocalConfigConnector.java @@ -0,0 +1,65 @@ +package org.springframework.cloud.localconfig; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * Provides an easy way to stub the {@code openFile} method on the local connector. + * + * @author Christopher Smith + * + */ +class StubbedOpenFileLocalConfigConnector extends LocalConfigConnector { + + static final Charset UTF_8 = Charset.forName("UTF-8"); + + private InputStreamProvider fileProvider; + + @Override + InputStream openFile(File file) throws IOException { + return fileProvider.openFile(file); + } + + public void setFileProvider(InputStreamProvider provider) { + this.fileProvider = provider; + } + + interface InputStreamProvider { + InputStream openFile(File file) throws IOException; + } + + /** + * Returns the supplied input stream. Used for reading out of the classpath for testing. + * + * @param filename + * the filename we expect the connector to open + * @param contents + * the contents to return + */ + static InputStreamProvider fileContentsFromStream(final String expectedFilename, final InputStream stream) { + return new InputStreamProvider() { + @Override + public InputStream openFile(File file) throws IOException { + assertEquals(expectedFilename, file.getPath()); + return stream; + } + }; + } + + /** + * Returns a stream view of the provided string. + * + * @param filename + * the filename we expect the connector to open + * @param contents + * the contents to return + */ + static InputStreamProvider fileContentsFromString(final String expectedFilename, final String contents) { + return fileContentsFromStream(expectedFilename, new ByteArrayInputStream(contents.getBytes(UTF_8))); + } +} diff --git a/spring-cloud-localconfig-connector/src/test/resources/spring-cloud-literal.properties b/spring-cloud-localconfig-connector/src/test/resources/spring-cloud-literal.properties new file mode 100644 index 0000000..e560957 --- /dev/null +++ b/spring-cloud-localconfig-connector/src/test/resources/spring-cloud-literal.properties @@ -0,0 +1 @@ +spring.cloud.propertiesFile=/foo/bar.properties \ No newline at end of file diff --git a/spring-cloud-localconfig-connector/src/test/resources/spring-cloud-template.properties b/spring-cloud-localconfig-connector/src/test/resources/spring-cloud-template.properties new file mode 100644 index 0000000..f36a1b9 --- /dev/null +++ b/spring-cloud-localconfig-connector/src/test/resources/spring-cloud-template.properties @@ -0,0 +1 @@ +spring.cloud.propertiesFile=${user.home}/bar.properties \ No newline at end of file