diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractSessionConfiguration.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractSessionConfiguration.java index b8504fc0d..bc78f2c36 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractSessionConfiguration.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractSessionConfiguration.java @@ -15,6 +15,8 @@ */ package org.springframework.data.cassandra.config; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -26,6 +28,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; import org.springframework.data.cassandra.SessionFactory; import org.springframework.data.cassandra.core.cql.CqlTemplate; import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification; @@ -224,6 +227,33 @@ public abstract class AbstractSessionConfiguration implements BeanFactoryAware { return null; } + /** + * Returns the {@link DriverConfigLoaderBuilderConfigurer}. The configuration gets applied after applying + * {@link System#getProperties() System Properties} config overrides and before + * {@link #getDriverConfigurationResource() the driver config file}. + * + * @return the {@link DriverConfigLoaderBuilderConfigurer}; may be {@literal null}. + * @since 3.1.2 + */ + @Nullable + protected DriverConfigLoaderBuilderConfigurer getDriverConfigLoaderBuilderConfigurer() { + return null; + } + + /** + * Returns the {@link Resource} pointing to a driver configuration file. The configuration file is applied after + * applying {@link System#getProperties() System Properties} and the configuration built by this configuration class. + * + * @return the {@link Resource}; may be {@literal null} if none provided. + * @since 3.1.2 + * @see Driver + * Configuration + */ + @Nullable + protected Resource getDriverConfigurationResource() { + return null; + } + /** * Returns the list of CQL scripts to be run on startup after {@link #getKeyspaceCreations() Keyspace creations} * and after initialization of the {@literal System} Keyspace. @@ -280,7 +310,9 @@ public abstract class AbstractSessionConfiguration implements BeanFactoryAware { private SessionBuilderConfigurer getSessionBuilderConfigurerWrapper() { - SessionBuilderConfigurer configurer = getSessionBuilderConfigurer(); + SessionBuilderConfigurer sessionConfigurer = getSessionBuilderConfigurer(); + DriverConfigLoaderBuilderConfigurer driverConfigLoaderConfigurer = getDriverConfigLoaderBuilderConfigurer(); + Resource driverConfigFile = getDriverConfigurationResource(); return sessionBuilder -> { @@ -302,16 +334,30 @@ public abstract class AbstractSessionConfiguration implements BeanFactoryAware { ConfigFactory.invalidateCaches(); - return ConfigFactory.defaultOverrides() // - .withFallback(options.build()) // - .withFallback(ConfigFactory.defaultReference()); + Config config = ConfigFactory.defaultOverrides() // + .withFallback(options.build()); + + if (driverConfigFile != null) { + try { + config = config + .withFallback(ConfigFactory.parseReader(new InputStreamReader(driverConfigFile.getInputStream()))); + } catch (IOException e) { + throw new IllegalStateException(String.format("Cannot parse driver config file %s", driverConfigFile), e); + } + } + + return config.withFallback(ConfigFactory.defaultReference()); }, DefaultDriverConfigLoader.DEFAULT_ROOT_PATH); + if (driverConfigLoaderConfigurer != null) { + driverConfigLoaderConfigurer.configure(builder); + } + sessionBuilder.withConfigLoader(builder.build()); - if (configurer != null) { - return configurer.configure(sessionBuilder); + if (sessionConfigurer != null) { + return sessionConfigurer.configure(sessionBuilder); } return sessionBuilder; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DriverConfigLoaderBuilderConfigurer.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DriverConfigLoaderBuilderConfigurer.java new file mode 100644 index 000000000..82d4d61b5 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DriverConfigLoaderBuilderConfigurer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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 + * + * https://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.data.cassandra.config; + +import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link ProgrammaticDriverConfigLoaderBuilder} via a {@link DriverConfigLoaderBuilderConfigurer} whilst retaining + * default configuration. + * + * @author Mark Paluch + * @since 3.1.2 + */ +public interface DriverConfigLoaderBuilderConfigurer { + + /** + * Customize the {@linkplain ProgrammaticDriverConfigLoaderBuilder DriverConfigLoader builder}. + * + * @param builder the builder to customize + */ + void configure(ProgrammaticDriverConfigLoaderBuilder builder); + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/AbstractSessionConfigurationIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/AbstractSessionConfigurationIntegrationTests.java new file mode 100644 index 000000000..874b25f57 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/AbstractSessionConfigurationIntegrationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 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 + * + * https://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.data.cassandra.config; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.data.cassandra.support.CassandraConnectionProperties; +import org.springframework.data.cassandra.test.util.CassandraExtension; +import org.springframework.lang.Nullable; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; + +/** + * Unit tests for {@link AbstractSessionConfiguration}. + * + * @author Mark Paluch + */ +@ExtendWith(CassandraExtension.class) +class AbstractSessionConfigurationIntegrationTests { + + @Test + void shouldApplyDriverConfigLoaderBuilderConfigurer() { + + MySessionConfiguration configuration = new MySessionConfiguration(); + CqlSessionFactoryBean bean = configuration.cassandraSession(); + bean.afterPropertiesSet(); + CqlSession session = bean.getObject(); + + assertThat(session.getContext().getConfig().getProfile("foo")).isNotNull(); + assertThat(session.getContext().getConfig().getProfiles()).doesNotContainKeys("bar"); + } + + @Test + void shouldApplyConfigurationFile() { + + MySessionConfiguration configuration = new MySessionConfiguration(); + CqlSessionFactoryBean bean = configuration.cassandraSession(); + bean.afterPropertiesSet(); + CqlSession session = bean.getObject(); + + assertThat(session.getContext().getConfig().getProfile("oltp")).isNotNull(); + assertThat(session.getContext().getConfig().getProfiles()).doesNotContainKeys("bar"); + } + + static class MySessionConfiguration extends AbstractSessionConfiguration { + + @Override + protected String getKeyspaceName() { + return "system"; + } + + @Nullable + @Override + protected String getLocalDataCenter() { + return "datacenter1"; + } + + @Override + protected int getPort() { + return new CassandraConnectionProperties().getCassandraPort(); + } + + @Nullable + @Override + protected DriverConfigLoaderBuilderConfigurer getDriverConfigLoaderBuilderConfigurer() { + return it -> { + it.startProfile("foo").withString(DefaultDriverOption.SESSION_NAME, "hello-world").endProfile(); + }; + } + + @Nullable + @Override + protected Resource getDriverConfigurationResource() { + return new ClassPathResource("application.conf"); + } + } + +} diff --git a/spring-data-cassandra/src/test/resources/application.conf b/spring-data-cassandra/src/test/resources/application.conf index 672734b89..451364b7e 100644 --- a/spring-data-cassandra/src/test/resources/application.conf +++ b/spring-data-cassandra/src/test/resources/application.conf @@ -11,6 +11,13 @@ # This file is in HOCON format, see https://github.com/typesafehub/config/blob/master/HOCON.md. datastax-java-driver { + profiles { + oltp { + basic.request.timeout = 100 milliseconds + basic.request.consistency = ONE + } + } + basic.load-balancing-policy { class = DcInferringLoadBalancingPolicy }