diff --git a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java index d4317f54..f14ba8f1 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java @@ -33,7 +33,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.test.context.ContextConfiguration; @@ -53,13 +53,13 @@ public class CouchbaseRepositoryViewTests { private Bucket client; @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; private CustomUserRepository repository; @Before public void setup() throws Exception { - repository = new CouchbaseRepositoryFactory(template).getRepository(CustomUserRepository.class); + repository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(CustomUserRepository.class); } @Test diff --git a/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java index 17df466c..d19d9bd8 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java @@ -19,14 +19,13 @@ package org.springframework.data.couchbase.repository; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import com.couchbase.client.java.Bucket; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -48,13 +47,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class N1qlCouchbaseRepositoryTests { @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; private PartyPagingRepository repository; @Before public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping); repository = factory.getRepository(PartyPagingRepository.class); } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryTests.java index 94efb4b7..bf966c66 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryTests.java @@ -31,7 +31,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -47,7 +47,7 @@ public class N1qlCrudRepositoryTests { private Bucket client; @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; private PartyRepository partyRepository; private ItemRepository itemRepository; @@ -57,8 +57,8 @@ public class N1qlCrudRepositoryTests { @Before public void setup() throws Exception { - partyRepository = new CouchbaseRepositoryFactory(template).getRepository(PartyRepository.class); - itemRepository = new CouchbaseRepositoryFactory(template).getRepository(ItemRepository.class); + partyRepository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(PartyRepository.class); + itemRepository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(ItemRepository.class); itemRepository.save(item); partyRepository.save(party); diff --git a/src/integration/java/org/springframework/data/couchbase/repository/PageAndSliceTests.java b/src/integration/java/org/springframework/data/couchbase/repository/PageAndSliceTests.java index 26edd4f8..556e7ded 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/PageAndSliceTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/PageAndSliceTests.java @@ -14,7 +14,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -33,13 +33,13 @@ public class PageAndSliceTests { private Bucket client; @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; private UserRepository repository; @Before public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping); repository = factory.getRepository(UserRepository.class); } @Test diff --git a/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionTests.java b/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionTests.java index 3cb14592..a6e72d9f 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionTests.java @@ -15,7 +15,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.test.context.ContextConfiguration; @@ -34,13 +34,13 @@ public class QueryDerivationConversionTests { private Bucket client; @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; private PartyRepository repository; @Before public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping); repository = factory.getRepository(PartyRepository.class); } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java index d5edf346..4252e552 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java @@ -32,10 +32,8 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.couchbase.IntegrationTestApplicationConfig; import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; @@ -53,13 +51,13 @@ public class SimpleCouchbaseRepositoryTests { private Bucket client; @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; private UserRepository repository; @Before public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping); repository = factory.getRepository(UserRepository.class); } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java index f0bec185..9e7eb0f5 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java @@ -30,10 +30,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.UnsupportedCouchbaseFeatureException; import org.springframework.data.couchbase.repository.User; import org.springframework.data.couchbase.repository.UserRepository; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.test.context.ContextConfiguration; @@ -49,7 +50,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class FeatureDetectionRepositoryTests { @Autowired - private CouchbaseTemplate template; + private RepositoryOperationsMapping operationsMapping; @Autowired private ClusterInfo clusterInfo; @@ -61,7 +62,7 @@ public class FeatureDetectionRepositoryTests { @Test public void testN1qlIncompatibleClusterFailsFastForN1qlBasedRepository() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping); try { factory.getRepository(UserRepository.class); fail("expected UnsupportedCouchbaseFeatureException"); @@ -72,13 +73,15 @@ public class FeatureDetectionRepositoryTests { @Test public void testN1qlIncompatibleClusterDoesntFailForViewBasedRepository() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping); ViewOnlyUserRepository repository = factory.getRepository(ViewOnlyUserRepository.class); assertNotNull(repository); } @Test public void testN1qlIncompatibleClusterTemplateFails() { + final CouchbaseOperations template = operationsMapping.getDefault(); + N1qlQuery query = N1qlQuery.simple("SELECT * FROM `" + template.getCouchbaseBucket().name() + "`"); try { template.findByN1QL(query, User.class); diff --git a/src/integration/java/org/springframework/data/couchbase/repository/wiring/RepositoryTemplateWiringTests.java b/src/integration/java/org/springframework/data/couchbase/repository/wiring/RepositoryTemplateWiringTests.java new file mode 100644 index 00000000..288443bc --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/wiring/RepositoryTemplateWiringTests.java @@ -0,0 +1,160 @@ +package org.springframework.data.couchbase.repository.wiring; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.List; + +import com.couchbase.client.java.cluster.ClusterInfo; +import com.couchbase.client.java.util.features.CouchbaseFeature; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +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.config.AbstractCouchbaseConfiguration; +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.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; +import org.springframework.stereotype.Repository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * This test case demonstrates (with a bit of mocking) that the framework will take the + * {@link RepositoryOperationsMapping} into account in its wiring of repositories with + * underlying {@link CouchbaseOperations} beans. + * + * @author Simon Baslé + */ +@SuppressWarnings("SpringJavaAutowiringInspection") +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class RepositoryTemplateWiringTests { + + private static CouchbaseOperations mockOpsA; + private static CouchbaseOperations mockOpsB; + private static CouchbaseOperations mockOpsC; + + @BeforeClass + public static void initMocks() { + ClusterInfo info = mock(ClusterInfo.class); + when(info.checkAvailable(any(CouchbaseFeature.class))).thenReturn(true); + + mockOpsA = mock(CouchbaseOperations.class); + when(mockOpsA.getCouchbaseClusterInfo()).thenReturn(info); + when(mockOpsA.exists(any(String.class))).thenReturn(true); + + mockOpsB = mock(CouchbaseOperations.class); + when(mockOpsB.getCouchbaseClusterInfo()).thenReturn(info); + when(mockOpsB.exists(any(String.class))).thenReturn(false); + + mockOpsC = spy(new CouchbaseTemplate(info, null)); + Misc cValue = new Misc(); + cValue.id = "mock"; + cValue.random = true; + doReturn(cValue).when(mockOpsC).findById(any(String.class), any(Class.class)); + } + + @Autowired + BucketARepository repositoryA; + + @Autowired + BucketBRepository repositoryB; + + @Autowired + BucketCRepository repositoryC; + + @Configuration + @EnableCouchbaseRepositories(basePackageClasses = RepositoryTemplateWiringTests.class, considerNestedRepositories = true) + static class Config extends AbstractCouchbaseConfiguration { + + @Override + protected List getBootstrapHosts() { + return Arrays.asList("127.0.0.1"); + } + + @Override + protected String getBucketName() { + return "default"; + } + + @Override + protected String getBucketPassword() { + return ""; + } + + @Bean + public CouchbaseOperations templateA() { + return mockOpsA; + } + + @Bean + public CouchbaseOperations templateB() { + return mockOpsB; + } + + @Bean + public CouchbaseOperations templateC() { + return mockOpsC; + } + + @Override + public RepositoryOperationsMapping repositoryOperationsMapping() throws Exception { + return new RepositoryOperationsMapping(templateC()) + .map(BucketBRepository.class, templateB()) + .mapEntity(Item.class, templateA()); + } + } + + @Test + public void testRepositoriesAreInstanciatedWithCorrectTemplates() { + assertNotNull(repositoryA); + assertNotNull(repositoryB); + assertNotNull(repositoryC); + + boolean existA = repositoryA.exists("testA"); + boolean existB = repositoryB.exists("testB"); + Misc valueC = repositoryC.findOne("toto"); + + assertTrue(existA); + assertFalse(existB); + assertNotNull(valueC); + assertEquals("mock", valueC.id); + assertEquals(true, valueC.random); + + verify(mockOpsA).exists("testA"); + verify(mockOpsB).exists("testB"); + verify(mockOpsC).findById(any(String.class), eq(Misc.class)); + } + + private static class Item { + @Id + public String id; + + public String value; + } + + private static class Misc { + @Id + public String id; + + public boolean random; + } + + @Repository + static interface BucketARepository extends CouchbaseRepository {} + + @Repository + static interface BucketBRepository extends CouchbaseRepository {} + + @Repository + static interface BucketCRepository extends CouchbaseRepository {} + +} diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index 8fdf367e..7a30ce7e 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -19,6 +19,7 @@ public class Config extends AbstractCouchbaseConfiguration { } ---- ==== +An advanced usage is described in <>. XML-based configuration is also available: @@ -327,3 +328,59 @@ Provide the implementation and directly use `queryView` and `queryN1QL` methods - for example for views `ViewQuery.stale(Stale.FALSE)` - for example for N1QL `Query.simple("SELECT * FROM default", QueryParams.build().consistency(ScanConsistency.REQUEST_PLUS));` + +[[couchbase.repository.multibucket]] +== Working with multiple buckets +The Java Config version allows you to define multiple `Bucket` and `CouchbaseTemplate`, but in order to have different +repositories use different underlying buckets/templates, you need to follow these steps: + + * in your `AbstractCouchbaseConfiguration` implementation, override the `repositoryOperationsMapping` bean method. + * have it return a new `RepositoryOperationsMapping` (or reuse the `super()` one) + * configure the mapping by chaining calls to `map`, `mapEntity` and `setDefault`. + ** `map` maps a specific repository interface to the `CouchbaseOperations` it should use + ** `mapEntity` maps all unmapped repositories of a domain type / entity class to a common `CouchbaseOperations` + ** finally the value passed to the constructor or `setDefault` maps all remaining unmapped repositories to a default + `CouchaseOperations` (the default, using `couchbaseTemplate` bean unless modified). + +The idea is that the framework will look for an entry corresponding to the repository's interface when instantiating it. +If none is found it will look at the mapping for the repository's domain type. Eventually it will fallback to the default setting. +Here is an example: + +.Example of configuring multiple templates and repositories. +==== +[source,java] +---- +@Configuration +@EnableCouchbaseRepositories +public class ConcreteCouchbaseConfig extends AbstractCouchbaseConfig { + + //the default bucket and template must be created, implement abstract methods here to that end + + //we want all User objects to be stored in a second bucket + //let's define the bucket reference... + @Bean + public Bucket userBucket() { + return couchbaseCluster().openBucket("users", ""); + } + + //... then the template (inspired by couchbaseTemplate() method)... + @Bean + public CouchbaseTemplate userTemplate() { + CouchbaseTemplate template = new CouchbaseTemplate( + couchbaseClusterInfo(), //reuse the default bean + userBucket(), //the bucket is non-default + mappingCouchbaseConverter(), translationService() //default beans here as well + ); + template.setDefaultConsistency(getDefaultConsistency()); + return template; + } + + //... then finally make sure all repositories of Users will use it + @Override + public RepositoryOperationsMapping repositoryOperationsMapping() throws Exception { + return super.repositoryOperationsMapping() //this is already using couchbaseTemplate as default + .mapEntity(User.class, userTemplate()); //every repository dealing with User will be backed by userTemplate() + } +} +---- +==== 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 b2e53fe9..d0234fd5 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen 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.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.convert.CustomConversions; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; @@ -44,6 +45,8 @@ import org.springframework.data.couchbase.core.convert.translation.TranslationSe import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.data.couchbase.core.view.Consistency; +import org.springframework.data.couchbase.repository.CouchbaseRepository; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; @@ -151,6 +154,17 @@ public abstract class AbstractCouchbaseConfiguration { return template; } + /** + * Creates the {@link RepositoryOperationsMapping} bean which will be used by the framework to choose which + * {@link CouchbaseOperations} should back which {@link CouchbaseRepository}. + * + * @throws Exception + */ + @Bean(name = BeanNames.REPO_OPERATIONS_MAPPING) + public RepositoryOperationsMapping repositoryOperationsMapping() throws Exception { + return new RepositoryOperationsMapping(couchbaseTemplate()); + } + /** * Determines the name of the field that will store the type information for complex types when * using the {@link #mappingCouchbaseConverter()}. diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java index b7dfa01f..c06b8964 100644 --- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java @@ -22,7 +22,7 @@ package org.springframework.data.couchbase.config; * @author Michael Nitschinger * @author Simon Baslé */ -class BeanNames { +public class BeanNames { /** * Refers to the "<couchbase:env />" bean. @@ -52,4 +52,9 @@ class BeanNames { * Refers to the "<couchbase:clusterInfo>" bean */ static final String COUCHBASE_CLUSTER_INFO = "couchbaseClusterInfo"; + + /** + * The bean that stores custom mapping between repositories and their backing couchbaseOperations. + */ + public static final String REPO_OPERATIONS_MAPPING = "repositoryOperationsMapping"; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java index e3ca2ab9..39d9e5e3 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java +++ b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java @@ -23,6 +23,7 @@ import java.lang.annotation.Annotation; import java.util.Set; import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.repository.cdi.CdiRepositoryBean; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; @@ -61,7 +62,8 @@ public class CouchbaseRepositoryBean extends CdiRepositoryBean { @Override protected T create(CreationalContext creationalContext, Class repositoryType, Object customImplementation) { CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class); - return new CouchbaseRepositoryFactory(couchbaseOperations).getRepository(repositoryType, customImplementation); + RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations); + return new CouchbaseRepositoryFactory(couchbaseOperationsMapping).getRepository(repositoryType, customImplementation); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java index 1515b29f..5965ac9e 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java @@ -16,14 +16,15 @@ package org.springframework.data.couchbase.repository.config; +import org.w3c.dom.Element; + import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.data.config.ParsingUtils; +import org.springframework.data.couchbase.config.BeanNames; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; -import org.w3c.dom.Element; /** * @author Michael Nitschinger @@ -49,7 +50,7 @@ public class CouchbaseRepositoryConfigurationExtension extends RepositoryConfigu @Override public void postProcess(final BeanDefinitionBuilder builder, final AnnotationRepositoryConfigurationSource config) { - AnnotationAttributes attributes = config.getAttributes(); - builder.addPropertyReference("couchbaseOperations", attributes.getString("couchbaseTemplateRef")); + builder.addDependsOn(BeanNames.REPO_OPERATIONS_MAPPING); + builder.addPropertyReference("couchbaseOperationsMapping", BeanNames.REPO_OPERATIONS_MAPPING); } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java b/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java index a96cdef9..110cdd3a 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java @@ -16,8 +16,10 @@ package org.springframework.data.couchbase.repository.config; +import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; +import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactoryBean; import org.springframework.data.repository.config.DefaultRepositoryBaseClass; @@ -100,7 +102,13 @@ public @interface EnableCouchbaseRepositories { Class repositoryFactoryBeanClass() default CouchbaseRepositoryFactoryBean.class; /** - * Configures the name of the {@link CouchbaseTemplate} bean to be used with the repositories detected. + * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the + * repositories infrastructure. + */ + boolean considerNestedRepositories() default false; + + /** + * Configures the name of the {@link CouchbaseTemplate} bean to be used by default with the repositories detected. * * @return */ diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java b/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java new file mode 100644 index 00000000..86c34daa --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2015 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.repository.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.couchbase.core.CouchbaseOperations; + +/** + * A utility class for configuration allowing to tell which {@link CouchbaseOperations} should be backing + * repositories. Its fluent API allows to set that (in order of precedence) for specific repository interfaces, + * by repository domain type or as a default fallback. + * + * @author Simon Baslé + */ +public class RepositoryOperationsMapping { + private CouchbaseOperations defaultOperations; + private Map byRepository = new HashMap(); + private Map byEntity = new HashMap(); + + /** + * Creates a new mapping, setting the default fallback to use by otherwise non mapped repositories. + * + * @param defaultOperations the default fallback couchbase operations. + */ + public RepositoryOperationsMapping(CouchbaseOperations defaultOperations) { + this.defaultOperations = defaultOperations; + } + + /** + * Change the default couchbase operations in an existing mapping. + * + * @param aDefault the new default couchbase operations. + * @return the mapping, for chaining. + */ + public RepositoryOperationsMapping setDefault(CouchbaseOperations aDefault) { + this.defaultOperations = aDefault; + return this; + } + + /** + * Add a highest priority mapping that will associate a specific repository interface with a given {@link CouchbaseOperations}. + * + * @param repositoryInterface the repository interface {@link Class}. + * @param couchbaseOperations the CouchbaseOperations to use. + * @return the mapping, for chaining. + */ + public RepositoryOperationsMapping map(Class repositoryInterface, CouchbaseOperations couchbaseOperations) { + byRepository.put(repositoryInterface.getName(), couchbaseOperations); + return this; + } + + /** + * Add a middle priority mapping that will associate any un-mapped repository that deals with the given domain type + * Class with a given {@link CouchbaseOperations}. + * + * @param entityClass the domain type's {@link Class}. + * @param couchbaseOperations the CouchbaseOperations to use. + * @return the mapping, for chaining. + */ + public RepositoryOperationsMapping mapEntity(Class entityClass, CouchbaseOperations couchbaseOperations) { + byEntity.put(entityClass.getName(), couchbaseOperations); + return this; + } + + /** + * @return the configured default {@link CouchbaseOperations}. + */ + public CouchbaseOperations getDefault() { + return defaultOperations; + } + + /** + * Given a repository interface and its domain type, resolves which {@link CouchbaseOperations} it should be backed with. + * + * Starts by looking for a direct mapping to the interface, then a common mapping for the domain type, then falls back + * to the default CouchbaseOperations. + * + * @param repositoryInterface the repository's interface. + * @param domainType the repository's domain type / entity. + * @return the CouchbaseOperations to back the repository. + */ + public CouchbaseOperations resolve(Class repositoryInterface, Class domainType) { + CouchbaseOperations result = byRepository.get(repositoryInterface.getName()); + if (result != null) { + return result; + } else { + result = byEntity.get(domainType.getName()); + if (result != null) { + return result; + } else { + return defaultOperations; + } + } + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index 3bf70679..6d25a7f2 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -28,6 +28,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.view.Query; import org.springframework.data.couchbase.core.view.View; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod; import org.springframework.data.couchbase.repository.query.PartTreeN1qlBasedQuery; @@ -54,7 +55,7 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { /** * Holds the reference to the template. */ - private final CouchbaseOperations couchbaseOperations; + private final RepositoryOperationsMapping couchbaseOperationsMapping; /** * Holds the mapping context. @@ -66,26 +67,19 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { */ private final ViewPostProcessor viewPostProcessor; - /** - * Flag indicating if N1QL is available on the underlying cluster (at the time of the factory's creation). - */ - private final boolean isN1qlAvailable; - /** * Create a new factory. * - * @param couchbaseOperations the template for the underlying actions. + * @param couchbaseOperationsMapping the template for the underlying actions. */ - public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations) { - Assert.notNull(couchbaseOperations); + public CouchbaseRepositoryFactory(final RepositoryOperationsMapping couchbaseOperationsMapping) { + Assert.notNull(couchbaseOperationsMapping); - this.couchbaseOperations = couchbaseOperations; - mappingContext = couchbaseOperations.getConverter().getMappingContext(); + this.couchbaseOperationsMapping = couchbaseOperationsMapping; + mappingContext = this.couchbaseOperationsMapping.getDefault().getConverter().getMappingContext(); viewPostProcessor = ViewPostProcessor.INSTANCE; addRepositoryProxyPostProcessor(viewPostProcessor); - - this.isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); } /** @@ -117,10 +111,12 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { */ @Override protected Object getTargetRepository(final RepositoryInformation metadata) { - checkFeatures(metadata); + CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); + boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); + checkFeatures(metadata, isN1qlAvailable); CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); - if (this.isN1qlAvailable) { + if (isN1qlAvailable) { //this implementation also conforms to PagingAndSortingRepository N1qlCouchbaseRepository n1qlRepository = new N1qlCouchbaseRepository(entityInformation, couchbaseOperations); n1qlRepository.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider()); @@ -132,7 +128,7 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { } } - private void checkFeatures(RepositoryInformation metadata) { + private void checkFeatures(RepositoryInformation metadata, boolean isN1qlAvailable) { boolean needsN1ql = metadata.isPagingRepository(); //paging repo will need N1QL, other repos might also if they don't have only @View methods if (!needsN1ql) { @@ -148,7 +144,7 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { } } - if (needsN1ql && !this.isN1qlAvailable) { + if (needsN1ql && !isN1qlAvailable) { throw new UnsupportedCouchbaseFeatureException("Repository uses N1QL", CouchbaseFeature.N1QL); } } @@ -162,6 +158,9 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { */ @Override protected Class getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) { + CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(repositoryMetadata.getRepositoryInterface(), + repositoryMetadata.getDomainType()); + boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); if (isN1qlAvailable) { return N1qlCouchbaseRepository.class; } @@ -179,6 +178,9 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { private class CouchbaseQueryLookupStrategy implements QueryLookupStrategy { @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { + CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), + metadata.getDomainType()); + CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, mappingContext); String namedQueryName = queryMethod.getNamedQueryName(); diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java index e2eb78a9..c6686392 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java @@ -19,6 +19,7 @@ package org.springframework.data.couchbase.repository.support; import java.io.Serializable; import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -35,7 +36,7 @@ public class CouchbaseRepositoryFactoryBean, S, ID e /** * Contains the reference to the template. */ - private CouchbaseOperations operations; + private RepositoryOperationsMapping operationsMapping; /** * Set the template reference. @@ -43,8 +44,12 @@ public class CouchbaseRepositoryFactoryBean, S, ID e * @param operations the reference to the operations template. */ public void setCouchbaseOperations(final CouchbaseOperations operations) { - this.operations = operations; - setMappingContext(operations.getConverter().getMappingContext()); + setCouchbaseOperationsMapping(new RepositoryOperationsMapping(operations)); + } + + public void setCouchbaseOperationsMapping(final RepositoryOperationsMapping mapping) { + this.operationsMapping = mapping; + setMappingContext(operationsMapping.getDefault().getConverter().getMappingContext()); } /** @@ -54,17 +59,17 @@ public class CouchbaseRepositoryFactoryBean, S, ID e */ @Override protected RepositoryFactorySupport createRepositoryFactory() { - return getFactoryInstance(operations); + return getFactoryInstance(operationsMapping); } /** * Get the factory instance for the operations. * - * @param operations the reference to the template. + * @param operationsMapping the reference to the template. * @return the factory instance. */ - private RepositoryFactorySupport getFactoryInstance(final CouchbaseOperations operations) { - return new CouchbaseRepositoryFactory(operations); + private RepositoryFactorySupport getFactoryInstance(final RepositoryOperationsMapping operationsMapping) { + return new CouchbaseRepositoryFactory(operationsMapping); } /** @@ -73,6 +78,6 @@ public class CouchbaseRepositoryFactoryBean, S, ID e @Override public void afterPropertiesSet() { super.afterPropertiesSet(); - Assert.notNull(operations, "CouchbaseTemplate must not be null!"); + Assert.notNull(operationsMapping, "operationsMapping must not be null!"); } }