11/*
2- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3- * or more contributor license agreements. Licensed under the Elastic License
4- * 2.0; you may not use this file except in compliance with the Elastic License
5- * 2.0.
2+ * Licensed to Elasticsearch B.V. under one or more contributor
3+ * license agreements. See the NOTICE file distributed with
4+ * this work for additional information regarding copyright
5+ * ownership. Elasticsearch B.V. licenses this file to you under
6+ * the Apache License, Version 2.0 (the "License"); you may
7+ * not use this file except in compliance with the License.
8+ * You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing,
13+ * software distributed under the License is distributed on an
14+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+ * KIND, either express or implied. See the License for the
16+ * specific language governing permissions and limitations
17+ * under the License.
18+ *
19+ * This project is based on a modification of https://github.com/tdunning/t-digest which is licensed under the Apache 2.0 License.
620 */
721
8- package org .elasticsearch .xpack . analytics . mapper ;
22+ package org .elasticsearch .tdigest . parsing ;
923
10- import org .elasticsearch .index .mapper .DocumentParsingException ;
1124import org .elasticsearch .xcontent .ParseField ;
25+ import org .elasticsearch .xcontent .XContentLocation ;
1226import org .elasticsearch .xcontent .XContentParser ;
1327
1428import java .io .IOException ;
1529import java .util .ArrayList ;
1630import java .util .List ;
17-
18- import static org .elasticsearch .common .xcontent .XContentParserUtils .ensureExpectedToken ;
19- import static org .elasticsearch .xpack .analytics .mapper .TDigestFieldMapper .CENTROIDS_NAME ;
20- import static org .elasticsearch .xpack .analytics .mapper .TDigestFieldMapper .COUNTS_NAME ;
21- import static org .elasticsearch .xpack .analytics .mapper .TDigestFieldMapper .MAX_FIELD_NAME ;
22- import static org .elasticsearch .xpack .analytics .mapper .TDigestFieldMapper .MIN_FIELD_NAME ;
23- import static org .elasticsearch .xpack .analytics .mapper .TDigestFieldMapper .SUM_FIELD_NAME ;
31+ import java .util .function .BiFunction ;
2432
2533public class TDigestParser {
34+ public static final String CENTROIDS_NAME = "centroids" ;
35+ public static final String COUNTS_NAME = "counts" ;
36+ public static final String SUM_FIELD_NAME = "sum" ;
37+ public static final String MIN_FIELD_NAME = "min" ;
38+ public static final String MAX_FIELD_NAME = "max" ;
2639
2740 private static final ParseField COUNTS_FIELD = new ParseField (COUNTS_NAME );
2841 private static final ParseField CENTROIDS_FIELD = new ParseField (CENTROIDS_NAME );
@@ -91,9 +104,15 @@ public Long count() {
91104 *
92105 * @param mappedFieldName the name of the field being parsed, used for error messages
93106 * @param parser the parser to use
107+ * @param documentParsingExceptionProvider factory function for generating document parsing exceptions. Required for visibility.
94108 * @return the parsed histogram
95109 */
96- public static ParsedTDigest parse (String mappedFieldName , XContentParser parser ) throws IOException {
110+ public static ParsedTDigest parse (
111+ String mappedFieldName ,
112+ XContentParser parser ,
113+ BiFunction <XContentLocation , String , RuntimeException > documentParsingExceptionProvider ,
114+ ParsingExceptionProvider parsingExceptionProvider
115+ ) throws IOException {
97116 ArrayList <Double > centroids = null ;
98117 ArrayList <Long > counts = null ;
99118 Double sum = null ;
@@ -102,46 +121,46 @@ public static ParsedTDigest parse(String mappedFieldName, XContentParser parser)
102121 XContentParser .Token token = parser .currentToken ();
103122 while (token != XContentParser .Token .END_OBJECT ) {
104123 // should be a field
105- ensureExpectedToken (XContentParser .Token .FIELD_NAME , token , parser );
124+ ensureExpectedToken (XContentParser .Token .FIELD_NAME , token , parser , parsingExceptionProvider );
106125 String fieldName = parser .currentName ();
107126 if (fieldName .equals (CENTROIDS_FIELD .getPreferredName ())) {
108- centroids = getCentroids (mappedFieldName , parser );
127+ centroids = getCentroids (mappedFieldName , parser , documentParsingExceptionProvider , parsingExceptionProvider );
109128 } else if (fieldName .equals (COUNTS_FIELD .getPreferredName ())) {
110- counts = getCounts (mappedFieldName , parser );
129+ counts = getCounts (mappedFieldName , parser , documentParsingExceptionProvider , parsingExceptionProvider );
111130 } else if (fieldName .equals (SUM_FIELD .getPreferredName ())) {
112131 token = parser .nextToken ();
113- ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser );
132+ ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser , parsingExceptionProvider );
114133 sum = parser .doubleValue ();
115134 } else if (fieldName .equals (MIN_FIELD .getPreferredName ())) {
116135 token = parser .nextToken ();
117- ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser );
136+ ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser , parsingExceptionProvider );
118137 min = parser .doubleValue ();
119138 } else if (fieldName .equals (MAX_FIELD .getPreferredName ())) {
120139 token = parser .nextToken ();
121- ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser );
140+ ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser , parsingExceptionProvider );
122141 max = parser .doubleValue ();
123142 } else {
124- throw new DocumentParsingException (
143+ throw documentParsingExceptionProvider . apply (
125144 parser .getTokenLocation (),
126145 "error parsing field [" + mappedFieldName + "], with unknown parameter [" + fieldName + "]"
127146 );
128147 }
129148 token = parser .nextToken ();
130149 }
131150 if (centroids == null ) {
132- throw new DocumentParsingException (
151+ throw documentParsingExceptionProvider . apply (
133152 parser .getTokenLocation (),
134153 "error parsing field [" + mappedFieldName + "], expected field called [" + CENTROIDS_FIELD .getPreferredName () + "]"
135154 );
136155 }
137156 if (counts == null ) {
138- throw new DocumentParsingException (
157+ throw documentParsingExceptionProvider . apply (
139158 parser .getTokenLocation (),
140159 "error parsing field [" + mappedFieldName + "], expected field called [" + COUNTS_FIELD .getPreferredName () + "]"
141160 );
142161 }
143162 if (centroids .size () != counts .size ()) {
144- throw new DocumentParsingException (
163+ throw documentParsingExceptionProvider . apply (
145164 parser .getTokenLocation (),
146165 "error parsing field ["
147166 + mappedFieldName
@@ -165,20 +184,25 @@ public static ParsedTDigest parse(String mappedFieldName, XContentParser parser)
165184 return new ParsedTDigest (centroids , counts , sum , min , max );
166185 }
167186
168- private static ArrayList <Long > getCounts (String mappedFieldName , XContentParser parser ) throws IOException {
187+ private static ArrayList <Long > getCounts (
188+ String mappedFieldName ,
189+ XContentParser parser ,
190+ BiFunction <XContentLocation , String , RuntimeException > documentParsingExceptionProvider ,
191+ ParsingExceptionProvider parsingExceptionProvider
192+ ) throws IOException {
169193 ArrayList <Long > counts ;
170194 XContentParser .Token token ;
171195 token = parser .nextToken ();
172196 // should be an array
173- ensureExpectedToken (XContentParser .Token .START_ARRAY , token , parser );
197+ ensureExpectedToken (XContentParser .Token .START_ARRAY , token , parser , parsingExceptionProvider );
174198 counts = new ArrayList <>();
175199 token = parser .nextToken ();
176200 while (token != XContentParser .Token .END_ARRAY ) {
177201 // should be a number
178- ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser );
202+ ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser , parsingExceptionProvider );
179203 long count = parser .longValue ();
180204 if (count < 0 ) {
181- throw new DocumentParsingException (
205+ throw documentParsingExceptionProvider . apply (
182206 parser .getTokenLocation (),
183207 "error parsing field [" + mappedFieldName + "], [" + COUNTS_FIELD + "] elements must be >= 0 but got " + count
184208 );
@@ -189,22 +213,27 @@ private static ArrayList<Long> getCounts(String mappedFieldName, XContentParser
189213 return counts ;
190214 }
191215
192- private static ArrayList <Double > getCentroids (String mappedFieldName , XContentParser parser ) throws IOException {
216+ private static ArrayList <Double > getCentroids (
217+ String mappedFieldName ,
218+ XContentParser parser ,
219+ BiFunction <XContentLocation , String , RuntimeException > documentParsingExceptionProvider ,
220+ ParsingExceptionProvider parsingExceptionProvider
221+ ) throws IOException {
193222 XContentParser .Token token ;
194223 ArrayList <Double > centroids ;
195224 token = parser .nextToken ();
196225 // should be an array
197- ensureExpectedToken (XContentParser .Token .START_ARRAY , token , parser );
226+ ensureExpectedToken (XContentParser .Token .START_ARRAY , token , parser , parsingExceptionProvider );
198227 centroids = new ArrayList <>();
199228 token = parser .nextToken ();
200229 double previousVal = -Double .MAX_VALUE ;
201230 while (token != XContentParser .Token .END_ARRAY ) {
202231 // should be a number
203- ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser );
232+ ensureExpectedToken (XContentParser .Token .VALUE_NUMBER , token , parser , parsingExceptionProvider );
204233 double val = parser .doubleValue ();
205234 if (val < previousVal ) {
206235 // centroids must be in increasing order
207- throw new DocumentParsingException (
236+ throw documentParsingExceptionProvider . apply (
208237 parser .getTokenLocation (),
209238 "error parsing field ["
210239 + mappedFieldName
@@ -224,4 +253,23 @@ private static ArrayList<Double> getCentroids(String mappedFieldName, XContentPa
224253 return centroids ;
225254 }
226255
256+ /**
257+ * Interface for throwing a parsing exception, needed for visibility
258+ */
259+ @ FunctionalInterface
260+ public interface ParsingExceptionProvider {
261+ RuntimeException apply (XContentParser parser , XContentParser .Token expected , XContentParser .Token actual ) throws IOException ;
262+ }
263+
264+ public static void ensureExpectedToken (
265+ XContentParser .Token expected ,
266+ XContentParser .Token actual ,
267+ XContentParser parser ,
268+ ParsingExceptionProvider parsingExceptionProvider
269+ ) throws IOException {
270+ if (actual != expected ) {
271+ throw parsingExceptionProvider .apply (parser , expected , actual );
272+ }
273+ }
274+
227275}
0 commit comments