Skip to content

Commit 8fad48b

Browse files
authored
Add reactive SearchHits implementation.
Original Pull Request #2017 Closes #2015
1 parent 989c280 commit 8fad48b

File tree

8 files changed

+332
-24
lines changed

8 files changed

+332
-24
lines changed

Diff for: src/main/asciidoc/reference/elasticsearch-operations.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, i
144144
.SearchHitsIterator<T>
145145
An Iterator returned by the streaming functions of the `SearchOperations` interface.
146146

147+
.ReactiveSearchHits
148+
`ReactiveSearchOperations` has methods returning a `Mono<ReactiveSearchHits<T>>`, this contains the same information as a `SearchHits<T>` object, but will provide the contained `SearchHit<T>` objects as a `Flux<SearchHit<T>>` and not as a list.
149+
147150
[[elasticsearch.operations.queries]]
148151
== Queries
149152

Diff for: src/main/java/org/springframework/data/elasticsearch/backend/elasticsearch7/ReactiveElasticsearchTemplate.java

+32-7
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,31 @@ public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, C
754754
.map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
755755
}
756756

757+
@Override
758+
public <T> Mono<ReactiveSearchHits<T>> searchForHits(Query query, Class<?> entityType, Class<T> resultType) {
759+
return searchForHits(query, entityType, resultType, getIndexCoordinatesFor(entityType));
760+
}
761+
762+
@Override
763+
public <T> Mono<ReactiveSearchHits<T>> searchForHits(Query query, Class<?> entityType, Class<T> resultType,
764+
IndexCoordinates index) {
765+
766+
Assert.notNull(query, "query must not be null");
767+
Assert.notNull(entityType, "entityType must not be null");
768+
Assert.notNull(resultType, "resultType must not be null");
769+
Assert.notNull(index, "index must not be null");
770+
771+
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
772+
773+
return doFindForResponse(query, entityType, index) //
774+
.flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) //
775+
.flatMap(callback::toEntity) //
776+
.collectList() //
777+
.map(entities -> SearchHitMapping.mappingFor(resultType, converter) //
778+
.mapHits(searchDocumentResponse, entities))) //
779+
.map(ReactiveSearchHitSupport::searchHitsFor);
780+
}
781+
757782
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
758783

759784
return Flux.defer(() -> {
@@ -777,8 +802,9 @@ private Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> cla
777802
request = prepareSearchRequest(request, false);
778803

779804
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
780-
781-
return doFindForResponse(request, searchDocument -> documentCallback.toEntity(searchDocument).block());
805+
Function<SearchDocument, Object> entityCreator = searchDocument -> documentCallback.toEntity(searchDocument)
806+
.block();
807+
return doFindForResponse(request, entityCreator);
782808
});
783809
}
784810

@@ -895,19 +921,18 @@ protected Flux<SearchDocument> doFind(SearchRequest request) {
895921
* Customization hook on the actual execution result {@link Mono}. <br />
896922
*
897923
* @param request the already prepared {@link SearchRequest} ready to be executed.
898-
* @param suggestEntityCreator
924+
* @param entityCreator
899925
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
900926
*/
901927
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
902-
Function<SearchDocument, ? extends Object> suggestEntityCreator) {
928+
Function<SearchDocument, ? extends Object> entityCreator) {
903929

904930
if (QUERY_LOGGER.isDebugEnabled()) {
905931
QUERY_LOGGER.debug(String.format("Executing doFindForResponse: %s", request));
906932
}
907933

908-
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(searchResponse -> {
909-
return SearchDocumentResponse.from(searchResponse, suggestEntityCreator);
910-
});
934+
return Mono.from(execute(client -> client.searchForResponse(request)))
935+
.map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
911936
}
912937

913938
/**

Diff for: src/main/java/org/springframework/data/elasticsearch/backend/elasticsearch7/document/SearchDocumentResponse.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ public Suggest getSuggest() {
9999
* creates a SearchDocumentResponse from the {@link SearchResponse}
100100
*
101101
* @param searchResponse must not be {@literal null}
102-
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
102+
* @param entityCreator function to create an entity from a {@link SearchDocument}
103103
* @param <T> entity type
104104
* @return the SearchDocumentResponse
105105
*/
106106
public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
107-
Function<SearchDocument, T> suggestEntityCreator) {
107+
Function<SearchDocument, T> entityCreator) {
108108

109109
Assert.notNull(searchResponse, "searchResponse must not be null");
110110

@@ -113,7 +113,7 @@ public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
113113
Aggregations aggregations = searchResponse.getAggregations();
114114
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
115115

116-
return from(searchHits, scrollId, aggregations, suggest, suggestEntityCreator);
116+
return from(searchHits, scrollId, aggregations, suggest, entityCreator);
117117
}
118118

