15
15
import org .hibernate .search .backend .elasticsearch .types .codec .impl .ElasticsearchFieldCodec ;
16
16
import org .hibernate .search .engine .backend .types .converter .runtime .FromDocumentValueConvertContext ;
17
17
import org .hibernate .search .engine .backend .types .converter .spi .ProjectionConverter ;
18
+ import org .hibernate .search .engine .search .aggregation .AggregationKey ;
19
+ import org .hibernate .search .engine .search .aggregation .SearchAggregation ;
18
20
import org .hibernate .search .engine .search .aggregation .spi .TermsAggregationBuilder ;
19
21
import org .hibernate .search .engine .search .common .ValueModel ;
20
22
import org .hibernate .search .util .common .impl .CollectionHelper ;
28
30
* @param <K> The type of keys in the returned map. It can be {@code F}
29
31
* or a different type if value converters are used.
30
32
*/
31
- public class ElasticsearchTermsAggregation <F , K , T >
32
- extends AbstractElasticsearchBucketAggregation <K , Long > {
33
+ public class ElasticsearchTermsAggregation <F , K , T , V >
34
+ extends AbstractElasticsearchBucketAggregation <K , V > {
33
35
34
36
private final String absoluteFieldPath ;
35
37
36
38
private final ProjectionConverter <T , ? extends K > fromFieldValueConverter ;
37
39
private final BiFunction <JsonElement , JsonElement , T > decodeFunction ;
38
40
41
+ private final ElasticsearchSearchAggregation <V > aggregation ;
42
+
39
43
private final JsonObject order ;
40
44
private final int size ;
41
45
private final int minDocCount ;
42
46
43
- private ElasticsearchTermsAggregation (Builder <F , K , T > builder ) {
47
+ // TODO: do not store these two here:
48
+ private Extractor <V > innerExtractor ;
49
+ private AggregationKey <V > innerExtractorKey ;
50
+
51
+ private ElasticsearchTermsAggregation (Builder <F , K , T , V > builder ) {
44
52
super ( builder );
45
53
this .absoluteFieldPath = builder .field .absolutePath ();
46
54
this .fromFieldValueConverter = builder .fromFieldValueConverter ;
47
55
this .decodeFunction = builder .decodeFunction ;
48
56
this .order = builder .order ;
49
57
this .size = builder .size ;
50
58
this .minDocCount = builder .minDocCount ;
59
+ this .aggregation = builder .aggregation ;
51
60
}
52
61
53
62
@ Override
@@ -59,11 +68,19 @@ protected void doRequest(JsonObject outerObject, JsonObject innerObject, Aggrega
59
68
}
60
69
innerObject .addProperty ( "size" , size );
61
70
innerObject .addProperty ( "min_doc_count" , minDocCount );
71
+
72
+ // TODO: not really good that we have state saved into aggregation within the request, we should pass it up instead
73
+ JsonObject subOuterObject = new JsonObject ();
74
+ innerExtractorKey = AggregationKey .of ( "agg" );
75
+ innerExtractor = aggregation .request ( context , innerExtractorKey , subOuterObject );
76
+ if ( !subOuterObject .isEmpty () ) {
77
+ outerObject .add ( "aggs" , subOuterObject );
78
+ }
62
79
}
63
80
64
81
@ Override
65
- protected Extractor <Map <K , Long >> extractor (AggregationRequestContext context ) {
66
- return new TermsBucketExtractor ( nestedPathHierarchy , filter );
82
+ protected Extractor <Map <K , V >> extractor (AggregationRequestContext context ) {
83
+ return new TermsBucketExtractor ( nestedPathHierarchy , filter , innerExtractorKey , innerExtractor );
67
84
}
68
85
69
86
public static class Factory <F >
@@ -93,34 +110,40 @@ private TypeSelector(ElasticsearchFieldCodec<F> codec,
93
110
94
111
@ SuppressWarnings ("unchecked" )
95
112
@ Override
96
- public <T > Builder <F , T , ?> type (Class <T > expectedType , ValueModel valueModel ) {
113
+ public <T > Builder <F , T , ?, Long > type (Class <T > expectedType , ValueModel valueModel ) {
97
114
if ( ValueModel .RAW .equals ( valueModel ) ) {
98
- return new Builder <>(
99
- (key , string ) -> string != null && !string .isJsonNull () ? string : key ,
115
+ return new CountBuilder <>(
100
116
scope , field ,
117
+ (key , string ) -> string != null && !string .isJsonNull () ? string : key ,
101
118
// unchecked cast to make eclipse-compiler happy
102
119
// we know that Elasticsearch projection converters work with the String
103
120
( (ProjectionConverter <JsonElement , ?>) field .type ().rawProjectionConverter () )
104
121
.withConvertedType ( expectedType , field )
105
122
);
106
123
}
107
124
else {
108
- return new Builder <>( codec :: decodeAggregationKey , scope , field ,
125
+ return new CountBuilder <>( scope , field , codec :: decodeAggregationKey ,
109
126
field .type ().projectionConverter ( valueModel ).withConvertedType ( expectedType , field ) );
110
127
}
111
128
}
112
129
}
113
130
114
- protected class TermsBucketExtractor extends AbstractBucketExtractor <K , Long > {
131
+ protected class TermsBucketExtractor extends AbstractBucketExtractor <K , V > {
132
+ private final AggregationKey <V > innerExtractorKey ;
133
+ private final Extractor <V > innerExtractor ;
134
+
115
135
protected TermsBucketExtractor (List <String > nestedPathHierarchy ,
116
- ElasticsearchSearchPredicate filter ) {
136
+ ElasticsearchSearchPredicate filter , AggregationKey <V > innerExtractorKey , Extractor <V > innerExtractor
137
+ ) {
117
138
super ( nestedPathHierarchy , filter );
139
+ this .innerExtractorKey = innerExtractorKey ;
140
+ this .innerExtractor = innerExtractor ;
118
141
}
119
142
120
143
@ Override
121
- protected Map <K , Long > doExtract (AggregationExtractContext context , JsonElement buckets ) {
144
+ protected Map <K , V > doExtract (AggregationExtractContext context , JsonElement buckets ) {
122
145
JsonArray bucketArray = buckets .getAsJsonArray ();
123
- Map <K , Long > result = CollectionHelper .newLinkedHashMap ( bucketArray .size () );
146
+ Map <K , V > result = CollectionHelper .newLinkedHashMap ( bucketArray .size () );
124
147
FromDocumentValueConvertContext convertContext = context .fromDocumentValueConvertContext ();
125
148
for ( JsonElement bucketElement : bucketArray ) {
126
149
JsonObject bucket = bucketElement .getAsJsonObject ();
@@ -130,29 +153,60 @@ protected Map<K, Long> doExtract(AggregationExtractContext context, JsonElement
130
153
decodeFunction .apply ( keyJson , keyAsStringJson ),
131
154
convertContext
132
155
);
133
- long documentCount = getBucketDocCount ( bucket );
134
- result .put ( key , documentCount );
156
+
157
+ if ( bucket .has ( innerExtractorKey .name () ) ) {
158
+ bucket = bucket .getAsJsonObject ( innerExtractorKey .name () );
159
+ }
160
+ result .put ( key , innerExtractor .extract ( bucket , context ) );
135
161
}
136
162
return result ;
137
163
}
138
164
}
139
165
140
- private static class Builder <F , K , T > extends AbstractBuilder <K , Long >
141
- implements TermsAggregationBuilder <K > {
166
+ private static class CountBuilder <F , K , T > extends Builder <F , K , T , Long > {
167
+
168
+ protected CountBuilder (ElasticsearchSearchIndexScope <?> scope ,
169
+ ElasticsearchSearchIndexValueFieldContext <F > field ,
170
+ BiFunction <JsonElement , JsonElement , T > decodeFunction ,
171
+ ProjectionConverter <T , ? extends K > fromFieldValueConverter ) {
172
+ super ( scope , field , decodeFunction , fromFieldValueConverter ,
173
+ ElasticsearchSearchAggregation .from ( scope ,
174
+ ElasticsearchCountDocumentAggregation .factory ( !field .nestedPathHierarchy ().isEmpty () )
175
+ .create ( scope , null ).type ().build () ) );
176
+ }
177
+ }
178
+
179
+ private static class Builder <F , K , T , V > extends AbstractBuilder <K , V >
180
+ implements TermsAggregationBuilder <K , V > {
142
181
143
182
private final BiFunction <JsonElement , JsonElement , T > decodeFunction ;
144
183
private final ProjectionConverter <T , ? extends K > fromFieldValueConverter ;
184
+ private final ElasticsearchSearchAggregation <V > aggregation ;
145
185
146
186
private JsonObject order ;
147
- private int minDocCount = 1 ;
148
- private int size = 100 ;
187
+ private int minDocCount ;
188
+ private int size ;
149
189
150
- private Builder (BiFunction < JsonElement , JsonElement , T > decodeFunction , ElasticsearchSearchIndexScope <?> scope ,
190
+ private Builder (ElasticsearchSearchIndexScope <?> scope ,
151
191
ElasticsearchSearchIndexValueFieldContext <F > field ,
152
- ProjectionConverter <T , ? extends K > fromFieldValueConverter ) {
192
+ BiFunction <JsonElement , JsonElement , T > decodeFunction ,
193
+ ProjectionConverter <T , ? extends K > fromFieldValueConverter ,
194
+ ElasticsearchSearchAggregation <V > aggregation ) {
195
+ this ( scope , field , decodeFunction , fromFieldValueConverter , aggregation , null , 1 , 100 );
196
+ }
197
+
198
+ public Builder (ElasticsearchSearchIndexScope <?> scope , ElasticsearchSearchIndexValueFieldContext <?> field ,
199
+ BiFunction <JsonElement , JsonElement , T > decodeFunction ,
200
+ ProjectionConverter <T , ? extends K > fromFieldValueConverter ,
201
+ ElasticsearchSearchAggregation <V > aggregation ,
202
+ JsonObject order , int minDocCount , int size ) {
153
203
super ( scope , field );
204
+ this .order = order ;
154
205
this .decodeFunction = decodeFunction ;
155
206
this .fromFieldValueConverter = fromFieldValueConverter ;
207
+ this .aggregation = aggregation ;
208
+ this .minDocCount = minDocCount ;
209
+ this .size = size ;
156
210
}
157
211
158
212
@ Override
@@ -186,7 +240,13 @@ public void maxTermCount(int maxTermCount) {
186
240
}
187
241
188
242
@ Override
189
- public ElasticsearchTermsAggregation <F , K , T > build () {
243
+ public <R > TermsAggregationBuilder <K , R > withValue (SearchAggregation <R > aggregation ) {
244
+ return new Builder <>( scope , field , decodeFunction , fromFieldValueConverter ,
245
+ ElasticsearchSearchAggregation .from ( scope , aggregation ), order , minDocCount , size );
246
+ }
247
+
248
+ @ Override
249
+ public ElasticsearchTermsAggregation <F , K , T , V > build () {
190
250
return new ElasticsearchTermsAggregation <>( this );
191
251
}
192
252
0 commit comments