Merge branch 'chrylis-local-connector'

This commit is contained in:
Ramnivas Laddad
2014-07-15 15:04:41 -07:00
60 changed files with 1198 additions and 230 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build
Servers
.gradle
_site
/bin

View File

@@ -3,4 +3,5 @@ rootProject.name = "spring-cloud"
include "${rootProject.name}-core"
include "${rootProject.name}-cloudfoundry-connector"
include "${rootProject.name}-spring-service-connector"
include "${rootProject.name}-heroku-connector"
include "${rootProject.name}-heroku-connector"
include "${rootProject.name}-localconfig-connector"

View File

@@ -5,20 +5,20 @@ import java.util.Map;
import org.springframework.cloud.service.common.AmqpServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class AmqpServiceInfoCreator extends CloudFoundryServiceInfoCreator<AmqpServiceInfo> {
public AmqpServiceInfoCreator() {
super(new Tags("rabbitmq"), "amqp");
super(new Tags("rabbitmq"), AmqpServiceInfo.URI_SCHEME);
}
public AmqpServiceInfo createServiceInfo(Map<String,Object> serviceData) {
@SuppressWarnings("unchecked")
Map<String,Object> credentials = (Map<String, Object>) serviceData.get("credentials");
String id = (String) serviceData.get("name");
String uri = getStringFromCredentials(credentials, "uri", "url");

View File

@@ -12,8 +12,8 @@ import org.springframework.cloud.service.common.MongoServiceInfo;
public class MongoServiceInfoCreator extends CloudFoundryServiceInfoCreator<MongoServiceInfo> {
public MongoServiceInfoCreator() {
super(new Tags("mongodb"), "mongodb");
// the literal in the tag is CloudFoundry-specific
super(new Tags("mongodb"), MongoServiceInfo.URI_SCHEME);
}
public MongoServiceInfo createServiceInfo(Map<String,Object> serviceData) {

View File

@@ -3,14 +3,15 @@ package org.springframework.cloud.cloudfoundry;
import org.springframework.cloud.service.common.MysqlServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class MysqlServiceInfoCreator extends RelationalServiceInfoCreator<MysqlServiceInfo> {
public MysqlServiceInfoCreator() {
super(new Tags("mysql"), "mysql");
// the literal in the tag is CloudFoundry-specific
super(new Tags("mysql"), MysqlServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -4,7 +4,7 @@ import org.springframework.cloud.service.common.OracleServiceInfo;
public class OracleServiceInfoCreator extends RelationalServiceInfoCreator<OracleServiceInfo> {
public OracleServiceInfoCreator() {
super(new Tags(), "oracle");
super(new Tags(), OracleServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -3,14 +3,14 @@ package org.springframework.cloud.cloudfoundry;
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class PostgresqlServiceInfoCreator extends RelationalServiceInfoCreator<PostgresqlServiceInfo> {
public PostgresqlServiceInfoCreator() {
super(new Tags("postgresql"), "postgres");
super(new Tags("postgresql"), PostgresqlServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -12,7 +12,8 @@ import org.springframework.cloud.service.common.RedisServiceInfo;
public class RedisServiceInfoCreator extends CloudFoundryServiceInfoCreator<RedisServiceInfo> {
public RedisServiceInfoCreator() {
super(new Tags("redis"), "redis");
// the literal in the tag is CloudFoundry-specific
super(new Tags("redis"), RedisServiceInfo.URI_SCHEME);
}
public RedisServiceInfo createServiceInfo(Map<String,Object> serviceData) {

View File

@@ -6,7 +6,7 @@ import org.springframework.cloud.service.common.SmtpServiceInfo;
import org.springframework.cloud.util.UriInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
@@ -15,16 +15,17 @@ public class SmtpServiceInfoCreator extends CloudFoundryServiceInfoCreator<SmtpS
private static final int DEFAULT_SMTP_PORT = 587;
public SmtpServiceInfoCreator() {
super(new Tags("smtp"), "smtp");
// the literal in the tag is CloudFoundry-specific
super(new Tags("smtp"), SmtpServiceInfo.URI_SCHEME);
}
public SmtpServiceInfo createServiceInfo(Map<String,Object> serviceData) {
String id = (String) serviceData.get("name");
@SuppressWarnings("unchecked")
Map<String,Object> credentials = (Map<String, Object>) serviceData.get("credentials");
String host = (String) credentials.get("hostname");
int port = DEFAULT_SMTP_PORT;
if (credentials.containsKey("port")) {
port = Integer.parseInt(credentials.get("port").toString());
@@ -33,8 +34,8 @@ public class SmtpServiceInfoCreator extends CloudFoundryServiceInfoCreator<SmtpS
String username = (String) credentials.get("username");
String password = (String) credentials.get("password");
String uri = new UriInfo("smtp", host, port, username, password).toString();
String uri = new UriInfo(SmtpServiceInfo.URI_SCHEME, host, port, username, password).toString();
return new SmtpServiceInfo(id, uri);
}

View File

@@ -1,7 +1,7 @@
package org.springframework.cloud.cloudfoundry;
/**
*
*
* @author Ramnivas Laddad
*
*/
@@ -20,12 +20,13 @@ public abstract class AbstractCloudFoundryConnectorRelationalServiceTest extends
}
protected static String getJdbcUrl(String databaseType, String name) {
// this should be cleaned up more broadly; pull into RelationalServiceInfo interface?
String jdbcUrlDatabaseType = databaseType;
if (databaseType.equals("postgres")) {
jdbcUrlDatabaseType = "postgresql";
}
return "jdbc:" + jdbcUrlDatabaseType + "://" + hostname + ":" + port + "/" + name +
return "jdbc:" + jdbcUrlDatabaseType + "://" + hostname + ":" + port + "/" + name +
"?user=" + username + "&password=" + password;
}

View File

@@ -2,10 +2,12 @@ Spring Cloud Core Library
=========================
The core library to let cloud applications access application information and services.
While Spring applications is one of the main target for this library, it may be used in
While Spring applications is one of the main target for this library, it may be used in
non-Spring projects as well. In fact, **this library doesn't even depend on Spring**.
This library is cloud-agnostic. Through connectors, it supports multiple clouds
This library requires Java 6,
This library is cloud-agnostic. Through connectors, it supports multiple clouds
(with Cloud Foundry and Heroku as the example clouds).
This library also supports an extension to create services connectors of user-desired types.
@@ -13,11 +15,11 @@ This library also supports an extension to create services connectors of user-de
Usage pattern: Application Developers
=====================================
> **Note:** If you are using spring-cloud in a Spring application, you should consider using the
[Java config](../spring-cloud-spring-service-connector#the-java-config) or the
> **Note:** If you are using spring-cloud in a Spring application, you should consider using the
[Java config](../spring-cloud-spring-service-connector#the-java-config) or the
[XML namespace support](../spring-cloud-spring-service-connector#the-cloud-namespace) instead.
* Create a [`CloudFactory`](src/main/java/org/springframework/cloud/CloudFactory.java) instance.
* Create a [`CloudFactory`](src/main/java/org/springframework/cloud/CloudFactory.java) instance.
Creation of a `CloudFactory` instance is a bit expensive, so caching such an instance is recommended.
If you are using a dependency injection frameworks such as Spring, creating a bean for `CloudFactory`
will achieve the caching effect.
@@ -25,45 +27,45 @@ Usage pattern: Application Developers
```java
CloudFactory cloudFactory = new CloudFactory();
```
* Obtain a suitable [`Cloud`](src/main/java/org/springframework/cloud/Cloud.java) for the environment
* Obtain a suitable [`Cloud`](src/main/java/org/springframework/cloud/Cloud.java) for the environment
in which the application is running.
```java
Cloud cloud = cloudFactory.getCloud();
```
Note that you must have a `CloudConnector` implementation suitable
for the environment in which the application is being deployed in your classpath. For example, if you are
deploying the application in Cloud Foundry, you must add [cloudfoundry-connector](../spring-cloud-cloudfoundry-connector)
deploying the application in Cloud Foundry, you must add [cloudfoundry-connector](../spring-cloud-cloudfoundry-connector)
in your classpath. If no suitable `CloudConnctor` is found, the `getCloud()` method will throw a `CloudException`.
* Use the `Cloud` instance to get access to application info, service infos, and create service
* Use the `Cloud` instance to get access to application info, service infos, and create service
connectors.
```java
// ServiceInfo has all the information necessary to connect to the underlying service
cloud.getServiceInfos();
```
```java
// Alternatively, let the cloud create a service connector for you
DataSource ds = cloud.getServiceConnector("inventory-db", DataSource.class, null /* default config */);
```
Usage pattern: Cloud and Service Providers
==========================================
A cloud provider may extends the functionality in two ways:
1. Add new [`CloudConnector`](src/main/java/org/springframework/cloud/CloudConnector.java)s to make
spring-cloud related libraries work with a new cloud.
See [cloudfoundry-connector](../spring-cloud-cloudfoundry-connector)
or [heroku-connector](../spring-cloud-heroku-connector) for an example.
1. Add new [`CloudConnector`](src/main/java/org/springframework/cloud/CloudConnector.java)s to make
spring-cloud related libraries work with a new cloud.
See [cloudfoundry-connector](../spring-cloud-cloudfoundry-connector)
or [heroku-connector](../spring-cloud-heroku-connector) for an example.
This is done declaratively by adding connector classes to:
```
META-INF/services/org.springframework.cloud.CloudConnector
```
2. Add new [`ServiceConnectorCreator`](src/main/java/org/springframework/cloud/service/ServiceConnectorCreator.java)s
to allow creation of service connector objects.
See [spring-service-connector](../spring-cloud-spring-service-connector) for an example.
This is done declaratively by adding creator classes to:
2. Add new [`ServiceConnectorCreator`](src/main/java/org/springframework/cloud/service/ServiceConnectorCreator.java)s
to allow creation of service connector objects.
See [spring-service-connector](../spring-cloud-spring-service-connector) for an example.
This is done declaratively by adding creator classes to:
```
META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator
```

View File

@@ -10,25 +10,25 @@ import org.springframework.cloud.service.ServiceInfo;
/**
* Helper abstract class to simplify {@link CloudConnector} implementations.
*
*
* User the {@link ServiceLoader} approach to looks for file name matching the class passed in constructor
* and registers {@link ServiceInfoCreator} found there.
*
*
* Implementation of {@link CloudConnector}s that wish to support the recommended service scanning approach
* should extends this approach to gain that functionality automatically.
*
*
* @author Ramnivas Laddad
*
*/
public abstract class AbstractCloudConnector<SD> implements CloudConnector {
private static Logger logger = Logger.getLogger(AbstractCloudConnector.class.getName());
protected List<ServiceInfoCreator<?,SD>> serviceInfoCreators = new ArrayList<ServiceInfoCreator<?,SD>>();
protected abstract List<SD> getServicesData();
protected abstract FallbackServiceInfoCreator<?,SD> getFallbackServiceInfoCreator();
public AbstractCloudConnector(Class<? extends ServiceInfoCreator<? extends ServiceInfo, ?>> serviceInfoCreatorClass) {
scanServiceInfoCreators(serviceInfoCreatorClass);
}
@@ -39,7 +39,7 @@ public abstract class AbstractCloudConnector<SD> implements CloudConnector {
for (SD serviceData : getServicesData()) {
serviceInfos.add(getServiceInfo(serviceData));
}
return serviceInfos;
}
@@ -54,14 +54,14 @@ public abstract class AbstractCloudConnector<SD> implements CloudConnector {
registerServiceInfoCreator(serviceInfoCreator);
}
}
private ServiceInfo getServiceInfo(SD serviceData) {
for (ServiceInfoCreator<? extends ServiceInfo,SD> serviceInfoCreator : serviceInfoCreators) {
if (serviceInfoCreator.accept(serviceData)) {
return serviceInfoCreator.createServiceInfo(serviceData);
}
}
// Fallback with a warning
ServiceInfo fallackServiceInfo = getFallbackServiceInfoCreator().createServiceInfo(serviceData);
logger.warning("No suitable service info creator found for service " + fallackServiceInfo.getId()

View File

@@ -38,8 +38,6 @@ import org.springframework.cloud.service.ServiceInfo.ServiceProperty;
*
*/
public class Cloud {
private static Logger logger = Logger.getLogger(Cloud.class.getName());
private CloudConnector cloudConnector;
private ServiceConnectorCreatorRegistry serviceConnectorCreatorRegistry = new ServiceConnectorCreatorRegistry();

View File

@@ -0,0 +1,10 @@
package org.springframework.cloud.service;
import org.springframework.cloud.FallbackServiceInfoCreator;
public class FallbackBaseServiceInfoCreator extends FallbackServiceInfoCreator<BaseServiceInfo, UriBasedServiceData> {
@Override
public BaseServiceInfo createServiceInfo(UriBasedServiceData serviceData) {
return new BaseServiceInfo(serviceData.getKey());
}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.cloud.service;
public class UriBasedServiceData {
private final String key;
private final String uri;
public UriBasedServiceData(String key, String uri) {
this.key = key;
this.uri = uri;
}
public String getKey() {
return key;
}
public String getUri() {
return uri;
}
}

View File

@@ -6,84 +6,96 @@ import org.springframework.cloud.util.UriInfoFactory;
/**
* Common class for all {@link ServiceInfo}s
*
*
* @author Ramnivas Laddad
*
*/
public abstract class UriBasedServiceInfo extends BaseServiceInfo {
private UriInfo uriInfo;
private UriInfo uriInfo;
private static UriInfoFactory uriFactory = new StandardUriInfoFactory();
private static UriInfoFactory uriFactory = new StandardUriInfoFactory();
public UriBasedServiceInfo(String id, String scheme, String host, int port, String username, String password, String path) {
super(id);
this.uriInfo = getUriInfoFactory().createUri(scheme, host, port, username, password, path);
this.uriInfo = validateAndCleanUriInfo(uriInfo);
}
public UriBasedServiceInfo(String id, String uriString) {
super(id);
this.uriInfo = getUriInfoFactory().createUri(uriString);
this.uriInfo = validateAndCleanUriInfo(uriInfo);
}
public UriBasedServiceInfo(String id, String scheme, String host, int port, String username, String password, String path) {
super(id);
this.uriInfo = getUriInfoFactory().createUri(scheme, host, port, username, password, path);
this.uriInfo = validateAndCleanUriInfo(uriInfo);
}
/**
* For URI-based (@link ServiceInfo}s which don't conform to the standard URI
* format, override this method in your own ServiceInfo class to return a
* {@link UriInfoFactory} which will create the appropriate URIs.
*
* @return your special UriInfoFactory
*/
public UriInfoFactory getUriInfoFactory() {
return uriFactory;
}
public UriBasedServiceInfo(String id, String uriString) {
super(id);
this.uriInfo = getUriInfoFactory().createUri(uriString);
this.uriInfo = validateAndCleanUriInfo(uriInfo);
}
@ServiceProperty(category="connection")
public String getUri() {
return uriInfo.getUri().toString();
}
@ServiceProperty(category="connection")
public String getUserName() {
return uriInfo.getUserName();
}
@ServiceProperty(category="connection")
public String getPassword() {
return uriInfo.getPassword();
}
/**
* For URI-based (@link ServiceInfo}s which don't conform to the standard URI
* format, override this method in your own ServiceInfo class to return a {@link UriInfoFactory} which will create the
* appropriate URIs.
*
* @return your special UriInfoFactory
*/
public UriInfoFactory getUriInfoFactory() {
return uriFactory;
}
@ServiceProperty(category="connection")
public String getHost() {
return uriInfo.getHost();
}
@ServiceProperty(category = "connection")
public String getUri() {
return uriInfo.getUri().toString();
}
@ServiceProperty(category="connection")
public int getPort() {
return uriInfo.getPort();
}
@ServiceProperty(category = "connection")
public String getUserName() {
return uriInfo.getUserName();
}
@ServiceProperty(category="connection")
public String getPath() {
return uriInfo.getPath();
}
@ServiceProperty(category = "connection")
public String getPassword() {
return uriInfo.getPassword();
}
@ServiceProperty(category="connection")
public String getQuery() {
return uriInfo.getQuery();
}
@ServiceProperty(category = "connection")
public String getHost() {
return uriInfo.getHost();
}
/**
* Validate the URI and clean it up by using defaults for any missing information, if possible.
*
* @param uriInfo uri info based on parsed payload
* @return cleaned up uri info
*/
protected UriInfo validateAndCleanUriInfo(UriInfo uriInfo) {
return uriInfo;
}
protected UriInfo getUriInfo() {
return uriInfo;
}
@ServiceProperty(category = "connection")
public int getPort() {
return uriInfo.getPort();
}
@ServiceProperty(category = "connection")
public String getPath() {
return uriInfo.getPath();
}
@ServiceProperty(category = "connection")
public String getQuery() {
return uriInfo.getQuery();
}
@ServiceProperty(category = "connection")
public String getScheme() {
return uriInfo.getScheme();
}
/**
* Validate the URI and clean it up by using defaults for any missing information, if possible.
*
* @param uriInfo
* uri info based on parsed payload
* @return cleaned up uri info
*/
protected UriInfo validateAndCleanUriInfo(UriInfo uriInfo) {
return uriInfo;
}
protected UriInfo getUriInfo() {
return uriInfo;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getScheme() + "://" + getUserName() + ":****@" + getHost() + ":" + getPort()
+ "/" + getPath() + "]";
}
}

View File

@@ -0,0 +1,25 @@
package org.springframework.cloud.service;
import org.springframework.cloud.ServiceInfoCreator;
public abstract class UriBasedServiceInfoCreator<SI extends ServiceInfo> implements
ServiceInfoCreator<ServiceInfo, UriBasedServiceData> {
private final String uriScheme;
public UriBasedServiceInfoCreator(String uriScheme) {
this.uriScheme = uriScheme;
}
@Override
public boolean accept(UriBasedServiceData serviceData) {
return serviceData.getUri().toString().startsWith(uriScheme + "://");
}
public abstract SI createServiceInfo(String id, String uri);
@Override
public SI createServiceInfo(UriBasedServiceData serviceData) {
return createServiceInfo(serviceData.getKey(), serviceData.getUri());
}
}

View File

@@ -1,8 +1,8 @@
package org.springframework.cloud.service.common;
import org.springframework.cloud.CloudException;
import org.springframework.cloud.service.UriBasedServiceInfo;
import org.springframework.cloud.service.ServiceInfo.ServiceLabel;
import org.springframework.cloud.service.UriBasedServiceInfo;
import org.springframework.cloud.util.UriInfo;
/**
@@ -13,29 +13,32 @@ import org.springframework.cloud.util.UriInfo;
*/
@ServiceLabel("rabbitmq")
public class AmqpServiceInfo extends UriBasedServiceInfo {
public static final String URI_SCHEME = "amqp";
public AmqpServiceInfo(String id, String host, int port, String username, String password, String virtualHost) {
super(id, "amqp", host, port, username, password, virtualHost);
super(id, URI_SCHEME, host, port, username, password, virtualHost);
}
public AmqpServiceInfo(String id, String uri) throws CloudException {
super(id, uri);
}
@ServiceProperty(category="connection")
public String getVirtualHost() {
return getUriInfo().getPath();
}
@Override
protected UriInfo validateAndCleanUriInfo(UriInfo uriInfo) {
if (!"amqp".equals(uriInfo.getScheme())) {
if (!URI_SCHEME.equals(uriInfo.getScheme())) {
throw new IllegalArgumentException("wrong scheme in amqp URI: " + uriInfo);
}
if (uriInfo.getHost() == null) {
throw new IllegalArgumentException("missing authority in amqp URI: " + uriInfo);
}
int port = uriInfo.getPort();
if (port == -1) {
port = 5672;
@@ -43,7 +46,7 @@ public class AmqpServiceInfo extends UriBasedServiceInfo {
String userName = uriInfo.getUserName();
String password = uriInfo.getPassword();
if (userName == null || password == null) {
throw new IllegalArgumentException("missing userinfo in amqp URI: " + uriInfo);
}

View File

@@ -4,23 +4,26 @@ import org.springframework.cloud.service.UriBasedServiceInfo;
import org.springframework.cloud.service.ServiceInfo.ServiceLabel;
/**
*
*
* @author Ramnivas Laddad
*
*/
@ServiceLabel("mongo")
public class MongoServiceInfo extends UriBasedServiceInfo {
public static final String URI_SCHEME = "mongodb";
public MongoServiceInfo(String id, String host, int port, String username, String password, String db) {
super(id, "mongodb", host, port, username, password, db);
super(id, URI_SCHEME, host, port, username, password, db);
}
public MongoServiceInfo(String id, String uri) {
super(id, uri);
}
@ServiceProperty(category="connection")
public String getDatabase() {
return getUriInfo().getPath();
}
}

View File

@@ -3,14 +3,18 @@ package org.springframework.cloud.service.common;
import org.springframework.cloud.service.ServiceInfo.ServiceLabel;
/**
*
*
* @author Ramnivas Laddad
*
*/
@ServiceLabel("mysql")
public class MysqlServiceInfo extends RelationalServiceInfo {
public MysqlServiceInfo(String id, String url) {
super(id, url, "mysql");
}
public static final String JDBC_URL_TYPE = "mysql";
public static final String URI_SCHEME = JDBC_URL_TYPE;
public MysqlServiceInfo(String id, String url) {
super(id, url, URI_SCHEME);
}
}

View File

@@ -5,8 +5,12 @@ import org.springframework.cloud.service.ServiceInfo;
@ServiceInfo.ServiceLabel("oracle")
public class OracleServiceInfo extends RelationalServiceInfo {
public static final String JDBC_URL_TYPE = "oracle";
public static final String URI_SCHEME = JDBC_URL_TYPE;
public OracleServiceInfo(String id, String url) {
super(id, url, "oracle");
super(id, url, JDBC_URL_TYPE);
}
@Override

View File

@@ -4,13 +4,18 @@ import org.springframework.cloud.service.ServiceInfo.ServiceLabel;
/**
*
*
* @author Ramnivas Laddad
*
*/
@ServiceLabel("postgresql")
public class PostgresqlServiceInfo extends RelationalServiceInfo {
public static final String JDBC_URL_TYPE = "postgresql";
public static final String URI_SCHEME = "postgres";
public PostgresqlServiceInfo(String id, String url) {
super(id, url, "postgresql");
super(id, url, JDBC_URL_TYPE);
}
}

View File

@@ -4,16 +4,19 @@ import org.springframework.cloud.service.UriBasedServiceInfo;
import org.springframework.cloud.service.ServiceInfo.ServiceLabel;
/**
*
*
* @author Ramnivas Laddad
*
*/
@ServiceLabel("redis")
public class RedisServiceInfo extends UriBasedServiceInfo {
public static final String URI_SCHEME = "redis";
public RedisServiceInfo(String id, String host, int port, String password) {
super(id, "redis", host, port, null, password, null);
super(id, URI_SCHEME, host, port, null, password, null);
}
public RedisServiceInfo(String id, String uri) {
super(id, uri);
}

View File

@@ -7,7 +7,7 @@ import org.springframework.cloud.service.UriBasedServiceInfo;
*/
public abstract class RelationalServiceInfo extends UriBasedServiceInfo {
protected String jdbcUrlDatabaseType;
protected final String jdbcUrlDatabaseType;
public RelationalServiceInfo(String id, String uriString, String jdbcUrlDatabaseType) {
super(id, uriString);

View File

@@ -4,10 +4,12 @@ import org.springframework.cloud.service.UriBasedServiceInfo;
public class SmtpServiceInfo extends UriBasedServiceInfo {
public static final String URI_SCHEME = "smtp";
public SmtpServiceInfo(String id, String host, int port, String username, String password) {
super(id, "smtp", host, port, username, password, "");
super(id, URI_SCHEME, host, port, username, password, "");
}
public SmtpServiceInfo(String id, String url) {
super(id, url);
}

View File

@@ -3,14 +3,14 @@ package org.springframework.cloud.heroku;
import org.springframework.cloud.service.common.AmqpServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class AmqpServiceInfoCreator extends HerokuServiceInfoCreator<AmqpServiceInfo> {
public AmqpServiceInfoCreator() {
super("amqp");
super(AmqpServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -10,26 +10,27 @@ import org.springframework.cloud.CloudException;
import org.springframework.cloud.FallbackServiceInfoCreator;
import org.springframework.cloud.ServiceInfoCreator;
import org.springframework.cloud.app.ApplicationInstanceInfo;
import org.springframework.cloud.heroku.HerokuConnector.KeyValuePair;
import org.springframework.cloud.service.BaseServiceInfo;
import org.springframework.cloud.service.FallbackBaseServiceInfoCreator;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.UriBasedServiceData;
import org.springframework.cloud.util.EnvironmentAccessor;
/**
* Implementation of CloudConnector for Heroku
*
*
* Currently support Postgres (default provided), Mysql (Cleardb), MongoDb (MongoLab, MongoHQ, MongoSoup),
* Redis (RedisToGo, RedisCloud, OpenRedis, RedisGreen), and AMQP (CloudAmqp).
*
*
* @author Ramnivas Laddad
*
*/
public class HerokuConnector extends AbstractCloudConnector<HerokuConnector.KeyValuePair> {
public class HerokuConnector extends AbstractCloudConnector<UriBasedServiceData> {
private EnvironmentAccessor environment = new EnvironmentAccessor();
private ApplicationInstanceInfoCreator applicationInstanceInfoCreator
private ApplicationInstanceInfoCreator applicationInstanceInfoCreator
= new ApplicationInstanceInfoCreator(environment);
private List<String> serviceEnvPrefixes;
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -41,28 +42,28 @@ public class HerokuConnector extends AbstractCloudConnector<HerokuConnector.KeyV
public boolean isInMatchingCloud() {
return environment.getEnvValue("DYNO") != null;
}
@Override
public ApplicationInstanceInfo getApplicationInstanceInfo() {
try {
return applicationInstanceInfoCreator.createApplicationInstanceInfo();
} catch (Exception e) {
throw new CloudException(e);
}
}
}
/* package for testing purpose */
void setCloudEnvironment(EnvironmentAccessor environment) {
this.environment = environment;
this.applicationInstanceInfoCreator = new ApplicationInstanceInfoCreator(environment);
}
@Override
protected void registerServiceInfoCreator(ServiceInfoCreator<? extends ServiceInfo, HerokuConnector.KeyValuePair> serviceInfoCreator) {
protected void registerServiceInfoCreator(ServiceInfoCreator<? extends ServiceInfo, UriBasedServiceData> serviceInfoCreator) {
super.registerServiceInfoCreator(serviceInfoCreator);
HerokuServiceInfoCreator<?> herokuServiceInfoCreator = (HerokuServiceInfoCreator<?>)serviceInfoCreator;
String[] envPrefixes = herokuServiceInfoCreator.getEnvPrefixes();
// need to do this since this method gets called during construction and we cannot initialize serviceEnvPrefixes before this
if (serviceEnvPrefixes == null) {
serviceEnvPrefixes = new ArrayList<String>();
@@ -75,17 +76,17 @@ public class HerokuConnector extends AbstractCloudConnector<HerokuConnector.KeyV
* <p>
* Returns map whose key is the env key and value is the associated url
* </p>
* @return information about services bound to the app
* @return information about services bound to the app
*/
protected List<KeyValuePair> getServicesData() {
List<KeyValuePair> serviceData = new ArrayList<KeyValuePair>();
protected List<UriBasedServiceData> getServicesData() {
List<UriBasedServiceData> serviceData = new ArrayList<UriBasedServiceData>();
Map<String,String> env = environment.getEnv();
for (Map.Entry<String, String> envEntry : env.entrySet()) {
for (String envPrefix : serviceEnvPrefixes) {
if (envEntry.getKey().startsWith(envPrefix)) {
serviceData.add(new KeyValuePair(envEntry.getKey(), envEntry.getValue()));
serviceData.add(new UriBasedServiceData(envEntry.getKey(), envEntry.getValue()));
}
}
}
@@ -94,32 +95,7 @@ public class HerokuConnector extends AbstractCloudConnector<HerokuConnector.KeyV
}
@Override
protected FallbackServiceInfoCreator<BaseServiceInfo,KeyValuePair> getFallbackServiceInfoCreator() {
return new HerokuFallbackServiceInfoCreator();
protected FallbackServiceInfoCreator<BaseServiceInfo,UriBasedServiceData> getFallbackServiceInfoCreator() {
return new FallbackBaseServiceInfoCreator();
}
public static class KeyValuePair {
private String key;
private String value;
public KeyValuePair(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}
}
class HerokuFallbackServiceInfoCreator extends FallbackServiceInfoCreator<BaseServiceInfo,KeyValuePair> {
@Override
public BaseServiceInfo createServiceInfo(KeyValuePair serviceData) {
return new BaseServiceInfo(serviceData.getKey());
}
}
}

View File

@@ -1,40 +1,27 @@
package org.springframework.cloud.heroku;
import org.springframework.cloud.ServiceInfoCreator;
import org.springframework.cloud.heroku.HerokuConnector.KeyValuePair;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.UriBasedServiceInfoCreator;
/**
*
*
* @author Ramnivas Laddad
*
*/
public abstract class HerokuServiceInfoCreator<SI extends ServiceInfo> implements ServiceInfoCreator<SI,KeyValuePair> {
public abstract class HerokuServiceInfoCreator<SI extends ServiceInfo> extends UriBasedServiceInfoCreator<SI> {
private String urlProtocol;
public HerokuServiceInfoCreator(String uriScheme) {
super(uriScheme);
}
public HerokuServiceInfoCreator(String urlProtocol) {
this.urlProtocol = urlProtocol;
}
public boolean accept(KeyValuePair serviceData) {
return serviceData.getValue().toString().startsWith(urlProtocol + "://");
}
public abstract SI createServiceInfo(String id, String uri);
public SI createServiceInfo(KeyValuePair serviceData) {
return createServiceInfo(serviceData.getKey(), serviceData.getValue());
}
/**
* Get prefixes for env variable with which the associated {@link ServiceInfo} may be created.
*
*
* Unlike CloudFoundry which exposes VCAP_SERVICES as a single environment to encompass all services bound
* to the app, Heroku expose one environment variable per app. This method allows each info creator to declare
* appropriate env variables.
*
* @return prefixes for the relevant environment variables
* appropriate env variables.
*
* @return prefixes for the relevant environment variables
*/
public abstract String[] getEnvPrefixes();
}

View File

@@ -3,14 +3,14 @@ package org.springframework.cloud.heroku;
import org.springframework.cloud.service.common.MongoServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class MongoServiceInfoCreator extends HerokuServiceInfoCreator<MongoServiceInfo> {
public MongoServiceInfoCreator() {
super("mongodb");
super(MongoServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -3,14 +3,14 @@ package org.springframework.cloud.heroku;
import org.springframework.cloud.service.common.MysqlServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class MysqlServiceInfoCreator extends RelationalServiceInfoCreator<MysqlServiceInfo> {
public MysqlServiceInfoCreator() {
super("mysql");
super(MysqlServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -3,14 +3,14 @@ package org.springframework.cloud.heroku;
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class PostgresqlServiceInfoCreator extends RelationalServiceInfoCreator<PostgresqlServiceInfo> {
public PostgresqlServiceInfoCreator() {
super("postgres");
super(PostgresqlServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -3,14 +3,14 @@ package org.springframework.cloud.heroku;
import org.springframework.cloud.service.common.RedisServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class RedisServiceInfoCreator extends HerokuServiceInfoCreator<RedisServiceInfo> {
public RedisServiceInfoCreator() {
super("redis");
super(RedisServiceInfo.URI_SCHEME);
}
@Override

View File

@@ -10,7 +10,7 @@ import org.springframework.cloud.util.EnvironmentAccessor;
/**
* Base test class that provides setup and utility methods to generate test payload
*
*
* @author Ramnivas Laddad
*
*/
@@ -20,7 +20,7 @@ public abstract class AbstractHerokuConnectorTest {
protected static final String hostname = "10.20.30.40";
protected static final int port = 1234;
protected static String username = "myuser";
protected static final String username = "myuser";
protected static final String password = "mypass";
@Before
@@ -28,7 +28,7 @@ public abstract class AbstractHerokuConnectorTest {
MockitoAnnotations.initMocks(this);
testCloudConnector.setCloudEnvironment(mockEnvironment);
}
protected static ServiceInfo getServiceInfo(List<ServiceInfo> serviceInfos, String serviceId) {
for (ServiceInfo serviceInfo : serviceInfos) {
if (serviceInfo.getId().equals(serviceId)) {

View File

@@ -13,15 +13,15 @@ import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MysqlServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class HerokuConnectorMysqlServiceTest extends AbstractHerokuConnectorRelationalServiceTest {
public HerokuConnectorMysqlServiceTest() {
super("mysql");
super(MysqlServiceInfo.URI_SCHEME);
}
@Test
public void mysqlServiceCreation() {
Map<String, String> env = new HashMap<String, String>();

View File

@@ -13,15 +13,15 @@ import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
/**
*
*
* @author Ramnivas Laddad
*
*/
public class HerokuConnectorPostgresqlServiceTest extends AbstractHerokuConnectorRelationalServiceTest {
public HerokuConnectorPostgresqlServiceTest() {
super("postgres");
super(PostgresqlServiceInfo.URI_SCHEME);
}
@Test
public void postgresqlServiceCreation() {
Map<String, String> env = new HashMap<String, String>();

View File

@@ -0,0 +1 @@
/bin

View File

@@ -0,0 +1,80 @@
Local-configuration connector for Spring Cloud
=======================================
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.
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` property or by passing in an open `InputStream`:
````java
InputStream propertyStream = new FileInputStream("/path/to/spring-cloud.properties");
LocalConfigConnector.supplyProperties(propertyStream);
Cloud cloud = new CloudFactory().getCloud();
````
The property file should contain an application ID and the desired services in this format:
````properties
spring.cloud.appId: myApp
; spring.cloud.{id}: URI
spring.cloud.database: mysql://user:pass@host:1234/dbname
````
Service type is determined by the URI scheme.
Property sources
----------------
This connector first attempts to read the system properties generally and a system property named
`spring.cloud.propertiesFile` specifically. If the system properties are not readable
(the security manager denies `checkPropertiesAccess`), then they will be treated as empty.
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.
###Property order
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
`WARN` if you override a service ID.
Activating the connector
------------------------
The Spring Cloud core expects exactly one cloud connector to return `true` for
`isInMatchingCloud()`. This connector identifies the "local cloud" by the presence of
a property named `spring.cloud.appId`, which will be used in the `ApplicationInstanceInfo`.
Service definitions
-------------------
If the connector is activated, it will iterate through all the available properties
for keys matching the pattern `spring.cloud.{serviceId}`. Each value is interpreted as a URI
to the services, and the type of service is determined from the scheme. All of the standard
`UriBasedServiceInfo`s are supported.
Supporting additional services
------------------------------
Please see the documentation for [cloudfoundry-connector](../spring-cloud-cloudfoundry-connector), since the same
mechanism applies to any cloud connector.
Instance ID
-----------
This connector will create a UUID for use as the instance ID, as Java does not provide
any portable mechanism for reliably determining hostnames or PIDs.

View File

@@ -0,0 +1,6 @@
description = 'Spring Cloud local-configuration connector'
dependencies {
compile project(':spring-cloud-core')
testCompile 'com.github.stefanbirkner:system-rules:1.5.0'
}

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.localconfig;
import org.springframework.cloud.service.common.AmqpServiceInfo;
/**
*
* @author Christopher Smith
*
*/
public class AmqpServiceInfoCreator extends LocalConfigServiceInfoCreator<AmqpServiceInfo>{
public AmqpServiceInfoCreator() {
super(AmqpServiceInfo.URI_SCHEME);
}
@Override
public AmqpServiceInfo createServiceInfo(String id, String uri) {
return new AmqpServiceInfo(id, uri);
}
}

View File

@@ -0,0 +1,190 @@
package org.springframework.cloud.localconfig;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.springframework.cloud.AbstractCloudConnector;
import org.springframework.cloud.FallbackServiceInfoCreator;
import org.springframework.cloud.app.ApplicationInstanceInfo;
import org.springframework.cloud.app.BasicApplicationInstanceInfo;
import org.springframework.cloud.service.BaseServiceInfo;
import org.springframework.cloud.service.FallbackBaseServiceInfoCreator;
import org.springframework.cloud.service.UriBasedServiceData;
/**
*
* @author Christopher Smith
*
*/
public class LocalConfigConnector extends AbstractCloudConnector<UriBasedServiceData> {
private static final Logger logger = Logger.getLogger(LocalConfigConnector.class.getName());
/*--------------- String constants for property keys ---------------*/
public static final String PROPERTY_PREFIX = "spring.cloud.";
public static final Pattern SERVICE_PROPERTY_PATTERN = Pattern.compile("\\A" + Pattern.quote(PROPERTY_PREFIX) + "(.+)" + "\\Z");
public static final String APP_ID_PROPERTY = PROPERTY_PREFIX + "appId";
public static final String PROPERTIES_FILE_PROPERTY = PROPERTY_PREFIX + "propertiesFile";
/**
* These properties configure the connector itself and aren't service definitions.
*/
public static final List<String> META_PROPERTIES = Collections.unmodifiableList(
Arrays.asList(new String[] { APP_ID_PROPERTY, PROPERTIES_FILE_PROPERTY }));
/*--------------- sources for service-definition properties ---------------*/
static Properties programmaticProperties = new Properties();
private Properties fileProperties = null;
/*--------------- API implementation ---------------*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public LocalConfigConnector() {
super((Class) LocalConfigServiceInfoCreator.class);
}
/**
* Returns {@code true} if a property named {@code spring.cloud.appId} is present in any of the property sources.
* On the first call, attempts to load properties from a file specified in {@code spring.cloud.propertiesFile}.
*/
@Override
public boolean isInMatchingCloud() {
if (fileProperties == null)
readFileProperties();
return findProperty(APP_ID_PROPERTY) != null;
}
@Override
public ApplicationInstanceInfo getApplicationInstanceInfo() {
return new BasicApplicationInstanceInfo(UUID.randomUUID().toString(), findProperty(APP_ID_PROPERTY),
Collections.<String, Object> emptyMap());
}
@Override
protected List<UriBasedServiceData> getServicesData() {
if(fileProperties == null)
throw new IllegalStateException("isInMatchingCloud() must be called first to initialize connector");
LinkedHashMap<String, Properties> propertySources = new LinkedHashMap<String, Properties>();
propertySources.put("programmatic properties", programmaticProperties);
propertySources.put("properties from file", fileProperties);
try {
propertySources.put("system properties", System.getProperties());
} catch (SecurityException e) {
logger.log(Level.WARNING,
"couldn't read system properties; no service definitions from system properties will be applied", e);
}
return LocalConfigUtil.readServicesData(propertySources);
}
@Override
protected FallbackServiceInfoCreator<BaseServiceInfo, UriBasedServiceData> getFallbackServiceInfoCreator() {
return new FallbackBaseServiceInfoCreator();
}
/*--------------- methods for manipulating properties and sources ---------------*/
/**
* Adds properties to be scanned from the supplied {@link InputStream}, overwriting
* existing properties with the same name. Closes the stream after loading.
*
* @param propertiesInputStream
* a property list
* @throws IOException
* if the underlying load operation throws an exception
*/
public static void supplyProperties(final InputStream propertiesInputStream) throws IOException {
programmaticProperties.load(propertiesInputStream);
propertiesInputStream.close();
}
/**
* Checks for the presence of a supplied or system property named {@code spring.cloud.propertiesFile}. If the property
* is present, load its contents into {@link #fileProperties}. If there's a problem, log but continue.
*/
private void readFileProperties() {
fileProperties = new Properties();
logger.fine("looking for a properties file");
String filename = null;
filename = programmaticProperties.getProperty(PROPERTIES_FILE_PROPERTY);
try {
filename = System.getProperty(PROPERTIES_FILE_PROPERTY, filename);
} catch (SecurityException e) {
logSystemReadException(PROPERTIES_FILE_PROPERTY, e);
return;
}
if (filename == null) {
logger.info("did not find a system property " + PROPERTIES_FILE_PROPERTY);
return;
}
logger.info("loading properties from file " + filename);
try {
InputStream fis = openFile(filename);
fileProperties.load(fis);
} catch (IOException e) {
logger.log(Level.SEVERE, "exception while loading properties from file " + filename, e);
return;
}
logger.info("properties loaded successfully");
}
/**
* Broken out into a separate method for mocking the filesystem.
* @param filename the file to open
* @return a {@code FileInputStream} to the file
* @throws IOException if opening the file throws
*/
InputStream openFile(String filename) throws IOException {
return new FileInputStream(filename);
}
/**
* Look for a specific property in programmatically-supplied properties, properties from a file,
* or the system properties. Last source wins.
*
* @param key
* the property to look for
* @return the highest-priority value for the key, or {@code null} if the key is not found
*/
private String findProperty(String key) {
String value = programmaticProperties.getProperty(key);
value = fileProperties.getProperty(key, value);
try {
value = System.getProperty(key, value);
} catch (SecurityException e) {
logSystemReadException(key, e);
}
return value;
}
private static void logSystemReadException(String key, SecurityException e) {
logger.log(Level.WARNING, "couldn't read system property " + key, e);
}
}

View File

@@ -0,0 +1,11 @@
package org.springframework.cloud.localconfig;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.UriBasedServiceInfoCreator;
public abstract class LocalConfigServiceInfoCreator<SI extends ServiceInfo> extends UriBasedServiceInfoCreator<SI> {
protected LocalConfigServiceInfoCreator(String uriScheme) {
super(uriScheme);
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.cloud.localconfig;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.springframework.cloud.service.UriBasedServiceData;
public final class LocalConfigUtil {
private static final Logger logger = Logger.getLogger(LocalConfigConnector.class.getName());
private LocalConfigUtil() {
}
static List<UriBasedServiceData> readServicesData(LinkedHashMap<String, Properties> propertySources) {
// we'll turn this into KVPs to return but need to eliminate duplicates first
Map<String, String> collectedServices = new HashMap<String, String>();
// iterate over the property sources in order, extracting matching properties
for (Map.Entry<String, Properties> propertySource : propertySources.entrySet()) {
if(propertySource.getValue().isEmpty()) {
logger.info("no " + propertySource.getKey());
continue;
}
logger.info("reading services from " + propertySource.getKey());
Map<String, String> services = readServices(propertySource.getValue());
// add each of the found services to the list, warning about duplicates
for (Map.Entry<String, String> service : services.entrySet()) {
String oldUri = collectedServices.put(service.getKey(), service.getValue());
if (oldUri == null)
logger.info("added service '" + service.getKey() + "' from " + propertySource.getKey());
else
logger.warning("replaced service '" + service.getKey() + "' with new URI from " + propertySource.getKey());
}
}
// now we have a collated set of service IDs and URIs
List<UriBasedServiceData> serviceData = new ArrayList<UriBasedServiceData>(collectedServices.size());
for (Map.Entry<String, String> serviceInfo : collectedServices.entrySet()) {
serviceData.add(new UriBasedServiceData(serviceInfo.getKey(), serviceInfo.getValue()));
}
return serviceData;
}
/**
* Goes through a {@code Properties} object, finding all service definitions (properties
* prefixed with {@code spring.cloud.} but not in {@code META_PROPERTIES}) and collects {@code (id,URI)} pairs.
*
* @param properties
* the {@code Properties} object to read
* @return all of the service definitions found
*/
static Map<String, String> readServices(Properties properties) {
Map<String, String> services = new HashMap<String, String>();
for (String propertyName : properties.stringPropertyNames()) {
if (LocalConfigConnector.META_PROPERTIES.contains(propertyName)) {
logger.finer("skipping meta property " + propertyName);
continue;
}
Matcher m = LocalConfigConnector.SERVICE_PROPERTY_PATTERN.matcher(propertyName);
if (!m.matches()) {
logger.finest("skipping non-Spring-Cloud property " + propertyName);
continue;
}
String serviceId = m.group(1);
String serviceUri = properties.getProperty(propertyName);
// no URI here because they will contain passwords
logger.fine("found service URI for service " + serviceId);
services.put(serviceId, serviceUri);
}
return services;
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.localconfig;
import org.springframework.cloud.service.common.MongoServiceInfo;
/**
*
* @author Christopher Smith
*
*/
public class MongoServiceInfoCreator extends LocalConfigServiceInfoCreator<MongoServiceInfo>{
public MongoServiceInfoCreator() {
super(MongoServiceInfo.URI_SCHEME);
}
@Override
public MongoServiceInfo createServiceInfo(String id, String uri) {
return new MongoServiceInfo(id, uri);
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.localconfig;
import org.springframework.cloud.service.common.MysqlServiceInfo;
/**
*
* @author Christopher Smith
*
*/
public class MysqlServiceInfoCreator extends LocalConfigServiceInfoCreator<MysqlServiceInfo>{
public MysqlServiceInfoCreator() {
super(MysqlServiceInfo.URI_SCHEME);
}
@Override
public MysqlServiceInfo createServiceInfo(String id, String uri) {
return new MysqlServiceInfo(id, uri);
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.localconfig;
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
/**
*
* @author Christopher Smith
*
*/
public class PostgresqlServiceInfoCreator extends LocalConfigServiceInfoCreator<PostgresqlServiceInfo>{
public PostgresqlServiceInfoCreator() {
super(PostgresqlServiceInfo.URI_SCHEME);
}
@Override
public PostgresqlServiceInfo createServiceInfo(String id, String uri) {
return new PostgresqlServiceInfo(id, uri);
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.localconfig;
import org.springframework.cloud.service.common.RedisServiceInfo;
/**
*
* @author Christopher Smith
*
*/
public class RedisServiceInfoCreator extends LocalConfigServiceInfoCreator<RedisServiceInfo>{
public RedisServiceInfoCreator() {
super(RedisServiceInfo.URI_SCHEME);
}
@Override
public RedisServiceInfo createServiceInfo(String id, String uri) {
return new RedisServiceInfo(id, uri);
}
}

View File

@@ -0,0 +1 @@
org.springframework.cloud.localconfig.LocalConfigConnector

View File

@@ -0,0 +1,5 @@
org.springframework.cloud.localconfig.AmqpServiceInfoCreator
org.springframework.cloud.localconfig.MongoServiceInfoCreator
org.springframework.cloud.localconfig.MysqlServiceInfoCreator
org.springframework.cloud.localconfig.PostgresqlServiceInfoCreator
org.springframework.cloud.localconfig.RedisServiceInfoCreator

View File

@@ -0,0 +1,54 @@
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;
public class AbstractLocalConfigConnectorTest {
public static final String PROPERTIES_FILE = "localconfig.testuris.properties";
protected LocalConfigConnector connector = new LocalConfigConnector();
protected static final String HOSTNAME = "10.20.30.40";
protected static final int PORT = 1234;
protected static final String USERNAME = "myuser";
protected static final String PASSWORD = "mypass";
@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();
}
protected static ServiceInfo getServiceInfo(List<ServiceInfo> serviceInfos, String serviceId) {
for (ServiceInfo serviceInfo : serviceInfos) {
if (serviceInfo.getId().equals(serviceId)) {
return serviceInfo;
}
}
return null;
}
protected static void assertUriParameters(UriBasedServiceInfo serviceInfo) {
assertEquals(HOSTNAME, serviceInfo.getHost());
assertEquals(PORT, serviceInfo.getPort());
assertEquals(USERNAME, serviceInfo.getUserName());
assertEquals(PASSWORD, serviceInfo.getPassword());
}
}

View File

@@ -0,0 +1,23 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.AmqpServiceInfo;
public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorTest {
@Test
public void serviceCreation() {
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "rabbit");
assertNotNull(service);
assertTrue(service instanceof AmqpServiceInfo);
assertUriParameters((AmqpServiceInfo) service);
}
}

View File

@@ -0,0 +1,23 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MongoServiceInfo;
public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorTest {
@Test
public void serviceCreation() {
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "candygram");
assertNotNull(service);
assertTrue(service instanceof MongoServiceInfo);
assertUriParameters((MongoServiceInfo) service);
}
}

View File

@@ -0,0 +1,23 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MysqlServiceInfo;
public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorTest {
@Test
public void serviceCreation() {
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "maria");
assertNotNull(service);
assertTrue(service instanceof MysqlServiceInfo);
assertUriParameters((MysqlServiceInfo) service);
}
}

View File

@@ -0,0 +1,23 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorTest {
@Test
public void serviceCreation() {
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "ingres");
assertNotNull(service);
assertTrue(service instanceof PostgresqlServiceInfo);
assertUriParameters((PostgresqlServiceInfo) service);
}
}

View File

@@ -0,0 +1,23 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.RedisServiceInfo;
public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorTest {
@Test
public void serviceCreation() {
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "blue");
assertNotNull(service);
assertTrue(service instanceof RedisServiceInfo);
assertUriParameters((RedisServiceInfo) service);
}
}

View File

@@ -0,0 +1,145 @@
package org.springframework.cloud.localconfig;
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 {
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 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 testLoadFromFile() throws IOException {
LocalConfigConnector.supplyProperties(propertiesFile);
assertTrue(connector.isInMatchingCloud());
assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId());
List<UriBasedServiceData> services = connector.getServicesData();
assertEquals(2, services.size());
for (UriBasedServiceData service : services)
if ("foo".equals(service.getKey()))
assertEquals("bar", service.getUri());
}
@Rule
public ProvideSystemProperty BAZ_PROPERTY = new ProvideSystemProperty("spring.cloud.baz", "inline!");
@Test
public void testLoadFromInputStreamWithOverride() throws IOException {
LocalConfigConnector.supplyProperties(propertiesFile);
assertTrue(connector.isInMatchingCloud());
assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId());
List<UriBasedServiceData> services = connector.getServicesData();
assertEquals(2, services.size());
for(UriBasedServiceData service: services)
if("baz".equals(service.getKey()))
assertEquals("inline!", service.getUri());
}
}

View File

@@ -0,0 +1,34 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
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");
@Test
public void serviceOverride() {
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "candygram");
assertNotNull(service);
assertTrue(service instanceof MongoServiceInfo);
MongoServiceInfo mongo = (MongoServiceInfo) service;
assertEquals("youruser", mongo.getUserName());
assertEquals(4321, mongo.getPort());
}
}

View File

@@ -0,0 +1,74 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.service.UriBasedServiceData;
public class LocalConfigUtilTest {
private Properties first, second;
private LinkedHashMap<String, Properties> propertySources;
@Before
public void initProperties(){
first = new Properties();
second = new Properties();
propertySources = new LinkedHashMap<String, Properties>();
propertySources.put("first", first);
propertySources.put("second", second);
}
@Test
public void testPropertyParsing() {
first.setProperty("spring.cloud.appId", "should skip me because I'm meta");
first.setProperty("spring.cloud.service1", "one");
first.setProperty("spring.cloud.", "should skip me because I don't have an ID");
first.setProperty("spring.cloud.service.two", "two");
first.setProperty("foobar", "should skip me because I don't match the prefix");
Map<String, String> services = LocalConfigUtil.readServices(first);
assertEquals(2, services.size());
assertEquals("one", services.get("service1"));
assertEquals("two", services.get("service.two"));
}
@Test
public void testCollation() {
first.setProperty("spring.cloud.first", "firstUri");
second.setProperty("spring.cloud.second", "secondUri");
List<UriBasedServiceData> serviceData = LocalConfigUtil.readServicesData(propertySources);
assertEquals(2, serviceData.size());
boolean foundFirst = false;
for(UriBasedServiceData kvp : serviceData) {
if(kvp.getKey().equals("first")) {
assertEquals("firstUri", kvp.getUri());
foundFirst = true;
}
}
assertTrue(foundFirst);
}
@Test
public void testOverride() {
first.setProperty("spring.cloud.duplicate", "firstUri");
second.setProperty("spring.cloud.duplicate", "secondUri");
List<UriBasedServiceData> serviceData = LocalConfigUtil.readServicesData(propertySources);
assertEquals(1, serviceData.size());
UriBasedServiceData kvp = serviceData.get(0);
assertEquals("duplicate", kvp.getKey());
assertEquals("secondUri", kvp.getUri());
}
}

View File

@@ -0,0 +1,3 @@
spring.cloud.appId: testApp
spring.cloud.foo: bar
spring.cloud.baz: quux

View File

@@ -0,0 +1,6 @@
spring.cloud.appId: testAppWithUris
spring.cloud.rabbit: amqp://myuser:mypass@10.20.30.40:1234/queue
spring.cloud.maria: mysql://myuser:mypass@10.20.30.40:1234/dbname
spring.cloud.candygram: mongodb://myuser:mypass@10.20.30.40:1234/dbname
spring.cloud.ingres: postgres://myuser:mypass@10.20.30.40:1234/dbname
spring.cloud.blue: redis://myuser:mypass@10.20.30.40:1234/dbname

View File

@@ -9,7 +9,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.Cloud;
import org.springframework.cloud.CloudException;
import org.springframework.cloud.CloudFactory;
import org.springframework.cloud.config.java.ServiceScan;
import org.springframework.cloud.service.GenericCloudServiceConnectorFactory;