DATACOUCH-451 - Should be able to run integration tests and all unit tests

This change enables running the integration tests with the command:

    mvn verify

Update the integration tests so they compile and pass. Rename them according
to Spring Data convention (*IntegrationTests.java) so they are executed by the
failsafe plugin instead of the surefire plugin. Move them to same folder as
unit tests according to Spring Data convention.

Update the surefire plugin configuration to include unit tests in classes
named `*Test` as well as `*Tests`. Prior to this change about half of the unit
tests were being ignored.

Integration test changes
========================

Use static factory methods for Sort and PageRequest instead of calling the
constructors which are now private.

Import EvaluationContextExtension from its new package. Remove reference to
deprecated EvaluationContextExtensionSupport.

Retry all calls to `getRepository` because otherwise they may fail due to
concurrent index creation.

Fix test `shouldDeriveViewParameters` to assume results are unordered.

Fail fast if the `server.properties` resource is missing.

Increase query and view timeouts; container is a big sluggish sometimes.
This commit is contained in:
David Nault
2019-05-20 18:46:21 -07:00
committed by GitHub
parent d25531078d
commit 9080c2dede
113 changed files with 217 additions and 133 deletions

View File

@@ -0,0 +1,20 @@
package org.springframework.data.couchbase;
import org.junit.ClassRule;
import org.junit.runners.model.InitializationError;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* This runner initializes container for the container based testing.
*
* @author Subhashni Balakrishnan
*/
public class ContainerResourceRunner extends SpringJUnit4ClassRunner {
@ClassRule
public static final TestContainerResource resource = TestContainerResource.getResource();
public ContainerResourceRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
}

View File

@@ -0,0 +1,46 @@
package org.springframework.data.couchbase;
import java.util.concurrent.Callable;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
/**
* Helper to check if the Couchbase http endpoints are up.
*/
public class CouchbaseHttpPortListeningCheck implements Callable<Boolean> {
private final int port;
private final String path;
public CouchbaseHttpPortListeningCheck(int port, String path) {
this.port = port;
this.path = path;
}
private Boolean executeRequest(URIBuilder builder) throws Exception {
try {
HttpGet request = new HttpGet(builder.build());
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(request);
int status = response.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
return false;
}
return true;
} catch (Exception ex) {
Thread.sleep(1000);
throw ex;
}
}
@Override
public Boolean call() throws Exception {
URIBuilder builder = new URIBuilder();
builder.setScheme("http").setHost("localhost").setPort(this.port).setPath(this.path);
return executeRequest(builder);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2019 Couchbase, Inc.
*
* 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;
import org.rnorth.ducttape.unreliables.Unreliables;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class CouchbaseTestHelper {
private CouchbaseTestHelper() {
throw new AssertionError("not instantiable");
}
/**
* Returns a repository instance for the given interface.
* <p>
* Retry because concurrent index creation throws exception.
* See https://issues.couchbase.com/browse/MB-32238
*/
public static <T> T getRepositoryWithRetry(RepositoryFactorySupport factory, Class<T> repositoryInterface) {
return retryUntilSuccess(() -> factory.getRepository(repositoryInterface));
}
private static <T> T retryUntilSuccess(final Callable<T> lambda) {
return Unreliables.retryUntilSuccess(10, TimeUnit.SECONDS, lambda);
}
}

View File

@@ -0,0 +1,123 @@
package org.springframework.data.couchbase;
import static java.time.temporal.ChronoUnit.SECONDS;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import com.couchbase.client.java.util.features.Version;
import org.rnorth.ducttape.ratelimits.RateLimiterBuilder;
import org.rnorth.ducttape.unreliables.Unreliables;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.WaitStrategy;
/**
* WaitStrategy for Couchbase containers which makes the Server node is
* initialized, RBAC user and default bucket is created.
*/
public class CouchbaseWaitStrategy implements WaitStrategy {
private Duration startupTimeout = Duration.of(60, SECONDS);
private final Boolean rbacEnabled;
public CouchbaseWaitStrategy(String serverVersion) {
Version version = Version.parseVersion(serverVersion);
rbacEnabled = version.major() >= 5;
}
private void checkResult(Container.ExecResult result, String command) throws Exception {
if (!result.getStdout().contains("SUCCESS")) {
throw new Exception(command + " command failed");
}
}
private void checkService(int port, String path) {
Callable<Boolean> externalCheck = new CouchbaseHttpPortListeningCheck(port, path);
Unreliables.retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () ->
externalCheck.call());
}
@Override
public void waitUntilReady(GenericContainer container) {
try {
checkService(8091, "/pools");
Container.ExecResult result;
if (rbacEnabled) {
result = container.execInContainer("/opt/couchbase/bin/couchbase-cli",
"cluster-init",
"--cluster=127.0.0.1:8091",
"--services=data,index,query",
"--cluster-name=localcontainer",
"--cluster-username=Administrator",
"--cluster-password=password",
"--cluster-ramsize=512",
"--cluster-index-ramsize=512",
"--index-storage-setting=default");
checkResult(result, "Cluster init");
result = container.execInContainer("/opt/couchbase/bin/couchbase-cli",
"user-manage",
"--cluster=127.0.0.1:8091",
"--username=Administrator",
"--password=password",
"--set",
"--rbac-username=protected",
"--rbac-password=password",
"--rbac-name=default",
"--roles=admin",
"--auth-domain=local");
checkResult(result, "User manage");
result = container.execInContainer("/opt/couchbase/bin/couchbase-cli",
"bucket-create",
"--cluster=127.0.0.1:8091",
"--username=Administrator",
"--password=password",
"--bucket=protected",
"--bucket-type=couchbase",
"--bucket-ramsize=200",
"--enable-flush=1",
"--wait");
} else {
result = container.execInContainer("/opt/couchbase/bin/couchbase-cli",
"cluster-init",
"--cluster=127.0.0.1:8091",
"--services=data,index,query",
"-u",
"Administrator",
"-p",
"password",
"--cluster-ramsize=512",
"--cluster-index-ramsize=512",
"--index-storage-setting=default");
checkResult(result, "Cluster init");
result = container.execInContainer("/opt/couchbase/bin/couchbase-cli",
"bucket-create",
"--cluster=127.0.0.1:8091",
"-u",
"Administrator",
"-p",
"password",
"--bucket=protected",
"--bucket-password=password",
"--bucket-type=couchbase",
"--bucket-ramsize=200",
"--enable-flush=1",
"--wait");
}
checkResult(result, "Bucket create");
checkService(8093, "/query/ping");
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
@Override
public WaitStrategy withStartupTimeout(Duration startupTimeout) {
this.startupTimeout = startupTimeout;
return this;
}
}

View File

@@ -0,0 +1,81 @@
package org.springframework.data.couchbase;
import java.util.Collections;
import java.util.List;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.config.CouchbaseConfigurer;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.WriteResultChecking;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.repository.support.IndexManager;
@Configuration
public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration {
@Bean
public String couchbaseAdminUser() {
return "Administrator";
}
@Bean
public String couchbaseAdminPassword() {
return "password";
}
@Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList("127.0.0.1");
}
@Override
protected String getBucketName() {
return "protected";
}
@Override
protected String getBucketPassword() {
return "password";
}
//TODO maybe create the bucket if doesn't exist
@Override
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.builder()
.connectTimeout(10000)
.kvTimeout(10000)
.queryTimeout(20000)
.viewTimeout(20000)
.build();
}
@Override
public CouchbaseTemplate couchbaseTemplate() throws Exception {
CouchbaseTemplate template = super.couchbaseTemplate();
template.setWriteResultChecking(WriteResultChecking.LOG);
return template;
}
//this is for dev so it is ok to auto-create indexes
@Override
public IndexManager indexManager() {
return new IndexManager();
}
@Override
protected Consistency getDefaultConsistency() {
return Consistency.READ_YOUR_OWN_WRITES;
}
@Override
protected CouchbaseConfigurer couchbaseConfigurer() {
return this;
}
}

View File

@@ -0,0 +1,7 @@
package org.springframework.data.couchbase;
import org.springframework.context.annotation.Configuration;
@Configuration
public class IntegrationTestCustomKeySettings extends IntegrationTestApplicationConfig {
}

View File

@@ -0,0 +1,13 @@
package org.springframework.data.couchbase;
import org.springframework.context.annotation.Configuration;
@Configuration
public class IntegrationTestCustomTypeKeyConfig extends IntegrationTestApplicationConfig {
//change the name of the field that will hold type information
@Override
public String typeKey() {
return "javaClass";
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.data.couchbase;
import java.util.Collections;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.config.CouchbaseConfigurer;
/**
* Configuration for testing no shutdown
*
* @author Subhashni Balakrishnan
*/
public class IntegrationTestNoShutdownApplicationConfig extends AbstractCouchbaseConfiguration {
@Bean
public String couchbaseAdminUser() {
return "Administrator";
}
@Bean
public String couchbaseAdminPassword() {
return "password";
}
@Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList("127.0.0.1");
}
@Override
protected String getBucketName() {
return "protected";
}
@Override
protected String getBucketPassword() {
return "password";
}
@Override
protected boolean isEnvironmentManagedBySpring() {
return false;
}
@Override
protected CouchbaseConfigurer couchbaseConfigurer() {
return this;
}
}

View File

@@ -0,0 +1,72 @@
package org.springframework.data.couchbase;
import java.util.Collections;
import java.util.List;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractReactiveCouchbaseConfiguration;
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
import org.springframework.data.couchbase.core.WriteResultChecking;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.repository.support.IndexManager;
@Configuration
public class ReactiveIntegrationTestApplicationConfig extends AbstractReactiveCouchbaseConfiguration {
@Bean
public String couchbaseAdminUser() {
return "Administrator";
}
@Bean
public String couchbaseAdminPassword() {
return "password";
}
@Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList("127.0.0.1");
}
@Override
protected String getBucketName() {
return "protected";
}
@Override
protected String getBucketPassword() {
return "password";
}
@Override
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.builder()
.connectTimeout(10000)
.kvTimeout(10000)
.queryTimeout(10000)
.viewTimeout(10000)
.build();
}
@Override
public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception {
RxJavaCouchbaseTemplate template = super.reactiveCouchbaseTemplate();
template.setWriteResultChecking(WriteResultChecking.LOG);
return template;
}
//this is for dev so it is ok to auto-create indexes
@Override
public IndexManager indexManager() {
return new IndexManager();
}
@Override
protected Consistency getDefaultConsistency() {
return Consistency.READ_YOUR_OWN_WRITES;
}
}

View File

@@ -0,0 +1,68 @@
package org.springframework.data.couchbase;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.rules.ExternalResource;
import org.testcontainers.containers.FixedHostPortGenericContainer;
/**
* Testcontainers as external resource. It is recommended to use it as ClassRule.
* It also does the internal reference counting, in case if the getResource is called again.
*
*/
public class TestContainerResource extends ExternalResource {
private static FixedHostPortGenericContainer couchbaseContainer = null;
private static final AtomicInteger referenceCount = new AtomicInteger();
private static TestContainerResource currentInstance;
private static String serverVersion;
public static TestContainerResource getResource() {
if (currentInstance == null) {
currentInstance = new TestContainerResource();
try {
Properties properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("server.properties"));
serverVersion = properties.getProperty("server.version");
if(!"container".equals(properties.getProperty("server.resource"))) {
return null;
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
couchbaseContainer = new FixedHostPortGenericContainer("couchbase:" + serverVersion)
.withFixedExposedPort(8091, 8091)
.withFixedExposedPort(18091, 18091)
.withFixedExposedPort(8092, 8092)
.withFixedExposedPort(18092, 18092)
.withFixedExposedPort(8093, 8093)
.withFixedExposedPort(18093, 18093)
.withFixedExposedPort(8094, 8094)
.withFixedExposedPort(18094, 18094)
.withFixedExposedPort(11210, 11210)
.withFixedExposedPort(11211, 11211)
.withFixedExposedPort(11207, 11207);
couchbaseContainer.waitingFor(new CouchbaseWaitStrategy(serverVersion));
couchbaseContainer.start();
}
return currentInstance;
}
@Override
protected void before() {
referenceCount.incrementAndGet();
}
@Override
protected void after() {
if (referenceCount.decrementAndGet() == 0 && couchbaseContainer != null) {
if(couchbaseContainer.isRunning()) {
couchbaseContainer.close();
}
currentInstance = null;
}
}
}

View File

@@ -0,0 +1,164 @@
package org.springframework.data.couchbase.config;
import static org.junit.Assert.*;
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.document.JsonDocument;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.stereotype.Repository;
import org.springframework.test.context.ContextConfiguration;
/**
* This test case demonstrates that the {@link AbstractCouchbaseDataConfiguration} can take its SDK beans
* from a sibling {@link Configuration}.
*
* Tests DATACOUCH-279
*
* @author Simon Baslé
*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration
public class AbstractCouchbaseDataConfigurationIntegrationTests {
@Autowired
ItemRepository repository;
@Autowired
Bucket client;
@Configuration
static class SdkConfig {
private static final String IP = "127.0.0.1";
private static final String BUCKET_NAME = "protected";
private static final String BUCKET_PASSWORD = "password";
public static Bucket bucket;
@Bean
public Cluster couchbaseCluster() {
return CouchbaseCluster.create(couchbaseEnv(), IP);
}
@Bean
public ClusterInfo couchbaseClusterInfo() {
return couchbaseCluster().clusterManager(BUCKET_NAME, BUCKET_PASSWORD).info();
}
@Bean
public Bucket couchbaseBucket() {
Bucket b = couchbaseCluster().openBucket(BUCKET_NAME, BUCKET_PASSWORD);
bucket = b;
return b;
}
@Bean
public CouchbaseEnvironment couchbaseEnv() {
return DefaultCouchbaseEnvironment.create();
}
}
@Configuration
@EnableCouchbaseRepositories(basePackageClasses = AbstractCouchbaseDataConfigurationIntegrationTests.class, considerNestedRepositories = true)
static class Config extends AbstractCouchbaseDataConfiguration {
@Autowired
Cluster c;
@Autowired
ClusterInfo ci;
@Autowired
Bucket b;
@Autowired
CouchbaseEnvironment e;
@Override
protected CouchbaseConfigurer couchbaseConfigurer() {
return new TestCouchbaseConfigurer(e, c, ci, b);
}
}
@Test
public void testInjectedBucketIsFromAdditionalConfig() {
assertSame(client, SdkConfig.bucket);
}
@Test
public void testTemplateIsUsable() {
String key = "simpleConfigTest";
assertNotNull(repository);
Item item = new Item();
item.id = key;
item.value = "Test if the SimpleCouchbaseConfiguration can correctly get Bucket/Cluster/etc... beans injected";
repository.save(item);
JsonDocument testDoc = client.get(key);
assertNotNull(testDoc);
assertNotNull(testDoc.content());
assertEquals(item.value, testDoc.content().getString("value"));
}
private static class Item {
@Id
public String id;
public String value;
}
private static class TestCouchbaseConfigurer implements CouchbaseConfigurer {
private CouchbaseEnvironment env;
private Cluster cluster;
private ClusterInfo info;
private Bucket bucket;
public TestCouchbaseConfigurer(CouchbaseEnvironment env, Cluster cluster, ClusterInfo info, Bucket bucket) {
this.env = env;
this.cluster = cluster;
this.info = info;
this.bucket = bucket;
}
@Override
public CouchbaseEnvironment couchbaseEnvironment() throws Exception {
return this.env;
}
@Override
public Cluster couchbaseCluster() throws Exception {
return this.cluster;
}
@Override
public ClusterInfo couchbaseClusterInfo() throws Exception {
return this.info;
}
@Override
public Bucket couchbaseClient() throws Exception {
return this.bucket;
}
}
@Repository
interface ItemRepository extends CouchbaseRepository<Item, String> {}
}

View File

@@ -0,0 +1,26 @@
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestNoShutdownApplicationConfig;
import org.springframework.test.context.ContextConfiguration;
/**
* Simple test to make sure that environment is not shutdown if not life cycle managed by Spring.
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestNoShutdownApplicationConfig.class)
public class CouchbaseEnvironmentNoShutdownProxyIntegrationTests {
@Autowired
public CouchbaseEnvironment environment;
@Test
public void testEnvironmentShutDown() {
Assert.assertEquals("Should return false", false, environment.shutdown());
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.util.ReflectionTestUtils;
import com.couchbase.client.core.env.DefaultCoreEnvironment;
import com.couchbase.client.java.env.CouchbaseEnvironment;
@@ -35,15 +34,15 @@ import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
* @author Simon Bland
*/
public class CouchbaseSingleEnvironmentParserTest {
/**
* @see DATACOUCH-235
* See DATACOUCH-235
*/
@Test
public void testSingleCouchbaseEnvironment() throws Exception {
Integer instanceCounterBefore = (Integer) ReflectionTestUtils.getField(DefaultCoreEnvironment.class, "instanceCounter");
int instanceCounterBefore = DefaultCoreEnvironment.instanceCounter();
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbaseSingleEnv-bean.xml"));
@@ -51,9 +50,9 @@ public class CouchbaseSingleEnvironmentParserTest {
context.refresh();
CouchbaseEnvironment env = context.getBean("singleEnv", CouchbaseEnvironment.class);
context.close();
Integer instanceCounterAfter = (Integer) ReflectionTestUtils.getField(DefaultCoreEnvironment.class, "instanceCounter");
int instanceCounterAfter = DefaultCoreEnvironment.instanceCounter();
assertThat(env, is(instanceOf(DefaultCouchbaseEnvironment.class)));
assertThat(instanceCounterAfter, is(instanceCounterBefore + 1));
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2013-2019 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
*
* https://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 static org.junit.Assert.*;
import com.couchbase.client.java.document.JsonDocument;
import com.couchbase.client.java.document.json.JsonObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.repository.User;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Michael Nitschinger
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = CouchbaseTemplateParserIntegrationTests.class)
public class CouchbaseTemplateParserIntegrationTests {
DefaultListableBeanFactory factory;
BeanDefinitionReader reader;
@Before
public void setUp() {
factory = new DefaultListableBeanFactory();
reader = new XmlBeanDefinitionReader(factory);
}
@Test
public void readsCouchbaseTemplateAttributesCorrectly() {
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-bean.xml"));
BeanDefinition definition = factory.getBeanDefinition(BeanNames.COUCHBASE_TEMPLATE);
assertEquals(2, definition.getConstructorArgumentValues().getArgumentCount());
factory.getBean(BeanNames.COUCHBASE_TEMPLATE);
}
@Test
public void readsCouchbaseTemplateWithTranslationServiceAttributesCorrectly() {
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml"));
BeanDefinition definition = factory.getBeanDefinition(BeanNames.COUCHBASE_TEMPLATE);
assertEquals(3, definition.getConstructorArgumentValues().getArgumentCount());
factory.getBean(BeanNames.COUCHBASE_TEMPLATE);
}
/**
* Test case for DATACOUCH-47.
*/
@Test
public void allowsMultipleBuckets() {
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-multi-bucket-bean.xml"));
factory.getBean("cb-template-first");
factory.getBean("cb-template-second");
}
/**
* Test case for DATACOUCH-134 in xml: field for storing type information can be renamed.
*/
@Test
public void testTypeFieldCanBeChosen() {
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-typekey.xml"));
CouchbaseTemplate template = factory.getBean(BeanNames.COUCHBASE_TEMPLATE, CouchbaseTemplate.class);
assertTrue(template.getConverter() instanceof MappingCouchbaseConverter);
MappingCouchbaseConverter converter = ((MappingCouchbaseConverter) template.getConverter());
assertEquals("javaXmlClass", converter.getTypeKey());
User u = new User("specialSaveUser", "John Locke", 46);
template.save(u);
JsonDocument uJsonDoc = template.getCouchbaseBucket().get("specialSaveUser");
template.getCouchbaseBucket().remove("specialSaveUser");
assertNotNull(uJsonDoc);
JsonObject uJson = uJsonDoc.content();
assertNull(uJson.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertEquals("org.springframework.data.couchbase.repository.User", uJson.getString("javaXmlClass"));
assertEquals("John Locke", uJson.getString("username"));
}
/**
* Test case for DATACOUCH-148, choosing an alternative default for view/N1QL staleness.
*/
@Test
public void shouldParseCustomStaleness() {
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-consistency.xml"));
CouchbaseTemplate template = factory.getBean("template", CouchbaseTemplate.class);
assertEquals(Consistency.EVENTUALLY_CONSISTENT, template.getDefaultConsistency());
assertNotEquals(Consistency.DEFAULT_CONSISTENCY, template.getDefaultConsistency());
}
/**
* Test case for DATACOUCH-148, choosing an unknown value for view/N1QL staleness.
*/
@Test
public void shouldIgnoreBadCustomStaleness() {
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-consistency.xml"));
CouchbaseTemplate template = factory.getBean("templateBad", CouchbaseTemplate.class);
assertEquals(Consistency.DEFAULT_CONSISTENCY, template.getDefaultConsistency());
}
@Test
public void shouldHaveDefaultsForStaleness() {
//use another resource where staleness isn't customized
reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml"));
CouchbaseTemplate template = factory.getBean(BeanNames.COUCHBASE_TEMPLATE, CouchbaseTemplate.class);
assertEquals(Consistency.DEFAULT_CONSISTENCY, template.getDefaultConsistency());
}
}

View File

@@ -0,0 +1,55 @@
package org.springframework.data.couchbase.core;
import rx.observers.TestSubscriber;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
public class AsyncUtils {
public static void executeConcurrently(int numThreads, Callable<Void> task) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(numThreads);
Collection<Callable<Void>> tasks = Collections.nCopies(numThreads, task);
List<Future<Void>> futures = pool.invokeAll(tasks);
for (Future future : futures) {
future.get(numThreads, TimeUnit.SECONDS);
}
}
public static <T> void awaitCompleted(TestSubscriber<T> testSubscriber) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertNoValues();
testSubscriber.assertCompleted();
}
public static <T> void awaitCompletedWithAnyValue(TestSubscriber<T> testSubscriber) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertCompleted();
}
public static <T> void awaitCompletedWithValueCount(TestSubscriber<T> testSubscriber, int count) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertValueCount(count);
testSubscriber.assertCompleted();
}
public static <T> void awaitError(TestSubscriber<T> testSubscriber, Class<? extends Throwable> throwableClazz) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertError(throwableClazz);
testSubscriber.assertNoValues();
}
public static <T> void awaitValue(TestSubscriber<T> testSubscriber, T value) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertValue(value);
testSubscriber.assertCompleted();
}
}

