DATAES-786 - Move the creation of SearchHit(s) from ElasticsearchConverter closer to ElasticsearchTemplate..

Original PR: #427
This commit is contained in:
Roman Puchkovskiy 2020-04-16 08:50:16 +04:00 committed by GitHub
parent 39fb25cbb9
commit ff08d06c45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 365 additions and 207 deletions

View File

@ -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<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> 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<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
res.add(elasticsearchConverter.read(it1.next(), SearchDocumentResponse.from(items[c++].getResponse())));
Class entityClass = it1.next();
SearchDocumentResponseCallback<SearchHits<?>> 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 <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
Class<T> clazz);
Class<T> clazz, IndexCoordinates index);
/*
* internal use only, not for public API
@ -497,4 +523,82 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
// endregion
protected interface DocumentCallback<T> {
@Nullable
T doWith(@Nullable Document document);
}
protected static class ReadDocumentCallback<T> implements DocumentCallback<T> {
private final EntityReader<? super T, Document> reader;
private final Class<T> type;
private final IndexCoordinates index;
public ReadDocumentCallback(EntityReader<? super T, Document> reader, Class<T> 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<T> {
@NonNull
T doWith(@NonNull SearchDocumentResponse response);
}
protected class ReadSearchDocumentResponseCallback<T> implements SearchDocumentResponseCallback<SearchHits<T>> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchDocumentResponseCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index);
this.type = type;
}
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream()
.map(delegate::doWith)
.collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext())
.mapHits(response, entities);
}
}
protected class ReadSearchScrollDocumentResponseCallback<T>
implements SearchDocumentResponseCallback<SearchScrollHits<T>> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchScrollDocumentResponseCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index);
this.type = type;
}
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream()
.map(delegate::doWith)
.collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext())
.mapScrollHits(response, entities);
}
}
}

View File

@ -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> T get(String id, Class<T> 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<T> 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<T> 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 <T> SearchHits<T> search(Query query, Class<T> 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<SearchHits<T>> 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<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(
clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
Class<T> clazz) {
Class<T> 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<SearchScrollHits<T>> callback = //
new ReadSearchScrollDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override

View File

@ -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> T get(String id, Class<T> clazz, IndexCoordinates index) {
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, index);
GetResponse response = getRequestBuilder.execute().actionGet();
return elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz);
DocumentCallback<T> 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<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
List<Document> 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 <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
SearchResponse response = getSearchResponse(searchRequestBuilder);
return elasticsearchConverter.read(clazz, SearchDocumentResponse.from(response));
SearchDocumentResponseCallback<SearchHits<T>> 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<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(
clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
Class<T> clazz) {
Class<T> clazz, IndexCoordinates index) {
ActionFuture<SearchResponse> 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<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(
clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override

View File

@ -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<T> 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<T> 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<GetResult> doGet(String id, ElasticsearchPersistentEntity<?> entity, IndexCoordinates index) {
@ -581,8 +580,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
// region SearchOperations
@Override
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
return doFind(query, entityType, index).map(searchDocument -> converter.read(resultType, searchDocument));
SearchDocumentCallback<T> 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<T> {
@NonNull
Mono<T> doWith(@Nullable Document document);
}
protected static class ReadDocumentCallback<T> implements DocumentCallback<T> {
private final EntityReader<? super T, Document> reader;
private final Class<T> type;
private final IndexCoordinates index;
public ReadDocumentCallback(EntityReader<? super T, Document> reader, Class<T> 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<T> doWith(@Nullable Document document) {
if (document == null) {
return Mono.empty();
}
T entity = reader.read(type, document);
return Mono.just(entity);
}
}
protected interface SearchDocumentCallback<T> {
@NonNull
Mono<SearchHit<T>> doWith(@NonNull SearchDocument response);
}
protected class ReadSearchDocumentCallback<T> implements SearchDocumentCallback<T> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchDocumentCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(converter, type, index);
this.type = type;
}
@Override
public Mono<SearchHit<T>> doWith(SearchDocument response) {
return delegate.doWith(response)
.map(entity -> SearchHitMapping.mappingFor(type, converter.getMappingContext())
.mapHit(response, entity));
}
}
}

View File

@ -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<T> {
private final Class<T> type;
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private SearchHitMapping(Class<T> type,
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(type, "type is null");
Assert.notNull(context, "context is null");
this.type = type;
this.mappingContext = context;
}
static <T> SearchHitMapping<T> mappingFor(Class<T> entityClass,
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
return new SearchHitMapping<>(entityClass, context);
}
SearchHit<T> 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<String, List<String>> highlightFields = getHighlightsAndRemapFieldNames(searchDocument);
return new SearchHit<>(id, score, sortValues, highlightFields, content);
}
SearchHits<T> mapHits(SearchDocumentResponse searchDocumentResponse, List<T> contents) {
return mapHitsFromResponse(searchDocumentResponse, contents);
}
SearchScrollHits<T> mapScrollHits(SearchDocumentResponse searchDocumentResponse, List<T> contents) {
return mapHitsFromResponse(searchDocumentResponse, contents);
}
private SearchHitsImpl<T> mapHitsFromResponse(SearchDocumentResponse searchDocumentResponse, List<T> 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<SearchHit<T>> searchHits = new ArrayList<>();
List<SearchDocument> searchDocuments = searchDocumentResponse.getSearchDocuments();
for (int i = 0; i < searchDocuments.size(); i++) {
SearchDocument document = searchDocuments.get(i);
T content = contents.get(i);
SearchHit<T> 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<String, List<String>> getHighlightsAndRemapFieldNames(SearchDocument searchDocument) {
Map<String, List<String>> 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));
}
}

View File

@ -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<ElasticsearchPersistentEntity<?>, 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 <T> 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> T mapDocument(@Nullable Document document, Class<T> 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 <T> the class of type
* @return a list obtained by calling {@link #mapDocument(Document, Class)} on the elements of the list.
* @since 4.0
*/
default <T> List<T> mapDocuments(List<Document> documents, Class<T> type) {
return documents.stream().map(document -> mapDocument(document, type)).collect(Collectors.toList());
}
/**
* builds a {@link SearchHits} from a {@link SearchDocumentResponse}.
*
* @param <T> 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
*/
<T> SearchHits<T> read(Class<T> type, SearchDocumentResponse searchDocumentResponse);
/**
* builds a {@link SearchScrollHits} from a {@link SearchDocumentResponse}.
*
* @param <T> 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
*/
<T> SearchScrollHits<T> readScroll(Class<T> type, SearchDocumentResponse searchDocumentResponse);
/**
* builds a {@link SearchHit} from a {@link SearchDocument}.
*
* @param <T> 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
*/
<T> SearchHit<T> read(Class<T> type, SearchDocument searchDocument);
// endregion
// region write
/**
* Convert a given {@literal idValue} to its {@link String} representation taking potentially registered

View File

@ -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 <T> SearchHits<T> read(Class<T> type, SearchDocumentResponse searchDocumentResponse) {
return readResponse(type, searchDocumentResponse);
}
@Override
public <T> SearchHit<T> read(Class<T> 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<String, List<String>> highlightFields = getHighlightsAndRemapFieldNames(type, searchDocument);
T content = mapDocument(searchDocument, type);
return new SearchHit<T>(id, score, sortValues, highlightFields, content);
}
@Override
public <T> SearchScrollHits<T> readScroll(Class<T> type, SearchDocumentResponse searchDocumentResponse) {
return readResponse(type, searchDocumentResponse);
}
private <T> SearchHitsImpl<T> readResponse(Class<T> 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<SearchHit<T>> 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<String, List<String>> getHighlightsAndRemapFieldNames(Class<?> type, SearchDocument searchDocument) {
Map<String, List<String>> 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> T mapDocument(@Nullable Document document, Class<T> 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> R read(Class<R> type, Document source) {

View File

@ -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

View File

@ -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();