DATACOUCH-161 - Allow repositories to use different templates
Added a configuration bean that allows to express what CouchbaseOperations each repository should use, individually or by domain type. Added documentation for the feature in repository section.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<String> 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<Item, String> {}
|
||||
|
||||
@Repository
|
||||
static interface BucketBRepository extends CouchbaseRepository<Item, String> {}
|
||||
|
||||
@Repository
|
||||
static interface BucketCRepository extends CouchbaseRepository<Misc, String> {}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class Config extends AbstractCouchbaseConfiguration {
|
||||
}
|
||||
----
|
||||
====
|
||||
An advanced usage is described in <<couchbase.repository.multibucket>>.
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -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()}.
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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<T> extends CdiRepositoryBean<T> {
|
||||
@Override
|
||||
protected T create(CreationalContext<T> creationalContext, Class<T> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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<String, CouchbaseOperations> byRepository = new HashMap<String, CouchbaseOperations>();
|
||||
private Map<String, CouchbaseOperations> byEntity = new HashMap<String, CouchbaseOperations>();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<?, Serializable> 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();
|
||||
|
||||
|
||||
@@ -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<T extends Repository<S, ID>, 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<T extends Repository<S, ID>, 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<T extends Repository<S, ID>, 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<T extends Repository<S, ID>, 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!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user