Fix repository methods value converting.

Original Pull Request #2339
Closes #2338
This commit is contained in:
Peter-Josef Meisch 2022-10-19 21:52:49 +02:00 committed by GitHub
parent f21285d33c
commit e67150a55b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 650 additions and 172 deletions

View File

@ -41,6 +41,7 @@ import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
@ -1186,6 +1187,13 @@ public class MappingElasticsearchConverter
Assert.notNull(query, "query must not be null");
if (query instanceof BaseQuery) {
if (((BaseQuery) query).queryIsUpdatedByConverter()) {
return;
}
}
if (domainClass == null) {
return;
}
@ -1195,6 +1203,10 @@ public class MappingElasticsearchConverter
if (query instanceof CriteriaQuery) {
updatePropertiesInCriteriaQuery((CriteriaQuery) query, domainClass);
}
if (query instanceof BaseQuery) {
((BaseQuery) query).setQueryIsUpdatedByConverter(true);
}
}
private void updatePropertiesInFieldsAndSourceFilter(Query query, Class<?> domainClass) {

View File

@ -74,6 +74,8 @@ public class BaseQuery implements Query {
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
@Nullable protected PointInTime pointInTime;
private boolean queryIsUpdatedByConverter = false;
public BaseQuery() {}
public <Q extends BaseQuery, B extends BaseQueryBuilder<Q, B>> BaseQuery(BaseQueryBuilder<Q, B> builder) {
@ -466,4 +468,20 @@ public class BaseQuery implements Query {
public void setPointInTime(@Nullable PointInTime pointInTime) {
this.pointInTime = pointInTime;
}
/**
* used internally. Not considered part of the API.
* @since 5.0
*/
public boolean queryIsUpdatedByConverter() {
return queryIsUpdatedByConverter;
}
/**
* used internally. Not considered part of the API.
* @since 5.0
*/
public void setQueryIsUpdatedByConverter(boolean queryIsUpdatedByConverter) {
this.queryIsUpdatedByConverter = queryIsUpdatedByConverter;
}
}

View File

@ -15,12 +15,24 @@
*/
package org.springframework.data.elasticsearch.repository.query;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.util.StreamUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.util.Collections;
/**
* AbstractElasticsearchRepositoryQuery
@ -55,8 +67,76 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
*/
public abstract boolean isCountQuery();
protected void prepareQuery(Query query, Class<?> clazz, ParameterAccessor parameterAccessor) {
protected abstract boolean isDeleteQuery();
protected abstract boolean isExistsQuery();
@Override
public Object execute(Object[] parameters) {
ParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
Class<?> clazz = getResultClass();
Query query = createQuery(parameters);
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
Object result = null;
if (isDeleteQuery()) {
result = countOrGetDocumentsForDelete(query, parameterAccessor);
elasticsearchOperations.delete(query, clazz, index);
elasticsearchOperations.indexOps(index).refresh();
} else if (isCountQuery()) {
result = elasticsearchOperations.count(query, clazz, index);
} else if (isExistsQuery()) {
result = elasticsearchOperations.count(query, clazz, index) > 0;
} else if (queryMethod.isPageQuery()) {
query.setPageable(parameterAccessor.getPageable());
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
if (queryMethod.isSearchPageMethod()) {
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
} else {
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
}
} else if (queryMethod.isStreamQuery()) {
query.setPageable(parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable()
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
} else if (queryMethod.isCollectionQuery()) {
if (parameterAccessor.getPageable().isUnpaged()) {
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
if (itemCount == 0) {
result = new SearchHitsImpl<>(0, TotalHitsRelation.EQUAL_TO, Float.NaN, null,
query.getPointInTime() != null ? query.getPointInTime().id() : null, Collections.emptyList(), null, null);
} else {
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
}
} else {
query.setPageable(parameterAccessor.getPageable());
}
if (result == null) {
result = elasticsearchOperations.search(query, clazz, index);
}
} else {
result = elasticsearchOperations.searchOne(query, clazz, index);
}
return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod())
? SearchHitSupport.unwrapSearchHits(result)
: result;
}
public Query createQuery(Object[] parameters) {
Class<?> clazz = getResultClass();
ParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
Query query = createQuery(parameterAccessor);
Assert.notNull(query, "unsupported query");
if (queryMethod.hasAnnotatedHighlight()) {
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
@ -68,6 +148,44 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
query.addSourceFilter(sourceFilter);
}
elasticsearchConverter.updateQuery(query, clazz);
// todo #2338 remove that call, this should be done when the real request is built
// elasticsearchConverter.updateQuery(query, clazz);
return query;
}
private Class<?> getResultClass() {
return queryMethod.getResultProcessor().getReturnedType().getDomainType();
}
private ParametersParameterAccessor getParameterAccessor(Object[] parameters) {
return new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
}
@Nullable
private Object countOrGetDocumentsForDelete(Query query, ParametersParameterAccessor accessor) {
Object result = null;
Class<?> entityClass = queryMethod.getEntityInformation().getJavaType();
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(entityClass);
if (queryMethod.isCollectionQuery()) {
if (accessor.getPageable().isUnpaged()) {
int itemCount = (int) elasticsearchOperations.count(query, entityClass, index);
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
} else {
query.setPageable(accessor.getPageable());
}
result = elasticsearchOperations.search(query, entityClass, index);
}
if (ClassUtils.isAssignable(Number.class, queryMethod.getReturnedObjectType())) {
result = elasticsearchOperations.count(query, entityClass, index);
}
return result;
}
protected abstract Query createQuery(ParametersParameterAccessor accessor);
}

View File

@ -15,25 +15,14 @@
*/
package org.springframework.data.elasticsearch.repository.query;
import java.util.Collections;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.util.StreamUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* ElasticsearchPartQuery
@ -62,104 +51,24 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
}
@Override
public Object execute(Object[] parameters) {
protected boolean isDeleteQuery() {
return tree.isDelete();
}
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(queryMethod.getParameters(),
parameters);
@Override
protected boolean isExistsQuery() {
return tree.isExistsProjection();
}
CriteriaQuery query = createQuery(parameterAccessor);
protected Query createQuery(ParametersParameterAccessor accessor) {
Assert.notNull(query, "unsupported query");
prepareQuery(query, clazz, parameterAccessor);
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
Object result = null;
BaseQuery query = new ElasticsearchQueryCreator(tree, accessor, mappingContext).createQuery();
if (tree.isLimiting()) {
// noinspection ConstantConditions
query.setMaxResults(tree.getMaxResults());
}
if (tree.isDelete()) {
result = countOrGetDocumentsForDelete(query, parameterAccessor);
elasticsearchOperations.delete(query, clazz, index);
elasticsearchOperations.indexOps(index).refresh();
} else if (queryMethod.isPageQuery()) {
query.setPageable(parameterAccessor.getPageable());
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
if (queryMethod.isSearchPageMethod()) {
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
} else {
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
}
} else if (queryMethod.isStreamQuery()) {
if (parameterAccessor.getPageable().isUnpaged()) {
query.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
} else {
query.setPageable(parameterAccessor.getPageable());
}
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
} else if (queryMethod.isCollectionQuery()) {
if (parameterAccessor.getPageable().isUnpaged()) {
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
if (itemCount == 0) {
result = new SearchHitsImpl<>(0, TotalHitsRelation.EQUAL_TO, Float.NaN, null,
query.getPointInTime() != null ? query.getPointInTime().id() : null, Collections.emptyList(), null, null);
} else {
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
}
} else {
query.setPageable(parameterAccessor.getPageable());
}
if (result == null) {
result = elasticsearchOperations.search(query, clazz, index);
}
} else if (tree.isCountProjection()) {
result = elasticsearchOperations.count(query, clazz, index);
} else if (tree.isExistsProjection()) {
long count = elasticsearchOperations.count(query, clazz, index);
result = count > 0;
} else {
result = elasticsearchOperations.searchOne(query, clazz, index);
}
return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod())
? SearchHitSupport.unwrapSearchHits(result)
: result;
}
@Nullable
private Object countOrGetDocumentsForDelete(CriteriaQuery query, ParametersParameterAccessor accessor) {
Object result = null;
Class<?> clazz = queryMethod.getEntityInformation().getJavaType();
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
if (queryMethod.isCollectionQuery()) {
if (accessor.getPageable().isUnpaged()) {
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
} else {
query.setPageable(accessor.getPageable());
}
result = elasticsearchOperations.search(query, clazz, index);
}
if (ClassUtils.isAssignable(Number.class, queryMethod.getReturnedObjectType())) {
result = elasticsearchOperations.count(query, clazz, index);
}
return result;
}
public CriteriaQuery createQuery(ParametersParameterAccessor accessor) {
return new ElasticsearchQueryCreator(tree, accessor, mappingContext).createQuery();
return query;
}
}

View File

@ -15,17 +15,11 @@
*/
package org.springframework.data.elasticsearch.repository.query;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.util.StreamUtils;
import org.springframework.util.Assert;
/**
@ -54,56 +48,20 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
}
@Override
public Object execute(Object[] parameters) {
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(queryMethod.getParameters(),
parameters);
Query query = createQuery(parameterAccessor);
Assert.notNull(query, "unsupported query");
if (queryMethod.hasAnnotatedHighlight()) {
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
}
prepareQuery(query, clazz, parameterAccessor);
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
Object result;
if (isCountQuery()) {
result = elasticsearchOperations.count(query, clazz, index);
} else if (queryMethod.isPageQuery()) {
query.setPageable(parameterAccessor.getPageable());
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
if (queryMethod.isSearchPageMethod()) {
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
} else {
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
}
} else if (queryMethod.isStreamQuery()) {
query.setPageable(parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable()
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
} else if (queryMethod.isCollectionQuery()) {
query.setPageable(
parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable() : Pageable.unpaged());
result = elasticsearchOperations.search(query, clazz, index);
} else {
result = elasticsearchOperations.searchOne(query, clazz, index);
}
return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod())
? SearchHitSupport.unwrapSearchHits(result)
: result;
protected boolean isDeleteQuery() {
return false;
}
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
@Override
protected boolean isExistsQuery() {
return false;
}
protected Query createQuery(ParametersParameterAccessor parameterAccessor) {
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
.replacePlaceholders(this.queryString, parameterAccessor);
return new StringQuery(queryString);
}
}

View File

@ -61,7 +61,6 @@ final public class StringQueryUtil {
String parameterValue = "null";
if (parameter != null) {
parameterValue = convert(parameter);
}

View File

@ -21,8 +21,8 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.ElasticsearchPartQueryIntegrationTests;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
/**
@ -36,12 +36,11 @@ public class ElasticsearchPartQueryELCIntegrationTests extends ElasticsearchPart
static class Config {}
@Override
protected String buildQueryString(CriteriaQuery criteriaQuery, Class<?> clazz) {
protected String buildQueryString(Query query, Class<?> clazz) {
JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper();
RequestConverter requestConverter = new RequestConverter(operations.getElasticsearchConverter(), jsonpMapper);
SearchRequest request = requestConverter.searchRequest(criteriaQuery, clazz, IndexCoordinates.of("dummy"), false,
false);
SearchRequest request = requestConverter.searchRequest(query, clazz, IndexCoordinates.of("dummy"), false, false);
return JsonUtils.toJson(request, jsonpMapper);
// return "{\"query\":" + JsonUtils.toJson(request.query(), jsonpMapper) + "}";

View File

@ -19,8 +19,8 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.ElasticsearchPartQueryIntegrationTests;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.test.context.ContextConfiguration;
@ -38,9 +38,9 @@ public class ElasticsearchPartQueryERHLCIntegrationTests extends ElasticsearchPa
@Import({ ElasticsearchRestTemplateConfiguration.class })
static class Config {}
protected String buildQueryString(CriteriaQuery criteriaQuery, Class<?> clazz) {
protected String buildQueryString(Query query, Class<?> clazz) {
SearchSourceBuilder source = new RequestFactory(operations.getElasticsearchConverter())
.searchRequest(criteriaQuery, clazz, IndexCoordinates.of("dummy")).source();
.searchRequest(query, clazz, IndexCoordinates.of("dummy")).source();
// remove defaultboost values
return source.toString().replaceAll("(\\^\\d+\\.\\d+)", "");
}

View File

@ -36,7 +36,6 @@ import org.springframework.data.elasticsearch.repository.query.ElasticsearchPart
import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.lang.Nullable;
/**
@ -649,19 +648,18 @@ public abstract class ElasticsearchPartQueryIntegrationTests {
new DefaultRepositoryMetadata(SampleRepository.class), new SpelAwareProxyProjectionFactory(),
operations.getElasticsearchConverter().getMappingContext());
ElasticsearchPartQuery partQuery = new ElasticsearchPartQuery(queryMethod, operations);
CriteriaQuery criteriaQuery = partQuery
.createQuery(new ParametersParameterAccessor(queryMethod.getParameters(), parameters));
return buildQueryString(criteriaQuery, Book.class);
Query query = partQuery.createQuery(parameters);
return buildQueryString(query, Book.class);
}
/**
* builds the query String that would be sent to Elasticsearch
*
* @param criteriaQuery the {@link CriteriaQuery}
* @param query the {@link Query}
* @param clazz the entity class
* @return the created query string
*/
abstract protected String buildQueryString(CriteriaQuery criteriaQuery, Class<?> clazz);
abstract protected String buildQueryString(Query query, Class<?> clazz);
@FunctionalInterface
interface AssertFunction {

View File

@ -0,0 +1,43 @@
/*
* Copyright 2022 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.elasticsearch.repository.query.valueconverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
* @since 5.0
*/
@ContextConfiguration(classes = { ReactiveValueConverterELCIntegrationTests.Config.class })
public class ReactiveValueConverterELCIntegrationTests extends ReactiveValueConverterIntegrationTests {
@Configuration
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("reactive-valueconverter");
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2022 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.elasticsearch.repository.query.valueconverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
* @since 5.0
*/
@ContextConfiguration(classes = { ReactiveValueConverterERHLCIntegrationTests.Config.class })
public class ReactiveValueConverterERHLCIntegrationTests extends ReactiveValueConverterIntegrationTests {
@Configuration
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("reactive-valueconverter-es7");
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2022 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.elasticsearch.repository.query.valueconverter;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.annotations.ValueConverter;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.lang.Boolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
/**
* Integration tests to check that {@link org.springframework.data.elasticsearch.annotations.ValueConverter} annotated
* properties are handle correctly (method name derived queries, for
*
* @{@link org.springframework.data.elasticsearch.core.query.Query} methods we don't know which parameters map to which
* property.
* @author Peter-Josef Meisch
*/
@SpringIntegrationTest
public abstract class ReactiveValueConverterIntegrationTests {
@Autowired private IndexNameProvider indexNameProvider;
@Autowired private ReactiveElasticsearchOperations operations;
@Autowired private EntityRepository repository;
@BeforeEach
void setUp() {
indexNameProvider.increment();
operations.indexOps(Entity.class).createWithMapping().block();
}
@Test
@Order(java.lang.Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete().block();
}
@Test // #2338
@DisplayName("should apply ValueConverter")
void shouldApplyValueConverter() {
ValueConverterIntegrationTests.Entity entity = new ValueConverterIntegrationTests.Entity();
entity.setId("42");
entity.setText("answer");
operations.save(entity).block();
repository.queryByText("text-answer") //
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
repository.findByText("answer") //
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
}
interface EntityRepository extends ReactiveElasticsearchRepository<Entity, String> {
Flux<SearchHit<Entity>> findByText(String text);
@Query("{ \"term\": { \"text\": \"?0\" } }")
Flux<SearchHit<Entity>> queryByText(String text);
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Entity {
@Id
@Nullable private String id;
@Field(type = FieldType.Keyword)
@ValueConverter(ValueConverterIntegrationTests.TextConverter.class)
@Nullable private String text;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public String getText() {
return text;
}
public void setText(@Nullable String text) {
this.text = text;
}
}
static class TextConverter implements PropertyValueConverter {
public static final String PREFIX = "text-";
@Override
public Object write(Object value) {
return PREFIX + value.toString();
}
@Override
public Object read(Object value) {
String valueString = value.toString();
if (valueString.startsWith(PREFIX)) {
return valueString.substring(PREFIX.length());
} else {
return value;
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2022 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.elasticsearch.repository.query.valueconverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* {@link ValueConverterIntegrationTests} using a Repository backed by an ElasticsearchTemplate.
*
* @author Peter-Josef Meisch
* @since 5.0
*/
@ContextConfiguration(classes = { ValueConverterELCIntegrationTests.Config.class })
public class ValueConverterELCIntegrationTests extends ValueConverterIntegrationTests {
@Configuration
@Import({ ElasticsearchTemplateConfiguration.class })
@EnableElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("valueconverter");
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2022 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.elasticsearch.repository.query.valueconverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* {@link ValueConverterIntegrationTests} using a Repository backed by an ElasticsearchTemplate.
*
* @author Peter-Josef Meisch
* @since 5.0
*/
@ContextConfiguration(classes = { ValueConverterERHLCIntegrationTests.Config.class })
public class ValueConverterERHLCIntegrationTests extends ValueConverterIntegrationTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
@EnableElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("valueconverter-es7");
}
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2022 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.elasticsearch.repository.query.valueconverter;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.annotations.ValueConverter;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
/**
* Integration tests to check that {@link org.springframework.data.elasticsearch.annotations.ValueConverter} annotated
* properties are handle correctly (method name derived queries, for
*
* @{@link org.springframework.data.elasticsearch.core.query.Query} methods we don't know which parameters map to which
* property.
* @author Peter-Josef Meisch
*/
@SpringIntegrationTest
abstract class ValueConverterIntegrationTests {
@Autowired private EntityRepository repository;
@Autowired ElasticsearchOperations operations;
@Autowired IndexNameProvider indexNameProvider;
@BeforeEach
public void before() {
indexNameProvider.increment();
operations.indexOps(Entity.class).createWithMapping();
}
@Test
@Order(Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete();
}
@Test // #2338
@DisplayName("should apply ValueConverter")
void shouldApplyValueConverter() {
Entity entity = new Entity();
entity.setId("42");
entity.setText("answer");
operations.save(entity);
SearchHits<Entity> searchHits = repository.queryByText("text-answer");
assertThat(searchHits.getTotalHits()).isEqualTo(1);
searchHits = repository.findByText("answer");
assertThat(searchHits.getTotalHits()).isEqualTo(1);
}
interface EntityRepository extends ElasticsearchRepository<Entity, String> {
SearchHits<Entity> findByText(String text);
@Query("{ \"term\": { \"text\": \"?0\" } }")
SearchHits<Entity> queryByText(String text);
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Entity {
@Id
@Nullable private String id;
@Field(type = FieldType.Keyword)
@ValueConverter(TextConverter.class)
@Nullable private String text;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public String getText() {
return text;
}
public void setText(@Nullable String text) {
this.text = text;
}
}
static class TextConverter implements PropertyValueConverter {
public static final String PREFIX = "text-";
@Override
public Object write(Object value) {
return PREFIX + value.toString();
}
@Override
public Object read(Object value) {
String valueString = value.toString();
if (valueString.startsWith(PREFIX)) {
return valueString.substring(PREFIX.length());
} else {
return value;
}
}
}
}