From 8bd5efb63cb740aba0d53e02bee3c68e2b0e7596 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 18 Aug 2016 17:00:20 +0200 Subject: [PATCH] Add support for Cassandra Add connectors for Cassandra Cluster instances with Java and XML-based configuration. Cassandra configuration is obtained using the single-tenant Datastax/Cassandra tile. --- build.gradle | 8 +- .../CassandraServiceInfoCreator.java | 53 +++++++ ...loudfoundry.CloudFoundryServiceInfoCreator | 1 + .../CassandraServiceInfoCreatorTests.java | 90 +++++++++++ .../cloudfoundry/test-cassandra-service.json | 25 +++ .../test-cassandra-with-credentials.json | 26 ++++ .../service/common/CassandraServiceInfo.java | 88 +++++++++++ .../common/CassandraServiceInfoUnitTests.java | 55 +++++++ .../build.gradle | 10 ++ .../java/CloudServiceConnectionFactory.java | 58 +++++++ .../config/java/ServiceConnectionFactory.java | 46 ++++++ .../xml/CloudCassandraSessionParser.java | 96 ++++++++++++ .../config/xml/CloudNamespaceHandler.java | 1 + .../column/CassandraClusterConfig.java | 145 ++++++++++++++++++ .../column/CassandraClusterCreator.java | 77 ++++++++++ .../column/CassandraClusterFactory.java | 36 +++++ ...work.cloud.service.ServiceConnectorCreator | 1 + .../cloud/config/xml/spring-cloud.xsd | 111 ++++++++++++++ .../cloud/StubCloudConnectorTest.java | 7 + ...CassandraClusterFactoryJavaConfigTest.java | 88 +++++++++++ .../xml/CassandraClusterXmlConfigTest.java | 72 +++++++++ .../config/xml/CloudAllServicesTest.java | 10 +- .../column/CassandraClusterCreatorTest.java | 107 +++++++++++++ .../column/CassandraClusterFactoryTest.java | 54 +++++++ .../cloud/config/xml/cloud-all-services.xml | 1 + .../xml/cloud-cassandra-with-config.xml | 23 +++ .../xml/cloud-cassandra-with-service-id.xml | 10 ++ .../cloud-cassandra-without-service-id.xml | 10 ++ 28 files changed, 1305 insertions(+), 4 deletions(-) create mode 100644 spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreator.java create mode 100644 spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreatorTests.java create mode 100644 spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-service.json create mode 100644 spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-with-credentials.json create mode 100644 spring-cloud-core/src/main/java/org/springframework/cloud/service/common/CassandraServiceInfo.java create mode 100644 spring-cloud-core/src/test/java/org/springframework/cloud/service/common/CassandraServiceInfoUnitTests.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudCassandraSessionParser.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterConfig.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterCreator.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterFactory.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CassandraClusterFactoryJavaConfigTest.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CassandraClusterXmlConfigTest.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterCreatorTest.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterFactoryTest.java create mode 100644 spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-config.xml create mode 100644 spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-service-id.xml create mode 100644 spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-without-service-id.xml diff --git a/build.gradle b/build.gradle index 979d677..70b5888 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,9 @@ ext { mariadbDriverVersion = "1.1.3" postgresDriverVersion = "9.0-801.jdbc4" + springDataCassandraVersion = "1.5.3.RELEASE" + cassandraDriverVersion = "3.1.3" + javaxMailVersion = "1.4.7" cglibVersion = "3.1" @@ -66,7 +69,7 @@ subprojects { apply plugin: 'propdeps-eclipse' apply plugin: "org.asciidoctor.gradle.asciidoctor" - asciidoctor { + asciidoctor { sourceDir = new File("docs/src/main/asciidoc") outputDir = new File("docs/target/generated-docs") options = [ @@ -208,6 +211,9 @@ ext { "jedis26-redis15" : [jedisVersion: "2.6.3", springDataRedisVersion: "1.5.2.RELEASE"], "jedis27-redis16" : [jedisVersion: "2.7.3", springDataRedisVersion: "1.6.4.RELEASE"], "jedis28-redis17" : [jedisVersion: "2.8.1", springDataRedisVersion: "1.7.2.RELEASE"], + "driver300-cass15" : [cassandraDriverVersion: "3.0.0", springDataCassandraVersion: "1.5.3.RELEASE"], + "driver301-cass15" : [cassandraDriverVersion: "3.0.1", springDataCassandraVersion: "1.5.3.RELEASE"], + "driver313-cass15" : [cassandraDriverVersion: "3.1.3", springDataCassandraVersion: "1.5.3.RELEASE"], // "lettuce34-redis15": [lettuceVersion: "3.4.3.Final", springDataRedisVersion: "1.5.2.RELEASE"], // "lettuce34-redis16": [lettuceVersion: "3.4.3.Final", springDataRedisVersion: "1.6.4.RELEASE"], // "lettuce34-redis17": [lettuceVersion: "3.4.3.Final", springDataRedisVersion: "1.7.2.RELEASE"], diff --git a/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreator.java b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreator.java new file mode 100644 index 0000000..f3a14b2 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.cloudfoundry; + +import java.util.List; +import java.util.Map; + +import org.springframework.cloud.service.common.CassandraServiceInfo; + + +/** + * Service info creator for Cassandra services. + * + * @author Mark Paluch + */ +public class CassandraServiceInfoCreator extends + CloudFoundryServiceInfoCreator { + + public CassandraServiceInfoCreator() { + super(new Tags("cassandra")); + } + + @Override + @SuppressWarnings("unchecked") + public CassandraServiceInfo createServiceInfo(Map serviceData) { + + String id = (String) serviceData.get("name"); + + Map credentials = getCredentials(serviceData); + + String username = getStringFromCredentials(credentials, "username"); + String password = getStringFromCredentials(credentials, "password"); + String port = getStringFromCredentials(credentials, "cqlsh_port"); + List contactpoints = (List) credentials.get("node_ips"); + + return new CassandraServiceInfo(id, contactpoints, Integer.parseInt(port), + username, password); + } +} diff --git a/spring-cloud-cloudfoundry-connector/src/main/resources/META-INF/services/org.springframework.cloud.cloudfoundry.CloudFoundryServiceInfoCreator b/spring-cloud-cloudfoundry-connector/src/main/resources/META-INF/services/org.springframework.cloud.cloudfoundry.CloudFoundryServiceInfoCreator index 9fdad86..2ca69e9 100644 --- a/spring-cloud-cloudfoundry-connector/src/main/resources/META-INF/services/org.springframework.cloud.cloudfoundry.CloudFoundryServiceInfoCreator +++ b/spring-cloud-cloudfoundry-connector/src/main/resources/META-INF/services/org.springframework.cloud.cloudfoundry.CloudFoundryServiceInfoCreator @@ -8,3 +8,4 @@ org.springframework.cloud.cloudfoundry.SmtpServiceInfoCreator org.springframework.cloud.cloudfoundry.OracleServiceInfoCreator org.springframework.cloud.cloudfoundry.DB2ServiceInfoCreator org.springframework.cloud.cloudfoundry.SqlServerServiceInfoCreator +org.springframework.cloud.cloudfoundry.CassandraServiceInfoCreator diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreatorTests.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreatorTests.java new file mode 100644 index 0000000..071f922 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CassandraServiceInfoCreatorTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.cloudfoundry; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.cloud.service.common.CassandraServiceInfo; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Unit tests for link {@link CassandraServiceInfoCreator}. + * + * @author Mark Paluch + */ +public class CassandraServiceInfoCreatorTests extends AbstractCloudFoundryConnectorTest { + + private ObjectMapper mapper = new ObjectMapper(); + + @Test + public void shouldCreateServiceInfo() throws Exception { + + CassandraServiceInfoCreator creator = new CassandraServiceInfoCreator(); + Map services = readServiceData("test-cassandra-service.json"); + Map serviceData = getServiceData(services, + "p-dse-cassandra-acceptance"); + + CassandraServiceInfo info = creator.createServiceInfo(serviceData); + + assertThat(info.getContactPoints(), hasItems("1.2.3.4", "5.6.7.8")); + assertThat(info.getPort(), is(equalTo(9042))); + assertThat(info.getUsername(), is(nullValue())); + assertThat(info.getPassword(), is(nullValue())); + } + + @Test + public void shouldCreateServiceInfoWithCredentials() throws Exception { + + CassandraServiceInfoCreator creator = new CassandraServiceInfoCreator(); + Map services = readServiceData("test-cassandra-with-credentials.json"); + Map serviceData = getServiceData(services, + "p-dse-cassandra-acceptance"); + + CassandraServiceInfo info = creator.createServiceInfo(serviceData); + + assertThat(info.getContactPoints(), hasItems("1.2.3.4")); + assertThat(info.getPort(), is(equalTo(9042))); + assertThat(info.getUsername(), is(equalTo("user"))); + assertThat(info.getPassword(), is(equalTo("pass"))); + } + + @Test + public void testAcceptService() throws Exception { + + CassandraServiceInfoCreator creator = new CassandraServiceInfoCreator(); + Map services = readServiceData("test-cassandra-service.json"); + Map serviceData = getServiceData(services, + "p-dse-cassandra-acceptance"); + + assertThat(creator.accept(serviceData), is(true)); + } + + private Map readServiceData(String resource) throws java.io.IOException { + return mapper.readValue(readTestDataFile(resource), Map.class); + } + + @SuppressWarnings("unchecked") + private Map getServiceData(Map services, String name) { + return (Map) ((List) services.get(name)).get(0); + } +} diff --git a/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-service.json b/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-service.json new file mode 100644 index 0000000..c77cc6f --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-service.json @@ -0,0 +1,25 @@ +{ + "p-dse-cassandra-acceptance": [ + { + "credentials": { + "cqlsh_port": "9042", + "node_ips": [ + "1.2.3.4", + "5.6.7.8" + ], + "opscenter_url": "http://datastax-opscenter-cassandra.cf-app.com/opscenter/index.html" + }, + "label": "p-dse-cassandra-acceptance", + "name": "mydse", + "plan": "single-node", + "provider": null, + "syslog_drain_url": null, + "tags": [ + "pivotal", + "cassandra", + "dse", + "datastax" + ] + } + ] +} diff --git a/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-with-credentials.json b/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-with-credentials.json new file mode 100644 index 0000000..50c7656 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-cassandra-with-credentials.json @@ -0,0 +1,26 @@ +{ + "p-dse-cassandra-acceptance": [ + { + "credentials": { + "cqlsh_port": "9042", + "node_ips": [ + "1.2.3.4" + ], + "opscenter_url": "http://datastax-opscenter-cassandra.cf-app.com/opscenter/index.html", + "password": "pass", + "username": "user" + }, + "label": "p-dse-cassandra-acceptance", + "name": "mydse", + "plan": "single-node", + "provider": null, + "syslog_drain_url": null, + "tags": [ + "pivotal", + "cassandra", + "dse", + "datastax" + ] + } + ] +} diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/CassandraServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/CassandraServiceInfo.java new file mode 100644 index 0000000..d335510 --- /dev/null +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/CassandraServiceInfo.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.common; + +import java.util.List; + +import org.springframework.cloud.service.BaseServiceInfo; +import org.springframework.cloud.service.ServiceInfo.ServiceLabel; + +/** + * {@link org.springframework.cloud.service.ServiceInfo} for a Cassandra/Datastax DSE + * service. + * + * @author Mark Paluch + */ +@ServiceLabel("cassandra") +public class CassandraServiceInfo extends BaseServiceInfo { + + private final List contactPoints; + private final int port; + private final String username; + private final String password; + + /** + * Creates a new {@link CassandraServiceInfo} using Contact points and port. + * + * @param id the service-id + * @param contactPoints list of contact-points + * @param port the port + */ + public CassandraServiceInfo(String id, List contactPoints, int port) { + this(id, contactPoints, port, null, null); + } + + /** + * Creates a new {@link CassandraServiceInfo} using Contact points with a port and + * username/password credentials. + * + * @param id the service-id + * @param contactPoints list of contact-points + * @param port the port + * @param username the user name + * @param password the password + */ + public CassandraServiceInfo(String id, List contactPoints, int port, + String username, String password) { + super(id); + + this.contactPoints = contactPoints; + this.port = port; + this.username = username; + this.password = password; + } + + @ServiceProperty(category = "contactpoints") + public List getContactPoints() { + return contactPoints; + } + + @ServiceProperty(category = "port") + public int getPort() { + return port; + } + + @ServiceProperty(category = "username") + public String getUsername() { + return username; + } + + @ServiceProperty(category = "password") + public String getPassword() { + return password; + } +} diff --git a/spring-cloud-core/src/test/java/org/springframework/cloud/service/common/CassandraServiceInfoUnitTests.java b/spring-cloud-core/src/test/java/org/springframework/cloud/service/common/CassandraServiceInfoUnitTests.java new file mode 100644 index 0000000..175eb6e --- /dev/null +++ b/spring-cloud-core/src/test/java/org/springframework/cloud/service/common/CassandraServiceInfoUnitTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.common; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.assertThat; + +import java.util.Collections; + + +import org.junit.Test; + +/** + * Unit tests for {@link CassandraServiceInfo}. + * + * @author Mark Paluch + */ +public class CassandraServiceInfoUnitTests { + + @Test + public void shouldContainContactPointsAndPort() { + + CassandraServiceInfo info = new CassandraServiceInfo("cassandra", + Collections.singletonList("10.0.0.1"), 9042); + + assertThat(info.getContactPoints(), hasItem("10.0.0.1")); + assertThat(info.getPort(), is(equalTo(9042))); + } + + @Test + public void shouldContainContactPointsAndPortAndCredentials() { + + CassandraServiceInfo info = new CassandraServiceInfo("cassandra", + Collections.singletonList("10.0.0.1"), 9042, "walter", "white"); + + assertThat(info.getContactPoints(), hasItem("10.0.0.1")); + assertThat(info.getPort(), is(equalTo(9042))); + assertThat(info.getUsername(), is(equalTo("walter"))); + assertThat(info.getPassword(), is(equalTo("white"))); + } +} diff --git a/spring-cloud-spring-service-connector/build.gradle b/spring-cloud-spring-service-connector/build.gradle index 2beacc6..6dab57d 100644 --- a/spring-cloud-spring-service-connector/build.gradle +++ b/spring-cloud-spring-service-connector/build.gradle @@ -40,4 +40,14 @@ dependencies { exclude(group: 'org.mongodb', module: 'mongo-java-driver') } optional("org.mongodb:mongo-java-driver:${mongoDriverVersion}") + + + optional("org.springframework.data:spring-data-cassandra:$springDataCassandraVersion") { + exclude(group: 'org.springframework', module: 'spring-beans') + exclude(group: 'org.springframework', module: 'spring-expression') + exclude(group: 'org.springframework', module: 'spring-tx') + // depend on cassandra-driver-core explicitly to control version used for testing + exclude(group: 'com.datastax.cassandra', module: 'cassandra-driver-core') + } + optional("com.datastax.cassandra:cassandra-driver-core:${cassandraDriverVersion}") } diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudServiceConnectionFactory.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudServiceConnectionFactory.java index 6ad5e05..a6be368 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudServiceConnectionFactory.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/CloudServiceConnectionFactory.java @@ -1,10 +1,12 @@ package org.springframework.cloud.config.java; +import com.datastax.driver.core.Cluster; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.cloud.Cloud; import org.springframework.cloud.CloudException; import org.springframework.cloud.service.PooledServiceConnectorConfig; import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.column.CassandraClusterConfig; import org.springframework.cloud.service.document.MongoDbFactoryConfig; import org.springframework.cloud.service.messaging.RabbitConnectionFactoryConfig; import org.springframework.cloud.service.relational.DataSourceConfig; @@ -294,6 +296,62 @@ public class CloudServiceConnectionFactory implements ServiceConnectionFactory { return cloud.getServiceConnector(serviceId, RedisConnectionFactory.class, redisConnectionFactoryConfig); } + /** + * Get the {@link Cluster} object associated with the only Cassandra service bound to + * the app. + * + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there are either 0 or more than + * 1 Cassandra database services. + */ + @Override + public Cluster cluster() { + return cluster((CassandraClusterConfig) null); + } + + /** + * Get the {@link Cluster} object associated with the only Cassandra service bound to + * the app configured as specified. + * + * @param config configuration for the Cluster created + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there are either 0 or more than + * 1 Cassandra database services. + */ + @Override + public Cluster cluster(CassandraClusterConfig config) { + return cloud.getSingletonServiceConnector(Cluster.class, config); + } + + /** + * Get the {@link Cluster} object associated with the Cassandra {@code serviceId} + * bound to the app. + * + * @param serviceId the Cassandra serviceId + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there is no service bound with + * the {@code serviceId} + */ + @Override + public Cluster cluster(String serviceId) { + return cluster(serviceId, null); + } + + /** + * Get the {@link Cluster} object associated with the Cassandra {@code serviceId} + * bound to the app configured as specified. + * + * @param serviceId the Cassandra serviceId + * @param config configuration for the Cluster created + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there is no service bound with + * the {@code serviceId} + */ + @Override + public Cluster cluster(String serviceId, CassandraClusterConfig config) { + return cloud.getServiceConnector(serviceId, Cluster.class, config); + } + // Generic service /** * Get the service connector object associated with the only service bound to the app. diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceConnectionFactory.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceConnectionFactory.java index c6e8307..7ce6bef 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceConnectionFactory.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/java/ServiceConnectionFactory.java @@ -1,8 +1,10 @@ package org.springframework.cloud.config.java; +import com.datastax.driver.core.Cluster; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.cloud.service.PooledServiceConnectorConfig; import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.column.CassandraClusterConfig; import org.springframework.cloud.service.document.MongoDbFactoryConfig; import org.springframework.cloud.service.messaging.RabbitConnectionFactoryConfig; import org.springframework.cloud.service.relational.DataSourceConfig; @@ -49,6 +51,50 @@ public interface ServiceConnectionFactory { RedisConnectionFactory redisConnectionFactory(String serviceId, PooledServiceConnectorConfig redisConnectionFactoryConfig); + /** + * Get the {@link Cluster} object associated with the only Cassandra service bound to + * the app. + * + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there are either 0 or more than + * 1 Cassandra database services. + */ + Cluster cluster(); + + /** + * Get the {@link Cluster} object associated with the only Cassandra service bound to + * the app configured as specified. + * + * @param config configuration for the Cluster created + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there are either 0 or more than + * 1 Cassandra database services. + */ + Cluster cluster(CassandraClusterConfig config); + + /** + * Get the {@link Cluster} object associated with the Cassandra {@code serviceId} + * bound to the app. + * + * @param serviceId the Cassandra serviceId + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there is no service bound with + * the {@code serviceId} + */ + Cluster cluster(String serviceId); + + /** + * Get the {@link Cluster} object associated with the Cassandra {@code serviceId} + * bound to the app configured as specified. + * + * @param serviceId the Cassandra serviceId + * @param config configuration for the Cluster created + * @return the Cassandra {@link Cluster} + * @throws org.springframework.cloud.CloudException if there is no service bound with + * the {@code serviceId} + */ + Cluster cluster(String serviceId, CassandraClusterConfig config); + Object service(); T service(Class serviceConnectorType); diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudCassandraSessionParser.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudCassandraSessionParser.java new file mode 100644 index 0000000..6186374 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudCassandraSessionParser.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.cloud.service.column.CassandraClusterConfig; +import org.springframework.cloud.service.column.CassandraClusterFactory; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +import com.datastax.driver.core.ProtocolOptions; + +/** + * @author Vinicius Carvalho + */ +public class CloudCassandraSessionParser + extends AbstractNestedElementCloudServiceFactoryParser { + + final String ELEMENT_CASSANDRA_OPTIONS = "cassandra-options"; + final String COMPRESSION_ATTRIBUTE = "compression"; + final String RETRY_POLICY_ATTRIBUTE = "retry-policy"; + final String LOAD_BALANCING_POLICY_ATTRIBUTE = "loadbalancing-policy"; + final String SOCKET_OPTIONS_ATTRIBUTE = "socket-options"; + final String RECONNECTION_POLICY_ATTRIBUTE = "reconnection-policy"; + + public CloudCassandraSessionParser() { + super(CassandraClusterFactory.class); + } + + @Override + protected void doParse(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + + Element optionsElement = DomUtils.getChildElementByTagName(element, + ELEMENT_CASSANDRA_OPTIONS); + + BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder + .genericBeanDefinition(CassandraClusterConfig.class.getName()); + + if (optionsElement != null) { + String compressionString = optionsElement.getAttribute(COMPRESSION_ATTRIBUTE); + if (!StringUtils.isEmpty(compressionString)) { + ProtocolOptions.Compression compression = ProtocolOptions.Compression + .valueOf(compressionString); + beanBuilder.addPropertyValue("compression", compression); + } + + String retryPolicyString = optionsElement + .getAttribute(RETRY_POLICY_ATTRIBUTE); + if (!StringUtils.isEmpty(retryPolicyString)) { + beanBuilder.addPropertyReference("retryPolicy", retryPolicyString); + } + + String loadBalancingPolicyString = optionsElement + .getAttribute(LOAD_BALANCING_POLICY_ATTRIBUTE); + if (!StringUtils.isEmpty(loadBalancingPolicyString)) { + beanBuilder.addPropertyReference("loadBalancingPolicy", + loadBalancingPolicyString); + } + + String socketOptionsString = optionsElement + .getAttribute(SOCKET_OPTIONS_ATTRIBUTE); + if (!StringUtils.isEmpty(socketOptionsString)) { + beanBuilder.addPropertyReference("socketOptions", socketOptionsString); + } + + String reconnectionPolicyString = optionsElement + .getAttribute(RECONNECTION_POLICY_ATTRIBUTE); + if (!StringUtils.isEmpty(reconnectionPolicyString)) { + beanBuilder.addPropertyReference("reconnectionPolicy", + reconnectionPolicyString); + } + + } + + builder.addConstructorArgValue(beanBuilder.getBeanDefinition()); + } + +} \ No newline at end of file diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java index 6533703..faa220c 100644 --- a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/config/xml/CloudNamespaceHandler.java @@ -25,6 +25,7 @@ public class CloudNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("redis-connection-factory", new CloudRedisConnectionFactoryParser()); registerBeanDefinitionParser("mongo-db-factory", new CloudMongoDbFactoryParser()); registerBeanDefinitionParser("data-source", new CloudDataSourceFactoryParser()); + registerBeanDefinitionParser("cassandra-session-factory", new CloudCassandraSessionParser()); registerBeanDefinitionParser("connection-properties", new ConnectionPropertiesParser()); diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterConfig.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterConfig.java new file mode 100644 index 0000000..1b8980f --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterConfig.java @@ -0,0 +1,145 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.column; + +import org.springframework.cloud.service.ServiceConnectorConfig; + +import com.datastax.driver.core.NettyOptions; +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.ProtocolOptions.Compression; +import com.datastax.driver.core.ProtocolVersion; +import com.datastax.driver.core.QueryOptions; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.policies.LoadBalancingPolicy; +import com.datastax.driver.core.policies.ReconnectionPolicy; +import com.datastax.driver.core.policies.RetryPolicy; + +/** + * Configuration for a Cassandra {@link com.datastax.driver.core.Cluster}. + * + * @author Mark Paluch + */ +public class CassandraClusterConfig implements ServiceConnectorConfig { + + public static final boolean DEFAULT_METRICS_ENABLED = true; + public static final boolean DEFAULT_JMX_REPORTING_ENABLED = true; + + // Protocol options + private Compression compression; + private NettyOptions nettyOptions; + private ProtocolVersion protocolVersion; + + // Policies + private LoadBalancingPolicy loadBalancingPolicy; + private ReconnectionPolicy reconnectionPolicy; + private RetryPolicy retryPolicy; + + private PoolingOptions poolingOptions; + private QueryOptions queryOptions; + private SocketOptions socketOptions; + + private boolean metricsEnabled = DEFAULT_METRICS_ENABLED; + private boolean jmxReportingEnabled = DEFAULT_JMX_REPORTING_ENABLED; + + public Compression getCompression() { + return compression; + } + + public void setCompression(Compression compression) { + this.compression = compression; + } + + public NettyOptions getNettyOptions() { + return nettyOptions; + } + + public void setNettyOptions(NettyOptions nettyOptions) { + this.nettyOptions = nettyOptions; + } + + public ProtocolVersion getProtocolVersion() { + return protocolVersion; + } + + public void setProtocolVersion(ProtocolVersion protocolVersion) { + this.protocolVersion = protocolVersion; + } + + public LoadBalancingPolicy getLoadBalancingPolicy() { + return loadBalancingPolicy; + } + + public void setLoadBalancingPolicy(LoadBalancingPolicy loadBalancingPolicy) { + this.loadBalancingPolicy = loadBalancingPolicy; + } + + public ReconnectionPolicy getReconnectionPolicy() { + return reconnectionPolicy; + } + + public void setReconnectionPolicy(ReconnectionPolicy reconnectionPolicy) { + this.reconnectionPolicy = reconnectionPolicy; + } + + public RetryPolicy getRetryPolicy() { + return retryPolicy; + } + + public void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + public PoolingOptions getPoolingOptions() { + return poolingOptions; + } + + public void setPoolingOptions(PoolingOptions poolingOptions) { + this.poolingOptions = poolingOptions; + } + + public QueryOptions getQueryOptions() { + return queryOptions; + } + + public void setQueryOptions(QueryOptions queryOptions) { + this.queryOptions = queryOptions; + } + + public SocketOptions getSocketOptions() { + return socketOptions; + } + + public void setSocketOptions(SocketOptions socketOptions) { + this.socketOptions = socketOptions; + } + + public boolean isMetricsEnabled() { + return metricsEnabled; + } + + public void setMetricsEnabled(boolean metricsEnabled) { + this.metricsEnabled = metricsEnabled; + } + + public boolean isJmxReportingEnabled() { + return jmxReportingEnabled; + } + + public void setJmxReportingEnabled(boolean jmxReportingEnabled) { + this.jmxReportingEnabled = jmxReportingEnabled; + } +} diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterCreator.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterCreator.java new file mode 100644 index 0000000..8d81ca7 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterCreator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.column; + +import org.springframework.cloud.service.AbstractServiceConnectorCreator; +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.ServiceConnectorCreator; +import org.springframework.cloud.service.common.CassandraServiceInfo; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Cluster.Builder; + +/** + * {@link ServiceConnectorCreator} implementation to provide a Cassandra {@link Cluster}. + * Allows optionally to apply a {@link CassandraClusterConfig} configuration. + * + * @author Mark Paluch + */ +public class CassandraClusterCreator + extends AbstractServiceConnectorCreator { + + @Override + public Cluster create(CassandraServiceInfo serviceInfo, + ServiceConnectorConfig serviceConnectorConfig) { + + Builder builder = Cluster.builder() + .addContactPoints(serviceInfo.getContactPoints().toArray(new String[0])) + .withPort(serviceInfo.getPort()); + + if (StringUtils.hasText(serviceInfo.getUsername())) { + builder.withCredentials(serviceInfo.getUsername(), serviceInfo.getPassword()); + } + + if (serviceConnectorConfig instanceof CassandraClusterConfig) { + + CassandraClusterConfig config = (CassandraClusterConfig) serviceConnectorConfig; + + if (config.getCompression() != null) { + builder.withCompression(config.getCompression()); + } + + builder.withPoolingOptions(config.getPoolingOptions()); + builder.withSocketOptions(config.getSocketOptions()); + builder.withQueryOptions(config.getQueryOptions()); + builder.withNettyOptions(config.getNettyOptions()); + builder.withLoadBalancingPolicy(config.getLoadBalancingPolicy()); + builder.withReconnectionPolicy(config.getReconnectionPolicy()); + builder.withRetryPolicy(config.getRetryPolicy()); + builder.withProtocolVersion(config.getProtocolVersion()); + + if (!config.isMetricsEnabled()) { + builder.withoutMetrics(); + } + + if (!config.isJmxReportingEnabled()) { + builder.withoutJMXReporting(); + } + } + + return builder.build(); + } +} diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterFactory.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterFactory.java new file mode 100644 index 0000000..4166466 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/column/CassandraClusterFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.column; + +import org.springframework.cloud.service.AbstractCloudServiceConnectorFactory; +import org.springframework.cloud.service.ServiceConnectorConfig; + +import com.datastax.driver.core.Cluster; + +/** + * Spring factory bean for Cassandra service. + * + * @author Mark Paluch + */ +public class CassandraClusterFactory + extends AbstractCloudServiceConnectorFactory { + + public CassandraClusterFactory(String serviceId, + ServiceConnectorConfig serviceConnectorConfiguration) { + super(serviceId, Cluster.class, serviceConnectorConfiguration); + } +} diff --git a/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator b/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator index 35ddb08..400024f 100644 --- a/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator +++ b/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator @@ -7,3 +7,4 @@ org.springframework.cloud.service.document.MongoDbFactoryCreator org.springframework.cloud.service.messaging.RabbitConnectionFactoryCreator org.springframework.cloud.service.smtp.MailSenderCreator org.springframework.cloud.service.relational.SqlServerDataSourceCreator +org.springframework.cloud.service.column.CassandraClusterCreator diff --git a/spring-cloud-spring-service-connector/src/main/resources/org/springframework/cloud/config/xml/spring-cloud.xsd b/spring-cloud-spring-service-connector/src/main/resources/org/springframework/cloud/config/xml/spring-cloud.xsd index b2cdb89..ff1dd13 100644 --- a/spring-cloud-spring-service-connector/src/main/resources/org/springframework/cloud/config/xml/spring-cloud.xsd +++ b/spring-cloud-spring-service-connector/src/main/resources/org/springframework/cloud/config/xml/spring-cloud.xsd @@ -349,4 +349,115 @@ + + + + Locates a cassandra service + + + + + + + + + + + + + The id for this Cassandra Session instance. + If not provided, the service name will be used as a fallback. + + + + + + + The name of the cassandra service. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 9a7eb20..7cfec39 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 @@ -1,7 +1,10 @@ package org.springframework.cloud; +import java.util.Arrays; + import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.cloud.service.ServiceInfo; +import org.springframework.cloud.service.common.CassandraServiceInfo; import org.springframework.cloud.service.common.MongoServiceInfo; import org.springframework.cloud.service.common.MysqlServiceInfo; import org.springframework.cloud.service.common.PostgresqlServiceInfo; @@ -70,6 +73,10 @@ abstract public class StubCloudConnectorTest { protected AmqpServiceInfo createRabbitService(String id) { return new AmqpServiceInfo(id, "10.20.30.40", 1234, "username", "password", "vh"); } + + protected CassandraServiceInfo createCassandraService(String id) { + return new CassandraServiceInfo(id, Arrays.asList("10.20.30.40"), 1234, "username", "password"); + } protected RedisServiceInfo createRedisService(String id) { return new RedisServiceInfo(id, "host", 1234, "password"); diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CassandraClusterFactoryJavaConfigTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CassandraClusterFactoryJavaConfigTest.java new file mode 100644 index 0000000..e546731 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/java/CassandraClusterFactoryJavaConfigTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.config.java; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.cloud.service.ServiceInfo; +import org.springframework.cloud.service.column.CassandraClusterConfig; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.SocketOptions; + +/** + * @author Mark Paluch + */ +public class CassandraClusterFactoryJavaConfigTest + extends AbstractServiceJavaConfigTest { + + public CassandraClusterFactoryJavaConfigTest() { + super(CassandraClusterConfigWithId.class, CassandraClusterConfigWithoutId.class); + } + + protected ServiceInfo createService(String id) { + return createCassandraService(id); + } + + protected Class getConnectorType() { + return Cluster.class; + } + + @Test + public void cloudRedisConnectionFactoryConfig() { + ApplicationContext testContext = getTestApplicationContext( + CassandraClusterConfigWithServiceConfig.class, + createService("my-service")); + + Cluster connector = testContext.getBean("my-service", + getConnectorType()); + + assertThat(connector.getConfiguration().getSocketOptions().getSendBufferSize(), + is(12345)); + } +} + +class CassandraClusterConfigWithId extends AbstractCloudConfig { + @Bean(name = "my-service") + public Cluster testClusterFactory() { + return connectionFactory().cluster("my-service"); + } +} + +class CassandraClusterConfigWithoutId extends AbstractCloudConfig { + @Bean(name = "my-service") + public Cluster testClusterFactory() { + return connectionFactory().cluster(); + } +} + +class CassandraClusterConfigWithServiceConfig extends AbstractCloudConfig { + @Bean(name = "my-service") + public Cluster testClusterFactoryWithConfig() { + + CassandraClusterConfig config = new CassandraClusterConfig(); + SocketOptions socketOptions = new SocketOptions(); + socketOptions.setSendBufferSize(12345); + config.setSocketOptions(socketOptions); + + return connectionFactory().cluster("my-service", config); + } +} diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CassandraClusterXmlConfigTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CassandraClusterXmlConfigTest.java new file mode 100644 index 0000000..a127a62 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CassandraClusterXmlConfigTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.config.xml; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.cloud.service.ServiceInfo; +import org.springframework.context.ApplicationContext; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.policies.ConstantReconnectionPolicy; +import com.datastax.driver.core.policies.DefaultRetryPolicy; +import com.datastax.driver.core.policies.RoundRobinPolicy; + +/** + * @author Vinicius Carvalho + */ +public class CassandraClusterXmlConfigTest extends AbstractServiceXmlConfigTest { + + @Test + public void cassandraSessionWithConfiguration() throws Exception { + ApplicationContext testContext = getTestApplicationContext( + "cloud-cassandra-with-config.xml", createService("my-service")); + Cluster cluster = testContext.getBean("cassandra-full-config", + getConnectorType()); + + assertNotNull(cluster.getConfiguration().getSocketOptions()); + assertEquals(15000, + cluster.getConfiguration().getSocketOptions().getConnectTimeoutMillis()); + assertTrue(DefaultRetryPolicy.class.isAssignableFrom( + cluster.getConfiguration().getPolicies().getRetryPolicy().getClass())); + assertTrue(RoundRobinPolicy.class.isAssignableFrom(cluster.getConfiguration() + .getPolicies().getLoadBalancingPolicy().getClass())); + assertTrue(ConstantReconnectionPolicy.class.isAssignableFrom(cluster + .getConfiguration().getPolicies().getReconnectionPolicy().getClass())); + } + + @Override + protected String getWithServiceIdContextFileName() { + return "cloud-cassandra-with-service-id.xml"; + } + + @Override + protected String getWithoutServiceIdContextFileName() { + return "cloud-cassandra-without-service-id.xml"; + } + + @Override + protected ServiceInfo createService(String id) { + return createCassandraService(id); + } + + @Override + protected Class getConnectorType() { + return Cluster.class; + } +} diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CloudAllServicesTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CloudAllServicesTest.java index ba159ac..7f31a00 100644 --- a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CloudAllServicesTest.java +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/config/xml/CloudAllServicesTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertNotNull; import javax.sql.DataSource; +import com.datastax.driver.core.Cluster; import org.junit.Test; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.beans.factory.BeanCreationException; @@ -36,7 +37,8 @@ public class CloudAllServicesTest extends StubCloudConnectorTest { createPostgresqlService("postDb"), createMongoService("mongoDb"), createRedisService("redisDb"), - createRabbitService("rabbit")); + createRabbitService("rabbit"), + createCassandraService("cassandra")); assertNotNull("Getting service by id", testContext.getBean("mysqlDb")); assertNotNull("Getting service by id and type", testContext.getBean("mysqlDb", DataSource.class)); @@ -51,7 +53,9 @@ public class CloudAllServicesTest extends StubCloudConnectorTest { assertNotNull("Getting service by id and type", testContext.getBean("redisDb", RedisConnectionFactory.class)); assertNotNull("Getting service by id", testContext.getBean("rabbit")); - assertNotNull("Getting service by id and type", testContext.getBean("rabbit", ConnectionFactory.class)); + assertNotNull("Getting service by id and type", testContext.getBean("rabbit", ConnectionFactory.class)); + + assertNotNull("Getting service by id", testContext.getBean("cassandra")); + assertNotNull("Getting service by id and type", testContext.getBean("cassandra", Cluster.class)); } - } diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterCreatorTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterCreatorTest.java new file mode 100644 index 0000000..b550766 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterCreatorTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.column; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; +import org.springframework.cloud.service.common.CassandraServiceInfo; + +import com.datastax.driver.core.AuthProvider; +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Configuration; +import com.datastax.driver.core.PlainTextAuthProvider; +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.ProtocolOptions; +import com.datastax.driver.core.ProtocolVersion; +import com.datastax.driver.core.QueryOptions; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.policies.ConstantReconnectionPolicy; +import com.datastax.driver.core.policies.DowngradingConsistencyRetryPolicy; +import com.datastax.driver.core.policies.Policies; +import com.datastax.driver.core.policies.RoundRobinPolicy; + +/** + * @author Mark Paluch + */ +public class CassandraClusterCreatorTest { + + CassandraClusterCreator creator = new CassandraClusterCreator(); + + @Test + public void shouldCreateCluster() throws Exception { + + CassandraServiceInfo info = new CassandraServiceInfo("local", + Collections.singletonList("127.0.0.1"), 9142); + + Cluster cluster = creator.create(info, null); + + Configuration configuration = cluster.getConfiguration(); + + assertThat(configuration.getProtocolOptions().getAuthProvider(), + is(AuthProvider.NONE)); + } + + @Test + public void shouldCreateClusterWithAuthentication() throws Exception { + + CassandraServiceInfo info = new CassandraServiceInfo("local", + Collections.singletonList("127.0.0.1"), 9142, "walter", "white"); + + Cluster cluster = creator.create(info, null); + + Configuration configuration = cluster.getConfiguration(); + + assertThat(configuration.getProtocolOptions().getAuthProvider(), + is(instanceOf(PlainTextAuthProvider.class))); + } + + @Test + public void shouldCreateClusterWithConfig() throws Exception { + + CassandraServiceInfo info = new CassandraServiceInfo("local", + Collections.singletonList("127.0.0.1"), 9142); + + CassandraClusterConfig config = new CassandraClusterConfig(); + config.setCompression(ProtocolOptions.Compression.NONE); + config.setPoolingOptions(new PoolingOptions().setPoolTimeoutMillis(1234)); + config.setQueryOptions(new QueryOptions()); + config.setProtocolVersion(ProtocolVersion.NEWEST_SUPPORTED); + config.setLoadBalancingPolicy(new RoundRobinPolicy()); + config.setReconnectionPolicy(new ConstantReconnectionPolicy(1)); + config.setRetryPolicy(DowngradingConsistencyRetryPolicy.INSTANCE); + config.setSocketOptions(new SocketOptions()); + + Cluster cluster = creator.create(info, config); + + Configuration configuration = cluster.getConfiguration(); + + assertThat(configuration.getProtocolOptions().getCompression(), + is(config.getCompression())); + assertThat(configuration.getQueryOptions(), is(config.getQueryOptions())); + assertThat(configuration.getSocketOptions(), is(config.getSocketOptions())); + + Policies policies = configuration.getPolicies(); + assertThat(policies.getLoadBalancingPolicy(), + is(config.getLoadBalancingPolicy())); + assertThat(policies.getReconnectionPolicy(), is(config.getReconnectionPolicy())); + assertThat(policies.getRetryPolicy(), is(config.getRetryPolicy())); + } +} diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterFactoryTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterFactoryTest.java new file mode 100644 index 0000000..1e91e00 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/column/CassandraClusterFactoryTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.service.column; + +import java.util.Arrays; + +import org.mockito.Mock; +import org.springframework.cloud.service.AbstractCloudServiceConnectorFactoryTest; +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.CassandraServiceInfo; + +import com.datastax.driver.core.Cluster; + +/** + * + * @author Mark Paluch + */ +public class CassandraClusterFactoryTest extends + AbstractCloudServiceConnectorFactoryTest { + @Mock + Cluster mockConnector; + + public CassandraClusterFactory createTestCloudServiceConnectorFactory(String id, + ServiceConnectorConfig config) { + return new CassandraClusterFactory(id, config); + } + + public Class getConnectorType() { + return Cluster.class; + } + + public Cluster getMockConnector() { + return mockConnector; + } + + public CassandraServiceInfo getTestServiceInfo(String id) { + return new CassandraServiceInfo(id, Arrays.asList("127.0.0.1"), 9042, "user", + "pass"); + } +} diff --git a/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-all-services.xml b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-all-services.xml index d40a454..f63b541 100644 --- a/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-all-services.xml +++ b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-all-services.xml @@ -10,5 +10,6 @@ + diff --git a/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-config.xml b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-config.xml new file mode 100644 index 0000000..f63f8f6 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-config.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-service-id.xml b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-service-id.xml new file mode 100644 index 0000000..eaf26c6 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-with-service-id.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-without-service-id.xml b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-without-service-id.xml new file mode 100644 index 0000000..b5abc21 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/resources/org/springframework/cloud/config/xml/cloud-cassandra-without-service-id.xml @@ -0,0 +1,10 @@ + + + + + +