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

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

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