DATAES-519 - Add reactive repository support.
Reactive Elasticsearch repository support builds on the core repository support utilizing
operations provided via ReactiveElasticsearchOperations executed by a ReactiveElasticsearchClient.
Spring Data Elasticsearchs reactive repository support uses Project Reactor as its reactive
composition library of choice.
There are 3 main interfaces to be used:
* ReactiveRepository
* ReactiveCrudRepository
* ReactiveSortingRepository
For Java configuration, use the @EnableReactiveElasticsearchRepositories annotation.
The following listing shows how to use Java configuration for a repository:
@Configuration
@EnableReactiveElasticsearchRepositories
public class Config extends AbstractReactiveElasticsearchConfiguration {
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
return ReactiveRestClients.create(ClientConfiguration.localhost());
}
}
Using a repository that extends ReactiveSortingRepository makes all CRUD operations available
as well as methods for sorted access to the entities. Working with the repository instance is a matter of dependency
injecting it into a client.
The repository itself allows defining additional methods backed by the inferred proxy.
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
Flux<Person> findByFirstname(String firstname);
Flux<Person> findByFirstname(Publisher<String> firstname);
Flux<Person> findByFirstnameOrderByLastname(String firstname);
Flux<Person> findByFirstname(String firstname, Sort sort);
Flux<Person> findByFirstname(String firstname, Pageable page);
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
Mono<Person> findFirstByLastname(String lastname);
@Query("{ \"bool\" : { \"must\" : { \"term\" : { \"lastname\" : \"?0\" } } } }")
Flux<Person> findByLastname(String lastname);
Mono<Long> countByFirstname(String firstname)
Mono<Boolean> existsByFirstname(String firstname)
Mono<Long> deleteByFirstname(String firstname)
}
Original Pull Request: #235
This commit is contained in:
@@ -17,14 +17,16 @@ package org.springframework.data.elasticsearch;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.RestClients;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
@@ -84,6 +86,18 @@ public final class TestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static boolean isEmptyIndex(String indexName) {
|
||||
|
||||
try (RestHighLevelClient client = restHighLevelClient()) {
|
||||
|
||||
return 0L == client
|
||||
.search(new SearchRequest(indexName)
|
||||
.source(SearchSourceBuilder.searchSource().query(QueryBuilders.matchAllQuery())), RequestOptions.DEFAULT)
|
||||
.getHits().getTotalHits();
|
||||
}
|
||||
}
|
||||
|
||||
public static OfType documentWithId(String id) {
|
||||
return new DocumentLookup(id);
|
||||
}
|
||||
@@ -106,19 +120,16 @@ public final class TestUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean existsIn(String index) {
|
||||
|
||||
GetRequest request = new GetRequest(index).id(id);
|
||||
if (StringUtils.hasText(type)) {
|
||||
request = request.type(type);
|
||||
}
|
||||
try {
|
||||
return restHighLevelClient().get(request, RequestOptions.DEFAULT).isExists();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try (RestHighLevelClient client = restHighLevelClient()) {
|
||||
return client.get(request, RequestOptions.DEFAULT).isExists();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -135,6 +136,16 @@ public class ReactiveElasticsearchClientTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void getOnNonExistingIndexShouldThrowException() {
|
||||
|
||||
client.get(new GetRequest(INDEX_I, TYPE_I, "nonono"))
|
||||
.as(StepVerifier::create)
|
||||
.expectError(ElasticsearchStatusException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
|
||||
@Test // DATAES-488
|
||||
public void getShouldFetchDocumentById() {
|
||||
|
||||
|
||||
@@ -177,6 +177,14 @@ public class ReactiveElasticsearchTemplateTests {
|
||||
template.save(null);
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findByIdShouldCompleteWhenIndexDoesNotExist() {
|
||||
|
||||
template.findById("foo", SampleEntity.class, "no-such-index") //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-504
|
||||
public void findByIdShouldReturnEntity() {
|
||||
|
||||
@@ -245,6 +253,15 @@ public class ReactiveElasticsearchTemplateTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void existsShouldReturnFalseWhenIndexDoesNotExist() {
|
||||
|
||||
template.exists("foo", SampleEntity.class, "no-such-index") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(false) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-504
|
||||
public void existsShouldReturnTrueWhenFound() {
|
||||
|
||||
@@ -269,6 +286,14 @@ public class ReactiveElasticsearchTemplateTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findShouldCompleteWhenIndexDoesNotExist() {
|
||||
|
||||
template.find(new CriteriaQuery(Criteria.where("message").is("some message")), SampleEntity.class, "no-such-index") //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-504
|
||||
public void findShouldApplyCriteria() {
|
||||
|
||||
@@ -392,6 +417,15 @@ public class ReactiveElasticsearchTemplateTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void countShouldReturnZeroWhenIndexDoesNotExist() {
|
||||
|
||||
template.count(SampleEntity.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(0L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-504
|
||||
public void countShouldReturnCountAllWhenGivenNoQuery() {
|
||||
|
||||
@@ -416,6 +450,14 @@ public class ReactiveElasticsearchTemplateTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteByIdShouldCompleteWhenIndexDoesNotExist() {
|
||||
|
||||
template.deleteById("does-not-exists", SampleEntity.class, "no-such-index") //
|
||||
.as(StepVerifier::create)//
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-504
|
||||
public void deleteByIdShouldRemoveExistingDocumentById() {
|
||||
|
||||
@@ -462,6 +504,18 @@ public class ReactiveElasticsearchTemplateTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
@ElasticsearchVersion(asOf = "6.5.0")
|
||||
public void deleteByQueryShouldReturnZeroWhenIndexDoesNotExist() {
|
||||
|
||||
CriteriaQuery query = new CriteriaQuery(new Criteria("message").contains("test"));
|
||||
|
||||
template.deleteBy(query, SampleEntity.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(0L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-504
|
||||
@ElasticsearchVersion(asOf = "6.5.0")
|
||||
public void deleteByQueryShouldReturnNumberOfDeletedDocuments() {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* 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.elasticsearch.repository.config;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.elasticsearch.TestUtils;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
|
||||
import org.springframework.data.elasticsearch.entities.SampleEntity;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Fool's Fate - Robin Hobb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class ReactiveElasticsearchRepositoriesRegistrarTests {
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public ReactiveElasticsearchTemplate reactiveElasticsearchTemplate() {
|
||||
return new ReactiveElasticsearchTemplate(TestUtils.reactiveClient());
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired ReactiveSampleEntityRepository repository;
|
||||
@Autowired ApplicationContext context;
|
||||
|
||||
@Test // DATAES-519
|
||||
public void testConfiguration() {
|
||||
|
||||
Assertions.assertThat(context).isNotNull();
|
||||
Assertions.assertThat(repository).isNotNull();
|
||||
|
||||
}
|
||||
|
||||
interface ReactiveSampleEntityRepository extends ReactiveElasticsearchRepository<SampleEntity, String> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* 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.elasticsearch.repository.config;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryConfiguration;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Fool's Fate - Robin Hobb
|
||||
*/
|
||||
public class ReactiveElasticsearchRepositoryConfigurationExtensionUnitTests {
|
||||
|
||||
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(Config.class, true);
|
||||
ResourceLoader loader = new PathMatchingResourcePatternResolver();
|
||||
Environment environment = new StandardEnvironment();
|
||||
BeanDefinitionRegistry registry = new DefaultListableBeanFactory();
|
||||
|
||||
RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata,
|
||||
EnableReactiveElasticsearchRepositories.class, loader, environment, registry);
|
||||
|
||||
@Test // DATAES-519
|
||||
public void isStrictMatchIfDomainTypeIsAnnotatedWithDocument() {
|
||||
|
||||
ReactiveElasticsearchRepositoryConfigurationExtension extension = new ReactiveElasticsearchRepositoryConfigurationExtension();
|
||||
assertHasRepo(CrudRepositoryForAnnotatedType.class,
|
||||
extension.getRepositoryConfigurations(configurationSource, loader, true));
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void isStrictMatchIfRepositoryExtendsStoreSpecificBase() {
|
||||
|
||||
ReactiveElasticsearchRepositoryConfigurationExtension extension = new ReactiveElasticsearchRepositoryConfigurationExtension();
|
||||
assertHasRepo(EsRepositoryForUnAnnotatedType.class,
|
||||
extension.getRepositoryConfigurations(configurationSource, loader, true));
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() {
|
||||
|
||||
ReactiveElasticsearchRepositoryConfigurationExtension extension = new ReactiveElasticsearchRepositoryConfigurationExtension();
|
||||
assertDoesNotHaveRepo(CrudRepositoryForUnAnnotatedType.class,
|
||||
extension.getRepositoryConfigurations(configurationSource, loader, true));
|
||||
}
|
||||
|
||||
private static void assertHasRepo(Class<?> repositoryInterface,
|
||||
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configs) {
|
||||
|
||||
for (RepositoryConfiguration<?> config : configs) {
|
||||
if (config.getRepositoryInterface().equals(repositoryInterface.getName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fail("Expected to find config for repository interface ".concat(repositoryInterface.getName()).concat(" but got ")
|
||||
.concat(configs.toString()));
|
||||
}
|
||||
|
||||
private static void assertDoesNotHaveRepo(Class<?> repositoryInterface,
|
||||
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configs) {
|
||||
|
||||
for (RepositoryConfiguration<?> config : configs) {
|
||||
if (config.getRepositoryInterface().equals(repositoryInterface.getName())) {
|
||||
fail("Expected not to find config for repository interface ".concat(repositoryInterface.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
@Document(indexName = "star-wars", type = "character")
|
||||
static class SwCharacter {}
|
||||
|
||||
static class Store {}
|
||||
|
||||
interface CrudRepositoryForAnnotatedType extends ReactiveCrudRepository<SwCharacter, String> {}
|
||||
|
||||
interface CrudRepositoryForUnAnnotatedType extends ReactiveCrudRepository<Store, String> {}
|
||||
|
||||
interface EsRepositoryForUnAnnotatedType extends ReactiveElasticsearchRepository<Store, String> {}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* 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.elasticsearch.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.entities.Person;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Fool's Fate - Robin Hobb
|
||||
*/
|
||||
public class ReactiveElasticsearchQueryMethodUnitTests {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mappingContext = new SimpleElasticsearchMappingContext();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void detectsCollectionFromRepoTypeIfReturnTypeNotAssignable() throws Exception {
|
||||
|
||||
ReactiveElasticsearchQueryMethod queryMethod = queryMethod(NonReactiveRepository.class, "method");
|
||||
ElasticsearchEntityMetadata<?> metadata = queryMethod.getEntityInformation();
|
||||
|
||||
assertThat(metadata.getJavaType()).isAssignableFrom(Person.class);
|
||||
assertThat(metadata.getIndexName()).isEqualTo("test-index-person");
|
||||
assertThat(metadata.getIndexTypeName()).isEqualTo("user");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAES-519
|
||||
public void rejectsNullMappingContext() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByName", String.class);
|
||||
|
||||
new ReactiveElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
|
||||
new SpelAwareProxyProjectionFactory(), null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class) // DATAES-519
|
||||
public void rejectsMonoPageableResult() throws Exception {
|
||||
queryMethod(PersonRepository.class, "findMonoByName", String.class, Pageable.class);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class) // DATAES-519
|
||||
public void throwsExceptionOnWrappedPage() throws Exception {
|
||||
queryMethod(PersonRepository.class, "findMonoPageByName", String.class, Pageable.class);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class) // DATAES-519
|
||||
public void throwsExceptionOnWrappedSlice() throws Exception {
|
||||
queryMethod(PersonRepository.class, "findMonoSliceByName", String.class, Pageable.class);
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void allowsPageableOnFlux() throws Exception {
|
||||
queryMethod(PersonRepository.class, "findByName", String.class, Pageable.class);
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void fallsBackToRepositoryDomainTypeIfMethodDoesNotReturnADomainType() throws Exception {
|
||||
|
||||
ReactiveElasticsearchQueryMethod method = queryMethod(PersonRepository.class, "deleteByName", String.class);
|
||||
|
||||
assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Person.class);
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters)
|
||||
throws Exception {
|
||||
|
||||
Method method = repository.getMethod(name, parameters);
|
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
return new ReactiveElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(repository), factory,
|
||||
mappingContext);
|
||||
}
|
||||
|
||||
interface PersonRepository extends Repository<Person, String> {
|
||||
|
||||
Mono<Person> findMonoByName(String name, Pageable pageRequest);
|
||||
|
||||
Mono<Page<Person>> findMonoPageByName(String name, Pageable pageRequest);
|
||||
|
||||
Mono<Slice<Person>> findMonoSliceByName(String name, Pageable pageRequest);
|
||||
|
||||
Flux<Person> findByName(String name);
|
||||
|
||||
Flux<Person> findByName(String name, Pageable pageRequest);
|
||||
|
||||
void deleteByName(String name);
|
||||
}
|
||||
|
||||
interface NonReactiveRepository extends Repository<Person, Long> {
|
||||
|
||||
List<Person> method();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* 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.elasticsearch.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.entities.Person;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Fool's Fate - Robin Hobb
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ReactiveElasticsearchStringQueryUnitTests {
|
||||
|
||||
SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
ElasticsearchConverter converter;
|
||||
|
||||
@Mock ReactiveElasticsearchOperations operations;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void bindsSimplePropertyCorrectly() throws Exception {
|
||||
|
||||
ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByName", String.class);
|
||||
StubParameterAccessor accesor = new StubParameterAccessor("Luke");
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accesor);
|
||||
StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }");
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource()).isEqualTo(reference.getSource());
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
@Ignore("TODO: fix spel query integration")
|
||||
public void bindsExpressionPropertyCorrectly() throws Exception {
|
||||
|
||||
ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByNameWithExpression",
|
||||
String.class);
|
||||
StubParameterAccessor accesor = new StubParameterAccessor("Luke");
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accesor);
|
||||
StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }");
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource()).isEqualTo(reference.getSource());
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchStringQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
ReactiveElasticsearchQueryMethod queryMethod = new ReactiveElasticsearchQueryMethod(method,
|
||||
new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext());
|
||||
return new ReactiveElasticsearchStringQuery(queryMethod, operations, PARSER,
|
||||
QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
|
||||
private interface SampleRepository extends Repository<Person, String> {
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }")
|
||||
Mono<Person> findByName(String name);
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?#{[0]}' } } } }")
|
||||
Flux<Person> findByNameWithExpression(String param0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* 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.elasticsearch.repository.query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
/**
|
||||
* Simple {@link ParameterAccessor} that returns the given parameters unfiltered.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Fool's Fate - Robin Hobb
|
||||
*/
|
||||
class StubParameterAccessor implements ElasticsearchParameterAccessor {
|
||||
|
||||
private final Object[] values;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public StubParameterAccessor(Object... values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ParameterAccessor#getPageable()
|
||||
*/
|
||||
public Pageable getPageable() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ParameterAccessor#getBindableValue(int)
|
||||
*/
|
||||
public Object getBindableValue(int index) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ParameterAccessor#hasBindableNullValue()
|
||||
*/
|
||||
public boolean hasBindableNullValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ParameterAccessor#getSort()
|
||||
*/
|
||||
public Sort getSort() {
|
||||
return Sort.unsorted();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ParameterAccessor#iterator()
|
||||
*/
|
||||
public Iterator<Object> iterator() {
|
||||
return Arrays.asList(values).iterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.repository.query.ElasticsearchParameterAccessor#getValues()
|
||||
*/
|
||||
@Override
|
||||
public Object[] getValues() {
|
||||
return this.values;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection()
|
||||
*/
|
||||
@Override
|
||||
public Optional<Class<?>> getDynamicProjection() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* 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.elasticsearch.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.elasticsearch.TestUtils;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.entities.SampleEntity;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Fool's Fate - Robin Hobb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class SimpleReactiveElasticsearchRepositoryTests {
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
return TestUtils.reactiveClient();
|
||||
}
|
||||
}
|
||||
|
||||
static final String INDEX = "test-index-sample";
|
||||
static final String TYPE = "test-type";
|
||||
|
||||
@Autowired ReactiveSampleEntityRepository repository;
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteIndex(INDEX);
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void saveShouldSaveSingleEntity() {
|
||||
|
||||
repository.save(SampleEntity.builder().build()) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(it -> {
|
||||
assertThat(TestUtils.documentWithId(it.getId()).ofType(TYPE).existsIn(INDEX)).isTrue();
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void saveShouldComputeMultipleEntities() {
|
||||
|
||||
repository
|
||||
.saveAll(Arrays.asList(SampleEntity.builder().build(), SampleEntity.builder().build(),
|
||||
SampleEntity.builder().build())) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(it -> {
|
||||
assertThat(TestUtils.documentWithId(it.getId()).ofType(TYPE).existsIn(INDEX)).isTrue();
|
||||
}) //
|
||||
.consumeNextWith(it -> {
|
||||
assertThat(TestUtils.documentWithId(it.getId()).ofType(TYPE).existsIn(INDEX)).isTrue();
|
||||
}) //
|
||||
.consumeNextWith(it -> {
|
||||
assertThat(TestUtils.documentWithId(it.getId()).ofType(TYPE).existsIn(INDEX)).isTrue();
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findByIdShouldCompleteIfIndexDoesNotExist() {
|
||||
repository.findById("id-two").as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findShouldRetrieveSingleEntityById() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build(), //
|
||||
SampleEntity.builder().id("id-three").build());
|
||||
|
||||
repository.findById("id-two").as(StepVerifier::create)//
|
||||
.consumeNextWith(it -> {
|
||||
assertThat(it.getId()).isEqualTo("id-two");
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findByIdShouldCompleteIfNothingFound() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build(), //
|
||||
SampleEntity.builder().id("id-three").build());
|
||||
|
||||
repository.findById("does-not-exist").as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findAllByIdByIdShouldCompleteIfIndexDoesNotExist() {
|
||||
repository.findAllById(Arrays.asList("id-two", "id-two")).as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findAllByIdShouldRetrieveMatchingDocuments() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build(), //
|
||||
SampleEntity.builder().id("id-three").build());
|
||||
|
||||
repository.findAllById(Arrays.asList("id-one", "id-two")) //
|
||||
.as(StepVerifier::create)//
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void findAllByIdShouldCompleteWhenNothingFound() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build(), //
|
||||
SampleEntity.builder().id("id-three").build());
|
||||
|
||||
repository.findAllById(Arrays.asList("can't", "touch", "this")) //
|
||||
.as(StepVerifier::create)//
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void countShouldReturnZeroWhenIndexDoesNotExist() {
|
||||
repository.count().as(StepVerifier::create).expectNext(0L).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void countShouldCountDocuments() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build());
|
||||
|
||||
repository.count().as(StepVerifier::create).expectNext(2L).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void existsByIdShouldReturnTrueIfExists() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.existsById("id-two") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void existsByIdShouldReturnFalseIfNotExists() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.existsById("wrecking ball") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(false) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void countShouldCountMatchingDocuments() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.countAllByMessage("test") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(2L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void existsShouldReturnTrueIfExists() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.existsAllByMessage("message") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void existsShouldReturnFalseIfNotExists() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.existsAllByMessage("these days") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(false) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteByIdShouldCompleteIfNothingDeleted() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build());
|
||||
|
||||
repository.deleteById("does-not-exist").as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteByIdShouldCompleteWhenIndexDoesNotExist() {
|
||||
repository.deleteById("does-not-exist").as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteByIdShouldDeleteEntry() {
|
||||
|
||||
SampleEntity toBeDeleted = SampleEntity.builder().id("id-two").build();
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), toBeDeleted);
|
||||
|
||||
repository.deleteById(toBeDeleted.getId()).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
assertThat(TestUtils.documentWithId(toBeDeleted.getId()).ofType(TYPE).existsIn(INDEX)).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteShouldDeleteEntry() {
|
||||
|
||||
SampleEntity toBeDeleted = SampleEntity.builder().id("id-two").build();
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), toBeDeleted);
|
||||
|
||||
repository.delete(toBeDeleted).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
assertThat(TestUtils.documentWithId(toBeDeleted.getId()).ofType(TYPE).existsIn(INDEX)).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteAllShouldDeleteGivenEntries() {
|
||||
|
||||
SampleEntity toBeDeleted = SampleEntity.builder().id("id-one").build();
|
||||
SampleEntity hangInThere = SampleEntity.builder().id("id-two").build();
|
||||
SampleEntity toBeDeleted2 = SampleEntity.builder().id("id-three").build();
|
||||
|
||||
bulkIndex(toBeDeleted, hangInThere, toBeDeleted2);
|
||||
|
||||
repository.deleteAll(Arrays.asList(toBeDeleted, toBeDeleted2)).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
assertThat(TestUtils.documentWithId(toBeDeleted.getId()).ofType(TYPE).existsIn(INDEX)).isFalse();
|
||||
assertThat(TestUtils.documentWithId(toBeDeleted2.getId()).ofType(TYPE).existsIn(INDEX)).isFalse();
|
||||
assertThat(TestUtils.documentWithId(hangInThere.getId()).ofType(TYPE).existsIn(INDEX)).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void deleteAllShouldDeleteAllEntries() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").build(), //
|
||||
SampleEntity.builder().id("id-two").build(), //
|
||||
SampleEntity.builder().id("id-three").build());
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
assertThat(TestUtils.isEmptyIndex(INDEX)).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedFinderMethodShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.findAllByMessageLike("test") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedFinderMethodShouldBeExecutedCorrectlyWhenGivenPublisher() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.findAllByMessage(Mono.just("test")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedFinderWithDerivedSortMethodShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("test").rate(3).build(), //
|
||||
SampleEntity.builder().id("id-two").message("test test").rate(1).build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").rate(2).build());
|
||||
|
||||
repository.findAllByMessageLikeOrderByRate("test") //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-two")) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-three")) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-one")) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedFinderMethodWithSortParameterShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("test").rate(3).build(), //
|
||||
SampleEntity.builder().id("id-two").message("test test").rate(1).build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").rate(2).build());
|
||||
|
||||
repository.findAllByMessage("test", Sort.by(Order.asc("rate"))) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-two")) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-three")) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-one")) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedFinderMethodWithPageableParameterShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("test").rate(3).build(), //
|
||||
SampleEntity.builder().id("id-two").message("test test").rate(1).build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").rate(2).build());
|
||||
|
||||
repository.findAllByMessage("test", PageRequest.of(0, 2, Sort.by(Order.asc("rate")))) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-two")) //
|
||||
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-three")) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedFinderMethodReturningMonoShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.findFirstByMessageLike("test") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void annotatedFinderMethodShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.findAllViaAnnotatedQueryByMessageLike("test") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void derivedDeleteMethodShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build());
|
||||
|
||||
repository.deleteAllByMessage("message") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
assertThat(TestUtils.documentWithId("id-one").ofType(TYPE).existsIn(INDEX)).isFalse();
|
||||
assertThat(TestUtils.documentWithId("id-two").ofType(TYPE).existsIn(INDEX)).isFalse();
|
||||
assertThat(TestUtils.documentWithId("id-three").ofType(TYPE).existsIn(INDEX)).isTrue();
|
||||
}
|
||||
|
||||
IndexRequest indexRequest(Map source, String index, String type) {
|
||||
|
||||
return new IndexRequest(index, type) //
|
||||
.id(source.containsKey("id") ? source.get("id").toString() : UUID.randomUUID().toString()) //
|
||||
.source(source) //
|
||||
.create(true);
|
||||
}
|
||||
|
||||
IndexRequest indexRequestFrom(SampleEntity entity) {
|
||||
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
|
||||
if (StringUtils.hasText(entity.getId())) {
|
||||
target.put("id", entity.getId());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(entity.getType())) {
|
||||
target.put("type", entity.getType());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(entity.getMessage())) {
|
||||
target.put("message", entity.getMessage());
|
||||
}
|
||||
|
||||
target.put("rate", entity.getRate());
|
||||
target.put("available", entity.isAvailable());
|
||||
|
||||
return indexRequest(target, INDEX, TYPE);
|
||||
}
|
||||
|
||||
void bulkIndex(SampleEntity... entities) {
|
||||
|
||||
BulkRequest request = new BulkRequest();
|
||||
Arrays.stream(entities).forEach(it -> request.add(indexRequestFrom(it)));
|
||||
|
||||
try (RestHighLevelClient client = TestUtils.restHighLevelClient()) {
|
||||
client.bulk(request.setRefreshPolicy(RefreshPolicy.IMMEDIATE), RequestOptions.DEFAULT);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
interface ReactiveSampleEntityRepository extends ReactiveCrudRepository<SampleEntity, String> {
|
||||
|
||||
Flux<SampleEntity> findAllByMessageLike(String message);
|
||||
|
||||
Flux<SampleEntity> findAllByMessageLikeOrderByRate(String message);
|
||||
|
||||
Flux<SampleEntity> findAllByMessage(String message, Sort sort);
|
||||
|
||||
Flux<SampleEntity> findAllByMessage(String message, Pageable pageable);
|
||||
|
||||
Flux<SampleEntity> findAllByMessage(Publisher<String> message);
|
||||
|
||||
@Query("{ \"bool\" : { \"must\" : { \"term\" : { \"message\" : \"?0\" } } } }")
|
||||
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageLike(String message);
|
||||
|
||||
Mono<SampleEntity> findFirstByMessageLike(String message);
|
||||
|
||||
Mono<Long> countAllByMessage(String message);
|
||||
|
||||
Mono<Boolean> existsAllByMessage(String message);
|
||||
|
||||
Mono<Long> deleteAllByMessage(String message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user