2727import org .elasticsearch .search .aggregations .AggregationExecutionException ;
2828
2929import java .util .AbstractMap ;
30- import java .util .ArrayList ;
31- import java .util .HashMap ;
3230import java .util .List ;
3331import java .util .Map ;
32+ import java .util .StringJoiner ;
3433import java .util .function .BiFunction ;
3534
3635/*
3938 */
4039public enum ValuesSourceRegistry {
4140 INSTANCE {
42- Map <String , Map <ValuesSourceType , AggregatorSupplier >> aggregatorRegistry = new HashMap <> ();
41+ Map <String , Map <ValuesSourceType , AggregatorSupplier >> aggregatorRegistry = Map . of ();
4342 // We use a List of Entries here to approximate an ordered map
4443 Map <String , List <AbstractMap .SimpleEntry <BiFunction <MappedFieldType , IndexFieldData , Boolean >, ValuesSourceType >>> resolverRegistry
45- = new HashMap <> ();
44+ = Map . of ();
4645
46+ /**
47+ * Threading behavior notes: This call is both synchronized and expensive. It copies the entire existing mapping structure each
48+ * time it is invoked. We expect that register will be called a small number of times during startup only (as plugins are being
49+ * registered) and we can tolerate the cost at that time. Once all plugins are registered, we should never need to call register
50+ * again. Comparatively, we expect to do many reads from the registry data structures, and those reads may be interleaved on
51+ * different worker threads. Thus we want to optimize the read case to be thread safe and fast, which the immutable
52+ * collections do well. Using immutable collections requires a copy on write mechanic, thus the somewhat non-intuitive
53+ * implementation of this method.
54+ *
55+ * @param aggregationName The name of the family of aggregations, typically found via ValuesSourceAggregationBuilder.getType()
56+ * @param valuesSourceType The ValuesSourceType this mapping applies to.
57+ * @param aggregatorSupplier An Aggregation-specific specialization of AggregatorSupplier which will construct the mapped aggregator
58+ * from the aggregation standard set of parameters
59+ * @param resolveValuesSourceType A predicate operating on MappedFieldType and IndexFieldData instances which decides if the mapped
60+ */
4761 @ Override
48- public void register (String aggregationName , ValuesSourceType valuesSourceType ,AggregatorSupplier aggregatorSupplier ,
62+ public synchronized void register (String aggregationName , ValuesSourceType valuesSourceType , AggregatorSupplier aggregatorSupplier ,
4963 BiFunction <MappedFieldType , IndexFieldData , Boolean > resolveValuesSourceType ) {
50- if (resolverRegistry .containsKey (aggregationName ) == false ) {
51- resolverRegistry .put (aggregationName , new ArrayList <>());
64+ // Aggregator registry block - do this first in case we need to throw on duplicate registration
65+ Map <ValuesSourceType , AggregatorSupplier > innerMap ;
66+ if (aggregatorRegistry .containsKey (aggregationName )) {
67+ if (aggregatorRegistry .get (aggregationName ).containsKey (valuesSourceType )) {
68+ throw new IllegalStateException ("Attempted to register already registered pair [" + aggregationName + ", "
69+ + valuesSourceType .toString () + "]" );
70+ }
71+ innerMap = copyAndAdd (aggregatorRegistry .get (aggregationName ),
72+ new AbstractMap .SimpleEntry <>(valuesSourceType , aggregatorSupplier ));
73+ } else {
74+ innerMap = Map .of (valuesSourceType , aggregatorSupplier );
5275 }
53- List <AbstractMap .SimpleEntry <BiFunction <MappedFieldType , IndexFieldData , Boolean >, ValuesSourceType >> resolverList
54- = resolverRegistry .get (aggregationName );
55- resolverList .add (new AbstractMap .SimpleEntry <>(resolveValuesSourceType , valuesSourceType ));
76+ aggregatorRegistry = copyAndAdd (aggregatorRegistry , new AbstractMap .SimpleEntry <>(aggregationName , innerMap ));
5677
57- if ( aggregatorRegistry . containsKey ( aggregationName ) == false ) {
58- aggregatorRegistry . put ( aggregationName , new HashMap <>()) ;
59- }
60- Map < ValuesSourceType , AggregatorSupplier > innerMap = aggregatorRegistry .get (aggregationName );
61- if ( innerMap . containsKey ( valuesSourceType )) {
62- throw new IllegalStateException ( "Attempted to register already registered pair [" + aggregationName + ", "
63- + valuesSourceType . toString () + "]" ) ;
78+ // Resolver registry block
79+ AbstractMap . SimpleEntry [] mappings ;
80+ if ( resolverRegistry . containsKey ( aggregationName )) {
81+ List currentMappings = resolverRegistry .get (aggregationName );
82+ mappings = ( AbstractMap . SimpleEntry []) currentMappings . toArray ( new AbstractMap . SimpleEntry [ currentMappings . size () + 1 ]);
83+ } else {
84+ mappings = new AbstractMap . SimpleEntry [ 1 ] ;
6485 }
65- innerMap .put (valuesSourceType , aggregatorSupplier );
86+ mappings [mappings .length - 1 ] = new AbstractMap .SimpleEntry <>(resolveValuesSourceType , valuesSourceType );
87+ resolverRegistry = copyAndAdd (resolverRegistry ,new AbstractMap .SimpleEntry <>(aggregationName , List .of (mappings )));
6688 }
6789
6890 @ Override
6991 public AggregatorSupplier getAggregator (ValuesSourceType valuesSourceType , String aggregationName ) {
70- if (aggregatorRegistry .containsKey (aggregationName )) {
92+ StringJoiner validSourceTypes = new StringJoiner ("," , "[" , "]" );
93+ if (aggregationName != null && aggregatorRegistry .containsKey (aggregationName )) {
7194 Map <ValuesSourceType , AggregatorSupplier > innerMap = aggregatorRegistry .get (aggregationName );
72- if (innerMap .containsKey (valuesSourceType )) {
95+ if (valuesSourceType != null && innerMap .containsKey (valuesSourceType )) {
7396 return innerMap .get (valuesSourceType );
7497 }
98+ for (ValuesSourceType validVST : innerMap .keySet ()) {
99+ validSourceTypes .add (validVST .toString ());
100+ }
101+ throw new AggregationExecutionException ("ValuesSource type " + valuesSourceType .toString () +
102+ " is not supported for aggregation" + aggregationName + ". Valid choices are " + validSourceTypes .toString ());
75103 }
76- // TODO: Error message should list valid ValuesSource types
77- throw new AggregationExecutionException ("ValuesSource type " + valuesSourceType .toString () +
78- " is not supported for aggregation" + aggregationName );
104+ throw new AggregationExecutionException ("Unregistered Aggregation [" + aggregationName + "]" );
79105 }
80106
81107 @ Override
82108 public ValuesSourceType getValuesSourceType (MappedFieldType fieldType , IndexFieldData indexFieldData , String aggregationName ,
83109 ValueType valueType ) {
84- if (resolverRegistry .containsKey (aggregationName )) {
110+ if (aggregationName != null && resolverRegistry .containsKey (aggregationName )) {
85111 List <AbstractMap .SimpleEntry <BiFunction <MappedFieldType , IndexFieldData , Boolean >, ValuesSourceType >> resolverList
86112 = resolverRegistry .get (aggregationName );
87113 for (AbstractMap .SimpleEntry <BiFunction <MappedFieldType , IndexFieldData , Boolean >, ValuesSourceType > entry : resolverList ) {
@@ -90,8 +116,9 @@ public ValuesSourceType getValuesSourceType(MappedFieldType fieldType, IndexFiel
90116 return entry .getValue ();
91117 }
92118 }
93- // TODO: Error message should list valid field types; not sure fieldType.toString() is the best choice.
94- throw new IllegalArgumentException ("Field type " + fieldType .toString () + " is not supported for aggregation "
119+ // TODO: Error message should list valid field types
120+ String fieldDescription = fieldType .name () + "(" + fieldType .toString () + ")" ;
121+ throw new IllegalArgumentException ("Field type " + fieldDescription + " is not supported for aggregation "
95122 + aggregationName );
96123 } else {
97124 // TODO: Legacy resolve logic; remove this after converting all aggregations to the new system
@@ -133,4 +160,26 @@ public abstract ValuesSourceType getValuesSourceType(MappedFieldType fieldType,
133160 ValueType valueType );
134161
135162 public static ValuesSourceRegistry getInstance () {return INSTANCE ;}
163+
164+ private static <K , V > Map copyAndAdd (Map <K , V > source , Map .Entry <K , V > newValue ) {
165+ Map .Entry [] entries ;
166+ if (source .containsKey (newValue .getKey ())) {
167+ // Replace with new value
168+ entries = new Map .Entry [source .size ()];
169+ int i = 0 ;
170+ for (Map .Entry entry : source .entrySet ()) {
171+ if (entry .getKey () == newValue .getKey ()) {
172+ entries [i ] = newValue ;
173+ } else {
174+ entries [i ] = entry ;
175+ }
176+ i ++;
177+ }
178+ } else {
179+ entries = source .entrySet ().toArray (new Map .Entry [source .size () + 1 ]);
180+ entries [entries .length - 1 ] = newValue ;
181+ }
182+ return Map .ofEntries (entries );
183+ }
184+
136185}
0 commit comments