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:
Simon Baslé
2015-10-01 20:18:38 +02:00
parent 46e16d951d
commit 2914f57897
17 changed files with 423 additions and 58 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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> {}
}

View File

@@ -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()
}
}
----
====

View File

@@ -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()}.

View File

@@ -22,7 +22,7 @@ package org.springframework.data.couchbase.config;
* @author Michael Nitschinger
* @author Simon Baslé
*/
class BeanNames {
public class BeanNames {
/**
* Refers to the "&lt;couchbase:env /&gt;" bean.
@@ -52,4 +52,9 @@ class BeanNames {
* Refers to the "&lt;couchbase:clusterInfo&gt;" 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";
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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
*/

View File

@@ -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;
}
}
}
}

View File

@@ -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();

View File

@@ -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!");
}
}