From e67150a55b4639405bcf49b52a6894a191805796 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 19 Oct 2022 21:52:49 +0200 Subject: [PATCH] Fix repository methods value converting. Original Pull Request #2339 Closes #2338 --- .../MappingElasticsearchConverter.java | 12 ++ .../elasticsearch/core/query/BaseQuery.java | 18 +++ .../AbstractElasticsearchRepositoryQuery.java | 124 +++++++++++++- .../query/ElasticsearchPartQuery.java | 115 ++----------- .../query/ElasticsearchStringQuery.java | 62 ++----- .../repository/support/StringQueryUtil.java | 1 - ...ticsearchPartQueryELCIntegrationTests.java | 7 +- ...csearchPartQueryERHLCIntegrationTests.java | 6 +- ...lasticsearchPartQueryIntegrationTests.java | 10 +- ...tiveValueConverterELCIntegrationTests.java | 43 +++++ ...veValueConverterERHLCIntegrationTests.java | 43 +++++ ...eactiveValueConverterIntegrationTests.java | 153 ++++++++++++++++++ .../ValueConverterELCIntegrationTests.java | 44 +++++ .../ValueConverterERHLCIntegrationTests.java | 44 +++++ .../ValueConverterIntegrationTests.java | 140 ++++++++++++++++ 15 files changed, 650 insertions(+), 172 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterIntegrationTests.java diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 6f10508c8..88f5a8fbf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -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) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java index 825f188f2..fb362704a 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java @@ -74,6 +74,8 @@ public class BaseQuery implements Query { protected final List runtimeFields = new ArrayList<>(); @Nullable protected PointInTime pointInTime; + private boolean queryIsUpdatedByConverter = false; + public BaseQuery() {} public > BaseQuery(BaseQueryBuilder 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; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java index e983d65de..ef2951516 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java @@ -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); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java index 9ea2e5aa2..214c76b26 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java @@ -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; } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java index 0d857b302..5cd6a4189 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java @@ -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); } - } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java index 6b02b6093..d5ce36337 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java @@ -61,7 +61,6 @@ final public class StringQueryUtil { String parameterValue = "null"; if (parameter != null) { - parameterValue = convert(parameter); } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java index 58f65d655..46507630a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java @@ -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) + "}"; diff --git a/src/test/java/org/springframework/data/elasticsearch/client/erhlc/ElasticsearchPartQueryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/erhlc/ElasticsearchPartQueryERHLCIntegrationTests.java index 4b0d4459c..f9ea259a2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/erhlc/ElasticsearchPartQueryERHLCIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/erhlc/ElasticsearchPartQueryERHLCIntegrationTests.java @@ -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+)", ""); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java index f360a5432..ef49e3800 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java @@ -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 { diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterELCIntegrationTests.java new file mode 100644 index 000000000..5ab5fc60b --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterELCIntegrationTests.java @@ -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"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterERHLCIntegrationTests.java new file mode 100644 index 000000000..ac16d0dab --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterERHLCIntegrationTests.java @@ -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"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterIntegrationTests.java new file mode 100644 index 000000000..e214025fe --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ReactiveValueConverterIntegrationTests.java @@ -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 { + Flux> findByText(String text); + + @Query("{ \"term\": { \"text\": \"?0\" } }") + Flux> 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; + } + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterELCIntegrationTests.java new file mode 100644 index 000000000..6bd1c087a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterELCIntegrationTests.java @@ -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"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterERHLCIntegrationTests.java new file mode 100644 index 000000000..be0984d35 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterERHLCIntegrationTests.java @@ -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"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterIntegrationTests.java new file mode 100644 index 000000000..d46ebef61 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/valueconverter/ValueConverterIntegrationTests.java @@ -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 searchHits = repository.queryByText("text-answer"); + assertThat(searchHits.getTotalHits()).isEqualTo(1); + + searchHits = repository.findByText("answer"); + assertThat(searchHits.getTotalHits()).isEqualTo(1); + } + + interface EntityRepository extends ElasticsearchRepository { + SearchHits findByText(String text); + + @Query("{ \"term\": { \"text\": \"?0\" } }") + SearchHits 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; + } + } + } +}