diff --git a/src/integration/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfigurationTest.java b/src/integration/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfigurationTest.java similarity index 81% rename from src/integration/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfigurationTest.java rename to src/integration/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfigurationTest.java index c1290079..2c57e024 100644 --- a/src/integration/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfigurationTest.java +++ b/src/integration/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfigurationTest.java @@ -16,8 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.Id; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.WriteResultChecking; import org.springframework.data.couchbase.repository.CouchbaseRepository; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; import org.springframework.stereotype.Repository; @@ -25,15 +23,17 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** - * This test case demonstrates that the {@link AbstractCouchbaseConfiguration} can take its SDK beans + * This test case demonstrates that the {@link AbstractCouchbaseDataConfiguration} can take its SDK beans * from a sibling {@link Configuration}. * + * Tests DATACOUCH-279 + * * @author Simon Baslé */ @SuppressWarnings("SpringJavaAutowiringInspection") @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class AbstractCouchbaseConfigurationTest { +public class AbstractCouchbaseDataConfigurationTest { @Autowired ItemRepository repository; @@ -74,8 +74,8 @@ public class AbstractCouchbaseConfigurationTest { } @Configuration - @EnableCouchbaseRepositories(basePackageClasses = AbstractCouchbaseConfigurationTest.class, considerNestedRepositories = true) - abstract static class Config extends AbstractCouchbaseConfiguration { + @EnableCouchbaseRepositories(basePackageClasses = AbstractCouchbaseDataConfigurationTest.class, considerNestedRepositories = true) + static class Config extends AbstractCouchbaseDataConfiguration { @Autowired Cluster c; @@ -89,25 +89,6 @@ public class AbstractCouchbaseConfigurationTest { @Autowired CouchbaseEnvironment e; - //TODO maybe create the bucket if doesn't exist - - @Override - protected CouchbaseEnvironment getEnvironment() { - return DefaultCouchbaseEnvironment.builder() - .connectTimeout(10000) - .kvTimeout(10000) - .queryTimeout(10000) - .viewTimeout(10000) - .build(); - } - - @Override - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = super.couchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } - @Override protected CouchbaseConfigurer couchbaseConfigurer() { return new TestCouchbaseConfigurer(e, c, ci, b); @@ -180,4 +161,4 @@ public class AbstractCouchbaseConfigurationTest { @Repository interface ItemRepository extends CouchbaseRepository {} -} +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index f3907b0b..8dece82a 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -16,23 +16,17 @@ package org.springframework.data.couchbase.config; -import java.util.HashSet; -import java.util.Set; +import java.util.List; import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.CouchbaseCluster; import com.couchbase.client.java.cluster.ClusterInfo; -import org.springframework.beans.factory.config.BeanDefinition; +import com.couchbase.client.java.env.CouchbaseEnvironment; +import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; + import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.Configuration; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.annotation.Persistent; -import org.springframework.data.couchbase.core.*; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Base class for Spring Data Couchbase configuration using JavaConfig. @@ -44,95 +38,91 @@ import org.springframework.util.StringUtils; */ @Configuration public abstract class AbstractCouchbaseConfiguration - extends CouchbaseConfigurationSupport { + extends AbstractCouchbaseDataConfiguration implements CouchbaseConfigurer { - @Override - protected CouchbaseConfigurer couchbaseConfigurer() { - return this; - } + /** + * The list of hostnames (or IP addresses) to bootstrap from. + * + * @return the list of bootstrap hosts. + */ + protected abstract List getBootstrapHosts(); + /** + * The name of the bucket to connect to. + * + * @return the name of the bucket. + */ + protected abstract String getBucketName(); - /** - * Creates a {@link CouchbaseTemplate}. - * - * This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()} - * for construction. - * - * Additionally, it will expect injection of a {@link ClusterInfo} and a {@link Bucket} beans from the context (most - * probably from another configuration). For a self-sufficient configuration that defines such beans, see - * {@link AbstractCouchbaseConfiguration}. - * - * @throws Exception on Bean construction failure. - */ - @Bean(name = BeanNames.COUCHBASE_TEMPLATE) - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = new CouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(), - couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService()); - template.setDefaultConsistency(getDefaultConsistency()); - return template; - } + /** + * The password of the bucket (can be an empty string). + * + * @return the password of the bucket. + */ + protected abstract String getBucketPassword(); - /** - * Creates the {@link RepositoryOperationsMapping} bean which will be used by the framework to choose which - * {@link CouchbaseOperations} should back which {@link CouchbaseRepository}. - * Override {@link #configureRepositoryOperationsMapping(RepositoryOperationsMapping)} in order to customize this. - * - * @throws Exception - */ - @Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING) - public RepositoryOperationsMapping repositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) throws Exception { - //create a base mapping that associates all repositories to the default template - RepositoryOperationsMapping baseMapping = new RepositoryOperationsMapping(couchbaseTemplate); - //let the user tune it - configureRepositoryOperationsMapping(baseMapping); - return baseMapping; - } - - /** - * In order to customize the mapping between repositories/entity types to couchbase templates, - * use the provided mapping's api (eg. in order to have different buckets backing different repositories). - * - * @param mapping the default mapping (will associate all repositories to the default template). - */ - protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) { - //NO_OP - } - - - /** - * Scans the mapping base package for classes annotated with {@link Document}. - * - * @throws ClassNotFoundException if initial entity sets could not be loaded. - */ - @Override - protected Set> getInitialEntitySet() throws ClassNotFoundException { - String basePackage = getMappingBasePackage(); - Set> initialEntitySet = new HashSet>(); - - if (StringUtils.hasText(basePackage)) { - ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); - for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { - initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), AbstractCouchbaseConfiguration.class.getClassLoader())); - } + /** + * Is the {@link #getEnvironment()} to be destroyed by Spring? + * + * @return true if Spring should destroy the environment with the context, false otherwise. + */ + protected boolean isEnvironmentManagedBySpring() { + return true; } - return initialEntitySet; - } + /** + * Override this method if you want a customized {@link CouchbaseEnvironment}. + * This environment will be managed by Spring, which will call its shutdown() + * method upon bean destruction, unless you override {@link #isEnvironmentManagedBySpring()} + * as well to return false. + * + * @return a customized environment, defaults to a {@link DefaultCouchbaseEnvironment}. + */ + protected CouchbaseEnvironment getEnvironment() { + return DefaultCouchbaseEnvironment.create(); + } - /** - * Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration - * class (the concrete class, not this one here) by default. - *

- *

So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package - * will be considered {@code com.acme} unless the method is overridden to implement alternate behavior.

- * - * @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for - * entities. - */ - protected String getMappingBasePackage() { - return getClass().getPackage().getName(); - } + @Override + protected CouchbaseConfigurer couchbaseConfigurer() { + return this; + } + @Override + @Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV) + public CouchbaseEnvironment couchbaseEnvironment() { + CouchbaseEnvironment env = getEnvironment(); + if (isEnvironmentManagedBySpring()) { + return env; + } + return new CouchbaseEnvironmentNoShutdownProxy(env); + } + + /** + * Returns the {@link Cluster} instance to connect to. + * + * @throws Exception on Bean construction failure. + */ + @Override + @Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER) + public Cluster couchbaseCluster() throws Exception { + return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts()); + } + + @Override + @Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO) + public ClusterInfo couchbaseClusterInfo() throws Exception { + return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info(); + } + + /** + * Return the {@link Bucket} instance to connect to. + * + * @throws Exception on Bean construction failure. + */ + @Override + @Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET) + public Bucket couchbaseClient() throws Exception { + //@Bean method can use another @Bean method in the same @Configuration by directly invoking it + return couchbaseCluster().openBucket(getBucketName(), getBucketPassword()); + } } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfiguration.java new file mode 100644 index 00000000..3ad92862 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-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.data.couchbase.config; + +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.cluster.ClusterInfo; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.CouchbaseRepository; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; + + +/** + * Provides beans to setup SDC using {@link CouchbaseConfigurer}. + * This is used by Spring boot to provide auto configuration support. + * + * + * @author Simon Baslé + * @author Subhashni Balakrishnan + */ +@Configuration +public abstract class AbstractCouchbaseDataConfiguration extends CouchbaseConfigurationSupport { + + protected abstract CouchbaseConfigurer couchbaseConfigurer(); + + /** + * Creates a {@link CouchbaseTemplate}. + * + * This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()} + * for construction. + * + * Additionally, it will expect injection of a {@link ClusterInfo} and a {@link Bucket} beans from the context (most + * probably from another configuration). For a self-sufficient configuration that defines such beans, see + * {@link AbstractCouchbaseConfiguration}. + * + * @throws Exception on Bean construction failure. + */ + @Bean(name = BeanNames.COUCHBASE_TEMPLATE) + public CouchbaseTemplate couchbaseTemplate() throws Exception { + CouchbaseTemplate template = new CouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(), + couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService()); + template.setDefaultConsistency(getDefaultConsistency()); + return template; + } + + /** + * Creates the {@link RepositoryOperationsMapping} bean which will be used by the framework to choose which + * {@link CouchbaseOperations} should back which {@link CouchbaseRepository}. + * Override {@link #configureRepositoryOperationsMapping(RepositoryOperationsMapping)} in order to customize this. + * + * @throws Exception + */ + @Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING) + public RepositoryOperationsMapping repositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) throws Exception { + //create a base mapping that associates all repositories to the default template + RepositoryOperationsMapping baseMapping = new RepositoryOperationsMapping(couchbaseTemplate); + //let the user tune it + configureRepositoryOperationsMapping(baseMapping); + return baseMapping; + } + + /** + * In order to customize the mapping between repositories/entity types to couchbase templates, + * use the provided mapping's api (eg. in order to have different buckets backing different repositories). + * + * @param mapping the default mapping (will associate all repositories to the default template). + */ + protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) { + //NO_OP + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java index 2bbf4e0e..d5bf3ff2 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java @@ -13,120 +13,114 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.couchbase.config; -import java.util.HashSet; -import java.util.Set; +import java.util.List; import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.CouchbaseCluster; import com.couchbase.client.java.cluster.ClusterInfo; -import org.springframework.beans.factory.config.BeanDefinition; +import com.couchbase.client.java.env.CouchbaseEnvironment; +import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; + import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.Configuration; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.annotation.Persistent; -import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; -import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** - * Base class for Reactive Spring Data Couchbase configuration using JavaConfig. + * + * Base class for Reactive Spring Data Couchbase configuration java config * * @author Subhashni Balakrishnan */ @Configuration -public abstract class AbstractReactiveCouchbaseConfiguration extends CouchbaseConfigurationSupport { +public abstract class AbstractReactiveCouchbaseConfiguration + extends AbstractReactiveCouchbaseDataConfiguration implements CouchbaseConfigurer { - @Override - protected CouchbaseConfigurer couchbaseConfigurer() { - return this; - } + /** + * The list of hostnames (or IP addresses) to bootstrap from. + * + * @return the list of bootstrap hosts. + */ + protected abstract List getBootstrapHosts(); - /** - * Creates a {@link ReactiveCouchbaseTemplate}. - * - * This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()} - * for construction. - * - * Additionally, it will expect injection of a {@link ClusterInfo} and a {@link Bucket} beans from the context (most - * probably from another configuration). For a self-sufficient configuration that defines such beans, see - * {@link CouchbaseConfigurationSupport}. - * - * @throws Exception on Bean construction failure. - */ - @Bean(name = BeanNames.RXJAVA1_COUCHBASE_TEMPLATE) - public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception { - RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(), - couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService()); - template.setDefaultConsistency(getDefaultConsistency()); - return template; - } + /** + * The name of the bucket to connect to. + * + * @return the name of the bucket. + */ + protected abstract String getBucketName(); - /** - * Creates the {@link ReactiveRepositoryOperationsMapping} bean which will be used by the framework to choose which - * {@link ReactiveCouchbaseOperations} should back which {@link ReactiveCouchbaseRepository}. - * Override {@link #configureReactiveRepositoryOperationsMapping} in order to customize this. - * - * @throws Exception - */ - @Bean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING) - public ReactiveRepositoryOperationsMapping reactiveRepositoryOperationsMapping(RxJavaCouchbaseTemplate couchbaseTemplate) throws Exception { - //create a base mapping that associates all repositories to the default template - ReactiveRepositoryOperationsMapping baseMapping = new ReactiveRepositoryOperationsMapping(couchbaseTemplate); - //let the user tune it - configureReactiveRepositoryOperationsMapping(baseMapping); - return baseMapping; - } + /** + * The password of the bucket (can be an empty string). + * + * @return the password of the bucket. + */ + protected abstract String getBucketPassword(); + /** + * Is the {@link #getEnvironment()} to be destroyed by Spring? + * + * @return true if Spring should destroy the environment with the context, false otherwise. + */ + protected boolean isEnvironmentManagedBySpring() { + return true; + } - /** - * In order to customize the mapping between repositories/entity types to couchbase templates, - * use the provided mapping's api (eg. in order to have different buckets backing different repositories). - * - * @param mapping the default mapping (will associate all repositories to the default template). - */ - protected void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping mapping) { - //NO_OP - } + /** + * Override this method if you want a customized {@link CouchbaseEnvironment}. + * This environment will be managed by Spring, which will call its shutdown() + * method upon bean destruction, unless you override {@link #isEnvironmentManagedBySpring()} + * as well to return false. + * + * @return a customized environment, defaults to a {@link DefaultCouchbaseEnvironment}. + */ + protected CouchbaseEnvironment getEnvironment() { + return DefaultCouchbaseEnvironment.create(); + } + @Override + protected CouchbaseConfigurer couchbaseConfigurer() { + return this; + } - /** - * Scans the mapping base package for classes annotated with {@link Document}. - * - * @throws ClassNotFoundException if initial entity sets could not be loaded. - */ - @Override - protected Set> getInitialEntitySet() throws ClassNotFoundException { - String basePackage = getMappingBasePackage(); - Set> initialEntitySet = new HashSet>(); + @Override + @Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV) + public CouchbaseEnvironment couchbaseEnvironment() { + CouchbaseEnvironment env = getEnvironment(); + if (isEnvironmentManagedBySpring()) { + return env; + } + return new CouchbaseEnvironmentNoShutdownProxy(env); + } - if (StringUtils.hasText(basePackage)) { - ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); - for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { - initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), AbstractReactiveCouchbaseConfiguration.class.getClassLoader())); - } - } - return initialEntitySet; - } + /** + * Returns the {@link Cluster} instance to connect to. + * + * @throws Exception on Bean construction failure. + */ + @Override + @Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER) + public Cluster couchbaseCluster() throws Exception { + return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts()); + } - /** - * Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration - * class (the concrete class, not this one here) by default. - *

- *

So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package - * will be considered {@code com.acme} unless the method is overridden to implement alternate behavior.

- * - * @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for - * entities. - */ - protected String getMappingBasePackage() { - return getClass().getPackage().getName(); - } + @Override + @Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO) + public ClusterInfo couchbaseClusterInfo() throws Exception { + return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info(); + } -} + /** + * Return the {@link Bucket} instance to connect to. + * + * @throws Exception on Bean construction failure. + */ + @Override + @Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET) + public Bucket couchbaseClient() throws Exception { + //@Bean method can use another @Bean method in the same @Configuration by directly invoking it + return couchbaseCluster().openBucket(getBucketName(), getBucketPassword()); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseDataConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseDataConfiguration.java new file mode 100644 index 00000000..efc64182 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseDataConfiguration.java @@ -0,0 +1,78 @@ +/* + * 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.data.couchbase.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate; +import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; +import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; +import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; + +/** + * Provides beans to setup reactive repositories in SDC using {@link CouchbaseConfigurer}. + * + * @author Subhashni Balakrishnan + */ +@Configuration +public abstract class AbstractReactiveCouchbaseDataConfiguration extends CouchbaseConfigurationSupport { + + protected abstract CouchbaseConfigurer couchbaseConfigurer(); + + /** + * Creates a {@link RxJavaCouchbaseTemplate}. + * + * This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()} + * for construction. + * + * + * @throws Exception on Bean construction failure. + */ + @Bean(name = BeanNames.RXJAVA1_COUCHBASE_TEMPLATE) + public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception { + RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(), + couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService()); + template.setDefaultConsistency(getDefaultConsistency()); + return template; + } + + /** + * Creates the {@link ReactiveRepositoryOperationsMapping} bean which will be used by the framework to choose which + * {@link RxJavaCouchbaseOperations} should back which {@link ReactiveCouchbaseRepository}. + * Override {@link #configureReactiveRepositoryOperationsMapping} in order to customize this. + * + * @throws Exception + */ + @Bean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING) + public ReactiveRepositoryOperationsMapping reactiveRepositoryOperationsMapping(RxJavaCouchbaseTemplate couchbaseTemplate) throws Exception { + //create a base mapping that associates all repositories to the default template + ReactiveRepositoryOperationsMapping baseMapping = new ReactiveRepositoryOperationsMapping(couchbaseTemplate); + //let the user tune it + configureReactiveRepositoryOperationsMapping(baseMapping); + return baseMapping; + } + + + /** + * In order to customize the mapping between repositories/entity types to couchbase templates, + * use the provided mapping's api (eg. in order to have different buckets backing different repositories). + * + * @param mapping the default mapping (will associate all repositories to the default template). + */ + protected void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping mapping) { + //NO_OP + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java index 76d7e74b..5441dae5 100644 --- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java @@ -17,23 +17,21 @@ package org.springframework.data.couchbase.config; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.Cluster; -import com.couchbase.client.java.CouchbaseCluster; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; import com.couchbase.client.java.query.N1qlQuery; import com.couchbase.client.java.view.ViewQuery; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.Persistent; import org.springframework.data.couchbase.core.convert.CustomConversions; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService; import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; +import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.data.couchbase.core.query.Consistency; import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; @@ -42,199 +40,155 @@ import org.springframework.data.couchbase.repository.support.IndexManager; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** + * Provides configuration support for common beans in both {@link AbstractCouchbaseDataConfiguration} + * and {@link AbstractReactiveCouchbaseDataConfiguration} configurations + * + * @author Simon Baslé * @author Subhashni Balakrishnan */ -public abstract class CouchbaseConfigurationSupport implements CouchbaseConfigurer { - /** - * The list of hostnames (or IP addresses) to bootstrap from. - * - * @return the list of bootstrap hosts. - */ - protected abstract List getBootstrapHosts(); +public class CouchbaseConfigurationSupport { + /** + * Scans the mapping base package for classes annotated with {@link Document}. + * + * @throws ClassNotFoundException if initial entity sets could not be loaded. + */ + protected Set> getInitialEntitySet() throws ClassNotFoundException { + String basePackage = getMappingBasePackage(); + Set> initialEntitySet = new HashSet>(); - /** - * The name of the bucket to connect to. - * - * @return the name of the bucket. - */ - protected abstract String getBucketName(); + if (StringUtils.hasText(basePackage)) { + ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class)); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); + for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { + initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), AbstractReactiveCouchbaseConfiguration.class.getClassLoader())); + } + } + return initialEntitySet; + } - /** - * The password of the bucket (can be an empty string). - * - * @return the password of the bucket. - */ - protected abstract String getBucketPassword(); + /** + * Determines the name of the field that will store the type information for complex types when + * using the {@link #mappingCouchbaseConverter()}. + * Defaults to {@value MappingCouchbaseConverter#TYPEKEY_DEFAULT}. + * + * @see MappingCouchbaseConverter#TYPEKEY_DEFAULT + * @see MappingCouchbaseConverter#TYPEKEY_SYNCGATEWAY_COMPATIBLE + */ + public String typeKey() { + return MappingCouchbaseConverter.TYPEKEY_DEFAULT; + } - /** - * Is the {@link #getEnvironment()} to be destroyed by Spring? - * - * @return true if Spring should destroy the environment with the context, false otherwise. - */ - protected boolean isEnvironmentManagedBySpring() { - return true; - } + /** + * Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}. + * + * @throws Exception on Bean construction failure. + */ + @Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER) + public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception { + MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext(), typeKey()); + converter.setCustomConversions(customConversions()); + return converter; + } - /** - * Override this method if you want a customized {@link CouchbaseEnvironment}. - * This environment will be managed by Spring, which will call its shutdown() - * method upon bean destruction, unless you override {@link #isEnvironmentManagedBySpring()} - * as well to return false. - * - * @return a customized environment, defaults to a {@link DefaultCouchbaseEnvironment}. - */ - protected CouchbaseEnvironment getEnvironment() { - return DefaultCouchbaseEnvironment.create(); - } + /** + * Creates a {@link TranslationService}. + * + * @return TranslationService, defaulting to JacksonTranslationService. + */ + @Bean(name = BeanNames.COUCHBASE_TRANSLATION_SERVICE) + public TranslationService translationService() { + final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService(); + jacksonTranslationService.afterPropertiesSet(); + return jacksonTranslationService; + } - @Override - @Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV) - public CouchbaseEnvironment couchbaseEnvironment() { - CouchbaseEnvironment env = getEnvironment(); - if (isEnvironmentManagedBySpring()) { - return env; - } - return new CouchbaseEnvironmentNoShutdownProxy(env); - } + /** + * Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package. + * + * @throws Exception on Bean construction failure. + */ + @Bean(name = BeanNames.COUCHBASE_MAPPING_CONTEXT) + public CouchbaseMappingContext couchbaseMappingContext() throws Exception { + CouchbaseMappingContext mappingContext = new CouchbaseMappingContext(); + mappingContext.setInitialEntitySet(getInitialEntitySet()); + mappingContext.setSimpleTypeHolder(customConversions().getSimpleTypeHolder()); + mappingContext.setFieldNamingStrategy(fieldNamingStrategy()); + return mappingContext; + } - /** - * Returns the {@link Cluster} instance to connect to. - * - * @throws Exception on Bean construction failure. - */ - @Override - @Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER) - public Cluster couchbaseCluster() throws Exception { - return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts()); - } + /** + * Register custom Converters in a {@link CustomConversions} object if required. These + * {@link CustomConversions} will be registered with the {@link #mappingCouchbaseConverter()} and + * {@link #couchbaseMappingContext()}. Returns an empty {@link CustomConversions} instance by default. + * + * @return must not be {@literal null}. + */ + @Bean(name = BeanNames.COUCHBASE_CUSTOM_CONVERSIONS) + public CustomConversions customConversions() { + return new CustomConversions(Collections.emptyList()); + } - @Override - @Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO) - public ClusterInfo couchbaseClusterInfo() throws Exception { - return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info(); - } + /** + * Register an {@link IndexManager} bean that will be used to process {@link ViewIndexed}, + * {@link N1qlPrimaryIndexed} and {@link N1qlSecondaryIndexed} annotations on repositories + * to automatically create indexes. By default, since such automatic creations are discouraged in + * production envrironment, the configuration will assume the worst and will ignore these annotations. + *

+ * If you are sure this configuration used in a context where such automatic creations are desired (eg. + * you want automatic index creation in Dev, just not in Prod, and this configuration is the Dev one), + * override the bean and use the {@link IndexManager#IndexManager()} constructor (or + * {@link IndexManager#IndexManager(boolean, boolean, boolean)} constructor with appropriate flags set to true to + * activate). + */ + @Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER) + public IndexManager indexManager() { + return new IndexManager(false, false, false); //this ignores view, N1QL primary and secondary annotations + } - /** - * Return the {@link Bucket} instance to connect to. - * - * @throws Exception on Bean construction failure. - */ - @Override - @Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET) - public Bucket couchbaseClient() throws Exception { - //@Bean method can use another @Bean method in the same @Configuration by directly invoking it - return couchbaseCluster().openBucket(getBucketName(), getBucketPassword()); - } + /** + * Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration + * class (the concrete class, not this one here) by default. + *

+ *

So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package + * will be considered {@code com.acme} unless the method is overridden to implement alternate behavior.

+ * + * @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for + * entities. + */ + protected String getMappingBasePackage() { + return getClass().getPackage().getName(); + } - /** - * Determines the name of the field that will store the type information for complex types when - * using the {@link #mappingCouchbaseConverter()}. - * Defaults to {@value MappingCouchbaseConverter#TYPEKEY_DEFAULT}. - * - * @see MappingCouchbaseConverter#TYPEKEY_DEFAULT - * @see MappingCouchbaseConverter#TYPEKEY_SYNCGATEWAY_COMPATIBLE - */ - public String typeKey() { - return MappingCouchbaseConverter.TYPEKEY_DEFAULT; - } + /** + * Set to true if field names should be abbreviated with the {@link CamelCaseAbbreviatingFieldNamingStrategy}. + * + * @return true if field names should be abbreviated, default is false. + */ + protected boolean abbreviateFieldNames() { + return false; + } - /** - * Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}. - * - * @throws Exception on Bean construction failure. - */ - @Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER) - public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception { - MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext(), typeKey()); - converter.setCustomConversions(customConversions()); - return converter; - } + /** + * Configures a {@link FieldNamingStrategy} on the {@link CouchbaseMappingContext} instance created. + * + * @return the naming strategy. + */ + protected FieldNamingStrategy fieldNamingStrategy() { + return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy() : PropertyNameFieldNamingStrategy.INSTANCE; + } - /** - * Creates a {@link TranslationService}. - * - * @return TranslationService, defaulting to JacksonTranslationService. - */ - @Bean(name = BeanNames.COUCHBASE_TRANSLATION_SERVICE) - public TranslationService translationService() { - final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService(); - jacksonTranslationService.afterPropertiesSet(); - return jacksonTranslationService; - } - - /** - * Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package. - * - * @throws Exception on Bean construction failure. - */ - @Bean(name = BeanNames.COUCHBASE_MAPPING_CONTEXT) - public CouchbaseMappingContext couchbaseMappingContext() throws Exception { - CouchbaseMappingContext mappingContext = new CouchbaseMappingContext(); - mappingContext.setInitialEntitySet(getInitialEntitySet()); - mappingContext.setSimpleTypeHolder(customConversions().getSimpleTypeHolder()); - mappingContext.setFieldNamingStrategy(fieldNamingStrategy()); - return mappingContext; - } - - /** - * Register custom Converters in a {@link CustomConversions} object if required. These - * {@link CustomConversions} will be registered with the {@link #mappingCouchbaseConverter()} and - * {@link #couchbaseMappingContext()}. Returns an empty {@link CustomConversions} instance by default. - * - * @return must not be {@literal null}. - */ - @Bean(name = BeanNames.COUCHBASE_CUSTOM_CONVERSIONS) - public CustomConversions customConversions() { - return new CustomConversions(Collections.emptyList()); - } - - /** - * Register an {@link IndexManager} bean that will be used to process {@link ViewIndexed}, - * {@link N1qlPrimaryIndexed} and {@link N1qlSecondaryIndexed} annotations on repositories - * to automatically create indexes. By default, since such automatic creations are discouraged in - * production envrironment, the configuration will assume the worst and will ignore these annotations. - *

- * If you are sure this configuration used in a context where such automatic creations are desired (eg. - * you want automatic index creation in Dev, just not in Prod, and this configuration is the Dev one), - * override the bean and use the {@link IndexManager#IndexManager()} constructor (or - * {@link IndexManager#IndexManager(boolean, boolean, boolean)} constructor with appropriate flags set to true to - * activate). - */ - @Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER) - public IndexManager indexManager() { - return new IndexManager(false, false, false); //this ignores view, N1QL primary and secondary annotations - } - /** - * Set to true if field names should be abbreviated with the {@link CamelCaseAbbreviatingFieldNamingStrategy}. - * - * @return true if field names should be abbreviated, default is false. - */ - protected boolean abbreviateFieldNames() { - return false; - } - - /** - * Configures a {@link FieldNamingStrategy} on the {@link CouchbaseMappingContext} instance created. - * - * @return the naming strategy. - */ - protected FieldNamingStrategy fieldNamingStrategy() { - return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy() : PropertyNameFieldNamingStrategy.INSTANCE; - } - - /** - * Configures the default consistency for generated {@link ViewQuery view queries} - * and {@link N1qlQuery N1QL queries} in repositories. - * - * @return the {@link Consistency consistency} to apply by default on generated queries. - */ - protected Consistency getDefaultConsistency() { - return Consistency.DEFAULT_CONSISTENCY; - } - - protected abstract CouchbaseConfigurer couchbaseConfigurer(); - - protected abstract Set> getInitialEntitySet() throws ClassNotFoundException; -} + /** + * Configures the default consistency for generated {@link ViewQuery view queries} + * and {@link N1qlQuery N1QL queries} in repositories. + * + * @return the {@link Consistency consistency} to apply by default on generated queries. + */ + protected Consistency getDefaultConsistency() { + return Consistency.DEFAULT_CONSISTENCY; + } +} \ No newline at end of file