View File

@@ -0,0 +1,147 @@
package org.springframework.data.couchbase.core;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.IdAttribute;
import org.springframework.data.couchbase.core.mapping.id.IdPrefix;
import org.springframework.data.couchbase.core.mapping.id.IdSuffix;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Subhashni Balakrishnan
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class CouchbaseTemplateIdGenerationIntegrationTests {
@Rule
public TestName testName = new TestName();
@Autowired
private Bucket client;
@Autowired
private CouchbaseTemplate template;
private void removeIfExist(String key) {
try {
client.remove(key);
}
catch (DocumentDoesNotExistException e) {
//ignore
}
}
@Test
public void shouldGenerateIdUsingAtrributes() throws Exception {
SimpleClassWithGeneratedIdValueUsingAttributes simpleClass = new SimpleClassWithGeneratedIdValueUsingAttributes();
String generatedId = template.getGeneratedId(simpleClass);
removeIfExist(generatedId);
assertEquals("Id generation should be correct", generatedId,
"prefix1::prefix2::0::1::2.0::3.0::4::Simple::Nested{value:simple}::suffix1::suffix2");
template.insert(simpleClass);
assertEquals("Exists after insert", true, template.exists(generatedId));
simpleClass.value = "modified";
template.save(simpleClass);
SimpleClassWithGeneratedIdValueUsingAttributes modifiedClass = template.findById(generatedId,
SimpleClassWithGeneratedIdValueUsingAttributes.class);
assertEquals("Get after save id should be correct", generatedId, modifiedClass.id);
template.update(simpleClass);
SimpleClassWithGeneratedIdValueUsingAttributes updatedClass = template.findById(generatedId,
SimpleClassWithGeneratedIdValueUsingAttributes.class);
assertEquals("Get after update id should be correct", generatedId, updatedClass.id);
template.remove(generatedId);
assertEquals("Exists after remove", false, template.exists(generatedId));
}
@Test
public void shouldGenerateIdUsingUUID() throws Exception {
SimpleClassWithGeneratedIdValueUsingUUID simpleClass = new SimpleClassWithGeneratedIdValueUsingUUID();
String generatedId = template.getGeneratedId(simpleClass);
simpleClass.id = generatedId;
template.insert(simpleClass);
assertEquals("Should not regenerate id", generatedId, simpleClass.id);
template.remove(generatedId);
assertEquals("Exists after remove", false, template.exists(generatedId));
}
@Document
static class SimpleClassWithGeneratedIdValueUsingAttributes {
@Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = "::")
public String id;
@IdAttribute(order = 6)
public Nested nested = new Nested("simple");
@IdAttribute(order = 5)
public String type = "Simple";
@IdAttribute(order = 4)
public int intNum = 4;
@IdAttribute(order = 2)
public float floatNum = 2F;
@IdAttribute(order = 3)
public double doubleNum = 3;
@IdAttribute
public long longNum = 0L;
@IdAttribute(order = 1)
public short shortNum = 1;
@IdPrefix(order = 1)
public String prefix2 = "prefix2";
@IdPrefix
public String prefix1 = "prefix1";
@IdSuffix(order = 1)
public String suffix2 = "suffix2";
@IdSuffix
public String suffix1 = "suffix1";
public String value = "new";
}
static class Nested {
private String value;
public Nested(String value) {
this.value = value;
}
@Override
public String toString() {
return "Nested{value:" + value + "}";
}
}
@Document
static class SimpleClassWithGeneratedIdValueUsingUUID {
@Id @GeneratedValue(strategy = UNIQUE)
public String id;
public String value = "new";
}
}

View File

@@ -0,0 +1,808 @@
/*
* Copyright 2013-2019 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
*
* https://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 static com.couchbase.client.java.query.Select.select;
import static com.couchbase.client.java.query.dsl.Expression.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.query.N1qlParams;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.query.consistency.ScanConsistency;
import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* @author Michael Nitschinger
* @author Simon Baslé
* @author Anastasiia Smirnova */
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(CouchbaseTemplateQueryListener.class)
public class CouchbaseTemplateIntegrationTests {
@Rule
public TestName testName = new TestName();
@Autowired
private Bucket client;
@Autowired
private CouchbaseTemplate template;
private static final ObjectMapper MAPPER = new ObjectMapper();
private void removeIfExist(String key) {
try {
template.remove(key);
}
catch (DocumentDoesNotExistException e) {
//ignore
}
}
@Test
public void saveSimpleEntityCorrectly() throws Exception {
String id = "beers:awesome-stout";
removeIfExist(id);
String name = "The Awesome Stout";
boolean active = false;
Beer beer = new Beer(id, name, active, "");
template.save(beer);
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
assertNotNull(result);
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {});
assertNotNull(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertNull(resultConv.get("javaClass"));
assertEquals("org.springframework.data.couchbase.core.Beer", resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertEquals(false, resultConv.get("is_active"));
assertEquals("The Awesome Stout", resultConv.get("name"));
}
@Test
public void saveDocumentWithExpiry() throws Exception {
String id = "simple-doc-with-expiry";
DocumentWithExpiry doc = new DocumentWithExpiry(id);
template.save(doc);
assertNotNull(client.get(id));
Thread.sleep(3000);
assertNull(client.get(id));
}
@Test
public void insertDoesNotOverride() throws Exception {
String id = "double-insert-test";
removeIfExist(id);
SimplePerson doc = new SimplePerson(id, "Mr. A");
template.insert(doc);
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
Map<String, String> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, String>>() {});
assertEquals("Mr. A", resultConv.get("name"));
doc = new SimplePerson(id, "Mr. B");
try {
template.insert(doc);
} catch (OptimisticLockingFailureException e) {
//ignore, since this insert should fail
}
resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
result = resultDoc.content();
resultConv = MAPPER.readValue(result, new TypeReference<Map<String, String>>() {});
assertEquals("Mr. A", resultConv.get("name"));
}
@Test
public void updateDoesNotInsert() {
String id = "update-does-not-insert";
SimplePerson doc = new SimplePerson(id, "Nice Guy");
template.update(doc);
assertNull(client.get(id));
}
@Test
public void removeDocument() {
String id = "beers:to-delete-stout";
Beer beer = new Beer(id, "", false, "");
template.save(beer);
Object result = client.get(id);
assertNotNull(result);
template.remove(beer);
result = client.get(id);
assertNull(result);
}
@Test
public void storeListsAndMaps() {
String id = "persons:lots-of-names";
List<String> names = new ArrayList<String>();
names.add("Michael");
names.add("Thomas");
names.add(null);
List<Integer> votes = new LinkedList<Integer>();
Map<String, Boolean> info1 = new HashMap<String, Boolean>();
info1.put("foo", true);
info1.put("bar", false);
info1.put("nullValue", null);
Map<String, Integer> info2 = new HashMap<String, Integer>();
ComplexPerson complex = new ComplexPerson(id, names, votes, info1, info2);
template.save(complex);
assertNotNull(client.get(id));
ComplexPerson response = template.findById(id, ComplexPerson.class);
assertEquals(names, response.getFirstnames());
assertEquals(votes, response.getVotes());
assertEquals(id, response.getId());
assertEquals(info1, response.getInfo1());
assertEquals(info2, response.getInfo2());
}
@Test
public void validFindById() {
String id = "beers:findme-stout";
String name = "The Findme Stout";
boolean active = true;
Beer beer = new Beer(id, name, active, "");
template.save(beer);
Beer found = template.findById(id, Beer.class);
assertNotNull(found);
assertEquals(id, found.getId());
assertEquals(name, found.getName());
assertEquals(active, found.getActive());
}
@Test
public void shouldLoadAndMapViewDocs() {
ViewQuery query = ViewQuery.from("test_beers", "by_name");
query.stale(Stale.FALSE);
final List<Beer> beers = template.findByView(query, Beer.class);
assertTrue(beers.size() > 0);
for (Beer beer : beers) {
assertNotNull(beer.getId());
assertNotNull(beer.getName());
assertNotNull(beer.getActive());
}
}
@Test
public void shouldQueryRaw() {
N1qlQuery query = N1qlQuery.simple(select("name").from(i(client.name()))
.where(x("name").isNotMissing()));
N1qlQueryResult queryResult = template.queryN1QL(query);
assertNotNull(queryResult);
assertTrue(queryResult.errors().toString(), queryResult.finalSuccess());
assertFalse(queryResult.allRows().isEmpty());
}
@Test
public void shouldQueryWithMapping() {
FullFragment ff1 = new FullFragment("fullFragment1", 1, "fullFragment", "test1");
FullFragment ff2 = new FullFragment("fullFragment2", 2, "fullFragment", "test2");
template.save(Arrays.asList(ff1, ff2));
N1qlQuery query = N1qlQuery.simple(select(i("value")) //"value" is a n1ql keyword apparently
.from(i(client.name()))
.where(x("type").eq(s("fullFragment"))
.and(x("criteria").gt(1))),
N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS));
List<Fragment> fragments = template.findByN1QLProjection(query, Fragment.class);
assertNotNull(fragments);
assertFalse(fragments.isEmpty());
assertEquals(1, fragments.size());
assertEquals("test2", fragments.get(0).value);
}
/**
* @see DATACOUCH-159
*/
@Test
public void shouldDeserialiseLongsAndInts() {
final long longValue = new Date().getTime();
final int intValue = new Random().nextInt();
template.save(new SimpleWithLongAndInt("simpleWithLong:simple", longValue, intValue));
SimpleWithLongAndInt document = template.findById("simpleWithLong:simple", SimpleWithLongAndInt.class);
assertNotNull(document);
assertEquals(longValue, document.getLongValue());
assertEquals(intValue, document.getIntValue());
template.save(new SimpleWithLongAndInt("simpleWithLong:simple:other", intValue, intValue));
document = template.findById("simpleWithLong:simple:other", SimpleWithLongAndInt.class);
assertNotNull(document);
assertEquals(intValue, document.getLongValue());
assertEquals(intValue, document.getIntValue());
}
@Test
public void shouldDeserialiseEnums() {
SimpleWithEnum simpleWithEnum = new SimpleWithEnum("simpleWithEnum:enum", SimpleWithEnum.Type.BIG);
template.save(simpleWithEnum);
simpleWithEnum = template.findById("simpleWithEnum:enum", SimpleWithEnum.class);
assertNotNull(simpleWithEnum);
assertEquals(simpleWithEnum.getType(), SimpleWithEnum.Type.BIG);
}
@Test
public void shouldDeserialiseClass() {
SimpleWithClass simpleWithClass = new SimpleWithClass("simpleWithClass:class", Integer.class);
simpleWithClass.setValue("The dish ran away with the spoon.");
template.save(simpleWithClass);
simpleWithClass = template.findById("simpleWithClass:class", SimpleWithClass.class);
assertNotNull(simpleWithClass);
assertThat(simpleWithClass.getValue(), equalTo("The dish ran away with the spoon."));
}
@Test
public void shouldHandleCASVersionOnInsert() throws Exception {
removeIfExist("versionedClass:1");
VersionedClass versionedClass = new VersionedClass("versionedClass:1", "foobar");
assertEquals(0, versionedClass.getVersion());
template.insert(versionedClass);
RawJsonDocument rawStored = client.get("versionedClass:1", RawJsonDocument.class);
assertEquals(rawStored.cas(), versionedClass.getVersion());
}
@Test
public void versionShouldNotUpdateOnSecondInsert() throws Exception {
removeIfExist("versionedClass:2");
VersionedClass versionedClass = new VersionedClass("versionedClass:2", "foobar");
template.insert(versionedClass);
long version1 = versionedClass.getVersion();
try {
template.insert(versionedClass);
} catch (OptimisticLockingFailureException e) {
//ignore, since this insert should fail
}
long version2 = versionedClass.getVersion();
assertTrue(version1 > 0);
assertTrue(version2 > 0);
assertEquals(version1, version2);
}
@Test
public void shouldSaveDocumentOnMatchingVersion() throws Exception {
removeIfExist("versionedClass:3");
VersionedClass versionedClass = new VersionedClass("versionedClass:3", "foobar");
template.insert(versionedClass);
long version1 = versionedClass.getVersion();
versionedClass.setField("foobar2");
template.save(versionedClass);
long version2 = versionedClass.getVersion();
assertTrue(version1 > 0);
assertTrue(version2 > 0);
assertNotEquals(version1, version2);
assertEquals("foobar2", template.findById("versionedClass:3", VersionedClass.class).getField());
}
@Test(expected = OptimisticLockingFailureException.class)
public void shouldNotSaveDocumentOnNotMatchingVersion() throws Exception {
removeIfExist("versionedClass:4");
VersionedClass versionedClass = new VersionedClass("versionedClass:4", "foobar");
template.insert(versionedClass);
RawJsonDocument toCompare = RawJsonDocument.create("versionedClass:4", "different");
assertNotNull(client.upsert(toCompare));
versionedClass.setField("foobar2");
//save (aka upsert) won't error in case of CAS mismatch anymore
template.update(versionedClass);
}
@Test
public void shouldUpdateDocumentOnMatchingVersion() throws Exception {
removeIfExist("versionedClass:5");
VersionedClass versionedClass = new VersionedClass("versionedClass:5", "foobar");
template.insert(versionedClass);
long version1 = versionedClass.getVersion();
versionedClass.setField("foobar2");
template.update(versionedClass);
long version2 = versionedClass.getVersion();
assertTrue(version1 > 0);
assertTrue(version2 > 0);
assertNotEquals(version1, version2);
assertEquals("foobar2", template.findById("versionedClass:5", VersionedClass.class).getField());
}
@Test(expected = OptimisticLockingFailureException.class)
public void shouldNotUpdateDocumentOnNotMatchingVersion() throws Exception {
removeIfExist("versionedClass:6");
VersionedClass versionedClass = new VersionedClass("versionedClass:6", "foobar");
template.insert(versionedClass);
RawJsonDocument toCompare = RawJsonDocument.create("versionedClass:6", "different");
assertNotNull(client.upsert(toCompare));
versionedClass.setField("foobar2");
template.update(versionedClass);
}
@Test
public void shouldLoadVersionPropertyOnFind() throws Exception {
removeIfExist("versionedClass:7");
VersionedClass versionedClass = new VersionedClass("versionedClass:7", "foobar");
template.insert(versionedClass);
assertTrue(versionedClass.getVersion() > 0);
VersionedClass foundClass = template.findById("versionedClass:7", VersionedClass.class);
assertEquals(versionedClass.getVersion(), foundClass.getVersion());
}
@Test
public void shouldUpdateAlreadyExistingDocument() throws Exception {
final String key = testName.getMethodName();
removeIfExist(key);
final AtomicLong counter = new AtomicLong();
VersionedClass initial = new VersionedClass(key, "value-0");
template.save(initial);
AsyncUtils.executeConcurrently(3, new Callable<Void>() {
@Override
public Void call() throws Exception {
boolean saved = false;
while(!saved) {
long counterValue = counter.incrementAndGet();
VersionedClass messageData = template.findById(key, VersionedClass.class);
messageData.field = "value-" + counterValue;
try {
template.save(messageData);
saved = true;
} catch (OptimisticLockingFailureException e) {
}
}
return null;
}
});
VersionedClass actual = template.findById(key, VersionedClass.class);
assertNotEquals(initial.field, actual.field);
assertNotEquals(initial.version, actual.version);
}
@Test
public void shouldInsertOnlyFirstDocumentAndNextAttemptsShouldFailWithOptimisticLockingException() throws Exception {
final String key = testName.getMethodName();
removeIfExist(key);
final AtomicLong counter = new AtomicLong();
final AtomicLong optimisticLockCounter = new AtomicLong();
AsyncUtils.executeConcurrently(5, new Callable<Void>() {
@Override
public Void call() throws Exception {
long counterValue = counter.incrementAndGet();
String data = "value-" + counterValue;
VersionedClass messageData = new VersionedClass(key, data);
try {
template.insert(messageData);
} catch (OptimisticLockingFailureException e) {
optimisticLockCounter.incrementAndGet();
}
//should save operation throw OptimisticLockingFailureException on next attempts to save?
return null;
}
});
assertEquals(4, optimisticLockCounter.intValue());
}
/**
* @see DATACOUCH-59
*/
@Test
public void expiryWhenTouchOnReadDocument() throws InterruptedException {
String id = "simple-doc-with-update-expiry-for-read";
DocumentWithTouchOnRead doc = new DocumentWithTouchOnRead(id);
template.save(doc);
Thread.sleep(1000);
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class));
Thread.sleep(1000);
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class));
Thread.sleep(3000);
assertNull(template.findById(id, DocumentWithTouchOnRead.class));
}
/**
* @see DATACOUCH-227
*/
@Test
public void shouldRetainOrderWhenQueryingViewOrdered() {
ViewQuery q = ViewQuery.from("test_beers", "by_name");
q.descending().includeDocsOrdered(true);
String prev = null;
List<Beer> beers = template.findByView(q, Beer.class);
assertTrue(q.isIncludeDocs());
assertTrue(q.isOrderRetained());
assertEquals(RawJsonDocument.class, q.includeDocsTarget());
for (Beer beer : beers) {
if (prev != null) {
assertThat(beer.getName() + " not alphabetically < to " + prev, beer.getName().compareTo(prev) < 0);
}
prev = beer.getName();
}
}
/**
* A sample document with just an id and property.
*/
@Document
static class SimplePerson {
@Id
private final String id;
@Field
private final String name;
public SimplePerson(String id, String name) {
this.id = id;
this.name = name;
}
}
/**
* A sample document that expires in 2 seconds.
*/
@Document(expiry = 2)
static class DocumentWithExpiry {
@Id
private final String id;
public DocumentWithExpiry(String id) {
this.id = id;
}
}
/**
* A sample document that expires in 2 seconds and touchOnRead set.
*/
@Document(expiry = 2, touchOnRead = true)
static class DocumentWithTouchOnRead {
@Id
private final String id;
public DocumentWithTouchOnRead(String id) {
this.id = id;
}
}
@Document
static class ComplexPerson {
@Id
private final String id;
@Field
private final List<String> firstnames;
@Field
private final List<Integer> votes;
@Field
private final Map<String, Boolean> info1;
@Field
private final Map<String, Integer> info2;
public ComplexPerson(String id, List<String> firstnames,
List<Integer> votes, Map<String, Boolean> info1,
Map<String, Integer> info2) {
this.id = id;
this.firstnames = firstnames;
this.votes = votes;
this.info1 = info1;
this.info2 = info2;
}
List<String> getFirstnames() {
return firstnames;
}
List<Integer> getVotes() {
return votes;
}
Map<String, Boolean> getInfo1() {
return info1;
}
Map<String, Integer> getInfo2() {
return info2;
}
String getId() {
return id;
}
}
@Document
static class SimpleWithLongAndInt {
@Id
private String id;
private long longValue;
private int intValue;
SimpleWithLongAndInt(final String id, final long longValue, int intValue) {
this.id = id;
this.longValue = longValue;
this.intValue = intValue;
}
String getId() {
return id;
}
long getLongValue() {
return longValue;
}
void setLongValue(final long value) {
this.longValue = value;
}
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
}
static class SimpleWithEnum {
@Id
private String id;
private enum Type {
BIG
}
private Type type;
SimpleWithEnum(final String id, final Type type) {
this.id = id;
this.type = type;
}
String getId() {
return id;
}
void setId(final String id) {
this.id = id;
}
Type getType() {
return type;
}
void setType(final Type type) {
this.type = type;
}
}
static class SimpleWithClass {
@Id
private String id;
private Class<Integer> integerClass;
private String value;
SimpleWithClass(final String id, final Class<Integer> integerClass) {
this.id = id;
this.integerClass = integerClass;
}
String getId() {
return id;
}
void setId(final String id) {
this.id = id;
}
Class<Integer> getIntegerClass() {
return integerClass;
}
void setIntegerClass(final Class<Integer> integerClass) {
this.integerClass = integerClass;
}
String getValue() {
return value;
}
void setValue(final String value) {
this.value = value;
}
}
static class VersionedClass {
@Id
private String id;
@Version
private long version;
private String field;
VersionedClass(String id, String field) {
this.id = id;
this.field = field;
}
public String getId() {
return id;
}
public long getVersion() {
return version;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
@Override
public String toString() {
return "VersionedClass{" +
"id='" + id + '\'' +
", version=" + version +
", field='" + field + '\'' +
'}';
}
}
@Document
static class FullFragment {
@Id
private String id;
private long criteria;
private String type;
private String value;
public FullFragment(String id, long criteria, String type, String value) {
this.id = id;
this.criteria = criteria;
this.type = type;
this.value = value;
}
public String getId() {
return id;
}
public long getCriteria() {
return criteria;
}
public String getType() {
return type;
}
public String getValue() {
return value;
}
public void setCriteria(long criteria) {
this.criteria = criteria;
}
public void setType(String type) {
this.type = type;
}
public void setValue(String value) {
this.value = value;
}
}
static class Fragment {
public String value;
}
}

