diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index acc20ed63..3f81731ae 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013-2020 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.core; import java.util.ArrayList; @@ -13,14 +28,17 @@ import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.search.MultiSearchRequest; import org.elasticsearch.action.search.MultiSearchResponse; +import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.data.convert.EntityReader; import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.event.AfterSaveCallback; import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback; @@ -38,6 +56,7 @@ import org.springframework.data.elasticsearch.support.VersionInfo; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.util.CloseableIterator; import org.springframework.data.util.Streamable; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -235,7 +254,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper return StreamQueries.streamResults( // searchScrollStart(scrollTimeInMillis, query, clazz, index), // - scrollId -> searchScrollContinue(scrollId, scrollTimeInMillis, clazz), // + scrollId -> searchScrollContinue(scrollId, scrollTimeInMillis, clazz, index), // this::searchScrollClear); } @@ -262,10 +281,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper MultiSearchResponse.Item[] items = getMultiSearchResult(request); + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); List> res = new ArrayList<>(queries.size()); int c = 0; for (Query query : queries) { - res.add(elasticsearchConverter.read(clazz, SearchDocumentResponse.from(items[c++].getResponse()))); + res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse()))); } return res; } @@ -285,7 +305,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper int c = 0; Iterator> it1 = classes.iterator(); for (Query query : queries) { - res.add(elasticsearchConverter.read(it1.next(), SearchDocumentResponse.from(items[c++].getResponse()))); + Class entityClass = it1.next(); + + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>( + entityClass, index); + + SearchResponse response = items[c++].getResponse(); + res.add(callback.doWith(SearchDocumentResponse.from(response))); } return res; } @@ -305,7 +331,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper * internal use only, not for public API */ abstract protected SearchScrollHits searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, - Class clazz); + Class clazz, IndexCoordinates index); /* * internal use only, not for public API @@ -497,4 +523,82 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper // endregion + protected interface DocumentCallback { + + @Nullable + T doWith(@Nullable Document document); + } + + protected static class ReadDocumentCallback implements DocumentCallback { + private final EntityReader reader; + private final Class type; + private final IndexCoordinates index; + + public ReadDocumentCallback(EntityReader reader, Class type, IndexCoordinates index) { + Assert.notNull(reader, "reader is null"); + Assert.notNull(type, "type is null"); + + this.reader = reader; + this.type = type; + this.index = index; + } + + @Nullable + public T doWith(@Nullable Document document) { + if (document == null) { + return null; + } + + return reader.read(type, document); + } + } + + protected interface SearchDocumentResponseCallback { + + @NonNull + T doWith(@NonNull SearchDocumentResponse response); + } + + protected class ReadSearchDocumentResponseCallback implements SearchDocumentResponseCallback> { + private final DocumentCallback delegate; + private final Class type; + + public ReadSearchDocumentResponseCallback(Class type, IndexCoordinates index) { + Assert.notNull(type, "type is null"); + + this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index); + this.type = type; + } + + @Override + public SearchHits doWith(SearchDocumentResponse response) { + List entities = response.getSearchDocuments().stream() + .map(delegate::doWith) + .collect(Collectors.toList()); + return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()) + .mapHits(response, entities); + } + } + + protected class ReadSearchScrollDocumentResponseCallback + implements SearchDocumentResponseCallback> { + private final DocumentCallback delegate; + private final Class type; + + public ReadSearchScrollDocumentResponseCallback(Class type, IndexCoordinates index) { + Assert.notNull(type, "type is null"); + + this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index); + this.type = type; + } + + @Override + public SearchScrollHits doWith(SearchDocumentResponse response) { + List entities = response.getSearchDocuments().stream() + .map(delegate::doWith) + .collect(Collectors.toList()); + return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()) + .mapScrollHits(response, entities); + } + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 9ce3c5285..7a3a70be3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core; import java.io.IOException; import java.util.List; +import java.util.stream.Collectors; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.delete.DeleteRequest; @@ -156,7 +157,9 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { public T get(String id, Class clazz, IndexCoordinates index) { GetRequest request = requestFactory.getRequest(id, index); GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT)); - return elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz); + + DocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + return callback.doWith(DocumentAdapters.from(response)); } @Override @@ -167,7 +170,11 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { MultiGetRequest request = requestFactory.multiGetRequest(query, index); MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT)); - return elasticsearchConverter.mapDocuments(DocumentAdapters.from(result), clazz); + + DocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + return DocumentAdapters.from(result).stream() + .map(callback::doWith) + .collect(Collectors.toList()); } @Override @@ -258,7 +265,9 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { public SearchHits search(Query query, Class clazz, IndexCoordinates index) { SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index); SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT)); - return elasticsearchConverter.read(clazz, SearchDocumentResponse.from(response)); + + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); + return callback.doWith(SearchDocumentResponse.from(response)); } @Override @@ -272,19 +281,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT)); - return elasticsearchConverter.readScroll(clazz, SearchDocumentResponse.from(response)); + SearchDocumentResponseCallback> callback = new ReadSearchScrollDocumentResponseCallback<>( + clazz, index); + return callback.doWith(SearchDocumentResponse.from(response)); } @Override public SearchScrollHits searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, - Class clazz) { + Class clazz, IndexCoordinates index) { SearchScrollRequest request = new SearchScrollRequest(scrollId); request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis)); SearchResponse response = execute(client -> client.scroll(request, RequestOptions.DEFAULT)); - return elasticsearchConverter.readScroll(clazz, SearchDocumentResponse.from(response)); + SearchDocumentResponseCallback> callback = // + new ReadSearchScrollDocumentResponseCallback<>(clazz, index); + return callback.doWith(SearchDocumentResponse.from(response)); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 6b94aadeb..72fee6f88 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.core; import java.util.List; +import java.util.stream.Collectors; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; @@ -37,6 +38,7 @@ import org.elasticsearch.search.suggest.SuggestBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -161,7 +163,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { public T get(String id, Class clazz, IndexCoordinates index) { GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, index); GetResponse response = getRequestBuilder.execute().actionGet(); - return elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz); + + DocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + return callback.doWith(DocumentAdapters.from(response)); } @Override @@ -172,7 +176,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, index); - return elasticsearchConverter.mapDocuments(DocumentAdapters.from(builder.execute().actionGet()), clazz); + DocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + List documents = DocumentAdapters.from(builder.execute().actionGet()); + return documents.stream() + .map(callback::doWith) + .collect(Collectors.toList()); } @Override @@ -265,7 +273,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { public SearchHits search(Query query, Class clazz, IndexCoordinates index) { SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index); SearchResponse response = getSearchResponse(searchRequestBuilder); - return elasticsearchConverter.read(clazz, SearchDocumentResponse.from(response)); + + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); + return callback.doWith(SearchDocumentResponse.from(response)); } @Override @@ -281,12 +291,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { SearchResponse response = getSearchResponseWithTimeout(action); - return elasticsearchConverter.readScroll(clazz, SearchDocumentResponse.from(response)); + SearchDocumentResponseCallback> callback = new ReadSearchScrollDocumentResponseCallback<>( + clazz, index); + return callback.doWith(SearchDocumentResponse.from(response)); } @Override public SearchScrollHits searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, - Class clazz) { + Class clazz, IndexCoordinates index) { ActionFuture action = client // .prepareSearchScroll(scrollId) // @@ -295,7 +307,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { SearchResponse response = getSearchResponseWithTimeout(action); - return elasticsearchConverter.readScroll(clazz, SearchDocumentResponse.from(response)); + SearchDocumentResponseCallback> callback = new ReadSearchScrollDocumentResponseCallback<>( + clazz, index); + return callback.doWith(SearchDocumentResponse.from(response)); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 2e1de1507..6a6d65fc6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -59,6 +59,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.data.convert.EntityReader; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.NoSuchIndexException; @@ -87,6 +88,7 @@ import org.springframework.data.elasticsearch.support.VersionInfo; import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.data.mapping.context.MappingContext; import org.springframework.http.HttpStatus; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -232,16 +234,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera Assert.notNull(query, "Query must not be null"); Assert.notEmpty(query.getIds(), "No Id define for Query"); + DocumentCallback callback = new ReadDocumentCallback<>(converter, clazz, index); + MultiGetRequest request = requestFactory.multiGetRequest(query, index); return Flux.from(execute(client -> client.multiGet(request))) // - .handle((result, sink) -> { - - Document document = DocumentAdapters.from(result); - T entity = converter.mapDocument(document, clazz); - if (entity != null) { - sink.next(entity); - } - }); + .concatMap(result -> callback.doWith(DocumentAdapters.from(result))); } @Override @@ -389,8 +386,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera Assert.notNull(id, "Id must not be null!"); + DocumentCallback callback = new ReadDocumentCallback<>(converter, entityType, index); + return doGet(id, getPersistentEntityFor(entityType), index) - .map(it -> converter.mapDocument(DocumentAdapters.from(it), entityType)); + .flatMap(it -> callback.doWith(DocumentAdapters.from(it))); } private Mono doGet(String id, ElasticsearchPersistentEntity entity, IndexCoordinates index) { @@ -581,8 +580,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera // region SearchOperations @Override public Flux> search(Query query, Class entityType, Class resultType, IndexCoordinates index) { - - return doFind(query, entityType, index).map(searchDocument -> converter.read(resultType, searchDocument)); + SearchDocumentCallback callback = new ReadSearchDocumentCallback<>(resultType, index); + return doFind(query, entityType, index).concatMap(callback::doWith); } @Override @@ -894,4 +893,60 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera return Mono.just(entity); } // endregion + + protected interface DocumentCallback { + + @NonNull + Mono doWith(@Nullable Document document); + } + + protected static class ReadDocumentCallback implements DocumentCallback { + private final EntityReader reader; + private final Class type; + private final IndexCoordinates index; + + public ReadDocumentCallback(EntityReader reader, Class type, IndexCoordinates index) { + Assert.notNull(reader, "reader is null"); + Assert.notNull(type, "type is null"); + + this.reader = reader; + this.type = type; + this.index = index; + } + + @NonNull + public Mono doWith(@Nullable Document document) { + if (document == null) { + return Mono.empty(); + } + + T entity = reader.read(type, document); + return Mono.just(entity); + } + } + + protected interface SearchDocumentCallback { + + @NonNull + Mono> doWith(@NonNull SearchDocument response); + } + + protected class ReadSearchDocumentCallback implements SearchDocumentCallback { + private final DocumentCallback delegate; + private final Class type; + + public ReadSearchDocumentCallback(Class type, IndexCoordinates index) { + Assert.notNull(type, "type is null"); + + this.delegate = new ReadDocumentCallback<>(converter, type, index); + this.type = type; + } + + @Override + public Mono> doWith(SearchDocument response) { + return delegate.doWith(response) + .map(entity -> SearchHitMapping.mappingFor(type, converter.getMappingContext()) + .mapHit(response, entity)); + } + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java new file mode 100644 index 000000000..43524ac92 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java @@ -0,0 +1,127 @@ +/* + * Copyright 2020 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.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.elasticsearch.search.aggregations.Aggregations; +import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * @author Rizwan Idrees + * @author Mohsin Husen + * @author Christoph Strobl + * @author Peter-Josef Meisch + * @author Mark Paluch + * @author Roman Puchkovskiy + * @since 4.0 + */ +class SearchHitMapping { + private final Class type; + private final MappingContext, ElasticsearchPersistentProperty> mappingContext; + + private SearchHitMapping(Class type, + MappingContext, ElasticsearchPersistentProperty> context) { + + Assert.notNull(type, "type is null"); + Assert.notNull(context, "context is null"); + + this.type = type; + this.mappingContext = context; + } + + static SearchHitMapping mappingFor(Class entityClass, + MappingContext, ElasticsearchPersistentProperty> context) { + return new SearchHitMapping<>(entityClass, context); + } + + SearchHit mapHit(SearchDocument searchDocument, T content) { + + Assert.notNull(searchDocument, "searchDocument is null"); + Assert.notNull(content, "content is null"); + + String id = searchDocument.hasId() ? searchDocument.getId() : null; + float score = searchDocument.getScore(); + Object[] sortValues = searchDocument.getSortValues(); + Map> highlightFields = getHighlightsAndRemapFieldNames(searchDocument); + + return new SearchHit<>(id, score, sortValues, highlightFields, content); + } + + SearchHits mapHits(SearchDocumentResponse searchDocumentResponse, List contents) { + return mapHitsFromResponse(searchDocumentResponse, contents); + } + + SearchScrollHits mapScrollHits(SearchDocumentResponse searchDocumentResponse, List contents) { + return mapHitsFromResponse(searchDocumentResponse, contents); + } + + private SearchHitsImpl mapHitsFromResponse(SearchDocumentResponse searchDocumentResponse, List contents) { + + Assert.notNull(searchDocumentResponse, "searchDocumentResponse is null"); + Assert.notNull(contents, "contents is null"); + + Assert.isTrue(searchDocumentResponse.getSearchDocuments().size() == contents.size(), + "Count of documents must match the count of entities"); + + long totalHits = searchDocumentResponse.getTotalHits(); + float maxScore = searchDocumentResponse.getMaxScore(); + String scrollId = searchDocumentResponse.getScrollId(); + + List> searchHits = new ArrayList<>(); + List searchDocuments = searchDocumentResponse.getSearchDocuments(); + for (int i = 0; i < searchDocuments.size(); i++) { + SearchDocument document = searchDocuments.get(i); + T content = contents.get(i); + SearchHit hit = mapHit(document, content); + searchHits.add(hit); + } + Aggregations aggregations = searchDocumentResponse.getAggregations(); + TotalHitsRelation totalHitsRelation = TotalHitsRelation + .valueOf(searchDocumentResponse.getTotalHitsRelation()); + + return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations); + } + + @Nullable + private Map> getHighlightsAndRemapFieldNames(SearchDocument searchDocument) { + Map> highlightFields = searchDocument.getHighlightFields(); + + if (highlightFields == null) { + return null; + } + + ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); + if (persistentEntity == null) { + return highlightFields; + } + + return highlightFields.entrySet().stream().collect(Collectors.toMap(entry -> { + ElasticsearchPersistentProperty property = persistentEntity.getPersistentPropertyWithFieldName + (entry.getKey()); + return property != null ? property.getName() : entry.getKey(); + }, Map.Entry::getValue)); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java index eeac39123..435763f82 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java @@ -15,16 +15,8 @@ */ package org.springframework.data.elasticsearch.core.convert; -import java.util.List; -import java.util.stream.Collectors; - import org.springframework.data.convert.EntityConverter; -import org.springframework.data.elasticsearch.core.SearchHit; -import org.springframework.data.elasticsearch.core.SearchHits; -import org.springframework.data.elasticsearch.core.SearchScrollHits; import org.springframework.data.elasticsearch.core.document.Document; -import org.springframework.data.elasticsearch.core.document.SearchDocument; -import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; @@ -39,6 +31,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Peter-Josef Meisch * @author Sasch Woo + * @author Roman Puchkovskiy */ public interface ElasticsearchConverter extends EntityConverter, ElasticsearchPersistentProperty, Object, Document> { @@ -53,67 +46,6 @@ public interface ElasticsearchConverter return new SpelAwareProxyProjectionFactory(); } - // region read - /** - * Map a single {@link Document} to an instance of the given type. - * - * @param document the document to map - * @param type must not be {@literal null}. - * @param the class of type - * @return can be {@literal null} if the document is null or {@link Document#isEmpty()} is true. - * @since 4.0 - */ - @Nullable - T mapDocument(@Nullable Document document, Class type); - - /** - * Map a list of {@link Document}s to a list of instance of the given type. - * - * @param documents must not be {@literal null}. - * @param type must not be {@literal null}. - * @param the class of type - * @return a list obtained by calling {@link #mapDocument(Document, Class)} on the elements of the list. - * @since 4.0 - */ - default List mapDocuments(List documents, Class type) { - return documents.stream().map(document -> mapDocument(document, type)).collect(Collectors.toList()); - } - - /** - * builds a {@link SearchHits} from a {@link SearchDocumentResponse}. - * - * @param the clazz of the type, must not be {@literal null}. - * @param type the type of the returned data, must not be {@literal null}. - * @param searchDocumentResponse the response to read from, must not be {@literal null}. - * @return a SearchHits object - * @since 4.0 - */ - SearchHits read(Class type, SearchDocumentResponse searchDocumentResponse); - - /** - * builds a {@link SearchScrollHits} from a {@link SearchDocumentResponse}. - * - * @param the clazz of the type, must not be {@literal null}. - * @param type the type of the returned data, must not be {@literal null}. - * @param searchDocumentResponse the response to read from, must not be {@literal null}. - * @return a {@link SearchScrollHits} object - * @since 4.0 - */ - SearchScrollHits readScroll(Class type, SearchDocumentResponse searchDocumentResponse); - - /** - * builds a {@link SearchHit} from a {@link SearchDocument}. - * - * @param the clazz of the type, must not be {@literal null}. - * @param type the type of the returned data, must not be {@literal null}. - * @param searchDocument must not be {@literal null} - * @return SearchHit with all available information filled in - * @since 4.0 - */ - SearchHit read(Class type, SearchDocument searchDocument); - - // endregion - // region write /** * Convert a given {@literal idValue} to its {@link String} representation taking potentially registered 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 566bf765b..6691656cf 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 @@ -15,20 +15,9 @@ */ package org.springframework.data.elasticsearch.core.convert; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.elasticsearch.search.aggregations.Aggregations; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; @@ -40,14 +29,8 @@ import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.CustomConversions; import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.annotations.ScriptedField; -import org.springframework.data.elasticsearch.core.SearchScrollHits; -import org.springframework.data.elasticsearch.core.SearchHit; -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.document.Document; import org.springframework.data.elasticsearch.core.document.SearchDocument; -import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter; @@ -77,6 +60,7 @@ import org.springframework.util.ObjectUtils; * @author Christoph Strobl * @author Peter-Josef Meisch * @author Mark Paluch + * @author Roman Puchkovskiy * @since 3.2 */ public class MappingElasticsearchConverter @@ -146,83 +130,6 @@ public class MappingElasticsearchConverter // region read - @Override - public SearchHits read(Class type, SearchDocumentResponse searchDocumentResponse) { - return readResponse(type, searchDocumentResponse); - } - - @Override - public SearchHit read(Class type, SearchDocument searchDocument) { - - Assert.notNull(type, "type must not be null"); - Assert.notNull(searchDocument, "searchDocument must not be null"); - - String id = searchDocument.hasId() ? searchDocument.getId() : null; - float score = searchDocument.getScore(); - Object[] sortValues = searchDocument.getSortValues(); - Map> highlightFields = getHighlightsAndRemapFieldNames(type, searchDocument); - T content = mapDocument(searchDocument, type); - - return new SearchHit(id, score, sortValues, highlightFields, content); - } - - @Override - public SearchScrollHits readScroll(Class type, SearchDocumentResponse searchDocumentResponse) { - return readResponse(type, searchDocumentResponse); - } - - private SearchHitsImpl readResponse(Class type, SearchDocumentResponse searchDocumentResponse) { - - Assert.notNull(type, "type must not be null"); - Assert.notNull(searchDocumentResponse, "searchDocumentResponse must not be null"); - - long totalHits = searchDocumentResponse.getTotalHits(); - float maxScore = searchDocumentResponse.getMaxScore(); - String scrollId = searchDocumentResponse.getScrollId(); - List> searchHits = searchDocumentResponse.getSearchDocuments().stream() // - .map(searchDocument -> read(type, searchDocument)) // - .collect(Collectors.toList()); - Aggregations aggregations = searchDocumentResponse.getAggregations(); - TotalHitsRelation totalHitsRelation = TotalHitsRelation - .valueOf(searchDocumentResponse.getTotalHitsRelation()); - - return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations); - } - - @Nullable - private Map> getHighlightsAndRemapFieldNames(Class type, SearchDocument searchDocument) { - Map> highlightFields = searchDocument.getHighlightFields(); - - if (highlightFields == null) { - return null; - } - - ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); - if (persistentEntity == null) { - return highlightFields; - } - - return highlightFields.entrySet().stream().collect(Collectors.toMap(entry -> { - ElasticsearchPersistentProperty property = persistentEntity.getPersistentPropertyWithFieldName(entry.getKey()); - return property != null ? property.getName() : entry.getKey(); - }, Entry::getValue)); - } - - @Override - @Nullable - public T mapDocument(@Nullable Document document, Class type) { - - if (document == null) { - return null; - } - - T mappedResult = read(type, document); - - return type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult) - ? getProjectionFactory().createProjection(type, mappedResult) - : type.cast(mappedResult); - } - @SuppressWarnings("unchecked") @Override public R read(Class type, Document source) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 145a8784f..d94ccc17f 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -1093,7 +1093,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); assertThat(sampleEntities).hasSize(30); @@ -1120,7 +1120,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); assertThat(sampleEntities).hasSize(30); @@ -1148,7 +1148,8 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); - scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class); + scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1175,7 +1176,8 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); - scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class); + scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1202,7 +1204,8 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); - scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class); + scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1229,7 +1232,8 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); - scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class); + scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1256,7 +1260,8 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); - scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class); + scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1283,7 +1288,8 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); - scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class); + scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1568,7 +1574,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), - scrollTimeInMillis, SampleEntity.class); + scrollTimeInMillis, SampleEntity.class, index); entities.addAll(scroll.getSearchHits()); } @@ -2469,7 +2475,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); @@ -2507,7 +2513,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); @@ -2540,7 +2546,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); assertThat(sampleEntities).hasSize(3); @@ -2587,7 +2593,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } // then @@ -2636,7 +2642,7 @@ public abstract class ElasticsearchTemplateTests { while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class); + SampleEntity.class, index); } // then diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index b3bb07cc1..aaf5e76fe 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -253,11 +253,11 @@ public class MappingElasticsearchConverterUnitTests { } @Test // DATAES-530 - public void shouldMapJsonStringToObject() { + public void shouldReadJsonStringToObject() { // Given // When - Car result = mappingElasticsearchConverter.mapDocument(Document.parse(JSON_STRING), Car.class); + Car result = mappingElasticsearchConverter.read(Car.class, Document.parse(JSON_STRING)); // Then assertThat(result).isNotNull();