diff --git a/.gitignore b/.gitignore index 1eb96eb..b88feb1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ Servers .gradle _site /bin +.idea +*.iml diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/UriBasedServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/UriBasedServiceInfo.java index fda552c..698d495 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/service/UriBasedServiceInfo.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/UriBasedServiceInfo.java @@ -11,91 +11,103 @@ import org.springframework.cloud.util.UriInfoFactory; * */ 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 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 uriString) { + super(id); + this.uriInfo = getUriInfoFactory().createUri(uriString); + 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; - } + /** + * 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 getUri() { - return uriInfo.getUri().toString(); - } + @ServiceProperty(category = "connection") + public String getUri() { + // converting a URI string that contains multiple hosts, ports, etc won't parse correctly. + // first attempt to use the parsed URI from the UriInfo, otherwise return the raw URI string + // that will be passed to the underlying driver / properties. + // + // TODO: either simply use URI strings or provide better support for URI's containing multiple hosts, etc. + // + if (uriInfo.getHost() != null) { + return uriInfo.getUri().toString(); + } - @ServiceProperty(category = "connection") - public String getUserName() { - return uriInfo.getUserName(); - } + return uriInfo.getRawUriString(); + } - @ServiceProperty(category = "connection") - public String getPassword() { - return uriInfo.getPassword(); - } + @ServiceProperty(category = "connection") + public String getUserName() { + return uriInfo.getUserName(); + } - @ServiceProperty(category = "connection") - public String getHost() { - return uriInfo.getHost(); - } + @ServiceProperty(category = "connection") + public String getPassword() { + return uriInfo.getPassword(); + } - @ServiceProperty(category = "connection") - public int getPort() { - return uriInfo.getPort(); - } + @ServiceProperty(category = "connection") + public String getHost() { + return uriInfo.getHost(); + } - @ServiceProperty(category = "connection") - public String getPath() { - return uriInfo.getPath(); - } + @ServiceProperty(category = "connection") + public int getPort() { + return uriInfo.getPort(); + } - @ServiceProperty(category = "connection") - public String getQuery() { - return uriInfo.getQuery(); - } + @ServiceProperty(category = "connection") + public String getPath() { + return uriInfo.getPath(); + } - @ServiceProperty(category = "connection") - public String getScheme() { - return uriInfo.getScheme(); - } + @ServiceProperty(category = "connection") + public String getQuery() { + return uriInfo.getQuery(); + } - /** - * 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; - } + @ServiceProperty(category = "connection") + public String getScheme() { + return uriInfo.getScheme(); + } - protected UriInfo getUriInfo() { - return uriInfo; - } + /** + * 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; + } - @Override - public String toString() { - return getClass().getSimpleName() + "[" + getScheme() + "://" + getUserName() + ":****@" + getHost() + ":" + getPort() - + "/" + getPath() + "]"; - } + protected UriInfo getUriInfo() { + return uriInfo; + } + + @Override + public String toString() { + // TODO: when using a simple URI string (see comments in getUri), the result of uriInfo.getRawUriString() + // would display the password which does not seem ideal. + return getClass().getSimpleName() + "[" + getScheme() + "://" + getUserName() + ":****@" + getHost() + ":" + getPort() + + "/" + getPath() + "]"; + } } diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/util/StandardUriInfoFactory.java b/spring-cloud-core/src/main/java/org/springframework/cloud/util/StandardUriInfoFactory.java index 8032b55..c5e89ba 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/util/StandardUriInfoFactory.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/util/StandardUriInfoFactory.java @@ -30,8 +30,7 @@ public class StandardUriInfoFactory implements UriInfoFactory { String password = uriDecode(userInfo[1]); return new UriInfo(tmpUri.getScheme(), tmpUri.getHost(), tmpUri.getPort(), - userName, password, - parsePath(tmpUri), tmpUri.getRawQuery()); + userName, password, parsePath(tmpUri), tmpUri.getRawQuery(), uriString); } private URI createTmpUri(String uriString) { diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java index 0a0310b..26f6c35 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java @@ -18,16 +18,20 @@ public class UriInfo { private String path; private URI uri; private String query; + private String rawUriString; public UriInfo(String scheme, String host, int port, String username, String password) { - this(scheme, host, port, username, password, ""); + this(scheme, host, port, username, password, + String.format("%s://%s:%s@%s:%s/", scheme, username, password, host, port)); } public UriInfo(String scheme, String host, int port, String username, String password, String path) { - this(scheme, host, port, username, password, path, null); + this(scheme, host, port, username, password, path, null, + String.format("%s://%s:%s@%s:%s/%s", scheme, username, password, host, port, path)); } - public UriInfo(String scheme, String host, int port, String username, String password, String path, String query) { + public UriInfo(String scheme, String host, int port, String username, String password, String path, + String query, String rawUriString) { this.scheme = scheme; this.host = host; this.port = port; @@ -35,6 +39,7 @@ public class UriInfo { this.password = password; this.path = path; this.query = query; + this.rawUriString = rawUriString; this.uri = buildUri(); } @@ -71,6 +76,10 @@ public class UriInfo { return uri; } + public String getRawUriString() { + return rawUriString; + } + private URI buildUri() { String userInfo = null; diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/document/MongoDbFactoryCreator.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/document/MongoDbFactoryCreator.java index ef301c8..850a13c 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/document/MongoDbFactoryCreator.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/document/MongoDbFactoryCreator.java @@ -4,7 +4,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import com.mongodb.MongoClientURI; import org.springframework.cloud.service.AbstractServiceConnectorCreator; import org.springframework.cloud.service.ServiceConnectorConfig; import org.springframework.cloud.service.ServiceConnectorCreationException; @@ -27,22 +30,21 @@ import com.mongodb.WriteConcern; * * @author Ramnivas Laddad * @author Thomas Risberg - * + * @author Chris Schaefer */ public class MongoDbFactoryCreator extends AbstractServiceConnectorCreator { @Override public MongoDbFactory create(MongoServiceInfo serviceInfo, ServiceConnectorConfig config) { try { MongoClientOptions mongoOptionsToUse = getMongoOptions((MongoDbFactoryConfig) config); - ServerAddress serverAddress = null; - if (serviceInfo.getPort() == -1) { - serverAddress = new ServerAddress(serviceInfo.getHost()); - } else { - serverAddress = new ServerAddress(serviceInfo.getHost(), serviceInfo.getPort()); - } - MongoClient mongo = new MongoClient(serverAddress, mongoOptionsToUse); - UserCredentials credentials = new UserCredentials(serviceInfo.getUserName(), serviceInfo.getPassword()); - SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongo, serviceInfo.getDatabase(), credentials); + + MongoClientURI mongoClientURI = new MongoClientURI(serviceInfo.getUri()); + List serverAddressList = getServerAddresses(mongoClientURI); + + MongoClient mongo = new MongoClient(serverAddressList, mongoOptionsToUse); + UserCredentials credentials = new UserCredentials(mongoClientURI.getUsername(), new String(mongoClientURI.getPassword())); + SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongo, mongoClientURI.getDatabase(), credentials); + return configure(mongoDbFactory, (MongoDbFactoryConfig) config); } catch (UnknownHostException e) { throw new ServiceConnectorCreationException(e); @@ -51,43 +53,53 @@ public class MongoDbFactoryCreator extends AbstractServiceConnectorCreator getServerAddresses(MongoClientURI mongoClientURI) throws UnknownHostException { + List servers = mongoClientURI.getHosts(); + List serverAddressList = new ArrayList(); + + for(String server : servers) { + serverAddressList.add(new ServerAddress(server)); + } + + return serverAddressList; + } + private MongoClientOptions getMongoOptions(MongoDbFactoryConfig config) { - MongoClientOptions.Builder builder = null; + MongoClientOptions.Builder builder; Method builderMethod = ClassUtils.getMethodIfAvailable(MongoClientOptions.class, "builder"); if (builderMethod != null) { - builder = (Builder) ReflectionUtils.invokeMethod(builderMethod, null); + builder = (Builder) ReflectionUtils.invokeMethod(builderMethod, null); } else { - Constructor builderConstructor = ClassUtils.getConstructorIfAvailable(MongoClientOptions.Builder.class); - try { - builder = builderConstructor.newInstance(new Object[0]); - } catch (InstantiationException e) { - throw new IllegalStateException(e); - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } catch (IllegalArgumentException e) { - throw new IllegalStateException(e); - } catch (InvocationTargetException e) { - throw new IllegalStateException(e); - } + Constructor builderConstructor = ClassUtils.getConstructorIfAvailable(MongoClientOptions.Builder.class); + try { + builder = builderConstructor.newInstance(new Object[0]); + } catch (InstantiationException e) { + throw new IllegalStateException(e); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } catch (IllegalArgumentException e) { + throw new IllegalStateException(e); + } catch (InvocationTargetException e) { + throw new IllegalStateException(e); + } } if (config != null) { - if (config.getConnectionsPerHost() != null) { - builder.connectionsPerHost(config.getConnectionsPerHost()); - } - if (config.getMaxWaitTime() != null) { - builder.maxWaitTime(config.getMaxWaitTime()); - } - if (config.getWriteConcern() != null) { - builder.writeConcern(new WriteConcern(config.getWriteConcern())); - } - } - + if (config.getConnectionsPerHost() != null) { + builder.connectionsPerHost(config.getConnectionsPerHost()); + } + if (config.getMaxWaitTime() != null) { + builder.maxWaitTime(config.getMaxWaitTime()); + } + if (config.getWriteConcern() != null) { + builder.writeConcern(new WriteConcern(config.getWriteConcern())); + } + } + return builder.build(); } - public SimpleMongoDbFactory configure(SimpleMongoDbFactory mongoDbFactory, MongoDbFactoryConfig config) { if (config != null && config.getWriteConcern() != null) { WriteConcern writeConcern = WriteConcern.valueOf(config.getWriteConcern()); @@ -97,5 +109,4 @@ public class MongoDbFactoryCreator extends AbstractServiceConnectorCreator> serviceCreators; @Before @@ -74,7 +74,6 @@ public class CloudTest extends StubCloudConnectorTest { assertNull(cloudProperties.get("cloud.services.mysql.connection.host")); } - @Test public void servicePropsOneServiceOfTheSameLabel() { MysqlServiceInfo mysqlServiceInfo = createMysqlService("my-mysql"); @@ -101,7 +100,7 @@ public class CloudTest extends StubCloudConnectorTest { assertBasicProps("cloud.services.my-redis", redisServiceInfo, cloudProperties); assertBasicProps("cloud.services.redis", redisServiceInfo, cloudProperties); } - + @Test public void servicePropsRabbit() { String serviceId = "my-rabbit"; @@ -113,6 +112,28 @@ public class CloudTest extends StubCloudConnectorTest { assertRabbitProps("cloud.services.my-rabbit", rabbitServiceInfo, cloudProperties); assertRabbitProps("cloud.services.rabbitmq", rabbitServiceInfo, cloudProperties); } + + @Test + public void servicePropsMongoMultipleHostsUriString() { + String serviceId = "my-mongo-multiple-hosts-uri"; + MongoServiceInfo mongoServiceInfo = createMongoServiceWithMultipleHostsByUri(serviceId); + CloudConnector stubCloudConnector = getTestCloudConnector(mongoServiceInfo); + Cloud testCloud = new Cloud(stubCloudConnector, serviceCreators); + + Properties cloudProperties = testCloud.getCloudProperties(); + assertMongoPropsWithMultipleHostsByUri("cloud.services.my-mongo-multiple-hosts-uri", mongoServiceInfo, cloudProperties); + assertMongoPropsWithMultipleHostsByUri("cloud.services.mongo", mongoServiceInfo, cloudProperties); + } + + private void assertMongoPropsWithMultipleHostsByUri(String leadKey, MongoServiceInfo serviceInfo, Properties cloudProperties) { + assertEquals(serviceInfo.getId(), cloudProperties.get(leadKey + ".id")); + assertEquals(serviceInfo.getUri(), cloudProperties.get(leadKey + ".connection.uri")); + assertEquals(-1, cloudProperties.get(leadKey + ".connection.port")); + + assertNull(cloudProperties.get(leadKey + ".connection.host")); + assertNull(cloudProperties.get(leadKey + ".connection.username")); + assertNull(cloudProperties.get(leadKey + ".connection.password")); + } private void assertBasicProps(String leadKey, UriBasedServiceInfo serviceInfo, Properties cloudProperties) { assertEquals(serviceInfo.getId(), cloudProperties.get(leadKey + ".id")); diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/StubCloudConnectorTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/StubCloudConnectorTest.java index 2c442e8..9a7eb20 100644 --- a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/StubCloudConnectorTest.java +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/StubCloudConnectorTest.java @@ -15,10 +15,9 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; * Base class for close-to-integration tests that use a stub {@link CloudConnector} to avoid the need for a real cloud environment. * * @author Ramnivas Laddad - * + * @author Chris Schaefer */ abstract public class StubCloudConnectorTest { - private static final String MOCK_CLOUD_BEAN_NAME = "mockCloud"; protected ApplicationContext getTestApplicationContext(String fileName, ServiceInfo... serviceInfos) { @@ -63,7 +62,11 @@ abstract public class StubCloudConnectorTest { protected MongoServiceInfo createMongoService(String id) { return new MongoServiceInfo(id, "10.20.30.40", 1234, "username", "password", "db"); } - + + protected MongoServiceInfo createMongoServiceWithMultipleHostsByUri(String id) { + return new MongoServiceInfo(id, "mongo://username:password@10.20.30.40,10.20.30.41,10.20.30.42:1234/db"); + } + protected AmqpServiceInfo createRabbitService(String id) { return new AmqpServiceInfo(id, "10.20.30.40", 1234, "username", "password", "vh"); } diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/mongo/MongoServiceConnectorCreatorTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/mongo/MongoServiceConnectorCreatorTest.java index 9b8a770..5169e7c 100644 --- a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/mongo/MongoServiceConnectorCreatorTest.java +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/mongo/MongoServiceConnectorCreatorTest.java @@ -14,53 +14,88 @@ import org.springframework.test.util.ReflectionTestUtils; import com.mongodb.Mongo; import com.mongodb.ServerAddress; +import org.springframework.util.StringUtils; /** - * - * @author Ramnivas Laddad + * Test cases for Mongo service connector creators. * + * @author Ramnivas Laddad + * @author Chris Schaefer */ public class MongoServiceConnectorCreatorTest { private static final String TEST_HOST = "10.20.30.40"; + private static final String TEST_HOST_1 = "10.20.30.41"; + private static final String TEST_HOST_2 = "10.20.30.42"; private static final int TEST_PORT = 1234; + private static final int TEST_PORT_DEFAULT = 27017; private static final String TEST_USERNAME = "myuser"; private static final String TEST_PASSWORD = "mypass"; private static final String TEST_DB = "mydb"; + private static final String MONGODB_SCHEME = "mongodb"; + private static final String[] TEST_HOSTS = new String[] { TEST_HOST, TEST_HOST_1, TEST_HOST_2 }; - private MongoDbFactoryCreator testCreator = new MongoDbFactoryCreator(); @Test public void cloudMongoCreationNoConfig() throws Exception { - MongoServiceInfo serviceInfo = createServiceInfo(); + MongoServiceInfo serviceInfo = new MongoServiceInfo("id", TEST_HOST, TEST_PORT, TEST_USERNAME, TEST_PASSWORD, TEST_DB); MongoDbFactory mongoDbFactory = testCreator.create(serviceInfo, null); - assertConnectorProperties(serviceInfo, mongoDbFactory); - } + assertNotNull(mongoDbFactory); - public MongoServiceInfo createServiceInfo() { - return new MongoServiceInfo("id", TEST_HOST, TEST_PORT, TEST_DB, TEST_USERNAME, TEST_PASSWORD); - } - - private void assertConnectorProperties(MongoServiceInfo serviceInfo, MongoDbFactory connector) { - assertNotNull(connector); - - Mongo mongo = (Mongo) ReflectionTestUtils.getField(connector, "mongo"); - UserCredentials credentials = (UserCredentials) ReflectionTestUtils.getField(connector, "credentials"); + Mongo mongo = (Mongo) ReflectionTestUtils.getField(mongoDbFactory, "mongo"); + UserCredentials credentials = (UserCredentials) ReflectionTestUtils.getField(mongoDbFactory, "credentials"); assertNotNull(mongo); - + List addresses = mongo.getAllAddress(); assertEquals(1, addresses.size()); - + ServerAddress address = addresses.get(0); - + assertEquals(serviceInfo.getHost(), address.getHost()); assertEquals(serviceInfo.getPort(), address.getPort()); assertEquals(serviceInfo.getUserName(), ReflectionTestUtils.getField(credentials, "username")); assertEquals(serviceInfo.getPassword(), ReflectionTestUtils.getField(credentials, "password")); - + // Don't do connector.getDatabase().getName() as that will try to initiate the connection - assertEquals(serviceInfo.getDatabase(), ReflectionTestUtils.getField(connector, "databaseName")); + assertEquals(serviceInfo.getDatabase(), ReflectionTestUtils.getField(mongoDbFactory, "databaseName")); + } + + @Test + public void cloudMongoCreationWithMultipleHostsByUri() throws Exception { + String uri = String.format("%s://%s:%s@%s:%s/%s", MONGODB_SCHEME, TEST_USERNAME, TEST_PASSWORD, + StringUtils.arrayToDelimitedString(TEST_HOSTS, ","), TEST_PORT, TEST_DB); + + MongoServiceInfo serviceInfo = new MongoServiceInfo("id", uri); + + MongoDbFactory mongoDbFactory = testCreator.create(serviceInfo, null); + + assertNotNull(mongoDbFactory); + + Mongo mongo = (Mongo) ReflectionTestUtils.getField(mongoDbFactory, "mongo"); + UserCredentials credentials = (UserCredentials) ReflectionTestUtils.getField(mongoDbFactory, "credentials"); + assertNotNull(mongo); + + List addresses = mongo.getAllAddress(); + assertEquals(3, addresses.size()); + + assertEquals(TEST_USERNAME, ReflectionTestUtils.getField(credentials, "username")); + assertEquals(TEST_PASSWORD, ReflectionTestUtils.getField(credentials, "password")); + + // Don't do connector.getDatabase().getName() as that will try to initiate the connection + assertEquals(TEST_DB, ReflectionTestUtils.getField(mongoDbFactory, "databaseName")); + + ServerAddress address1 = addresses.get(0); + assertEquals(TEST_HOST, address1.getHost()); + assertEquals(TEST_PORT_DEFAULT, address1.getPort()); + + ServerAddress address2 = addresses.get(1); + assertEquals(TEST_HOST_1, address2.getHost()); + assertEquals(TEST_PORT_DEFAULT, address2.getPort()); + + ServerAddress address3 = addresses.get(2); + assertEquals(TEST_HOST_2, address3.getHost()); + assertEquals(TEST_PORT, address3.getPort()); } }