View File

@@ -0,0 +1,61 @@
package org.springframework.data.couchbase.core;
import static org.junit.Assert.*;
import com.couchbase.client.java.repository.annotation.Id;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestCustomKeySettings;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.KeySettings;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Subhashni Balakrishnan
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestCustomKeySettings.class)
public class CouchbaseTemplateKeySettingsIntegrationTests {
@Rule
public TestName testName = new TestName();
@Autowired
private CouchbaseTemplate template;
@Before
public void setup() {
if (template.keySettings() == null) {
template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::"));
}
}
@Test
public void shouldAddCustomKeySettings() throws Exception {
SimpleClass simpleClass = new SimpleClass();
String generatedId = template.getGeneratedId(simpleClass);
assertEquals("Id generated should include custom key settings", "MyAppPrefix::myId::MyAppSuffix", generatedId);
}
@Test
public void shouldNotAllowKeySettingsToBeChanged() {
try {
template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::"));
fail("excepted unsupportedOperationException");
} catch(Exception ex) {
}
}
@Document
static class SimpleClass {
@Id
public String id = "myId";
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2013-2019 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
*
* https://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 java.util.Collections;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.query.Index;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.view.DefaultView;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.View;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* @author Michael Nitschinger
*/
public class CouchbaseTemplateQueryListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
createAndWaitForDesignDocs(client);
client.query(N1qlQuery.simple(Index.createPrimaryIndex().on(client.name())));
}
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, "MyBeer" + i, 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);
}
@Override
public void afterTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
for (int i = 0; i < 100; i++) {
Beer b = new Beer("testbeer-" + i, "MyBeer" + i, true, "");
template.remove(b);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2017-2019 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
*
* https://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 java.util.Collections;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.query.Index;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.view.*;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import rx.Observable;
/**
* @author Subhashni Balakrishnan
*/
public class RxCouchbaseTemplateQueryListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
createAndWaitForDesignDocs(client);
client.query(N1qlQuery.simple(Index.createPrimaryIndex().on(client.name())));
}
private void populateTestData(Bucket client, ClusterInfo clusterInfo) {
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client);
for (int i = 0; i < 100; i++) {
ReactiveBeer b = new ReactiveBeer("testbeer-" + i, "MyBeer" + i, true, "");
template.save(b).subscribe();
}
}
private void createAndWaitForDesignDocs(Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == "
+ "\"org.springframework.data.couchbase.core.ReactiveBeer\") { emit(doc.name, null); } }";
View view = DefaultView.create("by_name", mapFunction);
DesignDocument designDoc = DesignDocument.create("reactive_test_beers", Collections.singletonList(view));
client.bucketManager().upsertDesignDocument(designDoc);
}
@Override
public void afterTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client);
for (int i = 0; i < 100; i++) {
ReactiveBeer b = new ReactiveBeer("testbeer-" + i, "MyBeer" + i, true, "");
template.remove(b).subscribe();
}
}
}

View File

