DATACOUCH-144 - Detect N1QL dependency and fail fast if not available.
Detect N1QL is not available at configuration time for the repositories and at runtime for the template. This throws a UnsupportedCouchbaseFeatureException. The ClusterInfo is captured at bootstrap and used to determine if the feature is available. The xml configuration will need the ClusterInfo-related credentials to be provided explicitly.
This commit is contained in:
@@ -24,6 +24,7 @@ import java.util.Set;
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.CouchbaseCluster;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
import com.couchbase.client.java.env.CouchbaseEnvironment;
|
||||
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
|
||||
|
||||
@@ -116,6 +117,11 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts());
|
||||
}
|
||||
|
||||
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
|
||||
public ClusterInfo couchbaseClusterInfo() throws Exception {
|
||||
return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Bucket} instance to connect to.
|
||||
*
|
||||
@@ -134,7 +140,7 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
*/
|
||||
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
|
||||
public CouchbaseTemplate couchbaseTemplate() throws Exception {
|
||||
return new CouchbaseTemplate(couchbaseClient(), mappingCouchbaseConverter(), translationService());
|
||||
return new CouchbaseTemplate(couchbaseClusterInfo(), couchbaseClient(), mappingCouchbaseConverter(), translationService());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,4 +48,8 @@ class BeanNames {
|
||||
*/
|
||||
static final String TRANSLATION_SERVICE = "couchbaseTranslationService";
|
||||
|
||||
/**
|
||||
* Refers to the "<couchbase:clusterInfo>" bean
|
||||
*/
|
||||
static final String COUCHBASE_CLUSTER_INFO = "couchbaseClusterInfo";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
|
||||
|
||||
/**
|
||||
* The Factory Bean to help {@link CouchbaseClusterInfoParser} constructing a {@link ClusterInfo} from a given
|
||||
* {@link Cluster} reference.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class CouchbaseClusterInfoFactoryBean extends AbstractFactoryBean<ClusterInfo> implements PersistenceExceptionTranslator {
|
||||
|
||||
private final Cluster cluster;
|
||||
private final String login;
|
||||
private final String password;
|
||||
|
||||
private final PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
|
||||
|
||||
public CouchbaseClusterInfoFactoryBean(Cluster cluster, String login, String password) {
|
||||
this.cluster = cluster;
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return ClusterInfo.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClusterInfo createInstance() throws Exception {
|
||||
return cluster.clusterManager(login, password).info();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||
return exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The parser for XML definition of a {@link ClusterInfo}, to be constructed from a {@link Cluster} reference.
|
||||
* If no reference is given, the default reference <code>{@value BeanNames#COUCHBASE_CLUSTER_INFO}</code> is used.
|
||||
* <p/>
|
||||
* See attributes {@link #CLUSTER_REF_ATTR}, {@link #LOGIN_ATTR} and {@link #PASSWORD_ATTR}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class CouchbaseClusterInfoParser extends AbstractSingleBeanDefinitionParser {
|
||||
|
||||
/**
|
||||
* The <code>cluster-ref</code> attribute in a cluster info definition defines the cluster to build from.
|
||||
*/
|
||||
public static final String CLUSTER_REF_ATTR = "cluster-ref";
|
||||
|
||||
/**
|
||||
* The <code>login</code> attribute in a cluster info definition defines the credential to use (can also be a
|
||||
* bucket level credential).
|
||||
*/
|
||||
public static final String LOGIN_ATTR = "login";
|
||||
|
||||
/**
|
||||
* The <code>password</code> attribute in a cluster info definition defines the credential's password.
|
||||
*/
|
||||
public static final String PASSWORD_ATTR = "password";
|
||||
|
||||
/**
|
||||
* Resolve the bean ID and assign a default if not set.
|
||||
*
|
||||
* @param element the XML element which contains the attributes.
|
||||
* @param definition the bean definition to work with.
|
||||
* @param parserContext encapsulates the parsing state and configuration.
|
||||
* @return the ID to work with.
|
||||
*/
|
||||
@Override
|
||||
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
|
||||
String id = super.resolveId(element, definition, parserContext);
|
||||
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_CLUSTER_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the bean class that will be constructed.
|
||||
*
|
||||
* @param element the XML element which contains the attributes.
|
||||
* @return the class type to instantiate.
|
||||
*/
|
||||
@Override
|
||||
protected Class getBeanClass(final Element element) {
|
||||
return CouchbaseClusterInfoFactoryBean.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the bean definition and build up the bean.
|
||||
*
|
||||
* @param element the XML element which contains the attributes.
|
||||
* @param builder the builder which builds the bean.
|
||||
*/
|
||||
@Override
|
||||
protected void doParse(final Element element, final BeanDefinitionBuilder builder) {
|
||||
String clusterRef = element.getAttribute(CLUSTER_REF_ATTR);
|
||||
if (!StringUtils.hasText(clusterRef)) {
|
||||
clusterRef = BeanNames.COUCHBASE_CLUSTER;
|
||||
}
|
||||
builder.addConstructorArgReference(clusterRef);
|
||||
|
||||
String login = element.getAttribute(LOGIN_ATTR);
|
||||
if (!StringUtils.hasText(login)) {
|
||||
login = "default";
|
||||
}
|
||||
builder.addConstructorArgValue(login);
|
||||
|
||||
String password = element.getAttribute(PASSWORD_ATTR);
|
||||
if (!StringUtils.hasText(password)) {
|
||||
password = "";
|
||||
}
|
||||
builder.addConstructorArgValue(password);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class CouchbaseNamespaceHandler extends NamespaceHandlerSupport {
|
||||
registerBeanDefinitionParser("repositories", new RepositoryBeanDefinitionParser(extension));
|
||||
registerBeanDefinitionParser("env", new CouchbaseEnvironmentParser());
|
||||
registerBeanDefinitionParser("cluster", new CouchbaseClusterParser());
|
||||
registerBeanDefinitionParser("clusterInfo", new CouchbaseClusterInfoParser());
|
||||
registerBeanDefinitionParser("bucket", new CouchbaseBucketParser());
|
||||
registerBeanDefinitionParser("jmx", new CouchbaseJmxParser());
|
||||
registerBeanDefinitionParser("template", new CouchbaseTemplateParser());
|
||||
|
||||
@@ -67,10 +67,12 @@ public class CouchbaseTemplateParser extends AbstractSingleBeanDefinitionParser
|
||||
*/
|
||||
@Override
|
||||
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
|
||||
String clusterInfoRef = element.getAttribute("clusterInfo-ref");
|
||||
String bucketRef = element.getAttribute("bucket-ref");
|
||||
String converterRef = element.getAttribute("converter-ref");
|
||||
String translationServiceRef = element.getAttribute("translation-service-ref");
|
||||
|
||||
bean.addConstructorArgReference(StringUtils.hasText(clusterInfoRef) ? clusterInfoRef : BeanNames.COUCHBASE_CLUSTER_INFO);
|
||||
bean.addConstructorArgReference(StringUtils.hasText(bucketRef) ? bucketRef : BeanNames.COUCHBASE_BUCKET);
|
||||
|
||||
if (StringUtils.hasText(converterRef)) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.PersistTo;
|
||||
import com.couchbase.client.java.ReplicateTo;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
import com.couchbase.client.java.query.Query;
|
||||
import com.couchbase.client.java.query.QueryParams;
|
||||
import com.couchbase.client.java.query.QueryResult;
|
||||
@@ -328,6 +329,13 @@ public interface CouchbaseOperations {
|
||||
*/
|
||||
Bucket getCouchbaseBucket();
|
||||
|
||||
/**
|
||||
* Returns the {@link ClusterInfo} about the cluster linked to this template.
|
||||
*
|
||||
* @return the info about the cluster the template connects to.
|
||||
*/
|
||||
ClusterInfo getCouchbaseClusterInfo();
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link CouchbaseConverter}.
|
||||
*
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.PersistTo;
|
||||
import com.couchbase.client.java.ReplicateTo;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
import com.couchbase.client.java.document.Document;
|
||||
import com.couchbase.client.java.document.RawJsonDocument;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
@@ -37,6 +38,7 @@ import com.couchbase.client.java.error.TranscodingException;
|
||||
import com.couchbase.client.java.query.Query;
|
||||
import com.couchbase.client.java.query.QueryResult;
|
||||
import com.couchbase.client.java.query.QueryRow;
|
||||
import com.couchbase.client.java.util.features.CouchbaseFeature;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
import com.couchbase.client.java.view.ViewResult;
|
||||
import com.couchbase.client.java.view.ViewRow;
|
||||
@@ -88,6 +90,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
private final Bucket client;
|
||||
private final CouchbaseConverter converter;
|
||||
private final TranslationService translationService;
|
||||
private final ClusterInfo clusterInfo;
|
||||
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
@@ -96,16 +99,18 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
|
||||
protected final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
|
||||
|
||||
public CouchbaseTemplate(final Bucket client) {
|
||||
this(client, null, null);
|
||||
public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client) {
|
||||
this(clusterInfo, client, null, null);
|
||||
}
|
||||
|
||||
public CouchbaseTemplate(final Bucket client, final TranslationService translationService) {
|
||||
this(client, null, translationService);
|
||||
public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client, final TranslationService translationService) {
|
||||
this(clusterInfo, client, null, translationService);
|
||||
}
|
||||
|
||||
public CouchbaseTemplate(final Bucket client, final CouchbaseConverter converter,
|
||||
public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client,
|
||||
final CouchbaseConverter converter,
|
||||
final TranslationService translationService) {
|
||||
this.clusterInfo = clusterInfo;
|
||||
this.client = client;
|
||||
this.converter = converter == null ? getDefaultConverter() : converter;
|
||||
this.translationService = translationService == null ? getDefaultTranslationService() : translationService;
|
||||
@@ -315,6 +320,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
|
||||
@Override
|
||||
public <T> List<T> findByN1QL(Query n1ql, Class<T> entityClass) {
|
||||
checkN1ql();
|
||||
try {
|
||||
QueryResult queryResult = queryN1QL(n1ql);
|
||||
|
||||
@@ -351,6 +357,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
|
||||
@Override
|
||||
public <T> List<T> findByN1QLProjection(Query n1ql, Class<T> entityClass) {
|
||||
checkN1ql();
|
||||
try {
|
||||
QueryResult queryResult = queryN1QL(n1ql);
|
||||
|
||||
@@ -379,6 +386,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
|
||||
@Override
|
||||
public QueryResult queryN1QL(final Query query) {
|
||||
checkN1ql();
|
||||
return execute(new BucketCallback<QueryResult>() {
|
||||
@Override
|
||||
public QueryResult doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
|
||||
@@ -538,11 +546,23 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
return (T) readEntity;
|
||||
}
|
||||
|
||||
private void checkN1ql() {
|
||||
if (!getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL)) {
|
||||
throw new UnsupportedCouchbaseFeatureException("Detected usage of N1QL in template, which is unsupported on this cluster",
|
||||
CouchbaseFeature.N1QL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bucket getCouchbaseBucket() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterInfo getCouchbaseClusterInfo() {
|
||||
return this.clusterInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseConverter getConverter() {
|
||||
return this.converter;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import com.couchbase.client.java.util.features.CouchbaseFeature;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
|
||||
/**
|
||||
* A {@link NonTransientDataAccessException} that denotes that a particular feature is expected
|
||||
* on the server side but is not available.
|
||||
*/
|
||||
public class UnsupportedCouchbaseFeatureException extends InvalidDataAccessApiUsageException {
|
||||
|
||||
private final CouchbaseFeature feature;
|
||||
|
||||
public UnsupportedCouchbaseFeatureException(String msg, CouchbaseFeature feature) {
|
||||
super(msg);
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
public UnsupportedCouchbaseFeatureException(String msg, CouchbaseFeature feature, Throwable cause) {
|
||||
super(msg, cause);
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link CouchbaseFeature} that was missing (could be null if not
|
||||
* a registered CouchbaseFeature, in which case see {@link #getMessage()}).
|
||||
*/
|
||||
public CouchbaseFeature getFeature() {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.data.couchbase.repository.query;
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.view.Stale;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
import com.couchbase.client.java.view.ViewResult;
|
||||
import com.couchbase.client.java.view.ViewRow;
|
||||
|
||||
@@ -19,9 +19,15 @@ package org.springframework.data.couchbase.repository.support;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.couchbase.client.java.util.features.CouchbaseFeature;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.UnsupportedCouchbaseFeatureException;
|
||||
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.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
import org.springframework.data.couchbase.repository.query.PartTreeN1qlBasedQuery;
|
||||
@@ -104,12 +110,31 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
*/
|
||||
@Override
|
||||
protected Object getTargetRepository(final RepositoryInformation metadata) {
|
||||
checkFeatures(metadata);
|
||||
|
||||
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
final SimpleCouchbaseRepository simpleCouchbaseRepository = new SimpleCouchbaseRepository(entityInformation, couchbaseOperations);
|
||||
simpleCouchbaseRepository.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider());
|
||||
return simpleCouchbaseRepository;
|
||||
}
|
||||
|
||||
private void checkFeatures(RepositoryInformation metadata) {
|
||||
boolean needsN1ql = false;
|
||||
for (Method method : metadata.getQueryMethods()) {
|
||||
|
||||
boolean hasN1ql = AnnotationUtils.findAnnotation(method, Query.class) != null;
|
||||
boolean hasView = AnnotationUtils.findAnnotation(method, View.class) != null;
|
||||
|
||||
if (hasN1ql || !hasView) {
|
||||
needsN1ql = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (needsN1ql && !couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL)) {
|
||||
throw new UnsupportedCouchbaseFeatureException("Repository uses N1QL", CouchbaseFeature.N1QL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for this repository.
|
||||
*
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* The factory bean to create repositories.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user