16
16
package org .springframework .data .elasticsearch .core ;
17
17
18
18
import java .util .ArrayList ;
19
+ import java .util .LinkedHashMap ;
20
+ import java .util .LinkedList ;
19
21
import java .util .List ;
20
22
import java .util .Map ;
21
23
import java .util .stream .Collectors ;
22
24
23
25
import org .elasticsearch .search .aggregations .Aggregations ;
26
+ import org .slf4j .Logger ;
27
+ import org .slf4j .LoggerFactory ;
28
+ import org .springframework .data .elasticsearch .core .convert .ElasticsearchConverter ;
29
+ import org .springframework .data .elasticsearch .core .document .Document ;
30
+ import org .springframework .data .elasticsearch .core .document .NestedMetaData ;
24
31
import org .springframework .data .elasticsearch .core .document .SearchDocument ;
25
32
import org .springframework .data .elasticsearch .core .document .SearchDocumentResponse ;
26
33
import org .springframework .data .elasticsearch .core .mapping .ElasticsearchPersistentEntity ;
39
46
* @since 4.0
40
47
*/
41
48
class SearchHitMapping <T > {
49
+
50
+ private static final Logger LOGGER = LoggerFactory .getLogger (SearchHitMapping .class );
51
+
42
52
private final Class <T > type ;
53
+ private final ElasticsearchConverter converter ;
43
54
private final MappingContext <? extends ElasticsearchPersistentEntity <?>, ElasticsearchPersistentProperty > mappingContext ;
44
55
45
- private SearchHitMapping (Class <T > type ,
46
- MappingContext <? extends ElasticsearchPersistentEntity <?>, ElasticsearchPersistentProperty > context ) {
47
-
56
+ private SearchHitMapping (Class <T > type , ElasticsearchConverter converter ) {
48
57
Assert .notNull (type , "type is null" );
49
- Assert .notNull (context , "context is null" );
58
+ Assert .notNull (converter , "converter is null" );
50
59
51
60
this .type = type ;
52
- this .mappingContext = context ;
61
+ this .converter = converter ;
62
+ this .mappingContext = converter .getMappingContext ();
53
63
}
54
64
55
- static <T > SearchHitMapping <T > mappingFor (Class <T > entityClass ,
56
- MappingContext <? extends ElasticsearchPersistentEntity <?>, ElasticsearchPersistentProperty > context ) {
57
- return new SearchHitMapping <>(entityClass , context );
58
- }
59
-
60
- SearchHit <T > mapHit (SearchDocument searchDocument , T content ) {
61
-
62
- Assert .notNull (searchDocument , "searchDocument is null" );
63
- Assert .notNull (content , "content is null" );
64
-
65
- String index = searchDocument .getIndex ();
66
- String id = searchDocument .hasId () ? searchDocument .getId () : null ;
67
- float score = searchDocument .getScore ();
68
- Object [] sortValues = searchDocument .getSortValues ();
69
- Map <String , List <String >> highlightFields = getHighlightsAndRemapFieldNames (searchDocument );
70
-
71
- return new SearchHit <>(index , id , score , sortValues , highlightFields , content );
65
+ static <T > SearchHitMapping <T > mappingFor (Class <T > entityClass , ElasticsearchConverter converter ) {
66
+ return new SearchHitMapping <>(entityClass , converter );
72
67
}
73
68
74
69
SearchHits <T > mapHits (SearchDocumentResponse searchDocumentResponse , List <T > contents ) {
@@ -105,6 +100,21 @@ private SearchHitsImpl<T> mapHitsFromResponse(SearchDocumentResponse searchDocum
105
100
return new SearchHitsImpl <>(totalHits , totalHitsRelation , maxScore , scrollId , searchHits , aggregations );
106
101
}
107
102
103
+ SearchHit <T > mapHit (SearchDocument searchDocument , T content ) {
104
+
105
+ Assert .notNull (searchDocument , "searchDocument is null" );
106
+ Assert .notNull (content , "content is null" );
107
+
108
+ return new SearchHit <T >(searchDocument .getIndex (), //
109
+ searchDocument .hasId () ? searchDocument .getId () : null , //
110
+ searchDocument .getScore (), //
111
+ searchDocument .getSortValues (), //
112
+ getHighlightsAndRemapFieldNames (searchDocument ), //
113
+ mapInnerHits (searchDocument ), //
114
+ searchDocument .getNestedMetaData (), //
115
+ content ); //
116
+ }
117
+
108
118
@ Nullable
109
119
private Map <String , List <String >> getHighlightsAndRemapFieldNames (SearchDocument searchDocument ) {
110
120
Map <String , List <String >> highlightFields = searchDocument .getHighlightFields ();
@@ -123,4 +133,131 @@ private Map<String, List<String>> getHighlightsAndRemapFieldNames(SearchDocument
123
133
return property != null ? property .getName () : entry .getKey ();
124
134
}, Map .Entry ::getValue ));
125
135
}
136
+
137
+ private Map <String , SearchHits <?>> mapInnerHits (SearchDocument searchDocument ) {
138
+
139
+ Map <String , SearchHits <?>> innerHits = new LinkedHashMap <>();
140
+ Map <String , SearchDocumentResponse > documentInnerHits = searchDocument .getInnerHits ();
141
+
142
+ if (documentInnerHits != null && documentInnerHits .size () > 0 ) {
143
+
144
+ SearchHitMapping <SearchDocument > searchDocumentSearchHitMapping = SearchHitMapping
145
+ .mappingFor (SearchDocument .class , converter );
146
+
147
+ for (Map .Entry <String , SearchDocumentResponse > entry : documentInnerHits .entrySet ()) {
148
+ SearchDocumentResponse searchDocumentResponse = entry .getValue ();
149
+
150
+ SearchHits <SearchDocument > searchHits = searchDocumentSearchHitMapping
151
+ .mapHitsFromResponse (searchDocumentResponse , searchDocumentResponse .getSearchDocuments ());
152
+
153
+ // map Documents to real objects
154
+ SearchHits <?> mappedSearchHits = mapInnerDocuments (searchHits , type );
155
+
156
+ innerHits .put (entry .getKey (), mappedSearchHits );
157
+ }
158
+
159
+ }
160
+ return innerHits ;
161
+ }
162
+
163
+ /**
164
+ * try to convert the SearchDocument instances to instances of the inner property class.
165
+ *
166
+ * @param searchHits {@link SearchHits} containing {@link Document} instances
167
+ * @param type the class of the containing class
168
+ * @return a new {@link SearchHits} instance containing the mapped objects or the original inout if any error occurs
169
+ */
170
+ private SearchHits <?> mapInnerDocuments (SearchHits <SearchDocument > searchHits , Class <T > type ) {
171
+
172
+ if (searchHits .getTotalHits () == 0 ) {
173
+ return searchHits ;
174
+ }
175
+
176
+ try {
177
+ NestedMetaData nestedMetaData = searchHits .getSearchHit (0 ).getContent ().getNestedMetaData ();
178
+ ElasticsearchPersistentEntityWithNestedMetaData persistentEntityWithNestedMetaData = getPersistentEntity (
179
+ mappingContext .getPersistentEntity (type ), nestedMetaData );
180
+
181
+ List <SearchHit <Object >> convertedSearchHits = new ArrayList <>();
182
+
183
+ if (persistentEntityWithNestedMetaData .entity != null ) {
184
+ Class <?> targetType = persistentEntityWithNestedMetaData .entity .getType ();
185
+ // convert the list of SearchHit<SearchDocument> to list of SearchHit<Object>
186
+ searchHits .getSearchHits ().forEach (searchHit -> {
187
+ SearchDocument searchDocument = searchHit .getContent ();
188
+
189
+ Object targetObject = converter .read (targetType , searchDocument );
190
+ convertedSearchHits .add (new SearchHit <Object >(searchDocument .getIndex (), //
191
+ searchDocument .getId (), //
192
+ searchDocument .getScore (), //
193
+ searchDocument .getSortValues (), //
194
+ searchDocument .getHighlightFields (), //
195
+ mapInnerHits (searchDocument ), //
196
+ persistentEntityWithNestedMetaData .nestedMetaData , //
197
+ targetObject ));
198
+ });
199
+
200
+ String scrollId = null ;
201
+ if (searchHits instanceof SearchHitsImpl ) {
202
+ scrollId = ((SearchHitsImpl <?>) searchHits ).getScrollId ();
203
+ }
204
+
205
+ return new SearchHitsImpl <>(searchHits .getTotalHits (), //
206
+ searchHits .getTotalHitsRelation (), //
207
+ searchHits .getMaxScore (), //
208
+ scrollId , //
209
+ convertedSearchHits , //
210
+ searchHits .getAggregations ());
211
+ }
212
+ } catch (Exception e ) {
213
+ LOGGER .warn ("Could not map inner_hits" , e );
214
+ }
215
+
216
+ return searchHits ;
217
+ }
218
+
219
+ /**
220
+ * find a {@link ElasticsearchPersistentEntity} following the property chain defined by the nested metadata
221
+ *
222
+ * @param persistentEntity base entity
223
+ * @param nestedMetaData nested metadata
224
+ * @return The found entity or null
225
+ */
226
+ @ Nullable
227
+ private ElasticsearchPersistentEntityWithNestedMetaData getPersistentEntity (
228
+ @ Nullable ElasticsearchPersistentEntity <?> persistentEntity , @ Nullable NestedMetaData nestedMetaData ) {
229
+
230
+ NestedMetaData currentMetaData = nestedMetaData ;
231
+ List <NestedMetaData > mappedNestedMetaDatas = new LinkedList <>();
232
+
233
+ while (persistentEntity != null && currentMetaData != null ) {
234
+ ElasticsearchPersistentProperty persistentProperty = persistentEntity
235
+ .getPersistentPropertyWithFieldName (currentMetaData .getField ());
236
+
237
+ if (persistentProperty == null ) {
238
+ persistentEntity = null ;
239
+ } else {
240
+ persistentEntity = mappingContext .getPersistentEntity (persistentProperty .getActualType ());
241
+ mappedNestedMetaDatas .add (0 ,
242
+ NestedMetaData .of (persistentProperty .getName (), currentMetaData .getOffset (), null ));
243
+ currentMetaData = currentMetaData .getChild ();
244
+ }
245
+ }
246
+
247
+ NestedMetaData mappedNestedMetaData = mappedNestedMetaDatas .stream ().reduce (null ,
248
+ (result , nmd ) -> NestedMetaData .of (nmd .getField (), nmd .getOffset (), result ));
249
+
250
+ return new ElasticsearchPersistentEntityWithNestedMetaData (persistentEntity , mappedNestedMetaData );
251
+ }
252
+
253
+ private static class ElasticsearchPersistentEntityWithNestedMetaData {
254
+ @ Nullable private ElasticsearchPersistentEntity <?> entity ;
255
+ private NestedMetaData nestedMetaData ;
256
+
257
+ public ElasticsearchPersistentEntityWithNestedMetaData (@ Nullable ElasticsearchPersistentEntity <?> entity ,
258
+ NestedMetaData nestedMetaData ) {
259
+ this .entity = entity ;
260
+ this .nestedMetaData = nestedMetaData ;
261
+ }
262
+ }
126
263
}
0 commit comments