@@ -0,0 +1,732 @@
/*
* Copyright 2017-2019 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
*
* https://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 static com.couchbase.client.java.query.Select.select;
import static com.couchbase.client.java.query.dsl.Expression.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.*;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.query.AsyncN1qlQueryResult;
import com.couchbase.client.java.query.N1qlParams;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.consistency.ScanConsistency;
import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import rx.observers.TestSubscriber;
/**
* @author Subhashni Balakrishnan
* @author Alex Derkach
**/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
@TestExecutionListeners(RxCouchbaseTemplateQueryListener.class)
public class RxJavaCouchbaseTemplateIntegrationTests {
@Rule
public TestName testName = new TestName();
@Autowired
private Bucket client;
@Autowired
private RxJavaCouchbaseOperations template;
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String DEFAULT_ID = "reactivebeers:awesome-stout";
private static final String DEFAULT_NAME = "The Awesome Stout";
private static final boolean DEFAULT_ACTIVE = false;
private static final String DEFAULT_DESCRIPTION = "";
@Before
public void setUp() throws Exception {
removeIfExist(DEFAULT_ID);
}
private void removeIfExist(String key) {
TestSubscriber<Object> subscriber = TestSubscriber.create();
template.remove(key)
.subscribe(subscriber);
subscriber.awaitTerminalEvent();
}
private void removeCollectionIfExist(Collection<ReactiveBeer> beers) {
TestSubscriber<Object> subscriber = TestSubscriber.create();
template.remove(beers, PersistTo.MASTER, ReplicateTo.NONE)
.subscribe(subscriber);
subscriber.awaitTerminalEvent();
}
@Test
public void upsertNonVersionedEntityCorrectlyWhenSaveIsCalled() throws Exception {
String newName = DEFAULT_NAME + "Second";
ReactiveBeer firstBeer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
ReactiveBeer secondBeer = new ReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
TestSubscriber<ReactiveBeer> firstSaveSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> secondSaveSubscriber = TestSubscriber.create();
template.save(firstBeer).subscribe(firstSaveSubscriber);
template.save(secondBeer).subscribe(secondSaveSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstSaveSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(secondSaveSubscriber);
validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, ReactiveBeer.class);
}
@Test
public void replaceVersionedEntityCorrectlyWhenSaveIsCalledAndCasIsNotZero() throws Exception {
String newName = DEFAULT_NAME + "Second";
VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
long version = template.save(firstBeer).toBlocking().single().getVersion();
assertTrue(version > 0);
secondBeer.setVersion(version);
long newVersion = template.save(secondBeer).toBlocking().single().getVersion();
assertTrue(newVersion > 0);
assertNotEquals(version, newVersion);
validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void throwExceptionWhenSaveIsCalledAndCasIsMissmatched() throws Exception {
String newName = DEFAULT_NAME + "Second";
VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
TestSubscriber<VersionedReactiveBeer> secondSaveSubscriber = TestSubscriber.create();
long version = template.save(firstBeer).toBlocking().single().getVersion();
assertTrue(version > 0);
secondBeer.setVersion(version + 1234);
template.save(secondBeer).subscribe(secondSaveSubscriber);
AsyncUtils.awaitError(secondSaveSubscriber, OptimisticLockingFailureException.class);
validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void throwExceptionWhenSaveIsCalledAndCasIsZeroAndEntityAlreadyExists() throws Exception {
String newName = DEFAULT_NAME + "Second";
VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
TestSubscriber<VersionedReactiveBeer> secondSaveSubscriber = TestSubscriber.create();
long version = template.save(firstBeer).toBlocking().single().getVersion();
assertTrue(version > 0);
template.save(secondBeer).subscribe(secondSaveSubscriber);
AsyncUtils.awaitError(secondSaveSubscriber, OptimisticLockingFailureException.class);
validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void saveCollectionCorrectly() throws Exception {
Collection<ReactiveBeer> beers = new ArrayList<>();
TestSubscriber<ReactiveBeer> testSubscriber = TestSubscriber.create();
String name = DEFAULT_NAME;
int collectionSize = 10000;
for (int i = 0; i < collectionSize; i++) {
beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, ""));
}
removeCollectionIfExist(beers);
template.save(beers).subscribe(testSubscriber);
AsyncUtils.awaitCompletedWithValueCount(testSubscriber, collectionSize);
}
@Test
public void insertSimpleEntityCorrectly() throws Exception {
TestSubscriber<ReactiveBeer> testSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.insert(beer).subscribe(testSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(testSubscriber);
validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, ReactiveBeer.class);
}
@Test
public void expectErrorWhenDocumentExistsAndInsertIsCalled() throws Exception {
TestSubscriber<ReactiveBeer> firstInsertSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> secondInsertSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.insert(beer).subscribe(firstInsertSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber);
template.insert(beer).subscribe(secondInsertSubscriber);
AsyncUtils.awaitError(secondInsertSubscriber, OptimisticLockingFailureException.class);
}
@Test
public void insertCollectionCorrectly() throws Exception {
TestSubscriber<ReactiveBeer> testSubscriber = TestSubscriber.create();
Collection<ReactiveBeer> beers = new ArrayList<>();
String name = DEFAULT_NAME;
int collectionSize = 10000;
for (int i = 0; i < collectionSize; i++) {
beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, ""));
}
removeCollectionIfExist(beers);
template.insert(beers).subscribe(testSubscriber);
AsyncUtils.awaitCompletedWithValueCount(testSubscriber, collectionSize);
}
@Test
public void replaceSimpleEntityCorrectly() throws Exception {
TestSubscriber<VersionedReactiveBeer> firstInsertSubscriber = TestSubscriber.create();
TestSubscriber<VersionedReactiveBeer> replaceTestSubscriber = TestSubscriber.create();
String newName = DEFAULT_NAME + " New";
VersionedReactiveBeer beer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.insert(beer).subscribe(firstInsertSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber);
template.findById(DEFAULT_ID, VersionedReactiveBeer.class)
.doOnNext(v -> v.setName(newName))
.flatMap(v -> template.update(v))
.subscribe(replaceTestSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(replaceTestSubscriber);
validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void expectErrorWhenReplacingSimpleEntityWhichDoesNotExist() throws Exception {
TestSubscriber<VersionedReactiveBeer> testSubscriber = TestSubscriber.create();
VersionedReactiveBeer beer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.update(beer).subscribe(testSubscriber);
AsyncUtils.awaitError(testSubscriber, DataRetrievalFailureException.class);
}
@Test
public void removeDocument() {
TestSubscriber<ReactiveBeer> firstInsertSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> firstFindSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> removalTestSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> secondFindSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.save(beer).subscribe(firstInsertSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber);
template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(firstFindSubscriber);
AsyncUtils.awaitCompletedWithValueCount(firstFindSubscriber, 1);
template.remove(beer).subscribe(removalTestSubscriber);
AsyncUtils.awaitCompletedWithValueCount(removalTestSubscriber, 1);
template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(secondFindSubscriber);
AsyncUtils.awaitValue(secondFindSubscriber, null);
}
@Test
public void storeListsAndMaps() {
String id = "persons:lots-of-names";
List<String> names = new ArrayList<String>();
names.add("Michael");
names.add("Thomas");
names.add(null);
List<Integer> votes = new LinkedList<Integer>();
Map<String, Boolean> info1 = new HashMap<String, Boolean>();
info1.put("foo", true);
info1.put("bar", false);
info1.put("nullValue", null);
Map<String, Integer> info2 = new HashMap<String, Integer>();
ComplexPerson complex = new ComplexPerson(id, names, votes, info1, info2);
template.save(complex).subscribe();
assertNotNull(client.get(id));
ComplexPerson response = template.findById(id, ComplexPerson.class).toBlocking().single();
assertEquals(names, response.getFirstnames());
assertEquals(votes, response.getVotes());
assertEquals(id, response.getId());
assertEquals(info1, response.getInfo1());
assertEquals(info2, response.getInfo2());
}
@Test
public void validFindById() {
TestSubscriber<ReactiveBeer> saveSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> findSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.save(beer).subscribe(saveSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(saveSubscriber);
template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(findSubscriber);
AsyncUtils.awaitValue(findSubscriber, beer);
}
@Test
public void shouldLoadAndMapViewDocs() {
ViewQuery query = ViewQuery.from("reactive_test_beers", "by_name");
query.stale(Stale.FALSE);
final List<ReactiveBeer> beers = template.findByView(query, ReactiveBeer.class).toList().toBlocking().single();
assertTrue(beers.size() > 0);
for (ReactiveBeer beer : beers) {
assertNotNull(beer.getId());
assertNotNull(beer.getName());
assertNotNull(beer.getActive());
}
}
@Test
public void shouldQueryRaw() {
N1qlQuery query = N1qlQuery.simple(select("name").from(i(client.name())).limit(1));
AsyncN1qlQueryResult queryResult = template.queryN1QL(query).toBlocking().single();
assertTrue(queryResult.finalSuccess().toBlocking().single());
assertFalse(queryResult.rows().toList().toBlocking().single().isEmpty());
}
@Test
public void shouldQueryWithMapping() {
FullFragment ff1 = new FullFragment("fullFragment1", 1, "fullFragment", "test1");
FullFragment ff2 = new FullFragment("fullFragment2", 2, "fullFragment", "test2");
template.save(Arrays.asList(ff1, ff2)).subscribe();
N1qlQuery query = N1qlQuery.simple(select(i("value")) //"value" is a n1ql keyword apparently
.from(i(client.name()))
.where(x("type").eq(s("fullFragment"))
.and(x("criteria").gt(1))),
N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS));
List<Fragment> fragments = template.findByN1QLProjection(query, Fragment.class).toList().toBlocking().single();
assertNotNull(fragments);
assertFalse(fragments.isEmpty());
assertEquals(1, fragments.size());
assertEquals("test2", fragments.get(0).value);
}
@Test
public void shouldDeserialiseLongsAndInts() {
final long longValue = new Date().getTime();
final int intValue = new Random().nextInt();
template.save(new SimpleWithLongAndInt("simpleWithLong:simple", longValue, intValue)).toBlocking().single();
SimpleWithLongAndInt document = template.findById("simpleWithLong:simple", SimpleWithLongAndInt.class).toBlocking().single();
assertNotNull(document);
assertEquals(longValue, document.getLongValue());
assertEquals(intValue, document.getIntValue());
template.save(new SimpleWithLongAndInt("simpleWithLong:simple:other", intValue, intValue)).toBlocking().single();
document = template.findById("simpleWithLong:simple:other", SimpleWithLongAndInt.class).toBlocking().single();
assertNotNull(document);
assertEquals(intValue, document.getLongValue());
assertEquals(intValue, document.getIntValue());
}
@Test
public void shouldDeserialiseEnums() {
SimpleWithEnum simpleWithEnum = new SimpleWithEnum("simpleWithEnum:enum", SimpleWithEnum.Type.BIG);
template.save(simpleWithEnum).toBlocking().single();
simpleWithEnum = template.findById("simpleWithEnum:enum", SimpleWithEnum.class).toBlocking().single();
assertNotNull(simpleWithEnum);
assertEquals(simpleWithEnum.getType(), SimpleWithEnum.Type.BIG);
}
@Test
public void shouldDeserialiseClass() {
SimpleWithClass simpleWithClass = new SimpleWithClass("simpleWithClass:class", Integer.class);
simpleWithClass.setValue("The dish ran away with the spoon.");
template.save(simpleWithClass).toBlocking().single();
simpleWithClass = template.findById("simpleWithClass:class", SimpleWithClass.class).toBlocking().single();
assertNotNull(simpleWithClass);
assertThat(simpleWithClass.getValue(), equalTo("The dish ran away with the spoon."));
}
@Test
public void expiryWhenTouchOnReadDocument() throws InterruptedException {
String id = "simple-doc-with-update-expiry-for-read";
DocumentWithTouchOnRead doc = new DocumentWithTouchOnRead(id);
template.save(doc).subscribe();
Thread.sleep(1000);
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class).toBlocking().single());
Thread.sleep(1000);
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class).toBlocking().single());
Thread.sleep(3000);
assertNull(template.findById(id, DocumentWithTouchOnRead.class).toBlocking().single());
}
@Test
public void shouldRetainOrderWhenQueryingViewOrdered() {
ViewQuery q = ViewQuery.from("reactive_test_beers", "by_name");
q.descending().includeDocsOrdered(true);
String prev = null;
List<ReactiveBeer> beers = template.findByView(q, ReactiveBeer.class).toList().toBlocking().single();
assertTrue(q.isIncludeDocs());
assertTrue(q.isOrderRetained());
assertEquals(RawJsonDocument.class, q.includeDocsTarget());
for (ReactiveBeer beer : beers) {
if (prev != null) {
assertThat(beer.getName() + " not alphabetically < to " + prev, beer.getName().compareTo(prev) < 0);
}
prev = beer.getName();
}
}
private void validateBeer(String id, String name, boolean active, String description, Class<?> clazz) throws IOException {
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
assertNotNull(result);
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {});
assertNotNull(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertNull(resultConv.get("javaClass"));
assertEquals(clazz.getCanonicalName(), resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertEquals(active, resultConv.get("is_active"));
assertEquals(name, resultConv.get("name"));
assertEquals(description, resultConv.get("desc"));
}
/**
* A sample document with just an id and property.
*/
@Document
static class SimplePerson {
@Id
private final String id;
@Field
private final String name;
public SimplePerson(String id, String name) {
this.id = id;
this.name = name;
}
}
/**
* A sample document that expires in 2 seconds.
*/
@Document(expiry = 2)
static class DocumentWithExpiry {
@Id
private final String id;
public DocumentWithExpiry(String id) {
this.id = id;
}
}
/**
* A sample document that expires in 2 seconds and touchOnRead set.
*/
@Document(expiry = 2, touchOnRead = true)
static class DocumentWithTouchOnRead {
@Id
private final String id;
public DocumentWithTouchOnRead(String id) {
this.id = id;
}
}
@Document
static class ComplexPerson {
@Id
private final String id;
@Field
private final List<String> firstnames;
@Field
private final List<Integer> votes;
@Field
private final Map<String, Boolean> info1;
@Field
private final Map<String, Integer> info2;
public ComplexPerson(String id, List<String> firstnames,
List<Integer> votes, Map<String, Boolean> info1,
Map<String, Integer> info2) {
this.id = id;
this.firstnames = firstnames;
this.votes = votes;
this.info1 = info1;
this.info2 = info2;
}
List<String> getFirstnames() {
return firstnames;
}
List<Integer> getVotes() {
return votes;
}
Map<String, Boolean> getInfo1() {
return info1;
}
Map<String, Integer> getInfo2() {
return info2;
}
String getId() {
return id;
}
}
@Document
static class SimpleWithLongAndInt {
@Id
private String id;
private long longValue;
private int intValue;
SimpleWithLongAndInt(final String id, final long longValue, int intValue) {
this.id = id;
this.longValue = longValue;
this.intValue = intValue;
}
String getId() {
return id;
}
long getLongValue() {
return longValue;
}
void setLongValue(final long value) {
this.longValue = value;
}
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
}
static class SimpleWithEnum {
@Id
private String id;
private enum Type {
BIG
}
Type type;
SimpleWithEnum(final String id, final Type type) {
this.id = id;
this.type = type;
}
String getId() {
return id;
}
void setId(final String id) {
this.id = id;
}
Type getType() {
return type;
}
void setType(final Type type) {
this.type = type;
}
}
static class SimpleWithClass {
@Id
private String id;
private Class<Integer> integerClass;
private String value;
SimpleWithClass(final String id, final Class<Integer> integerClass) {
this.id = id;
this.integerClass = integerClass;
}
String getId() {
return id;
}
void setId(final String id) {
this.id = id;
}
Class<Integer> getIntegerClass() {
return integerClass;
}
void setIntegerClass(final Class<Integer> integerClass) {
this.integerClass = integerClass;
}
String getValue() {
return value;
}
void setValue(final String value) {
this.value = value;
}
}
static class VersionedClass {
@Id
private String id;
@Version
private long version;
private String field;
VersionedClass(String id, String field) {
this.id = id;
this.field = field;
}
public String getId() {
return id;
}
public long getVersion() {
return version;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
@Override
public String toString() {
return "VersionedClass{" +
"id='" + id + '\'' +
", version=" + version +
", field='" + field + '\'' +
'}';
}
}
@Document
static class FullFragment {
@Id
private String id;
private long criteria;
private String type;
private String value;
public FullFragment(String id, long criteria, String type, String value) {
this.id = id;
this.criteria = criteria;
this.type = type;
this.value = value;
}
public String getId() {
return id;
}
public long getCriteria() {
return criteria;
}
public String getType() {
return type;
}
public String getValue() {
return value;
}
public void setCriteria(long criteria) {
this.criteria = criteria;
}
public void setType(String type) {
this.type = type;
}
public void setValue(String value) {
this.value = value;
}
}
static class Fragment {
public String value;
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2013-2019 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
*
* https://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 static org.junit.Assert.*;
import java.util.Map;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.RawJsonDocument;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestCustomTypeKeyConfig;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.test.context.ContextConfiguration;
/**
* Tests the Java Config template around type key modification (DATACOUCH-134)
*
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestCustomTypeKeyConfig.class)
public class TypeKeyIntegrationTests {
@Autowired
private Bucket client;
@Autowired
private CouchbaseTemplate template;
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* see DATACOUCH-134
*/
@Test
public void saveSimpleEntityCorrectlyWithDifferentTypeKey() throws Exception {
String id = "beers:awesome-stout";
String name = "The Awesome Stout";
boolean active = false;
Beer beer = new Beer(id, name, active, "");
template.save(beer);
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
assertNotNull(result);
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {});
assertNull(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertNotNull(resultConv.get("javaClass"));
assertEquals("org.springframework.data.couchbase.core.Beer", resultConv.get("javaClass"));
assertEquals(false, resultConv.get("is_active"));
assertEquals("The Awesome Stout", resultConv.get("name"));
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2017-2019 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
*
* https://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.mapping;
import java.util.Arrays;
import java.util.UUID;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.TestContainerResource;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
/**
* @author Subhashni Balakrishnan
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class CustomConverterIntegrationTests {
@Autowired
private MappingCouchbaseConverter converter;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private TestUUIDRepository repository;
@WritingConverter
public enum UUIDToStringConverter implements Converter<UUID, String> {
INSTANCE;
@Override
public String convert(UUID source) {
return source.toString();
}
}
@ReadingConverter
public enum StringToUUIDConverter implements Converter<String, UUID> {
INSTANCE;
@Override
public UUID convert(String source) {
return UUID.fromString(source);
}
}
@Before
public void setup() {
converter.setCustomConversions(new CouchbaseCustomConversions(Arrays.asList(UUIDToStringConverter.INSTANCE, StringToUUIDConverter.INSTANCE)));
converter.afterPropertiesSet();
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = getRepositoryWithRetry(factory, TestUUIDRepository.class);
}
@Test
public void testIdConversion() {
TestUUID doc = new TestUUID();
doc.id = UUID.randomUUID();
repository.save(doc);
repository.findById(doc.id);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2017-2019 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
*
* https://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.mapping;
import java.util.UUID;
import com.couchbase.client.java.repository.annotation.Id;
import org.springframework.data.couchbase.core.query.ViewIndexed;
/**
* @author Subhashni Balakrishnan
*/
@ViewIndexed(designDoc = "testUUID", viewName = "all")
public class TestUUID {
@Id
public UUID id;
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2017-2019 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
*
* https://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.mapping;
import java.util.UUID;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
/**
* @author Subhashni Balakrishnan
*/
public interface TestUUIDRepository extends CouchbaseRepository<TestUUID, UUID> {
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2013-2019 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
*
* https://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.monitor;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.core.IsNot.not;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Michael Nitschinger
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class ClientInfoIntegrationTests {
/**
* Contains a reference to the actual CouchbaseClient.
*/
@Autowired
private Bucket client;
private ClientInfo ci;
@Before
public void setup() throws Exception {
ci = new ClientInfo(client);
}
@Test
public void hostNames() {
String hostnames = ci.getHostNames();
assertThat(hostnames, not(isEmptyString()));
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 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
*
* https://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.monitor;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import com.couchbase.client.java.Bucket;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.TestContainerResource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Michael Nitschinger
*/
@Ignore(value = "Cant run get cluster info on test container")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class ClusterInfoIntegrationTests {
/**
* Contains a reference to the actual CouchbaseClient.
*/
@Autowired
private Bucket client;
private ClusterInfo ci;
@Before
public void setup() throws Exception {
ci = new ClusterInfo(client);
}
@Test
public void totalDiskAssigned() {
assertThat(ci.getTotalDiskAssigned(), greaterThan(0L));
}
@Test
public void totalRAMUsed() {
assertThat(ci.getTotalRAMUsed(), greaterThan(0L));
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2019 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
*
* https://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;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.test.context.ContextConfiguration;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.UNIQUE;
/**
* @author Maxence Labusquiere
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class CouchbaseIdGenerationIntegrationTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private CrudRepository<SimpleClassWithGeneratedIdValueUsingUUID, String> entityRepository;
@Before
public void setUp() throws Exception {
entityRepository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(EntityRepository.class);
}
@Test
public void idFieldEntityIsFillWithGeneratedValueOnSave() {
SimpleClassWithGeneratedIdValueUsingUUID entity = new SimpleClassWithGeneratedIdValueUsingUUID();
SimpleClassWithGeneratedIdValueUsingUUID savedEntity = entityRepository.save(entity);
assertThat("Expected generated value", savedEntity.id != null);
if (entityRepository.existsById(savedEntity.id)) {
entityRepository.existsById(savedEntity.id);
}
}
@Test
public void ifIdFieldIsAlreadySetNothingIsDone() {
String id = "AnId";
SimpleClassWithGeneratedIdValueUsingUUID entity = new SimpleClassWithGeneratedIdValueUsingUUID();
entity.setId(id);
SimpleClassWithGeneratedIdValueUsingUUID savedEntity = entityRepository.save(entity);
assertThat("Expected same id instance", savedEntity.id == id);
if (entityRepository.existsById(savedEntity.id)) {
entityRepository.existsById(savedEntity.id);
}
}
@Document
static class SimpleClassWithGeneratedIdValueUsingUUID {
@Id
@GeneratedValue(strategy = UNIQUE)
public String id;
public String value = "new";
public void setId(String id) {
this.id = id;
}
}
@Repository
interface EntityRepository extends CrudRepository<SimpleClassWithGeneratedIdValueUsingUUID, String> {
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.couchbase.client.java.Bucket;
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 org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* @author David Harrigan
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(CouchbaseRepositoryViewListener.class)
public class CouchbaseRepositoryViewIntegrationTests {
@Autowired
private Bucket client;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private CustomUserRepository repository;
@Before
public void setup() throws Exception {
repository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(CustomUserRepository.class);
}
@Test
public void shouldFindAllWithCustomView() {
client.query(ViewQuery.from("user", "customFindAllView").stale(Stale.FALSE));
Iterable<User> allUsers = repository.findAll();
assertThat(allUsers, Matchers.iterableWithSize(100));
}
@Test
public void shouldCountWithCustomView() {
ViewResult clientResult = client.query(ViewQuery.from("userCustom", "customCountView")
.reduce().stale(Stale.FALSE));
final Object clientRowValue = clientResult.allRows().get(0).value();
final long value = repository.count();
assertThat(value, is(100L));
assertThat(clientRowValue, instanceOf(Number.class));
assertThat(((Number) clientRowValue).longValue(), is(value));
}
@Test
public void shouldDetectMethodNameWithoutPropertyAndIssueGenericQueryOnView() {
Iterable<User> users = repository.findRandomMethodName();
assertNotNull(users);
assertTrue(users.iterator().hasNext());
try {
repository.findIncorrectExplicitView();
fail("Expected InvalidDataAccessResourceException");
} catch (InvalidDataAccessResourceUsageException e) {
assertTrue(e.getMessage(), e.getMessage().startsWith("View user/allSomething does not exist"));
}
}
@Test(expected = PropertyReferenceException.class)
public void shouldFailDeriveOnBadProperty() {
repository.findAllByUsernameEqualAndUserblablaIs("uname-1", "blabla");
}
@Test
public void shouldDeriveViewParametersAndReduce() {
long count = repository.countByUsernameGreaterThanEqualAndUsernameLessThan("uname-8", "uname-9");
assertEquals(12, count);
}
@Test
public void shouldDeriveViewParametersAndReduceNonNumerical() {
JsonObject reduceResult = repository.findByAgeLessThan(50);
assertNotNull(reduceResult);
assertEquals(51, (long) reduceResult.getLong("count"));
assertEquals(50, (long) reduceResult.getLong("max"));
assertEquals(0, (long) reduceResult.getLong("min"));
assertEquals(1275, (long) reduceResult.getLong("sum"));
}
@Test
public void shouldDeriveViewParameters() {
String lowKey = "uname-1";
String middleKey = "uname-10";
String highKey = "uname-11";
List<String> keys = Arrays.asList(lowKey, middleKey, highKey);
User u1 = repository.findByUsernameIs(lowKey).get(0);
User u2 = repository.findByUsernameIs(middleKey).get(0);
User u3 = repository.findByUsernameIs(highKey).get(0);
assertEquals(lowKey, u1.getUsername());
assertEquals(middleKey, u2.getUsername());
assertEquals(highKey, u3.getUsername());
List<User> in = repository.findAllByUsernameIn(keys);
List<User> gteLte = repository.findByUsernameGreaterThanEqualAndUsernameLessThanEqual(lowKey, highKey);
List<User> between = repository.findByUsernameBetween(lowKey, highKey);
List<User> gteLimited = repository.findTop3ByUsernameGreaterThanEqual(lowKey);
// the results are unordered, so compare using Set
Set<User> expected = new HashSet<>(Arrays.asList(u1, u2, u3));
assertEquals(expected, new HashSet<>(in));
assertEquals(expected, new HashSet<>(gteLte));
assertEquals(expected, new HashSet<>(between));
assertEquals(expected, new HashSet<>(gteLimited));
}
@Test
public void shouldDeriveToEmptyClause() {
List<User> users = repository.findAllByUsername();
assertNotNull(users);
assertEquals(100, users.size());
}
@Test
public void shouldDetermineViewNameFromMethodPrefix() {
try {
repository.findByIncorrectView();
fail("Expected InvalidDataAccessResourceException");
} catch (InvalidDataAccessResourceUsageException e) {
assertTrue(e.getMessage(), e.getMessage().startsWith("View user/byIncorrectView does not exist"));
}
}
@Test
public void shouldDetermineViewNameFromCountPrefixAndReduce() {
long count = repository.countCustomFindAllView();
assertEquals(100, count);
try {
repository.countCustomFindInvalid();
fail("Expected InvalidDataAccessResourceException");
} catch (InvalidDataAccessResourceUsageException e) {
assertTrue(e.getMessage(), e.getMessage().startsWith("View user/customFindInvalid does not exist"));
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import java.util.Arrays;
import java.util.Collections;
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;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
createAndWaitForDesignDocs(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, i), PersistTo.MASTER, ReplicateTo.NONE);
}
}
private void createAndWaitForDesignDocs(final Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }";
String mapFunctionName = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(doc.username, null); } }";
String mapFunctionAge = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(doc.age, doc.age); } }";
View view = DefaultView.create("customFindAllView", mapFunction, "_count");
View customFindByNameView = DefaultView.create("customFindByNameView", mapFunctionName, "_count");
View customFindByAgeStatsView = DefaultView.create("customFindByAgeStatsView", mapFunctionAge, "_stats");
List<View> views = Arrays.asList(view, customFindByNameView, customFindByAgeStatsView);
DesignDocument designDoc = DesignDocument.create("user", views);
client.bucketManager().upsertDesignDocument(designDoc);
view = DefaultView.create("customCountView", mapFunction, "_count");
views = Collections.singletonList(view);
designDoc = DesignDocument.create("userCustom", views);
client.bucketManager().upsertDesignDocument(designDoc);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import java.util.List;
import com.couchbase.client.java.document.json.JsonObject;
import org.springframework.data.couchbase.core.query.View;
/**
* @author David Harrigan
* @author Simon Baslé
*/
public interface CustomUserRepository extends CouchbaseRepository<User, String> {
@Override
@View(designDocument = "user", viewName = "customFindAllView")
Iterable<User> findAll();
@Override
@View(designDocument = "userCustom", viewName = "customCountView")
long count();
@View(viewName = "allSomething")
Iterable<User> findIncorrectExplicitView();
@View(viewName = "customFindAllView")
Iterable<User> findRandomMethodName();
@View(viewName = "customFindByNameView")
long countByUsernameGreaterThanEqualAndUsernameLessThan(String lowBound, String highBound);
@View(viewName = "customFindByNameView")
List<User> findByUsernameIs(String lowKey);
@View(viewName = "customFindByNameView")
List<User> findAllByUsernameIn(List<String> keys);
@View(viewName = "customFindByNameView")
List<User> findByUsernameGreaterThanEqualAndUsernameLessThanEqual(String lowKey, String highKey);
@View(viewName = "customFindByNameView")
List<User> findByUsernameBetween(String lowKey, String highKey);
@View(viewName = "customFindByNameView")
List<User> findTop3ByUsernameGreaterThanEqual(String lowKey);
@View(viewName = "customFindAllView")
List<User> findAllByUsername();
@View(viewName = "customFindAllView")
List<User> findAllByUsernameEqualAndUserblablaIs(String s, String blabla);
@View
List<User> findByIncorrectView();
@View
long countCustomFindAllView();
@View
long countCustomFindInvalid();
@View(viewName = "customFindByAgeStatsView", reduce = true)
JsonObject findByAgeLessThan(int maxAge);
}

View File

@@ -0,0 +1,57 @@
package org.springframework.data.couchbase.repository;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import com.couchbase.client.java.document.json.JsonArray;
import org.springframework.data.couchbase.core.query.Dimensional;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.repository.CrudRepository;
public interface DimensionalPartyRepository extends CrudRepository<Party, String> {
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationNear(Point p, Distance d);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Box boundingBox);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Polygon zone);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Point[] points);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Point point);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(JsonArray range);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Point lowerLeft, Point upperRight);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3)
List<Party> findByLocationWithin(Circle zone);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3)
List<Party> findByLocationWithinAndAttendeesGreaterThan(Polygon zone, double minAttendees);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3)
List<Party> findByLocationWithin(JsonArray startRange, JsonArray endRange);
//TODO more coverage of operators?
@IndexedByLocation
List<Party> findByLocationIsWithin(Point a, Point b);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation", dimensions = 2)
@Retention(RetentionPolicy.RUNTIME)
@interface IndexedByLocation { }
}

View File

@@ -0,0 +1,337 @@
package org.springframework.data.couchbase.repository;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.couchbase.client.java.document.json.JsonArray;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(PartyPopulatorListener.class)
public class DimensionalQueryIntegrationTests {
@Autowired
private RepositoryOperationsMapping templateMapping;
@Autowired
private IndexManager indexManager;
private DimensionalPartyRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(templateMapping, indexManager);
repository = getRepositoryWithRetry(factory, DimensionalPartyRepository.class);
}
@Test
public void testFindWithingPolygon() {
Set<String> expectedKeys = new HashSet<String>();
expectedKeys.add("testparty-2");
expectedKeys.add("testparty-3");
expectedKeys.add("testparty-4");
expectedKeys.add("testparty-5");
//a zone that engulfs parties 2, 3, 4 and 5, in the shape of a downward arrow pointing right
Polygon zone = new Polygon(new Point(1, -2),
new Point(3, -1.5),
new Point(6, -4),
new Point(5.5, -5.5),
new Point(3, -5));
List<Party> parties = repository.findByLocationWithin(zone);
assertEquals(4, parties.size());
for (Party party : parties) {
assertTrue(expectedKeys.contains(party.getKey()));
}
}
@Test
public void testByLocationNear() {
Set<String> expectedKeys = new HashSet<String>();
expectedKeys.add("testparty-0");
expectedKeys.add("testparty-1");
List<Party> parties = repository.findByLocationNear(new Point(0, 0), new Distance(1.5));
assertEquals(2, parties.size());
for (Party party : parties) {
assertTrue(expectedKeys.contains(party.getKey()));
}
//with this one, testparty-2 is within the bounding box but not in correct distance
parties = repository.findByLocationNear(new Point(0, 0), new Distance(2.5));
assertEquals(2, parties.size());
for (Party party : parties) {
assertTrue(expectedKeys.contains(party.getKey()));
}
//here we adjust the distance so that testparty-2 falls just on the edge
parties = repository.findByLocationNear(new Point(0, 0), new Distance(2.8284271247461903));
expectedKeys.add("testparty-2");
assertEquals(3, parties.size());
for (Party party : parties) {
assertTrue(expectedKeys.contains(party.getKey()));
}
}
@Test
public void testByLocationAndSparseAttendees() {
Set<String> expectedKeys = new HashSet<String>();
expectedKeys.add("testparty-4");
expectedKeys.add("testparty-5");
//a zone that engulfs parties 2, 3, 4 and 5 (with resp. 120, 130, 140 and 150 attendees)
//in the shape of a downward arrow pointing right
Polygon zone = new Polygon(new Point(1, -2),
new Point(3, -1.5),
new Point(6, -4),
new Point(5.5, -5.5),
new Point(3, -5));
//first check the zone contains 4 parties
List<Party> allPartiesInZone = repository.findByLocationWithinAndAttendeesGreaterThan(zone, -1);
List<Party> allPartiesInZoneWithoutAttendeeCriteria = repository.findByLocationWithin(zone);
assertEquals(allPartiesInZone.toString(), 4, allPartiesInZone.size());
assertEquals(allPartiesInZoneWithoutAttendeeCriteria, allPartiesInZone);
//check parties are limited by the attendees
List<Party> parties = repository.findByLocationWithinAndAttendeesGreaterThan(zone, 140);
for (Party party : parties) {
System.out.println(party.getKey() + " : " + party.getLocation() + " " + party.getAttendees());
}
assertEquals(parties.toString(), 2, parties.size());
for (Party party : parties) {
assertTrue(party.getAttendees() >= 140);
assertTrue(expectedKeys.contains(party.getKey()));
}
}
@Test
public void testByLocationInCircle() {
Circle zoneBboxFalse = new Circle(new Point(-5,5), new Distance(5.5));
Circle zoneEdge = new Circle(new Point(-5, 0), new Distance(5));
Circle zoneInside = new Circle(new Point(6, -6), new Distance(10));
Circle zoneEmpty = new Circle(new Point(6,6), new Distance(3));
List<Party> parties = repository.findByLocationWithin(zoneBboxFalse);
assertEquals(0, parties.size());
parties = repository.findByLocationWithin(zoneEdge);
assertEquals(1, parties.size());
assertEquals("testparty-0", parties.get(0).getKey());
parties = repository.findByLocationWithin(zoneInside);
assertEquals(12, parties.size()); //all the parties except the special one at 100, 100
parties = repository.findByLocationWithin(zoneEmpty);
assertEquals(0, parties.size());
}
@Test
public void testByLocationInBox() {
Box zone1 = new Box(new Point(-10.5, -0.5), new Point(0.5, 10.5));
Box zone2 = new Box(new Point(-4, -16), new Point(16, 4));
Box zoneEmpty = new Box(new Point(3, 3), new Point(9, 9));
List<Party> parties = repository.findByLocationWithin(zone1);
assertEquals(1, parties.size());
assertEquals("testparty-0", parties.get(0).getKey());
parties = repository.findByLocationWithin(zone2);
assertEquals(12, parties.size()); //all the parties except the special one at 100, 100
parties = repository.findByLocationWithin(zoneEmpty);
assertEquals(0, parties.size());
}
@Test
public void testByLocationInPolygon() {
//slightly skewed square triangle that cuts just short of (0,0)
//bounding box of (-1,-1),(0.5,1)
Polygon zoneFalsePositive = new Polygon(
new Point(-1, 1),
new Point(0.5, 1),
new Point(-1, -1)
);
//square triangle that comes through (0,0)
//bounding box of (-1,-1),(1,1)
Polygon zoneEdge = new Polygon(
new Point(-1, 1),
new Point(1, 1),
new Point(-1, -1)
);
//bounding box of (-4, -16),(16,4)
Polygon zoneWithin = new Polygon(
new Point(-4, -10),
new Point(-3, 4),
new Point(14, 2),
new Point(16, -16));
//bounding box of (3,3)(9,9)
Polygon zoneEmpty = new Polygon(
new Point(3, 3),
new Point(3, 7),
new Point(6, 7),
new Point(6, 9),
new Point(9, 9),
new Point(9, 5),
new Point(6, 5),
new Point(6, 3));
List<Party> parties = repository.findByLocationWithin(zoneFalsePositive);
assertEquals("points outside a polygon but within bounding box shouldn't be considered within", 0, parties.size());
parties = repository.findByLocationWithin(zoneEdge);
assertEquals("point on edge of a polygon shouldn't be considered within", 0, parties.size());
parties = repository.findByLocationWithin(zoneWithin);
assertEquals(12, parties.size()); //all the parties except the special one at 100, 100
parties = repository.findByLocationWithin(zoneEmpty);
assertEquals(0, parties.size());
}
@Test
public void testByLocationWithinTwoPoints() {
Point zone1LowerLeft = new Point(-10.5, -0.5);
Point zone1UpperRight = new Point(0.5, 10.5);
Point zone2LowerLeft = new Point(-4, -16);
Point zone2UpperRight = new Point(16, 4);
Point zoneEmptyLowerLeft = new Point(3, 3);
Point zoneEmptyUpperRight = new Point(9, 9);
List<Party> parties = repository.findByLocationWithin(zone1LowerLeft, zone1UpperRight);
assertEquals(1, parties.size());
assertEquals("testparty-0", parties.get(0).getKey());
parties = repository.findByLocationWithin(zone2LowerLeft, zone2UpperRight);
assertEquals(12, parties.size()); //all the parties except the special one at 100, 100
parties = repository.findByLocationWithin(zoneEmptyLowerLeft, zoneEmptyUpperRight);
assertEquals(0, parties.size());
}
@Test
public void testPolygonAndArrayOfPointsProduceSameResult() {
Set<String> expectedKeys = new HashSet<String>();
expectedKeys.add("testparty-2");
expectedKeys.add("testparty-3");
expectedKeys.add("testparty-4");
expectedKeys.add("testparty-5");
//a zone that engulfs parties 2, 3, 4 and 5, in the shape of a downward arrow pointing right
Polygon zone = new Polygon(new Point(1, -2),
new Point(3, -1.5),
new Point(6, -4),
new Point(5.5, -5.5),
new Point(3, -5));
Point[] points = zone.getPoints().toArray(new Point[5]);
List<Party> fromZone = repository.findByLocationWithin(zone);
List<Party> fromPoints = repository.findByLocationWithin(points);
assertEquals(4, fromZone.size());
assertEquals(fromZone, fromPoints);
Set<String> keys = new HashSet<String>();
for (Party party : fromZone) {
keys.add(party.getKey());
}
assertEquals(expectedKeys, keys);
}
@Test
public void testProvidingOnePointIsRejected() {
try {
repository.findByLocationWithin(new Point(0, 0));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertEquals("Cannot compute a bounding box for within, 2 Point needed, missing parameter", e.getMessage());
}
try {
repository.findByLocationWithin(new Point(0, 0), null);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertEquals("Cannot compute a bounding box for within, 2 Point needed, got null", e.getMessage());
}
}
@Test
public void testProvidingOneJsonArrayIsRejected() {
try {
List<Party> parties = repository.findByLocationWithin(JsonArray.from(0,0));
fail();
} catch (IllegalArgumentException e) {
assertEquals("2 JsonArray required for within: startRange and endRange, missing parameter", e.getMessage());
}
}
@Test(expected = CouchbaseQueryExecutionException.class)
public void testJsonArrayWithNonNumericalValueProducesServerSideError() {
repository.findByLocationWithin(JsonArray.from("toto", -2), JsonArray.from(4, 1));
fail();
}
@Test
public void testWithinJsonArrayRangesFiltersLocationAndAttendees() {
List<Party> parties = repository.findByLocationWithin(JsonArray.from(0, -4, 115), JsonArray.from(4, 1, 132));
assertEquals(2, parties.size());
}
@Test
public void testDimensionalAnnotationCanBeUsedAsMeta() {
try {
//will trigger a specific message if parsed by SpatialViewQueryCreator
repository.findByLocationIsWithin(new Point(0,0), null);
fail("expected IllegalArgumentException from SpatialViewQueryCreator");
} catch (IllegalArgumentException e) {
assertEquals("Cannot compute a bounding box for within, 2 Point needed, got null", e.getMessage());
}
//when it is correctly formed, it actually returns data
assertEquals(1, repository.findByLocationIsWithin(new Point(-10.5, -0.5), new Point(0.5, 10.5)).size());
}
@Test
public void testFindWithinBoxCornerOrderDoesMatter() {
Point a = new Point(0, -4);
Point b = new Point(6, -2);
Box box1 = new Box(a, b);
Box box2 = new Box(b, a);
final List<Party> parties1 = repository.findByLocationWithin(box1);
final List<Party> parties2 = repository.findByLocationWithin(box2);
assertEquals(3, parties1.size());
assertNotEquals(parties1, parties2);
}
}

View File

@@ -0,0 +1,38 @@
package org.springframework.data.couchbase.repository;
import com.couchbase.client.java.repository.annotation.Field;
import org.springframework.data.annotation.Id;
public class Item {
@Id
public String id;
@Field("desc")
public String description;
public Item(String id, String description) {
this.id = id;
this.description = description;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
if (!id.equals(item.id)) return false;
return !(description != null ? !description.equals(item.description) : item.description != null);
}
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,12 @@
package org.springframework.data.couchbase.repository;
import java.util.List;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.repository.CrudRepository;
@N1qlPrimaryIndexed
public interface ItemRepository extends CrudRepository<Item, String> {
List<Object> findAllByDescriptionNotNull();
}

View File

@@ -0,0 +1,223 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.model.MappingInstantiationException;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* This tests PaginAndSortingRepository features in the Couchbase connector.
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(PartyPopulatorListener.class)
public class N1qlCouchbaseRepositoryIntegrationTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private PartyPagingRepository repository;
private PartyRepository partyRepository;
private ItemRepository itemRepository;
private final String KEY_PARTY = "Party1";
private final String KEY_ITEM = "Item1";
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = getRepositoryWithRetry(factory, PartyPagingRepository.class);
partyRepository = getRepositoryWithRetry(factory, PartyRepository.class);
itemRepository = getRepositoryWithRetry(factory, ItemRepository.class);
partyRepository.save(new Party(KEY_PARTY, "partyName", "MatchingDescription", null, 1, null));
itemRepository.save(new Item(KEY_ITEM, "MatchingDescription"));
}
@After
public void cleanUp() {
try { itemRepository.deleteById(KEY_ITEM); } catch (DataRetrievalFailureException e) {}
try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {}
}
@Test
public void shouldFindAllWithSort() {
Iterable<Party> allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees"));
long previousAttendance = Long.MAX_VALUE;
for (Party party : allByAttendanceDesc) {
assertTrue(party.getAttendees() <= previousAttendance);
previousAttendance = party.getAttendees();
}
assertFalse("Expected to find several parties", previousAttendance == Long.MAX_VALUE);
}
@Test
public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() {
Iterable<Party> parties = repository.findAll(Sort.by(Sort.Direction.DESC, "desc"));
String previousDesc = null;
for (Party party : parties) {
if (previousDesc != null) {
assertTrue(party.getDescription().compareTo(previousDesc) <= 0);
}
previousDesc = party.getDescription();
}
assertNotNull("Expected to find several parties", previousDesc);
}
@Test
public void shouldSortWithoutCaseSensitivity() {
Iterable<Party> parties = repository.findAll(Sort.by(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase()));
String previousDesc = null;
for (Party party : parties) {
if (previousDesc != null) {
assertTrue(party.getDescription().compareToIgnoreCase(previousDesc) <= 0);
}
previousDesc = party.getDescription();
}
assertNotNull("Expected to find several parties", previousDesc);
}
@Test
public void shouldPageThroughEntities() {
Pageable pageable = PageRequest.of(0, 8);
Page<Party> page1 = repository.findAll(pageable);
assertTrue("Query for parties should be atleast 12", page1.getTotalElements() >= 12);
assertEquals(8, page1.getNumberOfElements());
}
@Test
public void shouldPageThroughSortedEntities() {
Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees");
Page<Party> page1 = repository.findAll(pageable);
assertTrue("Query for parties should be atleast 12", page1.getTotalElements() >= 12);
assertEquals(8, page1.getNumberOfElements());
List<Party> parties = page1.getContent();
Long previousAttendees = null;
for (Party party : parties) {
if (previousAttendees != null) {
assertTrue(party.getAttendees() <= previousAttendees);
}
previousAttendees = party.getAttendees();
}
}
@Test
public void testWrapWhereCriteria() {
List<Party> partyList = partyRepository.findByDescriptionOrName("MatchingDescription", "partyName");
assertTrue(partyList.size() == 1);
}
@Test
public void shouldPageWithStringBasedQuery() {
Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees");
Page<Party> page1 = partyRepository.findPartiesWithAttendee(1, pageable);
assertTrue("Query for parties with attendees should be atleast 12", page1.getTotalElements() >= 12);
assertEquals(8, page1.getNumberOfElements());
List<Party> parties = page1.getContent();
Long previousAttendees = null;
for (Party party : parties) {
if (previousAttendees != null) {
assertTrue(party.getAttendees() <= previousAttendees);
}
previousAttendees = party.getAttendees();
}
Page<Party> page2 = partyRepository.findPartiesWithAttendee(1, page1.nextPageable());
assertEquals(8, page2.getNumberOfElements());
parties = page2.getContent();
for (Party party : parties) {
if (previousAttendees != null) {
assertTrue(party.getAttendees() <= previousAttendees);
}
previousAttendees = party.getAttendees();
}
}
//Fails on deserialization as a different entity item is also present
@Test(expected = MappingInstantiationException.class)
public void shouldFailWithMissingFilterStringBasedQuery() {
Sort sort = Sort.by(Sort.Direction.DESC, "attendees");
partyRepository.findParties(sort);
}
@Test
public void testDeleteQuery() {
partyRepository.save(new Party("testDeleteQuery", "delete", "delete", null, 0, null));
List<Party> partyList = partyRepository.removeByDescriptionOrName("delete", "delete");
assertTrue(partyList.size() == 1);
}
@Test
public void testSpelDateConvertion() {
final String key = "testSpelDateConvertion";
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2018, Calendar.SEPTEMBER, 10);
Date date = cal.getTime();
partyRepository.save(new Party(key, "", "", date, 0, null));
List<Party> partyList = partyRepository.getByEventDate(date);
assertTrue(partyList.size() == 1);
assertEquals("Key mismatch", partyList.get(0).getKey(), key);
}
@Test
public void testN1qlQueryWithInvalidValue() {
partyRepository.save(new Party("testN1qlQueryWithInvalidValue", "", "testN1qlQueryWithInvalidValue", null, 0, null));
final String description = "testN1qlQueryWithInvalidValue* OR `description` LIKE \"\"";
List<Party> partyList = partyRepository.findByDescriptionStartingWith(description);
assertTrue(partyList.size() == 0);
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.Date;
import java.util.List;
import com.couchbase.client.java.Bucket;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.geo.Point;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class N1qlCrudRepositoryIntegrationTests {
@Autowired
private Bucket client;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private PartyRepository partyRepository;
private ItemRepository itemRepository;
private static final String KEY_ITEM = "itemNotParty";
private static final String KEY_PARTY = "partyNotItem";
private static final String KEY_PARTY_KEYWORD = "partyHasKeyword";
private static final Item item = new Item(KEY_ITEM, "short description");
private static final Party party = new Party(KEY_PARTY, "partyName", "short description", new Date(), 120, new Point(500, 500));
@Before
public void setup() throws Exception {
CouchbaseRepositoryFactory factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
partyRepository = getRepositoryWithRetry(factory, PartyRepository.class);
itemRepository = getRepositoryWithRetry(factory, ItemRepository.class);
itemRepository.save(item);
partyRepository.save(party);
}
@After
public void cleanUp() {
try { itemRepository.deleteById(KEY_ITEM); } catch (DataRetrievalFailureException e) {}
try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {}
try { partyRepository.deleteById(KEY_PARTY_KEYWORD); } catch (DataRetrievalFailureException e) {}
}
@Test
public void shouldDistinguishBetweenItemsAndParties() {
List<Object> items = itemRepository.findAllByDescriptionNotNull();
List<Object> parties = partyRepository.findAllByDescriptionNotNull();
assertTrue(items.contains(item));
assertTrue(parties.contains(party));
assertFalse(items.contains(party));
assertFalse(parties.contains(item));
}
@Test
public void shouldSaveObjectWithN1qlKeywordField() {
Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "desc is a N1QL keyword", new Date(), 40, new Point(500, 500));
partyRepository.save(partyHasKeyword);
List<Object> parties = partyRepository.findAllByDescriptionNotNull();
assertTrue(client.exists(KEY_PARTY_KEYWORD));
assertTrue(parties.contains(partyHasKeyword));
for (Object o : parties) {
if (!(o instanceof Party)) {
fail("expected only Party objects");
}
}
}
@Test
public void shouldGenerateCountProjection() {
Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", null, new Date(), 40, new Point(500, 500));
partyRepository.save(partyHasKeyword);
long countTotal = partyRepository.count();
long countCustom = partyRepository.countAllByDescriptionNotNull();
assertEquals(countTotal - 1, countCustom);
}
@Test
public void shouldCountWhenReturningLongAndUsingStringSelectFromSpEL() {
Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "second party", new Date(), 40, new Point(500, 500));
partyRepository.save(partyHasKeyword);
long countTotal = partyRepository.count();
long countCustom = partyRepository.countCustom();
assertEquals(countTotal, countCustom);
}
@Test
public void shouldCustomCountWhenReturningLongAndUsingStringWithoutSpEL() {
Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "second party", new Date(), 40, new Point(500, 500));
partyRepository.save(partyHasKeyword);
long countTotal = partyRepository.count();
long countCustom = partyRepository.countCustomPlusFive();
assertEquals(countTotal + 5, countCustom);
}
@Test(expected = CouchbaseQueryExecutionException.class)
public void shouldFailConversionWithStringReturnType() {
Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "desc is a N1QL keyword", new Date(), 40, new Point(500, 500));
partyRepository.save(partyHasKeyword);
String someString = partyRepository.findSomeString();
}
@Test
public void shouldDoNumericProjectionWithStringBasedQuery() {
Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "desc is a N1QL keyword", new Date(), 4000000, new Point(500, 500));
partyRepository.save(partyHasKeyword);
long max = partyRepository.findMaxAttendees();
assertEquals(4000000, max);
}
@Test
public void shouldDoBooleanProjectionWithStringBasedQuery() {
boolean someBoolean = partyRepository.justABoolean();
assertEquals(true, someBoolean);
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.List;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(PartyPopulatorListener.class)
public class N1qlPlaceholderIntegrationTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private CouchbaseRepositoryFactory factory;
private PartyRepository partyRepository;
@Before
public void setup() throws Exception {
factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
partyRepository = getRepositoryWithRetry(factory, PartyRepository.class);
}
@Test
public void shouldFindUsingNamedParameters() {
String included = "90";
String excluded = "New Year";
int min = 200;
List<Party> result = partyRepository.findAllWithNamedParams(excluded, included, min);
assertEquals(2, result.size());
for (Party party : result) {
assertTrue(party.getDescription().contains(included));
assertFalse(party.getDescription().contains(excluded));
assertTrue(party.getAttendees() >= min);
}
}
@Test
public void shouldFindUsingPositionalParameters() {
String included = "90";
String excluded = "New Year";
int min = 200;
List<Party> result = partyRepository.findAllWithPositionalParams(excluded, included, min);
assertEquals(2, result.size());
for (Party party : result) {
assertTrue(party.getDescription().contains(included));
assertFalse(party.getDescription().contains(excluded));
assertTrue(party.getAttendees() >= min);
}
}
@Test
public void shouldIgnoreQuotedNamedParamsAndParamAnnotationIfPosUsed() {
String included = "90";
String excluded = "New Year";
int min = 200;
List<Party> result = partyRepository.findAllWithPositionalParamsAndQuotedNamedParams(excluded, included, min);
assertEquals(2, result.size());
for (Party party : result) {
assertTrue(party.getDescription().contains(included));
assertFalse(party.getDescription().contains(excluded));
assertTrue(party.getAttendees() >= min);
}
}
private interface BadRepository extends CrudRepository<Party, String> {
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
" AND `desc` NOT LIKE '%' || $included || '%'")
List<Party> findAllWithMixedParamsInQuery(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min);
}
@Test
public void shouldFailUsingMixedParameters() {
try {
factory.getRepository(BadRepository.class);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertEquals(e.toString(), "Using both named (1) and positional (2) placeholders is not supported, please choose " +
"one over the other in findAllWithMixedParamsInQuery", e.getMessage());
}
}
@Test
public void deleteQueryTest() {
String included = "90";
String excluded = "New Year";
int max = 200;
List<Party> result = partyRepository.removeWithPositionalParams(excluded, included, max);
assertEquals(10, result.size());
for (Party party : result) {
assertTrue(party.getDescription().contains(included));
assertFalse(party.getDescription().contains(excluded));
assertTrue(party.getAttendees() < max);
}
}
}