119119
/**
@@ -123,14 +123,14 @@ public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
123123
* @param scrollId scrollId
124124
* @param aggregations aggregations
125125
* @param suggestES the suggestion response from Elasticsearch
126-
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
126+
* @param entityCreator function to create an entity from a {@link SearchDocument}
127127
* @param <T> entity type
128128
* @return the {@link SearchDocumentResponse}
129129
* @since 4.3
130130
*/
131131
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
132132
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
133-
Function<SearchDocument, T> suggestEntityCreator) {
133+
Function<SearchDocument, T> entityCreator) {
134134

135135
TotalHits responseTotalHits = searchHits.getTotalHits();
136136

@@ -154,7 +154,7 @@ public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable S
154154
}
155155
}
156156

157-
Suggest suggest = suggestFrom(suggestES, suggestEntityCreator);
157+
Suggest suggest = suggestFrom(suggestES, entityCreator);
158158
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations,
159159
suggest);
160160
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core;
17+
18+
import org.springframework.util.Assert;
19+
20+
/**
21+
* @author Peter-Josef Meisch
22+
* @since 4.4
23+
*/
24+
public final class ReactiveSearchHitSupport {
25+
private ReactiveSearchHitSupport() {}
26+
27+
public static <T> ReactiveSearchHits<T> searchHitsFor(SearchHits<T> searchHits) {
28+
29+
Assert.notNull(searchHits, "searchHits must not be null");
30+
31+
return new ReactiveSearchHitsImpl<>(searchHits);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core;
17+
18+
import reactor.core.publisher.Flux;
19+
20+
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* Encapsulates a Flux of {@link SearchHit}s with additional information from the search.
25+
*
26+
* @param <T> the result data class.
27+
* @author Peter-Josef Meisch
28+
* @since 4.4
29+
*/
30+
public interface ReactiveSearchHits<T> {
31+
32+
/**
33+
* @return the aggregations.
34+
*/
35+
@Nullable
36+
AggregationsContainer<?> getAggregations();
37+
38+
float getMaxScore();
39+
40+
/**
41+
* @return the {@link SearchHit}s from the search result.
42+
*/
43+
Flux<SearchHit<T>> getSearchHits();
44+
45+
/**
46+
* @return the number of total hits.
47+
*/
48+
long getTotalHits();
49+
50+
/**
51+
* @return the relation for the total hits
52+
*/
53+
TotalHitsRelation getTotalHitsRelation();
54+
55+
/**
56+
* @return true if aggregations are available
57+
*/
58+
boolean hasAggregations();
59+
60+
/**
61+
* @return whether the {@link SearchHits} has search hits.
62+
*/
63+
boolean hasSearchHits();
64+
65+
/**
66+
* @return the suggest response
67+
*/
68+
@Nullable
69+
Suggest getSuggest();
70+
71+
/**
72+
* @return wether the {@link SearchHits} has a suggest response.
73+
*/
74+
boolean hasSuggest();
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core;
17+
18+
import reactor.core.publisher.Flux;
19+
20+
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* @author Peter-Josef Meisch
25+
* @since 4.4
26+
*/
27+
public class ReactiveSearchHitsImpl<T> implements ReactiveSearchHits<T> {
28+
29+
protected final SearchHits<T> delegate;
30+
31+
public ReactiveSearchHitsImpl(SearchHits<T> delegate) {
32+
this.delegate = delegate;
33+
}
34+
35+
@Override
36+
public long getTotalHits() {
37+
return delegate.getTotalHits();
38+
}
39+
40+
@Override
41+
public TotalHitsRelation getTotalHitsRelation() {
42+
return delegate.getTotalHitsRelation();
43+
}
44+
45+
@Override
46+
public boolean hasAggregations() {
47+
return delegate.hasAggregations();
48+
}
49+
50+
@Override
51+
@Nullable
52+
public AggregationsContainer<?> getAggregations() {
53+
return delegate.getAggregations();
54+
}
55+
56+
@Override
57+
public float getMaxScore() {
58+
return delegate.getMaxScore();
59+
}
60+
61+
@Override
62+
public boolean hasSearchHits() {
63+
return delegate.hasSearchHits();
64+
}
65+
66+
@Override
67+
public Flux<SearchHit<T>> getSearchHits() {
68+
return Flux.defer(() -> Flux.fromIterable(delegate.getSearchHits()));
69+
}
70+
71+
@Override
72+
@Nullable
73+
public Suggest getSuggest() {
74+
return delegate.getSuggest();
75+
}
76+
77+
@Override
78+
public boolean hasSuggest() {
79+
return delegate.hasSuggest();
80+
}
81+
}

0 commit comments

Comments
 (0)