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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springframework.data.couchbase;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class IntegrationTestCustomKeySettings extends IntegrationTestApplicationConfig {
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> {}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 { }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
public interface PartyPagingRepository extends CouchbasePagingAndSortingRepository<Party, String> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public interface ReactivePartySortingRepository extends ReactiveCouchbaseSortingRepository<Party, String> {
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springframework.data.couchbase.repository.extending.method;
|
||||
|
||||
public interface MyRepositoryCustom {
|
||||
|
||||
long customCountItems();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user