View File

@@ -0,0 +1,94 @@
package org.springframework.data.couchbase.repository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.ArrayList;
import java.util.List;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class)
public class PageAndSliceIntegrationTests {
@Autowired
private Bucket client;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private UserRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = getRepositoryWithRetry(factory, UserRepository.class);
}
@Test
public void shouldPageThroughResults() {
Page<User> page1 = repository.findByAgeGreaterThan(9, PageRequest.of(0, 40)); //there are 90 matching users
Page<User> page2 = repository.findByAgeGreaterThan(9, page1.nextPageable());
Page<User> page3 = repository.findByAgeGreaterThan(9, page2.nextPageable());
assertEquals(90, page1.getTotalElements());
assertEquals(3, page1.getTotalPages());
assertTrue(page1.hasContent());
assertTrue(page1.hasNext());
assertEquals(40, page1.getNumberOfElements());
assertTrue(page2.hasContent());
assertTrue(page2.hasNext());
assertEquals(40, page2.getNumberOfElements());
assertTrue(page3.hasContent());
assertFalse(page3.hasNext());
assertEquals(10, page3.getNumberOfElements());
}
@Test(expected = UnsupportedOperationException.class)
public void shouldThrowWhenPageableIsNullInPageQuery() {
repository.findByAgeGreaterThan(9, null);
}
@Test
public void shouldSliceThroughResults() {
int count = 0;
List<User> allMatching = new ArrayList<User>(10);
Slice<User> slice = repository.findByAgeLessThan(9, PageRequest.of(0, 3)); //9 matching users (ages 0-8)
allMatching.addAll(slice.getContent());
while(slice.hasNext()) {
slice = repository.findByAgeLessThan(9, slice.nextPageable());
allMatching.addAll(slice.getContent());
assertEquals(3, slice.getContent().size());
}
assertEquals(9, allMatching.size());
}
@Test(expected = UnsupportedOperationException.class)
public void shouldThrowWhenPageableIsNullSliceQuery() {
repository.findByAgeLessThan(9, null);
}
}

View File

