diff --git a/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java b/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java index 8a8de793..8a8d9002 100644 --- a/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java +++ b/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java @@ -51,7 +51,7 @@ public class CouchbaseTemplateParserIntegrationTests { reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-bean.xml")); BeanDefinition definition = factory.getBeanDefinition("couchbaseTemplate"); - assertEquals(1, definition.getConstructorArgumentValues().getArgumentCount()); + assertEquals(2, definition.getConstructorArgumentValues().getArgumentCount()); factory.getBean("couchbaseTemplate"); } @@ -61,7 +61,7 @@ public class CouchbaseTemplateParserIntegrationTests { reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml")); BeanDefinition definition = factory.getBeanDefinition("couchbaseTemplate"); - assertEquals(2, definition.getConstructorArgumentValues().getArgumentCount()); + assertEquals(3, definition.getConstructorArgumentValues().getArgumentCount()); factory.getBean("couchbaseTemplate"); } diff --git a/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateViewListener.java b/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateViewListener.java index 07900d62..2a2dee4d 100644 --- a/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateViewListener.java +++ b/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateViewListener.java @@ -19,6 +19,7 @@ package org.springframework.data.couchbase.core; import java.util.Collections; import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.cluster.ClusterInfo; import com.couchbase.client.java.view.DefaultView; import com.couchbase.client.java.view.DesignDocument; import com.couchbase.client.java.view.View; @@ -31,28 +32,29 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution */ public class CouchbaseTemplateViewListener extends DependencyInjectionTestExecutionListener { - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket"); - populateTestData(client); - createAndWaitForDesignDocs(client); - } + @Override + public void beforeTestClass(final TestContext testContext) throws Exception { + Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket"); + ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean("couchbaseClusterInfo"); + populateTestData(client, clusterInfo); + createAndWaitForDesignDocs(client); + } - private void populateTestData(Bucket client) { - CouchbaseTemplate template = new CouchbaseTemplate(client); + private void populateTestData(Bucket client, ClusterInfo clusterInfo) { + CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - for (int i = 0; i < 100; i++) { - Beer b = new Beer("testbeer-" + i).setName("MyBeer " + i).setActive(true); - template.save(b); - } - } + for (int i = 0; i < 100; i++) { + Beer b = new Beer("testbeer-" + i).setName("MyBeer " + i).setActive(true); + template.save(b); + } + } - private void createAndWaitForDesignDocs(Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == " - + "\"org.springframework.data.couchbase.core.Beer\") { emit(doc.name, null); } }"; - View view = DefaultView.create("by_name", mapFunction); - DesignDocument designDoc = DesignDocument.create("test_beers", Collections.singletonList(view)); - client.bucketManager().upsertDesignDocument(designDoc); - } + private void createAndWaitForDesignDocs(Bucket client) { + String mapFunction = "function (doc, meta) { if(doc._class == " + + "\"org.springframework.data.couchbase.core.Beer\") { emit(doc.name, null); } }"; + View view = DefaultView.create("by_name", mapFunction); + DesignDocument designDoc = DesignDocument.create("test_beers", Collections.singletonList(view)); + client.bucketManager().upsertDesignDocument(designDoc); + } } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java index a99e8709..510ac4de 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java @@ -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.view.DefaultView; import com.couchbase.client.java.view.DesignDocument; import com.couchbase.client.java.view.View; @@ -40,12 +41,13 @@ public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExec @Override public void beforeTestClass(final TestContext testContext) throws Exception { Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket"); - populateTestData(client); + ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean("couchbaseClusterInfo"); + populateTestData(client, clusterInfo); createAndWaitForDesignDocs(client); } - private void populateTestData(final Bucket client) { - CouchbaseTemplate template = new CouchbaseTemplate(client); + private void populateTestData(final Bucket client, ClusterInfo clusterInfo) { + CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); for (int i = 0; i < 100; i++) { template.save(new User("testuser-" + i, "uname-" + i), PersistTo.MASTER, ReplicateTo.NONE); } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionListener.java b/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionListener.java index b9bc209c..b91bbc82 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionListener.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/QueryDerivationConversionListener.java @@ -2,12 +2,12 @@ package org.springframework.data.couchbase.repository; import java.util.Calendar; import java.util.Collections; -import java.util.Date; 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.view.DefaultView; import com.couchbase.client.java.view.DesignDocument; import com.couchbase.client.java.view.View; @@ -24,12 +24,13 @@ public class QueryDerivationConversionListener extends DependencyInjectionTestEx @Override public void beforeTestClass(final TestContext testContext) throws Exception { Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket"); - populateTestData(client); + ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean("couchbaseClusterInfo"); + populateTestData(client, clusterInfo); createAndWaitForDesignDocs(client); } - private void populateTestData(Bucket client) { - CouchbaseTemplate template = new CouchbaseTemplate(client); + private void populateTestData(Bucket client, ClusterInfo clusterInfo) { + CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); Calendar cal = Calendar.getInstance(); cal.clear(); diff --git a/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java b/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java index b87fd345..b0aa2b0e 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java @@ -22,6 +22,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.view.DefaultView; import com.couchbase.client.java.view.DesignDocument; import com.couchbase.client.java.view.View; @@ -38,12 +39,13 @@ public class SimpleCouchbaseRepositoryListener extends DependencyInjectionTestEx @Override public void beforeTestClass(final TestContext testContext) throws Exception { Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket"); - populateTestData(client); + ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean("couchbaseClusterInfo"); + populateTestData(client, clusterInfo); createAndWaitForDesignDocs(client); } - private void populateTestData(Bucket client) { - CouchbaseTemplate template = new CouchbaseTemplate(client); + private void populateTestData(Bucket client, ClusterInfo clusterInfo) { + CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); for (int i = 0; i < 100; i++) { User u = new User("testuser-" + i, "uname-" + i); diff --git a/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClusterInfoProducer.java b/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClusterInfoProducer.java new file mode 100644 index 00000000..04762d67 --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClusterInfoProducer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 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.cdi; + +import javax.enterprise.inject.Produces; + +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 org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseTemplate; + +/** + * Produces a {@link ClusterInfo} instance for test usage. + * + * @author Simon Baslé + */ +class CouchbaseClusterInfoProducer { + + @Produces + public ClusterInfo createClusterInfo() throws Exception { + Cluster cluster = CouchbaseCluster.create(); + return cluster.clusterManager("default", "").info(); + } + +} diff --git a/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java b/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java index f95eacab..47b8b322 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java @@ -19,22 +19,24 @@ package org.springframework.data.couchbase.repository.cdi; import javax.enterprise.inject.Produces; import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.cluster.ClusterInfo; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseTemplate; /** * Produces a {@link CouchbaseOperations} instance for test usage. + * * @author Mark Paluch */ class CouchbaseOperationsProducer { - @Produces - public CouchbaseOperations createCouchbaseOperations(Bucket couchbaseClient) throws Exception { + @Produces + public CouchbaseOperations createCouchbaseOperations(Bucket couchbaseClient, ClusterInfo clusterInfo) throws Exception { - CouchbaseTemplate couchbaseTemplate = new CouchbaseTemplate(couchbaseClient); + CouchbaseTemplate couchbaseTemplate = new CouchbaseTemplate(clusterInfo, couchbaseClient); - return couchbaseTemplate; - } + return couchbaseTemplate; + } } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java new file mode 100644 index 00000000..1f57cf13 --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013 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.feature; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import com.couchbase.client.java.cluster.ClusterInfo; +import com.couchbase.client.java.query.Query; +import com.couchbase.client.java.util.features.CouchbaseFeature; +import com.couchbase.client.java.util.features.Version; +import org.junit.Assume; +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.core.CouchbaseTemplate; +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.support.CouchbaseRepositoryFactory; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * An integration test that validates feature checking with Java Config. + * + * @author Simon Baslé + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = FeatureDetectionTestApplicationConfig.class) +public class FeatureDetectionRepositoryTests { + + @Autowired + private CouchbaseTemplate template; + + @Autowired + private ClusterInfo clusterInfo; + + @Before + public void checkClusterInfo() { + Assume.assumeTrue(clusterInfo.getMinVersion() == Version.NO_VERSION); + } + + @Test + public void testN1qlIncompatibleClusterFailsFastForN1qlBasedRepository() throws Exception { + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + try { + factory.getRepository(UserRepository.class); + fail("expected UnsupportedCouchbaseFeatureException"); + } catch (UnsupportedCouchbaseFeatureException e) { + assertEquals(CouchbaseFeature.N1QL, e.getFeature()); + } + } + + @Test + public void testN1qlIncompatibleClusterDoesntFailForViewBasedRepository() throws Exception { + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + ViewOnlyUserRepository repository = factory.getRepository(ViewOnlyUserRepository.class); + assertNotNull(repository); + } + + @Test + public void testN1qlIncompatibleClusterTemplateFails() { + Query query = Query.simple("SELECT * FROM `" + template.getCouchbaseBucket().name() + "`"); + try { + template.findByN1QL(query, User.class); + fail("expected findByN1QL to fail with UnsupportedCouchbaseFeatureException"); + } catch (UnsupportedCouchbaseFeatureException e) { + assertEquals(CouchbaseFeature.N1QL, e.getFeature()); + } + + try { + template.findByN1QLProjection(query, User.class); + fail("expected findByN1QLProjection to fail with UnsupportedCouchbaseFeatureException"); + } catch (UnsupportedCouchbaseFeatureException e) { + assertEquals(CouchbaseFeature.N1QL, e.getFeature()); + } + + try { + template.queryN1QL(query); + fail("expected queryN1QL to fail with UnsupportedCouchbaseFeatureException"); + } catch (UnsupportedCouchbaseFeatureException e) { + assertEquals(CouchbaseFeature.N1QL, e.getFeature()); + } + } +} diff --git a/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionTestApplicationConfig.java b/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionTestApplicationConfig.java new file mode 100644 index 00000000..0b591b95 --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionTestApplicationConfig.java @@ -0,0 +1,79 @@ +package org.springframework.data.couchbase.repository.feature; + +import java.util.Collections; +import java.util.List; + +import com.couchbase.client.java.cluster.ClusterInfo; +import com.couchbase.client.java.cluster.DefaultClusterInfo; +import com.couchbase.client.java.document.json.JsonObject; +import com.couchbase.client.java.env.CouchbaseEnvironment; +import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.WriteResultChecking; + +@Configuration +public class FeatureDetectionTestApplicationConfig extends AbstractCouchbaseConfiguration { + + @Autowired + private Environment springEnv; + + @Bean + public String couchbaseAdminUser() { + return springEnv.getProperty("couchbase.adminUser", "Administrator"); + } + + @Bean + public String couchbaseAdminPassword() { + return springEnv.getProperty("couchbase.adminUser", "password"); + } + + @Override + protected List getBootstrapHosts() { + return Collections.singletonList(springEnv.getProperty("couchbase.host", "127.0.0.1")); + } + + @Override + protected String getBucketName() { + return springEnv.getProperty("couchbase.bucket", "default"); + } + + @Override + protected String getBucketPassword() { + return springEnv.getProperty("couchbase.password", ""); + } + + + @Override + protected CouchbaseEnvironment getEnvironment() { + return DefaultCouchbaseEnvironment.builder() + .connectTimeout(10000) + .kvTimeout(10000) + .queryTimeout(10000) + .viewTimeout(10000) + .build(); + } + + @Override + public CouchbaseTemplate couchbaseTemplate() throws Exception { + CouchbaseTemplate template = super.couchbaseTemplate(); + template.setWriteResultChecking(WriteResultChecking.LOG); + return template; + } + + @Override + public ClusterInfo couchbaseClusterInfo() throws Exception { + return new DefaultClusterInfo(JsonObject.empty()); + } + + //change the name of the field that will hold type information + @Override + public String typeKey() { + return "javaClass"; + } +} diff --git a/src/integration/java/org/springframework/data/couchbase/repository/feature/ViewOnlyUserRepository.java b/src/integration/java/org/springframework/data/couchbase/repository/feature/ViewOnlyUserRepository.java new file mode 100644 index 00000000..edc8a7dc --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/feature/ViewOnlyUserRepository.java @@ -0,0 +1,7 @@ +package org.springframework.data.couchbase.repository.feature; + +import org.springframework.data.couchbase.repository.User; +import org.springframework.data.repository.CrudRepository; + +public interface ViewOnlyUserRepository extends CrudRepository { +} diff --git a/src/integration/resources/configurations/couchbase-multi-bucket-bean.xml b/src/integration/resources/configurations/couchbase-multi-bucket-bean.xml index 1a480dd1..c2c2c487 100644 --- a/src/integration/resources/configurations/couchbase-multi-bucket-bean.xml +++ b/src/integration/resources/configurations/couchbase-multi-bucket-bean.xml @@ -8,6 +8,7 @@ + diff --git a/src/integration/resources/configurations/couchbase-template-bean.xml b/src/integration/resources/configurations/couchbase-template-bean.xml index 9b0ada0b..eb759027 100644 --- a/src/integration/resources/configurations/couchbase-template-bean.xml +++ b/src/integration/resources/configurations/couchbase-template-bean.xml @@ -7,6 +7,7 @@ + diff --git a/src/integration/resources/configurations/couchbase-template-with-translation-service-bean.xml b/src/integration/resources/configurations/couchbase-template-with-translation-service-bean.xml index e2f31a1f..588a61e9 100644 --- a/src/integration/resources/configurations/couchbase-template-with-translation-service-bean.xml +++ b/src/integration/resources/configurations/couchbase-template-with-translation-service-bean.xml @@ -7,6 +7,7 @@ + diff --git a/src/integration/resources/configurations/couchbase-typekey.xml b/src/integration/resources/configurations/couchbase-typekey.xml index dfe24e6b..0c7078ea 100644 --- a/src/integration/resources/configurations/couchbase-typekey.xml +++ b/src/integration/resources/configurations/couchbase-typekey.xml @@ -7,6 +7,7 @@ + diff --git a/src/main/asciidoc/configuration.adoc b/src/main/asciidoc/configuration.adoc index d4e8e435..cd265977 100644 --- a/src/main/asciidoc/configuration.adoc +++ b/src/main/asciidoc/configuration.adoc @@ -111,6 +111,10 @@ The library provides a custom namespace that you can use in your XML configurati 127.0.0.1 + + + + ---- diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index cbed5953..91dc7ffa 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -102,6 +102,8 @@ As of version `4.0`, Couchbase Server ships with a new query language called `N1 Prerequisite is to have a N1QL-compatible cluster and to have created a PRIMARY INDEX on the bucket where the entities will be stored. +WARNING: If it is detected at configuration time that the cluster doesn't support N1QL while there are @N1QL annotated methods or non-annotated methods in your repository interface, a `UnsupportedCouchbaseFeatureException` will be thrown. + Here is an example: .An extended User repository with N1QL queries diff --git a/src/main/asciidoc/template.adoc b/src/main/asciidoc/template.adoc index 14411e85..c04d0f23 100644 --- a/src/main/asciidoc/template.adoc +++ b/src/main/asciidoc/template.adoc @@ -16,6 +16,8 @@ Removing documents through the `remove` methods works exactly the same. If you want to load documents, you can do that through the `findById` method, which is the fastest and if possible your tool of choice. The find methods for views are `findByView` which converts it into the target entity, but also `queryView` which exposes lower level semantics. Similarly, find methods using N1QL are provided in `findByN1QL` and `queryN1QL`. Additionally, since N1QL allows you to select specific fields in documents (or even across documents using joins), `findByN1QLProjection` will allow you to skip full `Document` conversion and map these fields to an ad-hoc class. +WARNING: If it is detected at runtime that the cluster doesn't support N1QL, these methods will throw a `UnsupportedCouchbaseFeatureException`. + If you really need low-level semantics, the `couchbaseBucket` is also always in scope through `getCouchbaseBucket()`. [[couchbase.template.xml]] @@ -35,6 +37,7 @@ The template can be configured via xml, including setting a custom `TranslationS + @@ -45,3 +48,5 @@ The template can be configured via xml, including setting a custom `TranslationS ---- ==== +NOTE: In the example above most tags assume their default values, that is a localhost cluster and bucket "default". In production you would have to also provide specifics to these tags. + diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index d2529a83..48a7a1d7 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -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()); } /** diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java index 05add28b..b7dfa01f 100644 --- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java @@ -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"; } diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoFactoryBean.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoFactoryBean.java new file mode 100644 index 00000000..76b43cf4 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoFactoryBean.java @@ -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 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); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoParser.java new file mode 100644 index 00000000..5d0723e3 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoParser.java @@ -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 {@value BeanNames#COUCHBASE_CLUSTER_INFO} is used. + *

+ * See attributes {@link #CLUSTER_REF_ATTR}, {@link #LOGIN_ATTR} and {@link #PASSWORD_ATTR}. + * + * @author Simon Baslé + */ +public class CouchbaseClusterInfoParser extends AbstractSingleBeanDefinitionParser { + + /** + * The cluster-ref attribute in a cluster info definition defines the cluster to build from. + */ + public static final String CLUSTER_REF_ATTR = "cluster-ref"; + + /** + * The login 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 password 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); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java index a83e5371..30ab8ccb 100644 --- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java @@ -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()); diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java index 59a63b5c..344b6988 100644 --- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java @@ -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)) { diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java index bc8b19c0..91d5fa6d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java @@ -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}. * diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 80382ef1..53c8b175 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -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, 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 List findByN1QL(Query n1ql, Class entityClass) { + checkN1ql(); try { QueryResult queryResult = queryN1QL(n1ql); @@ -351,6 +357,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP @Override public List findByN1QLProjection(Query n1ql, Class 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() { @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; diff --git a/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java b/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java new file mode 100644 index 00000000..d4996e0b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java @@ -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; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java index 7ab12f86..c18a9930 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java @@ -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; diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index e71ada75..86cb0d15 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -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 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. * diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java index 31f4e79f..e2eb78a9 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java @@ -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. * diff --git a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd index 669cb5f1..b816b689 100644 --- a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd +++ b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd @@ -42,6 +42,19 @@ + + + + + + + + + + + + + @@ -50,6 +63,18 @@ The id of the couchbase definition (by default "couchbaseFactory").]]> + + + + The reference to a ClusterInfo object giving information about the cluster this template connects to. + + + + + + + + diff --git a/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java index 8a2379a6..13bb93d0 100644 --- a/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java +++ b/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java @@ -1,5 +1,7 @@ package org.springframework.data.couchbase; +import static org.mockito.Mockito.when; + import java.util.Collections; import java.util.List; @@ -7,6 +9,10 @@ import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.CouchbaseBucket; import com.couchbase.client.java.CouchbaseCluster; +import com.couchbase.client.java.cluster.ClusterInfo; +import com.couchbase.client.java.cluster.DefaultClusterInfo; +import com.couchbase.client.java.util.features.CouchbaseFeature; +import com.couchbase.client.java.util.features.Version; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; @@ -18,45 +24,53 @@ import org.springframework.data.couchbase.core.WriteResultChecking; @Configuration public class UnitTestApplicationConfig extends AbstractCouchbaseConfiguration { - @Bean - public String couchbaseAdminUser() { - return "someLogin"; - } + @Bean + public String couchbaseAdminUser() { + return "someLogin"; + } - @Bean - public String couchbaseAdminPassword() { - return "somePassword"; - } + @Bean + public String couchbaseAdminPassword() { + return "somePassword"; + } - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("192.1.2.3"); - } + @Override + protected List getBootstrapHosts() { + return Collections.singletonList("192.1.2.3"); + } - @Override - protected String getBucketName() { - return "someBucket"; - } + @Override + protected String getBucketName() { + return "someBucket"; + } - @Override - protected String getBucketPassword() { - return "someBucketPassword"; - } + @Override + protected String getBucketPassword() { + return "someBucketPassword"; + } - @Override - public Cluster couchbaseCluster() throws Exception { - return Mockito.mock(CouchbaseCluster.class); - } + @Override + public Cluster couchbaseCluster() throws Exception { + return Mockito.mock(CouchbaseCluster.class); + } - @Override - public Bucket couchbaseClient() throws Exception { - return Mockito.mock(CouchbaseBucket.class); - } + @Override + public ClusterInfo couchbaseClusterInfo() { + DefaultClusterInfo mock = Mockito.mock(DefaultClusterInfo.class); + when(mock.checkAvailable(CouchbaseFeature.N1QL)).thenReturn(true); + when(mock.getMinVersion()).thenReturn(new Version(4, 0, 0)); + return mock; + } - @Override - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = super.couchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } + @Override + public Bucket couchbaseClient() throws Exception { + return Mockito.mock(CouchbaseBucket.class); + } + + @Override + public CouchbaseTemplate couchbaseTemplate() throws Exception { + CouchbaseTemplate template = super.couchbaseTemplate(); + template.setWriteResultChecking(WriteResultChecking.LOG); + return template; + } }