@@ -0,0 +1,88 @@
package org.springframework.data.couchbase.repository;
import java.util.Date;
import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;
import com.couchbase.client.java.repository.annotation.Field;
/**
* An entity used to test conversion of parameters in query derivations.
*
* @author Simon Baslé
*/
public class Party {
@Id
private final String key;
private final String name;
@Field("desc")
private final String description;
private final Date eventDate;
private final long attendees;
private final Point location;
public Party(String key, String name, String description, Date eventDate, long attendees, Point location) {
this.key = key;
this.name = name;
this.description = description;
this.eventDate = eventDate;
this.attendees = attendees;
this.location = location;
}
public String getKey() {
return key;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Date getEventDate() {
return eventDate;
}
public long getAttendees() {
return attendees;
}
public Point getLocation() {
return location;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Party party = (Party) o;
return key.equals(party.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public String toString() {
return "Party{" +
"name='" + name + '\'' +
", eventDate=" + eventDate +
", location=" + location +
'}';
}
}

View File

@@ -0,0 +1,4 @@
package org.springframework.data.couchbase.repository;
public interface PartyPagingRepository extends CouchbasePagingAndSortingRepository<Party, String> {
}

View File

@@ -0,0 +1,87 @@
package org.springframework.data.couchbase.repository;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
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.SpatialView;
import com.couchbase.client.java.view.View;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.geo.Point;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* @author Simon Baslé
*/
public class PartyPopulatorListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
createAndWaitForDesignDocs(client);
}
private void populateTestData(Bucket client, ClusterInfo clusterInfo) {
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(Calendar.YEAR, 2015);
cal.set(Calendar.DAY_OF_MONTH, 10);
cal.set(Calendar.MONTH, Calendar.JANUARY);
for (int i = 0; i < 12; i++) {
Party p = new Party("testparty-" + i, "party like it's 199" + i,
"An awesome party, 90's themed, every 10 of the month",
cal.getTime(), 100 + i * 10,
new Point(i, -i));
template.save(p, PersistTo.MASTER, ReplicateTo.NONE);
cal.roll(Calendar.MONTH, true);
}
cal.clear();
cal.set(Calendar.YEAR, 1990);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH, 01);
template.save(new Party("aTestParty", "New Year's Eve 90", "Happy New Year", cal.getTime(), 1230000, new Point(100, 100)));
template.save(new Party("lowercaseParty", "lowercase party", "lowercase party", cal.getTime(), 1000, new Point(100, 100)));
template.save(new Party("uppercaseParty", "Uppercase party", "Uppercase party", cal.getTime(), 1000, new Point(100, 100)));
}
private void createAndWaitForDesignDocs(Bucket client) {
//standard views
List<View> views = new ArrayList<View>();
String mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " +
"{ emit(doc.eventDate, null); } }";
views.add(DefaultView.create("byDate", mapFunction, "_count"));
//create the view design document
DesignDocument designDoc = DesignDocument.create("party", views);
client.bucketManager().upsertDesignDocument(designDoc);
//geo views
List<View> geoViews = new ArrayList<View>();
mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " +
"{ emit([doc.location.x, doc.location.y], null); } }";
geoViews.add(SpatialView.create("byLocation", mapFunction));
mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " +
"{ emit([doc.location.x, doc.location.y, doc.attendees], null); } }";
geoViews.add(SpatialView.create("byLocationAndAttendees", mapFunction));
//create the geo views design document
DesignDocument geoDesignDoc = DesignDocument.create("partyGeo", geoViews);
client.bucketManager().upsertDesignDocument(geoDesignDoc);
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2017-2019 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
*
* https://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;
import java.util.Date;
import java.util.List;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.Param;
/**
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
@ViewIndexed(designDoc = "party", viewName = "all")
@N1qlPrimaryIndexed
@N1qlSecondaryIndexed(indexName = "party")
public interface PartyRepository extends CouchbaseRepository<Party, String> {
List<Party> findByAttendeesGreaterThanEqual(int minAttendees);
List<Party> findByEventDateIs(Date targetDate);
@View(designDocument = "party", viewName = "byDate")
List<Party> findFirst3ByEventDateGreaterThanEqual(Date targetDate);
List<Object> findAllByDescriptionNotNull();
long countAllByDescriptionNotNull();
@Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
long findMaxAttendees();
@Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
String findSomeString();
@Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
long countCustomPlusFive();
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
long countCustom();
@Query("SELECT 1 = 1")
boolean justABoolean();
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `attendees` >= $1")
Page<Party> findPartiesWithAttendee(int count, Pageable pageable);
@Query("#{#n1ql.selectEntity}")
List<Party> findParties(Sort sort);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" +
" AND `desc` NOT LIKE '%' || $excluded || '%'")
List<Party> findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long minimumAttendees);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
" AND `desc` NOT LIKE '%' || $1 || '%'")
List<Party> findAllWithPositionalParams(String ex, String inc, long minimumAttendees);
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees < $3" +
" AND `desc` NOT LIKE '%' || $1 || '%' #{#n1ql.returning}")
List<Party> removeWithPositionalParams(String ex, String inc, long minimumAttendees);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
" AND `desc` NOT LIKE '%' || $1 || '%' AND `desc` != \"this is \\\"$excluded\\\"\"")
List<Party> findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min);
List<Party> findByDescriptionOrName(String description, String name);
List<Party> removeByDescriptionOrName(String description, String name);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and eventDate = $1")
List<Party> getByEventDate(Date eventDate);
List<Party> findByDescriptionStartingWith(String description);
}

View File

@@ -0,0 +1,95 @@
package org.springframework.data.couchbase.repository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.JsonDocument;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* @author Simon Baslé
* @author Mark Paluch
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(PartyPopulatorListener.class)
public class QueryDerivationConversionIntegrationTests {
@Autowired
private Bucket client;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private PartyRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = factory.getRepository(PartyRepository.class);
}
@Test
public void testConvertsDateParameterInN1qlQuery() {
Optional<Party> partyApril = repository.findById("testparty-3");
assertTrue(partyApril.isPresent());
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2015, Calendar.APRIL, 10);
Date find = cal.getTime();
List<Party> parties = repository.findByEventDateIs(find);
assertNotNull(parties);
assertEquals(1, parties.size());
assertEquals(find, parties.get(0).getEventDate());
JsonDocument doc = client.get(parties.get(0).getKey());
assertEquals(find.getTime(), doc.content().get("eventDate"));
}
@Test
public void testAcceptLongParameterInN1qlQuery() {
List<Party> newYear90 = repository.findByAttendeesGreaterThanEqual(1200000);
assertNotNull(newYear90);
assertEquals(1, newYear90.size());
assertEquals("aTestParty", newYear90.get(0).getKey());
}
@Test
public void testConvertDateParameterInViewQuery() {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2015, Calendar.AUGUST, 12);
Date find = cal.getTime();
List<Party> afterSummerParties = repository.findFirst3ByEventDateGreaterThanEqual(find);
assertNotNull(afterSummerParties);
assertEquals(3, afterSummerParties.size());
for (Party afterSummerParty : afterSummerParties) {
assert(afterSummerParty.getEventDate().after(find));
}
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2017-2019 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
*
* https://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;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* This tests ReactiveSortingRepository features in the Couchbase connector.
*
* @author Subhashni Balakrishnan
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
@TestExecutionListeners(PartyPopulatorListener.class)
public class ReactiveN1qlCouchbaseRepositoryIntegrationTests {
@Autowired
private ReactiveRepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private ReactivePartySortingRepository repository;
private ReactivePartyRepository partyRepository;
private ItemRepository itemRepository;
private final String KEY_PARTY = "ReactiveParty1";
private final String KEY_ITEM = "ReactiveItem1";
@Before
public void setup() throws Exception {
ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = getRepositoryWithRetry(factory, ReactivePartySortingRepository.class);
partyRepository = getRepositoryWithRetry(factory, ReactivePartyRepository.class);
itemRepository = getRepositoryWithRetry(factory, ItemRepository.class);
}
@After
public void cleanUp() {
try { itemRepository.deleteById(KEY_ITEM); } catch (DataRetrievalFailureException e) {}
try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {}
}
@Test
public void shouldFindAllWithSort() {
Iterable<Party> allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees")).collectList().block();
long previousAttendance = Long.MAX_VALUE;
for (Party party : allByAttendanceDesc) {
assertTrue(party.getAttendees() <= previousAttendance);
previousAttendance = party.getAttendees();
}
assertFalse("Expected to find several parties", previousAttendance == Long.MAX_VALUE);
}
@Test
public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() {
Iterable<Party> parties = repository.findAll(Sort.by(Sort.Direction.DESC, "desc")).collectList().block();
String previousDesc = null;
for (Party party : parties) {
if (previousDesc != null) {
assertTrue(party.getDescription().compareTo(previousDesc) <= 0);
}
previousDesc = party.getDescription();
}
assertNotNull("Expected to find several parties", previousDesc);
}
@Test
public void shouldSortWithoutCaseSensitivity() {
Iterable<Party> parties = repository.findAll(Sort.by(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase())).collectList().block();
String previousDesc = null;
for(Party party : parties) {
if (previousDesc != null) {
assertTrue(party.getDescription().compareToIgnoreCase(previousDesc) <= 0);
}
previousDesc = party.getDescription();
}
assertNotNull("Expected to find several parties", previousDesc);
}
@Test
public void testCustomSpelCountQuery() {
long count = partyRepository.countCustom().block();
assertTrue("Count query for parties should be atleast 12", count >= 12);
}
@Test
public void testPartTreeQuery() {
long count = partyRepository.countAllByDescriptionNotNull().block();
assertTrue("Count query for parties with description not null should be atleast 12", count >= 12);
}
@Test
public void testSpelDateConvertion() {
final String key = "testReactiveSpelDateConvertion";
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2018, Calendar.SEPTEMBER, 15);
Date date = cal.getTime();
partyRepository.save(new Party(key, "", "", date, 0, null)).block();
List<Party> partyList = partyRepository.getByEventDate(date).collectList().block();
assertTrue(partyList.size() == 1);
assertEquals("Key mismatch", partyList.get(0).getKey(), key);
}
@Test
public void testN1qlQueryWithInvalidValue() {
partyRepository.save(new Party("testReactiveN1qlQueryWithInvalidValue", "", "testReactiveN1qlQueryWithInvalidValue", null, 0, null));
final String description = "testReactiveN1qlQueryWithInvalidValue* OR `description` LIKE \"\"";
List<Party> partyList = partyRepository.findByDescriptionStartingWith(description).collectList().block();
assertTrue(partyList.size() == 0);
}
}

View File

@@ -0,0 +1,64 @@
package org.springframework.data.couchbase.repository;
import java.util.Date;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.repository.query.Param;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Subhashni Balakrishnan
*/
@ViewIndexed(designDoc = "reactiveParty", viewName = "all")
@N1qlSecondaryIndexed(indexName = "reactiveParty")
public interface ReactivePartyRepository extends ReactiveCouchbaseRepository<Party, String> {
Flux<Party> findByAttendeesGreaterThanEqual(int minAttendees);
Flux<Party> findByEventDateIs(Date targetDate);
@View(designDocument = "reactiveParty", viewName = "byDate")
Flux<Party> findFirst3ByEventDateGreaterThanEqual(Date targetDate);
Flux<Object> findAllByDescriptionNotNull();
Mono<Long> countAllByDescriptionNotNull();
@Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
Mono<Long> findMaxAttendees();
@Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
Mono<String> findSomeString();
@Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
Mono<Long> countCustomPlusFive();
@Query("SELECT count(*) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
Mono<Long> countCustom();
@Query("SELECT 1 = 1")
Mono<Boolean> justABoolean();
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" +
" AND `desc` NOT LIKE '%' || $excluded || '%'")
Flux<Party> findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long minimumAttendees);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
" AND `desc` NOT LIKE '%' || $1 || '%'")
Flux<Party> findAllWithPositionalParams(String ex, String inc, long minimumAttendees);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
" AND `desc` NOT LIKE '%' || $1 || '%' AND `desc` != \"this is \\\"$excluded\\\"\"")
Flux<Party> findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min);
Flux<Party> findByDescriptionOrName(String description, String name);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and eventDate = $1")
Flux<Party> getByEventDate(Date eventDate);
Flux<Party> findByDescriptionStartingWith(String description);
}

View File

@@ -0,0 +1,9 @@
package org.springframework.data.couchbase.repository;
/**
* @author Subhashni Balakrishnan
*/
public interface ReactivePartySortingRepository extends ReactiveCouchbaseSortingRepository<Party, String> {
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2017-2019 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
*
* https://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;
import org.springframework.data.annotation.Id;
/**
* @author Subhashni Balakrishnan
*/
public class ReactiveUser {
@Id
private final String key;
private final String username;
private final int age;
public ReactiveUser(String key, String username, int age) {
this.key = key;
this.username = username;
this.age = age;
}
public String getUsername() {
return username;
}
public String getKey() {
return key;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return this.key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReactiveUser user = (ReactiveUser) o;
return key.equals(user.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017-2019 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
*
* https://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;
import java.util.List;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import reactor.core.publisher.Flux;
/**
* @author Subhashni Balakrishnan
*/
@ViewIndexed(designDoc = "reactiveUser", viewName = "all")
@N1qlSecondaryIndexed(indexName = "reactiveUser")
public interface ReactiveUserRepository extends ReactiveCouchbaseRepository<ReactiveUser, String> {
@View(designDocument = "user", viewName = "all")
Flux<ReactiveUser> customViewQuery(ViewQuery query);
@Query("#{#n1ql.selectEntity} WHERE username = $1 and #{#n1ql.filter}")
Flux<ReactiveUser> findByUsername(String username);
@Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1 and #{#n1ql.filter}")
Flux<ReactiveUser> findByUsernameBadSelect(String username);
@Query("#{#n1ql.selectEntity} WHERE username LIKE '%-#{3 + 1}' and #{#n1ql.filter}")
Flux<ReactiveUser> findByUsernameWithSpelAndPlaceholder();
@Query
Flux<ReactiveUser> findByUsernameRegexAndUsernameIn(String regex, List<String> sample);
Flux<ReactiveUser> findByUsernameContains(String contains);
Flux<ReactiveUser> findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails
}

View File

@@ -55,6 +55,8 @@ public class RepositoryIndexUsageTest {
CouchbaseConverter mockConverter = mock(CouchbaseConverter.class);
when(mockConverter.getTypeKey()).thenReturn("mockType");
when(mockConverter.convertForWriteIfNeeded(any(Object.class))).thenAnswer(
invocation -> invocation.getArgument(0));
couchbaseOperations = mock(CouchbaseOperations.class);
when(couchbaseOperations.getDefaultConsistency()).thenReturn(CONSISTENCY);
@@ -184,4 +186,4 @@ public class RepositoryIndexUsageTest {
verify(couchbaseOperations).remove("id1");
verify(couchbaseOperations).remove("id2");
}
}
}

View File

@@ -0,0 +1,364 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.AsyncUtils;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.JsonDocument;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
/**
* @author Michael Nitschinger
* @author Mark Paluch
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class)
public class SimpleCouchbaseRepositoryIntegrationTests {
@Rule
public TestName testName = new TestName();
@Autowired
private Bucket client;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private UserRepository repository;
private VersionedDataRepository versionedDataRepository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = getRepositoryWithRetry(factory, UserRepository.class);
versionedDataRepository = getRepositoryWithRetry(factory, VersionedDataRepository.class);
}
private void remove(String key) {
try {
client.remove(key);
} catch (DocumentDoesNotExistException e) {
}
}
@Test
public void simpleCrud() {
String key = "my_unique_user_key";
User instance = new User(key, "foobar", 22);
repository.save(instance);
Optional<User> found = repository.findById(key);
assertTrue(found.isPresent());
found.ifPresent(actual -> {
assertEquals(instance.getKey(), actual.getKey());
assertEquals(instance.getUsername(), actual.getUsername());
assertTrue(repository.existsById(key));
repository.delete(actual);
});
assertFalse(repository.findById(key).isPresent());
assertFalse(repository.existsById(key));
}
@Test
/**
* This test uses/assumes a default viewName called "all" that is configured on Couchbase.
*/
public void shouldFindAll() {
// do a non-stale query to populate data for testing.
client.query(ViewQuery.from("user", "all").stale(Stale.FALSE));
Iterable<User> allUsers = repository.findAll();
int size = 0;
for (User u : allUsers) {
size++;
assertNotNull(u.getKey());
assertNotNull(u.getUsername());
}
assertEquals(100, size);
}
@Test
public void shouldCount() {
// do a non-stale query to populate data for testing.
client.query(ViewQuery.from("user", "all").stale(Stale.FALSE));
assertEquals(100, repository.count());
}
@Test
@Ignore("View based query with copy of params from a ViewQuery in the method parameter not implemented")
//TODO re-enable test once ViewQuery parameters other than designDoc/viewName can be copied
public void shouldFindCustom() {
Iterable<User> users = repository.customViewQuery(ViewQuery.from("", "").limit(2).stale(Stale.FALSE));
int size = 0;
for (User u : users) {
size++;
assertNotNull(u.getKey());
assertNotNull(u.getUsername());
}
assertEquals(2, size);
}
@Test
public void shouldFindByUsernameUsingN1ql() {
User user = repository.findByUsername("uname-1");
assertNotNull(user);
assertEquals("testuser-1", user.getKey());
assertEquals("uname-1", user.getUsername());
}
@Test
public void shouldFailFindByUsernameWithNoIdOrCas() {
try {
User user = repository.findByUsernameBadSelect("uname-1");
fail("shouldFailFindByUsernameWithNoIdOrCas");
} catch (CouchbaseQueryExecutionException e) {
assertTrue("_ID expected in exception " + e, e.getMessage().contains("_ID"));
assertTrue("_CAS expected in exception " + e, e.getMessage().contains("_CAS"));
} catch (Exception e) {
fail("CouchbaseQueryExecutionException expected");
}
}
@Test
public void shouldFindFromUsernameInlineWithSpelParsing() {
User user = repository.findByUsernameWithSpelAndPlaceholder();
assertNotNull(user);
assertEquals("testuser-4", user.getKey());
assertEquals("uname-4", user.getUsername());
}
@Test
public void shouldFindFromDeriveQueryWithRegexpAndIn() {
User user = repository.findByUsernameRegexAndUsernameIn("uname-[123]", Arrays.asList("uname-2", "uname-4"));
assertNotNull(user);
assertEquals("testuser-2", user.getKey());
assertEquals("uname-2", user.getUsername());
}
@Test
public void shouldFindContainsWithoutAnnotation() {
List<User> users = repository.findByUsernameContains("-9");
assertNotNull(users);
assertFalse(users.isEmpty());
for (User user : users) {
assertTrue(user.getUsername().startsWith("uname-9"));
}
}
@Test
public void shouldDefaultToN1qlQueryDerivation() {
try {
User u = repository.findByUsernameNear("london");
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
if (!e.getMessage().contains("N1QL")) {
fail(e.getMessage());
}
}
}
@Test
public void shouldTakeVersionIntoAccountWhenDoingMultipleUpdates() {
final String key = "versionedUserTest";
VersionedData initial = new VersionedData(key, "ABCD");
versionedDataRepository.save(initial);
assertNotEquals(0L, initial.version);
Optional<VersionedData> fetch1 = versionedDataRepository.findById(key);
assertTrue(fetch1.isPresent());
fetch1.ifPresent(actual -> {
assertNotSame(initial, actual);
assertEquals(actual.version, initial.version);
});
VersionedData versionedData = fetch1.get();
JsonDocument bypass = client.get(key);
bypass.content().put("data", "BBBB");
JsonDocument bypassed = client.upsert(bypass);
assertNotEquals(bypassed.cas(), versionedData.version);
System.out.println(bypassed.cas());
try {
versionedData.setData("ZZZZ");
versionedDataRepository.save(versionedData);
fail("Expected CAS failure");
} catch (OptimisticLockingFailureException e) {
//success
assertTrue("optimistic locking should have CASMismatchException as cause, got " + e.getCause(),
e.getCause() instanceof CASMismatchException);
} finally {
client.remove(key);
}
}
@Test
public void shouldUpdateDocumentConcurrently() throws Exception {
final String key = testName.getMethodName();
remove(key);
final AtomicLong counter = new AtomicLong();
final AtomicLong updatedCounter = new AtomicLong();
VersionedData initial = new VersionedData(key, "value-initial");
versionedDataRepository.save(initial);
assertNotEquals(0L, initial.version);
Callable<Void> task = new Callable<Void>() {
@Override
public Void call() throws Exception {
boolean updated = false;
while(!updated) {
long counterValue = counter.incrementAndGet();
VersionedData messageData = versionedDataRepository.findById(key).get();
messageData.data = "value-" + counterValue;
try {
versionedDataRepository.save(messageData);
updated = true;
updatedCounter.incrementAndGet();
} catch (OptimisticLockingFailureException e) {
}
}
return null;
}
};
AsyncUtils.executeConcurrently(5, task);
assertNotEquals(initial.data, versionedDataRepository.findById(key).get().data);
assertEquals(5, updatedCounter.intValue());
}
@Test
public void shouldFailOnMultipleConcurrentSaves() throws Exception {
final String key = testName.getMethodName();
remove(key);
final AtomicLong counter = new AtomicLong();
final AtomicLong optimisticLockCounter = new AtomicLong();
Callable<Void> task = new Callable<Void>() {
@Override
public Void call() throws Exception {
long counterValue = counter.incrementAndGet();
VersionedData messageData = new VersionedData(key, "value-" + counterValue);
try {
versionedDataRepository.save(messageData);
} catch (OptimisticLockingFailureException e) {
optimisticLockCounter.incrementAndGet();
}
return null;
}
};
AsyncUtils.executeConcurrently(5, task);
assertEquals(4, optimisticLockCounter.intValue());
}
public interface VersionedDataRepository extends CouchbaseRepository<VersionedData, String> { }
@Document
public static class VersionedData {
@Id
private final String key;
@Version
public long version = 0L;
private String data;
public VersionedData(String key, String data) {
this.key = key;
this.data = data;
}
public String getKey() {
return key;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return this.key + " " + this.data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VersionedData vd = (VersionedData) o;
return key.equals(vd.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import java.util.Collections;
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;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* @author Michael Nitschinger
*/
public class SimpleCouchbaseRepositoryListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
createAndWaitForDesignDocs(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, i);
template.save(u, PersistTo.MASTER, ReplicateTo.NONE);
}
}
private void createAndWaitForDesignDocs(Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository." +
"User\") { emit(null, null); } }";
View view = DefaultView.create("all", mapFunction, "_count");
List<View> views = Collections.singletonList(view);
DesignDocument designDoc = DesignDocument.create("user", views);
client.bucketManager().upsertDesignDocument(designDoc);
}
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2017-2019 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
*
* https://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;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory;
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
/**
* @author Subhashni Balakrishnan
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
@TestExecutionListeners(SimpleReactiveCouchbaseRepositoryListener.class)
public class SimpleReactiveCouchbaseRepositoryIntegrationTests {
@Rule
public TestName testName = new TestName();
@Autowired
private Bucket client;
@Autowired
private ReactiveRepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private ReactiveUserRepository repository;
@Before
public void setup() throws Exception {
ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = getRepositoryWithRetry(factory, ReactiveUserRepository.class);
}
private void remove(String key) {
try {
client.remove(key);
} catch (DocumentDoesNotExistException e) {
}
}
@Test
public void simpleCrud() {
String key = "my_unique_user_key";
ReactiveUser instance = new ReactiveUser(key, "foobar", 22);
repository.save(instance).block();
ReactiveUser found = repository.findById(key).block();
assertEquals(instance.getKey(), found.getKey());
assertEquals(instance.getUsername(), found.getUsername());
assertTrue(repository.existsById(key).block());
repository.delete(found).block();
assertNull(repository.findById(key).block());
assertFalse(repository.existsById(key).block());
}
@Test
/**
* This test uses/assumes a default viewName called "all" that is configured on Couchbase.
*/
public void shouldFindAll() {
// do a non-stale query to populate data for testing.
client.query(ViewQuery.from("reactiveUser", "all").stale(Stale.FALSE));
List<ReactiveUser> allUsers = repository.findAll().collectList().block();
int size = 0;
for (ReactiveUser u : allUsers) {
size++;
assertNotNull(u.getKey());
assertNotNull(u.getUsername());
}
assertEquals(100, size);
}
@Test
public void shouldCount() {
// do a non-stale query to populate data for testing.
client.query(ViewQuery.from("reactiveUser", "all").stale(Stale.FALSE));
assertEquals("100", repository.count().block().toString());
}
@Test
public void shouldFindByUsernameUsingN1ql() {
ReactiveUser user = repository.findByUsername("reactiveuname-1").single().block();
assertNotNull(user);
assertEquals("reactivetestuser-1", user.getKey());
assertEquals("reactiveuname-1", user.getUsername());
}
@Test
public void shouldFailFindByUsernameWithNoIdOrCas() {
try {
ReactiveUser user = repository.findByUsernameBadSelect("reactiveuname-1").single().block();
fail("shouldFailFindByUsernameWithNoIdOrCas");
} catch (CouchbaseQueryExecutionException e) {
assertTrue("_ID expected in exception " + e, e.getMessage().contains("_ID"));
assertTrue("_CAS expected in exception " + e, e.getMessage().contains("_CAS"));
} catch (Exception e) {
fail("CouchbaseQueryExecutionException expected");
}
}
@Test
public void shouldFindFromUsernameInlineWithSpelParsing() {
ReactiveUser user = repository.findByUsernameWithSpelAndPlaceholder().take(1).blockLast();
assertNotNull(user);
assert(user.getUsername().startsWith("reactive"));
assert(user.getUsername().startsWith("reactive"));
}
@Test
public void shouldFindFromDeriveQueryWithRegexpAndIn() {
ReactiveUser user = repository.findByUsernameRegexAndUsernameIn("reactiveuname-[123]", Arrays.asList("reactiveuname-2", "reactiveuname-4")).take(1).blockLast();
assertNotNull(user);
assertEquals("reactivetestuser-2", user.getKey());
assertEquals("reactiveuname-2", user.getUsername());
}
@Test
public void shouldFindContainsWithoutAnnotation() {
List<ReactiveUser> users = repository.findByUsernameContains("reactive").collectList().block();
assertNotNull(users);
assertFalse(users.isEmpty());
for (ReactiveUser user : users) {
assertTrue(user.getUsername().startsWith("reactive"));
}
}
@Test
public void shouldDefaultToN1qlQueryDerivation() {
try {
ReactiveUser u = repository.findByUsernameNear("london").single().block();
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
if (!e.getMessage().contains("N1QL")) {
fail(e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.data.couchbase.repository;
import java.util.Collections;
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;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* @author Subhashni Balakrishnan
*/
public class SimpleReactiveCouchbaseRepositoryListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
createAndWaitForDesignDocs(client);
}
private void populateTestData(Bucket client, ClusterInfo clusterInfo) {
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client);
for (int i = 0; i < 100; i++) {
ReactiveUser u = new ReactiveUser("reactivetestuser-" + i, "reactiveuname-" + i, i);
template.save(u, PersistTo.MASTER, ReplicateTo.NONE).subscribe();
}
}
private void createAndWaitForDesignDocs(Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository." +
"ReactiveUser\") { emit(null, null); } }";
View view = DefaultView.create("all", mapFunction, "_count");
List<View> views = Collections.singletonList(view);
DesignDocument designDoc = DesignDocument.create("reactiveUser", views);
client.bucketManager().upsertDesignDocument(designDoc);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import org.springframework.data.annotation.Id;
/**
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class User {
@Id
private final String key;
private final String username;
private final int age;
public User(String key, String username, int age) {
this.key = key;
this.username = username;
this.age = age;
}
public String getUsername() {
return username;
}
public String getKey() {
return key;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return this.key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return key.equals(user.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2013-2019 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
*
* https://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;
import java.util.List;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
/**
* @author Michael Nitschinger
*/
@ViewIndexed(designDoc = "user", viewName = "all")
@N1qlSecondaryIndexed(indexName = "User")
public interface UserRepository extends CouchbaseRepository<User, String> {
@View(designDocument = "user", viewName = "all")
Iterable<User> customViewQuery(ViewQuery query);
@Query("#{#n1ql.selectEntity} WHERE username = $1 and #{#n1ql.filter}")
User findByUsername(String username);
@Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1 and #{#n1ql.filter} ")
User findByUsernameBadSelect(String username);
@Query("#{#n1ql.selectEntity} WHERE username LIKE '%-4' and #{#n1ql.filter}")
User findByUsernameWithSpelAndPlaceholder();
@Query
User findByUsernameRegexAndUsernameIn(String regex, List<String> sample);
List<User> findByUsernameContains(String contains);
User findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails
Page<User> findByAgeGreaterThan(int minAge, Pageable pageable);
Slice<User> findByAgeLessThan(int maxAge, Pageable pageable);
}

View File

@@ -0,0 +1,17 @@
package org.springframework.data.couchbase.repository.auditing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
@Configuration
@EnableCouchbaseRepositories
@EnableCouchbaseAuditing(modifyOnCreate = false)
public class AuditedApplicationConfig extends IntegrationTestApplicationConfig {
@Bean
public AuditedAuditorAware couchbaseAuditorAware() {
return new AuditedAuditorAware();
}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.data.couchbase.repository.auditing;
import org.springframework.data.domain.AuditorAware;
import java.util.Optional;
public class AuditedAuditorAware implements AuditorAware<String> {
private Optional<String> auditor = Optional.of("auditor");
@Override
public Optional<String> getCurrentAuditor() {
return auditor;
}
public void setAuditor(String auditor) {
this.auditor = Optional.of(auditor);
}
}

View File

@@ -0,0 +1,129 @@
package org.springframework.data.couchbase.repository.auditing;
import java.util.Date;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
@Document
public class AuditedItem {
@Id
private final String id;
private String value;
@CreatedBy
private String creator;
@LastModifiedBy
private String lastModifiedBy;
@LastModifiedDate
private Date lastModification;
@CreatedDate
private Date creationDate;
@Version
private long version;
public AuditedItem(String id, String value) {
this.id = id;
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public Date getLastModification() {
return lastModification;
}
public void setLastModification(Date lastModification) {
this.lastModification = lastModification;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
@Override
public String toString() {
return "AuditedItem{" +
"id='" + id + '\'' +
", value='" + value + '\'' +
", creator='" + creator + '\'' +
", lastModifiedBy='" + lastModifiedBy + '\'' +
", lastModification=" + lastModification +
", creationDate=" + creationDate +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuditedItem that = (AuditedItem) o;
if (!id.equals(that.id)) return false;
if (!value.equals(that.value)) return false;
if (creator != null ? !creator.equals(that.creator) : that.creator != null) return false;
if (lastModifiedBy != null ? !lastModifiedBy.equals(that.lastModifiedBy) : that.lastModifiedBy != null)
return false;
if (lastModification != null ? !lastModification.equals(that.lastModification) : that.lastModification != null)
return false;
return creationDate != null ? creationDate.equals(that.creationDate) : that.creationDate == null;
}
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + value.hashCode();
result = 31 * result + (creator != null ? creator.hashCode() : 0);
result = 31 * result + (lastModifiedBy != null ? lastModifiedBy.hashCode() : 0);
result = 31 * result + (lastModification != null ? lastModification.hashCode() : 0);
result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,7 @@
package org.springframework.data.couchbase.repository.auditing;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
public interface AuditedRepository extends CouchbaseRepository<AuditedItem, String> {
}

View File

@@ -0,0 +1,97 @@
package org.springframework.data.couchbase.repository.auditing;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.Optional;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.TestContainerResource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Simon Baslé
* @author Mark Paluch
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = AuditedApplicationConfig.class)
public class AuditingIntegrationTests {
@Autowired
private AuditedRepository repository;
@Autowired
private AuditedAuditorAware auditorAware;
private static final String KEY = "auditedTest";
@After
public void cleanupAuditedEntity() {
repository.getCouchbaseOperations().getCouchbaseBucket().remove(KEY);
}
@Test
public void testCreationEventIsRegistered() {
assertFalse(repository.existsById(KEY));
Date start = new Date();
AuditedItem item = new AuditedItem(KEY, "creation");
auditorAware.setAuditor("auditor");
repository.save(item);
Optional<AuditedItem> persisted = repository.findById(KEY);
assertTrue(persisted.isPresent());
persisted.ifPresent(actual -> {
assertNotNull("expected creation date audit trail", actual.getCreationDate());
assertEquals("expected creation user audit trail", "auditor", actual.getCreator());
assertTrue("creation date is too early", actual.getCreationDate().after(start));
assertTrue("creation date is too late", actual.getCreationDate().before(new Date()));
assertNull("expected modification date to be empty", actual.getLastModification());
assertNull("expected modification user to be empty", actual.getLastModifiedBy());
assertNotNull("expected version to be non null", actual.getVersion());
assertTrue("expected version to be greater than 0", actual.getVersion() > 0L);
});
}
@Test
public void testUpdateEventIsRegistered() {
assertFalse(repository.existsById(KEY));
String expectedCreator = "user1";
String expectedUpdater = "user2";
AuditedItem item = new AuditedItem(KEY, "creation");
auditorAware.setAuditor(expectedCreator);
repository.save(item);
AuditedItem created = repository.findById(KEY).orElse(null);
auditorAware.setAuditor(expectedUpdater);
repository.save(item);
AuditedItem updated = repository.findById(KEY).orElse(null);
assertNotNull("expected entity to be persisted", updated);
assertNotNull("expected creation date audit trail", updated.getCreationDate());
assertEquals("expected creation user audit trail", expectedCreator, updated.getCreator());
assertNotNull("expected modification date audit trail", updated.getLastModification());
assertTrue("expected modification date to be after creation date", updated.getCreationDate().before(updated.getLastModification()));
assertEquals("expected modification user to be the modifier", expectedUpdater, updated.getLastModifiedBy());
assertNotNull("expected version to be non null", updated.getVersion());
assertTrue("expected version to be greater than 0", updated.getVersion() > 0L);
assertTrue("expected updated version to be different from the one at creation", created.getVersion() != updated.getVersion());
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2014-2019 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
*
* https://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;
/**
* @author Mark Paluch
*/
public interface CdiPersonFragment {
int returnTwo();
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2014-2019 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
*
* https://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;
/**
* @author Mark Paluch
*/
public class CdiPersonFragmentImpl implements CdiPersonFragment {
@Override
public int returnTwo() {
return 2;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2014-2019 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
*
* https://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 org.springframework.data.couchbase.repository.CouchbaseRepository;
/**
* @author Mark Paluch
*/
public interface CdiPersonRepository extends CouchbaseRepository<Person, String>, CdiPersonFragment {
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2014-2019 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
*
* https://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.inject.Inject;
import com.couchbase.client.java.Bucket;
/**
* @author Mark Paluch
*/
class CdiRepositoryClient {
@Inject
private CdiPersonRepository cdiPersonRepository;
@Inject
@OtherQualifier
@PersonDB
private QualifiedPersonRepository qualifiedPersonRepository;
@Inject
private Bucket couchbaseClient;
public CdiPersonRepository getCdiPersonRepository() {
return cdiPersonRepository;
}
public QualifiedPersonRepository getQualifiedPersonRepository() {
return qualifiedPersonRepository;
}
public Bucket getCouchbaseClient() {
return couchbaseClient;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2014-2019 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
*
* https://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 static org.junit.Assert.*;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.view.DefaultView;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.View;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.test.context.ContextConfiguration;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
/**
* @author Mark Paluch
*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = CdiRepositoryIntegrationTests.class)
public class CdiRepositoryIntegrationTests {
private static SeContainer cdiContainer;
private CdiPersonRepository repository;
private QualifiedPersonRepository qualifiedPersonRepository;
private Bucket couchbaseClient;
@BeforeClass
public static void init() {
cdiContainer = SeContainerInitializer.newInstance() //
.disableDiscovery() //
.addPackages(CdiRepositoryClient.class) //
.initialize();
}
@AfterClass
public static void shutdown() {
cdiContainer.close();
}
@Before
public void setUp() {
CdiRepositoryClient repositoryClient = cdiContainer.select(CdiRepositoryClient.class).get();
repository = repositoryClient.getCdiPersonRepository();
qualifiedPersonRepository = repositoryClient.getQualifiedPersonRepository();
couchbaseClient = repositoryClient.getCouchbaseClient();
createAndWaitForDesignDocs(couchbaseClient);
}
private void createAndWaitForDesignDocs(Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == \"" + Person.class.getName()
+ "\") { emit(null, null); } }";
View view = DefaultView.create("all", mapFunction, "_count");
List<View> views = Collections.singletonList(view);
DesignDocument designDoc = DesignDocument.create("person", views);
client.bucketManager().upsertDesignDocument(designDoc);
}
/**
* @see DATACOUCH-109
*/
@Test
public void testCdiRepository() {
assertNotNull(repository);
repository.deleteAll();
Person bean = new Person("key", "username");
repository.save(bean);
assertTrue(repository.existsById(bean.getId()));
Optional<Person> retrieved = repository.findById(bean.getId());
assertTrue(retrieved.isPresent());
retrieved.ifPresent(actual -> {
assertEquals(bean.getName(), actual.getName());
assertEquals(bean.getId(), actual.getId());
});
}
/**
* @see DATACOUCH-203
*/
@Test
public void testQualifiedCdiRepository() {
assertNotNull(qualifiedPersonRepository);
qualifiedPersonRepository.deleteAll();
Person bean = new Person("key", "username");
qualifiedPersonRepository.save(bean);
assertTrue(qualifiedPersonRepository.existsById(bean.getId()));
Optional<Person> retrieved = qualifiedPersonRepository.findById(bean.getId());
assertTrue(retrieved.isPresent());
retrieved.ifPresent(actual -> {
assertEquals(bean.getName(), actual.getName());
assertEquals(bean.getId(), actual.getId());
});
}
/**
* @see DATACOUCH-109
*/
@Test
public void testCustomRepository() {
assertEquals(2, repository.returnTwo());
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2014-2019 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
*
* https://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.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import org.springframework.data.couchbase.config.CouchbaseBucketFactoryBean;
/**
* Producer for {@link Bucket}. A default {@link CouchbaseCluster} with defaults
* from {@link CouchbaseBucketFactoryBean} are sufficient for our test.
*
* @author Mark Paluch
* @author Simon Baslé
*/
class CouchbaseClientProducer {
@Produces
@ApplicationScoped
public Cluster cluster() {
return CouchbaseCluster.create();
}
@Produces
public Bucket createCouchbaseClient(Cluster cluster) throws Exception {
CouchbaseBucketFactoryBean couchbaseFactoryBean = new CouchbaseBucketFactoryBean(cluster, "protected", "protected", "password");
couchbaseFactoryBean.afterPropertiesSet();
return couchbaseFactoryBean.getObject();
}
public void close(@Disposes Bucket couchbaseClient) {
couchbaseClient.close();
}
public void close(@Disposes Cluster cluster) {
cluster.disconnect();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2014-2019 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
*
* https://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é
* @author Mark Paluch
*/
class CouchbaseClusterInfoProducer {
@Produces
public ClusterInfo createClusterInfo(Cluster cluster) throws Exception {
return cluster.clusterManager("protected", "password").info();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2014-2019 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
*
* https://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.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, ClusterInfo clusterInfo) throws Exception {
return new CouchbaseTemplate(clusterInfo, couchbaseClient);
}
@Produces
@OtherQualifier
@PersonDB
public CouchbaseOperations createQualifiedCouchbaseOperations(Bucket couchbaseClient, ClusterInfo clusterInfo) throws Exception {
return new CouchbaseTemplate(clusterInfo, couchbaseClient);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2016-2019 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
*
* https://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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* @author Mark Paluch
* @see DATACOUCH-203
*/
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@interface OtherQualifier {
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2019 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
*
* https://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 org.springframework.data.annotation.Id;
import com.couchbase.client.java.repository.annotation.Field;
/**
* @author Mark Paluch
*/
public class Person {
@Id private String id;
@Field private String name;
public Person() {}
public Person(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2016-2019 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
*
* https://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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* @author Mark Paluch
* @see DATACOUCH-203
*/
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@interface PersonDB {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2016-2019 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
*
* https://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 org.springframework.data.repository.CrudRepository;
/**
* @author Mark Paluch
* @see DATACOUCH-203
*/
@PersonDB
@OtherQualifier
public interface QualifiedPersonRepository extends CrudRepository<Person, String> {
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2013-2019 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
*
* https://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.extending.base;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.repository.User;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.extending.base.impl.MyRepository;
import org.springframework.data.couchbase.repository.extending.base.impl.MyRepositoryImpl;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.test.context.ContextConfiguration;
/**
* This tests custom implementation of base repository.
*
* @author Simon Baslé
*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration
public class RepositoryBaseIntegrationTests {
private static CouchbaseOperations mockOpsA;
@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);
}
@Autowired
ItemRepository repositoryA;
@Autowired
UserRepository repositoryB;
public interface ItemRepository extends MyRepository<Item, String> {
//
}
public interface UserRepository extends MyRepository<User, String> {
//
}
@Configuration
@EnableCouchbaseRepositories(considerNestedRepositories = true, repositoryBaseClass = MyRepositoryImpl.class)
static class Config extends AbstractCouchbaseConfiguration {
@Override
protected List<String> getBootstrapHosts() {
return Arrays.asList("127.0.0.1");
}
@Override
protected String getBucketName() {
return "protected";
}
@Override
protected String getBucketPassword() {
return "password";
}
@Bean
public CouchbaseOperations couchbaseOperations() {
return mockOpsA;
}
//this is for dev so it is ok to auto-create indexes
@Override
public IndexManager indexManager() {
return new IndexManager();
}
}
@Test
public void testRepositoryBaseIsChanged() {
assertNotNull(repositoryA);
assertNotNull(repositoryB);
assertEquals(4, repositoryA.sharedCustomMethod("toto"));
assertEquals(4000, repositoryA.sharedCustomMethod("anna"));
assertEquals(repositoryA.sharedCustomMethod("sameInput"), repositoryB.sharedCustomMethod("sameInput"));
assertEquals(repositoryA.sharedCustomMethod("anna"), repositoryB.sharedCustomMethod("anna"));
}
private static class Item {
@Id
public String id;
public String value;
}
}

View File

@@ -0,0 +1,12 @@
package org.springframework.data.couchbase.repository.extending.base.impl;
import java.io.Serializable;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable> extends CouchbaseRepository<T, ID> {
int sharedCustomMethod(ID id);
}

View File

@@ -0,0 +1,25 @@
package org.springframework.data.couchbase.repository.extending.base.impl;
import java.io.Serializable;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.repository.extending.base.impl.MyRepository;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.couchbase.repository.support.N1qlCouchbaseRepository;
public class MyRepositoryImpl<T, ID extends Serializable>
extends N1qlCouchbaseRepository<T, ID>
implements MyRepository<T, ID> {
public MyRepositoryImpl(CouchbaseEntityInformation<T, String> metadata, CouchbaseOperations couchbaseOperations) {
super(metadata, couchbaseOperations);
}
@Override
public int sharedCustomMethod(ID id) {
String key = String.valueOf(id);
if (key.startsWith("a"))
return key.length() * 1000;
return key.length();
}
}

View File

@@ -0,0 +1,15 @@
package org.springframework.data.couchbase.repository.extending.method;
import org.springframework.data.annotation.Id;
class MyItem {
@Id
public final String id;
public final String value;
public MyItem(String id, String value) {
this.id = id;
this.value = value;
}
}

View File

@@ -0,0 +1,10 @@
package org.springframework.data.couchbase.repository.extending.method;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.repository.CrudRepository;
@N1qlPrimaryIndexed
@ViewIndexed(designDoc = "myItem", viewName = "all")
public interface MyRepository extends CrudRepository<MyItem, String>, MyRepositoryCustom {
}

View File

@@ -0,0 +1,7 @@
package org.springframework.data.couchbase.repository.extending.method;
public interface MyRepositoryCustom {
long customCountItems();
}

View File

@@ -0,0 +1,58 @@
package org.springframework.data.couchbase.repository.extending.method;
import java.util.List;
import com.couchbase.client.java.query.N1qlParams;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.Statement;
import com.couchbase.client.java.query.consistency.ScanConsistency;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.repository.Item;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.couchbase.repository.query.CountFragment;
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
public class MyRepositoryImpl implements MyRepositoryCustom {
@Autowired
RepositoryOperationsMapping templateProvider;
@Override
public long customCountItems() {
CouchbaseOperations template = templateProvider.resolve(MyRepository.class, Item.class);
CouchbasePersistentEntity<Object> itemPersistenceEntity = (CouchbasePersistentEntity<Object>)
template.getConverter()
.getMappingContext()
.getRequiredPersistentEntity(MyItem.class);
CouchbaseEntityInformation<? extends Object, String> itemEntityInformation =
new MappingCouchbaseEntityInformation<Object, String>(itemPersistenceEntity);
Statement countStatement = N1qlUtils.createCountQueryForEntity(
template.getCouchbaseBucket().name(),
template.getConverter(),
itemEntityInformation);
ScanConsistency consistency = template.getDefaultConsistency().n1qlConsistency();
N1qlParams queryParams = N1qlParams.build().consistency(consistency);
N1qlQuery query = N1qlQuery.simple(countStatement, queryParams);
List<CountFragment> countFragments = template.findByN1QLProjection(query, CountFragment.class);
if (countFragments == null || countFragments.isEmpty()) {
return 0L;
} else {
return countFragments.get(0).count * -1L;
}
}
public long count() {
return 100;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2013-2019 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
*
* https://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.extending.method;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* This tests custom repository methods.
*
* @author Simon Baslé
*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class RepositoryCustomMethodIntegrationTests {
@Autowired
MyRepository repository;
@Configuration
@EnableCouchbaseRepositories
static class Config extends AbstractCouchbaseConfiguration {
@Override
protected List<String> getBootstrapHosts() {
return Arrays.asList("127.0.0.1");
}
@Override
protected String getBucketName() {
return "protected";
}
@Override
protected String getBucketPassword() {
return "password";
}
//this is for dev so it is ok to auto-create indexes
@Override
public IndexManager indexManager() {
return new IndexManager();
}
@Override
protected Consistency getDefaultConsistency() {
return Consistency.STRONGLY_CONSISTENT;
}
}
private static final String KEY = "customMethodTestItem";
@Before
public void initData() {
try { repository.deleteById(KEY); } catch (Exception e) { }
repository.save(new MyItem(KEY, "new item for custom count"));
}
@After
public void clearData() {
repository.deleteById(KEY);
}
@Test
public void testRepositoryCustomMethodIsWeavedIn() {
long customCount = repository.customCountItems();
assertEquals(-1L, customCount);
}
@Test
public void testRepositoryCrudMethodIsReplaced() {
long count = repository.count();
assertEquals(100L, count);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-2019 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
*
* https://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 static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.query.N1qlQuery;
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.ContainerResourceRunner;
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.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
/**
* An integration test that validates feature checking with Java Config.
*
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = FeatureDetectionTestApplicationConfig.class)
public class FeatureDetectionRepositoryIntegrationTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
@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(operationsMapping, indexManager);
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(operationsMapping, indexManager);
ViewOnlyUserRepository repository = getRepositoryWithRetry(factory, 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);
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());
}
}
}

View File

@@ -0,0 +1,81 @@
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.WriteResultChecking;
import org.springframework.data.couchbase.repository.support.IndexManager;
@Configuration
public class FeatureDetectionTestApplicationConfig extends AbstractCouchbaseConfiguration {
@Bean
public String couchbaseAdminUser() {
return "Administrator";
}
@Bean
public String couchbaseAdminPassword() {
return "password";
}
@Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList("127.0.0.1");
}
@Override
protected String getBucketName() {
return "protected";
}
@Override
protected String getBucketPassword() {
return "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());
}
//this is for dev so it is ok to auto-create indexes
@Override
public IndexManager indexManager() {
return new IndexManager();
}
//change the name of the field that will hold type information
@Override
public String typeKey() {
return "javaClass";
}
}

View File

@@ -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<User, String> {
}

View File

@@ -0,0 +1,15 @@
package org.springframework.data.couchbase.repository.index;
import java.util.List;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.User;
@N1qlSecondaryIndexed(indexName = IndexedRepositoryIntegrationTests.IGNORED_SECONDARY)
@ViewIndexed(designDoc = IndexedRepositoryIntegrationTests.VIEW_DOC, viewName = IndexedRepositoryIntegrationTests.IGNORED_VIEW_NAME)
public interface AnotherIndexedUserRepository extends CouchbaseRepository<User, String> {
public List<User> findByAge(int age);
}

View File

@@ -0,0 +1,26 @@
package org.springframework.data.couchbase.repository.index;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
@ViewIndexed(designDoc = "foo")
public interface IndexedFooRepository extends CouchbaseRepository<IndexedFooRepository.Foo, String> {
@Document
final class Foo {
@Id
private String id;
private String value1;
private int value2;
public Foo(String id, String value1, int value2) {
this.id = id;
this.value1 = value1;
this.value2 = value2;
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2013-2019 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
*
* https://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.index;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
import java.util.Arrays;
import com.couchbase.client.java.error.DesignDocumentDoesNotExistException;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.View;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
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.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* This tests automatic index creation features in the Couchbase connector.
* Automatic index creation is performed before construction of the repository implementation.
*
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(IndexedRepositoryTestListener.class)
public class IndexedRepositoryIntegrationTests {
public static final String SECONDARY = "autogeneratedIndexIndexedUserN1qlSecondary";
public static final String VIEW_DOC = "autogeneratedIndex";
public static final String VIEW_NAME = "IndexedUserView";
public static final String IGNORED_VIEW_NAME = "AnotherIndexedUserView";
public static final String IGNORED_SECONDARY = "AnotherIndexedUserN1qlSecondary";
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private CouchbaseOperations template;
private RepositoryFactorySupport factory;
private RepositoryFactorySupport ignoringIndexFactory;
private IndexManager ignoringIndexManager = new IndexManager(false, false, false);
@Before
public void setup() throws Exception {
factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
template = operationsMapping.getDefault();
ignoringIndexFactory = new CouchbaseRepositoryFactory(operationsMapping, ignoringIndexManager);
}
@Test
public void shouldFindN1qlPrimaryIndex() {
IndexedUserRepository repository = getRepositoryWithRetry(factory, IndexedUserRepository.class);
String bucket = template.getCouchbaseBucket().name();
N1qlQuery existQuery = N1qlQuery.simple("SELECT 1 FROM `"+ bucket +"`");
N1qlQueryResult exist = template.queryN1QL(existQuery);
assertTrue(exist.finalSuccess());
}
@Test
public void shouldFindN1qlSecondaryIndex() {
IndexedUserRepository repository = getRepositoryWithRetry(factory, IndexedUserRepository.class);
String bucket = template.getCouchbaseBucket().name();
N1qlQuery existQuery = N1qlQuery.simple("SELECT 1 FROM `"+ bucket +"` USE INDEX (" + SECONDARY +")");
N1qlQueryResult exist = template.queryN1QL(existQuery);
assertTrue(exist.finalSuccess());
}
@Test
public void shouldFindViewIndex() {
IndexedUserRepository repository = getRepositoryWithRetry(factory, IndexedUserRepository.class);
DesignDocument designDoc = null;
try {
designDoc = template.getCouchbaseBucket()
.bucketManager()
.getDesignDocument(VIEW_DOC);
} catch(DesignDocumentDoesNotExistException ex) {
}
assertNotNull(designDoc);
for (View view : designDoc.views()) {
if (view.name().equals(VIEW_NAME)) return;
}
fail("View not found");
}
@Test
public void shouldNotFindN1qlSecondaryIndexWithIgnoringIndexManager() {
AnotherIndexedUserRepository repository = getRepositoryWithRetry(ignoringIndexFactory, AnotherIndexedUserRepository.class);
String bucket = template.getCouchbaseBucket().name();
N1qlQuery existQuery = N1qlQuery.simple("SELECT 1 FROM `"+ bucket +"` USE INDEX (" + IGNORED_SECONDARY +")");
N1qlQueryResult exist = template.queryN1QL(existQuery);
assertFalse(exist.finalSuccess());
}
@Test
public void shouldNotFindViewIndexWithIgnoringIndexManager() {
AnotherIndexedUserRepository repository = getRepositoryWithRetry(ignoringIndexFactory, AnotherIndexedUserRepository.class);
DesignDocument designDoc = null;
try {
designDoc = template.getCouchbaseBucket()
.bucketManager()
.getDesignDocument(VIEW_DOC);
} catch(DesignDocumentDoesNotExistException ex) {
//ignored
}
if (designDoc != null) {
for (View view : designDoc.views()) {
if (view.name().equals(IGNORED_VIEW_NAME)) fail("Found unexpected " + IGNORED_VIEW_NAME);
}
}
}
@Test
public void shouldFindListOfIdsThroughDefaulViewIndexed() {
IndexedFooRepository.Foo foo1 = new IndexedFooRepository.Foo("foo1", "foo", 1);
IndexedFooRepository.Foo foo2 = new IndexedFooRepository.Foo("foo2", "bar", 2);
IndexedFooRepository repository = getRepositoryWithRetry(factory, IndexedFooRepository.class);
DesignDocument designDoc = template.getCouchbaseBucket()
.bucketManager()
.getDesignDocument("foo");
assertNotNull(designDoc);
boolean foundView = false;
for (View view : designDoc.views()) {
if (view.name().equals("all")) {
foundView = true;
break;
}
}
assertTrue("Expected to find view \"all\" on design document \"foo\"", foundView);
repository.save(foo1);
repository.save(foo2);
int count = 0;
for (Object o : repository.findAllById(Arrays.asList("foo1", "foo2"))) {
count++;
}
assertEquals(2L, count);
count = 0;
for (Object o : repository.findAllById(Arrays.asList("foo1", "foo3"))) {
count++;
}
assertEquals(1L, count);
}
}

View File

@@ -0,0 +1,31 @@
package org.springframework.data.couchbase.repository.index;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.error.DesignDocumentDoesNotExistException;
import com.couchbase.client.java.query.Index;
import com.couchbase.client.java.query.N1qlQuery;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* A test listener that will remove the indexes created in {@link IndexedRepositoryIntegrationTests} before test case is run.
*
* @author Simon Baslé
*/
public class IndexedRepositoryTestListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
try {
client.bucketManager().removeDesignDocument(IndexedRepositoryIntegrationTests.VIEW_DOC);
client.bucketManager().removeDesignDocument("foo");
} catch (DesignDocumentDoesNotExistException ex) {
//ignore
}
client.query(N1qlQuery.simple(Index.dropPrimaryIndex(client.name())));
client.query(N1qlQuery.simple(Index.dropIndex(client.name(), IndexedRepositoryIntegrationTests.SECONDARY)));
}
}

View File

@@ -0,0 +1,13 @@
package org.springframework.data.couchbase.repository.index;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.User;
@N1qlPrimaryIndexed
@N1qlSecondaryIndexed(indexName = IndexedRepositoryIntegrationTests.SECONDARY)
@ViewIndexed(designDoc = IndexedRepositoryIntegrationTests.VIEW_DOC, viewName = IndexedRepositoryIntegrationTests.VIEW_NAME)
public interface IndexedUserRepository extends CouchbaseRepository<User, String> {
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import org.springframework.data.annotation.Id;
/**
* @author Tayeb Chlyah
*/
public class Address {
@Id
String id;
String name;
String country;
public Address(String id, String name, String country) {
this.id = id;
this.name = name;
this.country = country;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getCountry() {
return country;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
/**
* @author Tayeb Chlyah
*/
@N1qlSecondaryIndexed(indexName = "addressIndex")
interface AddressRepository extends CouchbaseRepository<Address, String> {
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import java.util.List;
import com.couchbase.client.java.repository.annotation.Field;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.query.FetchType;
import org.springframework.data.couchbase.core.query.N1qlJoin;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
/**
* Author test class for N1QL Join tests
*
* @author Tayeb Chlyah
*/
@N1qlPrimaryIndexed
public class Author {
@Id
String id;
@Field("name")
String name;
@N1qlJoin(on = "lks.name=rks.authorName", fetchType = FetchType.IMMEDIATE)
List<Book> books;
@N1qlJoin(on = "lks.name=rks.name", fetchType = FetchType.IMMEDIATE)
Address address;
public Author(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setBooks(List<Book> books) {
this.books = books;
}
public List<Book> getBooks() {
return books;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* Populates author and book documents for N1ql Join tests
*
* @author Tayeb Chlyah
*/
public class AuthorAndBookPopulatorListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
populateTestData(client, clusterInfo);
}
void populateTestData(Bucket client, ClusterInfo clusterInfo) {
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
for(int i=0;i<5;i++) {
Author author = new Author("Author" + i,"foo"+ i);
template.save(author);
for (int j=0;j<5;j++) {
Book book = new Book("Book" + i+j, "foo"+i, "");
template.save(book);
}
Address address = new Address("Address" + i, "foo" + i, "bar");
template.save(address);
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
@N1qlPrimaryIndexed
public interface AuthorRepository extends CouchbaseRepository<Author, String> {
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import org.springframework.data.annotation.Id;
/**
* Book test class for N1QL Join tests
*/
public class Book {
@Id
String name;
String authorName;
String description;
public Book(String name, String authorName, String description) {
this.name = name;
this.authorName = authorName;
this.description = description;
}
public String getName() {
return this.name;
}
public String getAuthorName() {
return this.authorName;
}
public String getDescription() {
return this.description;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
@N1qlSecondaryIndexed(indexName = "bookIndex")
interface BookRepository extends CouchbaseRepository<Book, String> {
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2012-2019 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
*
* https://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.join;
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.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import static org.junit.Assert.*;
import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry;
/**
* N1ql Join tests
*
* @author Subhashni Balakrishnan
* @author Tayeb Chlyah
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(listeners = {AuthorAndBookPopulatorListener.class})
public class N1qlJoinIntegrationTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private BookRepository bookRepository;
private AuthorRepository authorRepository;
private AddressRepository addressRepository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
bookRepository = getRepositoryWithRetry(factory, BookRepository.class);
authorRepository = getRepositoryWithRetry(factory, AuthorRepository.class);
addressRepository = getRepositoryWithRetry(factory, AddressRepository.class);
}
@Test
public void testN1qlJoin() {
Author a = authorRepository.findById("Author" + 1).get();
assertTrue(a.books.size() == 5);
for(Book b:a.books) {
assertEquals("Book Join on author name mismatch", a.name, b.authorName);
}
assertNotNull(a.address);
assertEquals("Address Join on author name mismatch", a.name, a.address.name);
}
@Test
public void testN1qlJoinWithNoResults() {
final String name = "testN1qlJoinWithNoResults";
Author a = new Author(name, name);
authorRepository.save(a);
Author saveda = authorRepository.findById(name).get();
assertTrue(saveda.books.isEmpty());
assertNull(saveda.address);
}
}

View File

@@ -0,0 +1,40 @@
package org.springframework.data.couchbase.repository.spel;
import java.util.Collections;
import java.util.Map;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.spel.spi.EvaluationContextExtension;
@Configuration
@EnableCouchbaseRepositories
public class SpelConfig extends IntegrationTestApplicationConfig {
@Bean
public EvaluationContextExtension customSpelExtension() {
return new CustomSpelExtension();
}
public static class CustomSpelExtension implements EvaluationContextExtension {
/**
* Returns the identifier of the extension. The id can be leveraged by users to fully qualify property lookups and
* thus overcome ambiguities in case multiple extensions expose properties with the same name.
*
* @return the extension id, must not be {@literal null}.
*/
@Override
public String getExtensionId() {
return "custom";
}
@Override
public Map<String, Object> getProperties() {
return Collections.<String, Object>singletonMap("oneCustomer", "uname-3");
}
}
}

View File

@@ -0,0 +1,22 @@
package org.springframework.data.couchbase.repository.spel;
import java.util.List;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.repository.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@N1qlPrimaryIndexed
public interface SpelRepository extends CrudRepository<User, String> {
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND username = \"#{oneCustomer}\"")
List<User> findCustomUsers();
//notice how the SpEL syntax #{[0]} considers the first method argument,
//and N1QL placeholder resolution will still consider every method argument as a placeholder value.
//thus N1QL placeholder used is $2, to match criteriaValue
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2")
List<User> findUserWithDynamicCriteria(String criteriaField, Object criteriaValue);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2019 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
*
* https://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.spel;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.*;
import java.util.List;
import com.couchbase.client.java.Bucket;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.repository.SimpleCouchbaseRepositoryListener;
import org.springframework.data.couchbase.repository.User;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
/**
* @author Simon Baslé
*/
@RunWith(ContainerResourceRunner.class)
@ContextConfiguration(classes = SpelConfig.class)
@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class)
public class SpelRepositoryIntegrationTests {
@Autowired
private Bucket client;
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
@Autowired
private SpelRepository repository;
@Test
public void testSpelExtensionResolved() {
List<User> users = repository.findCustomUsers();
assertEquals(1, users.size());
assertEquals("testuser-3", users.get(0).getKey());
assertEquals("uname-3", users.get(0).getUsername());
}
@Test
public void testSpelArgumentResolution() {
List<User> usersByName = repository.findUserWithDynamicCriteria("username", "uname-5");
List<User> usersByAge = repository.findUserWithDynamicCriteria("age", 4);
assertThat(usersByName, hasSize(1));
assertThat(usersByAge, hasSize(1));
assertThat(usersByName.get(0).getKey(), is("testuser-5"));
assertThat(usersByAge.get(0).getKey(), is("testuser-4"));
}
}

Some files were not shown because too many files have changed in this diff Show More