Merge pull request #11778 from polyfractal/bugfix/11487

Aggregations: moving_avg model parser should accept any numeric
This commit is contained in:
Zachary Tong 2015-06-19 10:55:28 -04:00
commit 00cc16cc6a
8 changed files with 99 additions and 41 deletions

View File

@ -33,6 +33,7 @@ import org.elasticsearch.search.aggregations.support.format.ValueFormatter;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -144,7 +145,14 @@ public class MovAvgParser implements PipelineAggregator.Parser {
throw new SearchParseException(context, "Unknown model [" + model + "] specified. Valid options are:" throw new SearchParseException(context, "Unknown model [" + model + "] specified. Valid options are:"
+ movAvgModelParserMapper.getAllNames().toString(), parser.getTokenLocation()); + movAvgModelParserMapper.getAllNames().toString(), parser.getTokenLocation());
} }
MovAvgModel movAvgModel = modelParser.parse(settings, pipelineAggregatorName, context, window);
MovAvgModel movAvgModel;
try {
movAvgModel = modelParser.parse(settings, pipelineAggregatorName, window);
} catch (ParseException exception) {
throw new SearchParseException(context, "Could not parse settings for model [" + model + "].", null, exception);
}
return new MovAvgPipelineAggregator.Factory(pipelineAggregatorName, bucketsPaths, formatter, gapPolicy, window, predict, return new MovAvgPipelineAggregator.Factory(pipelineAggregatorName, bucketsPaths, formatter, gapPolicy, window, predict,
movAvgModel); movAvgModel);

View File

@ -28,6 +28,7 @@ import org.elasticsearch.search.aggregations.pipeline.movavg.MovAvgParser;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -92,9 +93,9 @@ public class EwmaModel extends MovAvgModel {
} }
@Override @Override
public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, SearchContext context, int windowSize) { public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException {
double alpha = parseDoubleParam(context, settings, "alpha", 0.5); double alpha = parseDoubleParam(settings, "alpha", 0.5);
return new EwmaModel(alpha); return new EwmaModel(alpha);
} }

View File

@ -28,6 +28,7 @@ import org.elasticsearch.search.aggregations.pipeline.movavg.MovAvgParser;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.*; import java.util.*;
/** /**
@ -151,10 +152,10 @@ public class HoltLinearModel extends MovAvgModel {
} }
@Override @Override
public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, SearchContext context, int windowSize) { public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException {
double alpha = parseDoubleParam(context, settings, "alpha", 0.5); double alpha = parseDoubleParam(settings, "alpha", 0.5);
double beta = parseDoubleParam(context, settings, "beta", 0.5); double beta = parseDoubleParam(settings, "beta", 0.5);
return new HoltLinearModel(alpha, beta); return new HoltLinearModel(alpha, beta);
} }
} }

View File

@ -32,6 +32,7 @@ import org.elasticsearch.search.aggregations.pipeline.movavg.MovAvgParser;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.*; import java.util.*;
/** /**
@ -316,17 +317,17 @@ public class HoltWintersModel extends MovAvgModel {
} }
@Override @Override
public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, SearchContext context, int windowSize) { public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException {
double alpha = parseDoubleParam(context, settings, "alpha", 0.5); double alpha = parseDoubleParam(settings, "alpha", 0.5);
double beta = parseDoubleParam(context, settings, "beta", 0.5); double beta = parseDoubleParam(settings, "beta", 0.5);
double gamma = parseDoubleParam(context, settings, "gamma", 0.5); double gamma = parseDoubleParam(settings, "gamma", 0.5);
int period = parseIntegerParam(context, settings, "period", 1); int period = parseIntegerParam(settings, "period", 1);
if (windowSize < 2 * period) { if (windowSize < 2 * period) {
throw new SearchParseException(context, "Field [window] must be at least twice as large as the period when " + throw new ParseException("Field [window] must be at least twice as large as the period when " +
"using Holt-Winters. Value provided was [" + windowSize + "], which is less than (2*period) == " "using Holt-Winters. Value provided was [" + windowSize + "], which is less than (2*period) == "
+ (2 * period), null); + (2 * period), 0);
} }
SeasonalityType seasonalityType = SeasonalityType.ADDITIVE; SeasonalityType seasonalityType = SeasonalityType.ADDITIVE;
@ -337,13 +338,13 @@ public class HoltWintersModel extends MovAvgModel {
if (value instanceof String) { if (value instanceof String) {
seasonalityType = SeasonalityType.parse((String)value); seasonalityType = SeasonalityType.parse((String)value);
} else { } else {
throw new SearchParseException(context, "Parameter [type] must be a String, type `" throw new ParseException("Parameter [type] must be a String, type `"
+ value.getClass().getSimpleName() + "` provided instead", null); + value.getClass().getSimpleName() + "` provided instead", 0);
} }
} }
} }
boolean pad = parseBoolParam(context, settings, "pad", seasonalityType.equals(SeasonalityType.MULTIPLICATIVE)); boolean pad = parseBoolParam(settings, "pad", seasonalityType.equals(SeasonalityType.MULTIPLICATIVE));
return new HoltWintersModel(alpha, beta, gamma, period, seasonalityType, pad); return new HoltWintersModel(alpha, beta, gamma, period, seasonalityType, pad);
} }

View File

@ -29,6 +29,7 @@ import org.elasticsearch.search.aggregations.pipeline.movavg.MovAvgParser;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -79,7 +80,7 @@ public class LinearModel extends MovAvgModel {
} }
@Override @Override
public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, SearchContext context, int windowSize) { public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException {
return new LinearModel(); return new LinearModel();
} }
} }

View File

@ -27,6 +27,7 @@ import org.elasticsearch.search.SearchParseException;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -125,26 +126,24 @@ public abstract class MovAvgModel {
* *
* @param settings Map of settings, extracted from the request * @param settings Map of settings, extracted from the request
* @param pipelineName Name of the parent pipeline agg * @param pipelineName Name of the parent pipeline agg
* @param context The parser context that we are in
* @param windowSize Size of the window for this moving avg * @param windowSize Size of the window for this moving avg
* @return A fully built moving average model * @return A fully built moving average model
*/ */
public abstract MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, SearchContext context, int windowSize); public abstract MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException;
/** /**
* Extracts a 0-1 inclusive double from the settings map, otherwise throws an exception * Extracts a 0-1 inclusive double from the settings map, otherwise throws an exception
* *
* @param context Search query context
* @param settings Map of settings provided to this model * @param settings Map of settings provided to this model
* @param name Name of parameter we are attempting to extract * @param name Name of parameter we are attempting to extract
* @param defaultValue Default value to be used if value does not exist in map * @param defaultValue Default value to be used if value does not exist in map
* *
* @throws SearchParseException * @throws ParseException
* *
* @return Double value extracted from settings map * @return Double value extracted from settings map
*/ */
protected double parseDoubleParam(SearchContext context, @Nullable Map<String, Object> settings, String name, double defaultValue) { protected double parseDoubleParam(@Nullable Map<String, Object> settings, String name, double defaultValue) throws ParseException {
if (settings == null) { if (settings == null) {
return defaultValue; return defaultValue;
} }
@ -152,33 +151,32 @@ public abstract class MovAvgModel {
Object value = settings.get(name); Object value = settings.get(name);
if (value == null) { if (value == null) {
return defaultValue; return defaultValue;
} else if (value instanceof Double) { } else if (value instanceof Number) {
double v = (Double)value; double v = ((Number) value).doubleValue();
if (v >= 0 && v <= 1) { if (v >= 0 && v <= 1) {
return v; return v;
} }
throw new SearchParseException(context, "Parameter [" + name + "] must be between 0-1 inclusive. Provided" throw new ParseException("Parameter [" + name + "] must be between 0-1 inclusive. Provided"
+ "value was [" + v + "]", null); + "value was [" + v + "]", 0);
} }
throw new SearchParseException(context, "Parameter [" + name + "] must be a double, type `" throw new ParseException("Parameter [" + name + "] must be a double, type `"
+ value.getClass().getSimpleName() + "` provided instead", null); + value.getClass().getSimpleName() + "` provided instead", 0);
} }
/** /**
* Extracts an integer from the settings map, otherwise throws an exception * Extracts an integer from the settings map, otherwise throws an exception
* *
* @param context Search query context
* @param settings Map of settings provided to this model * @param settings Map of settings provided to this model
* @param name Name of parameter we are attempting to extract * @param name Name of parameter we are attempting to extract
* @param defaultValue Default value to be used if value does not exist in map * @param defaultValue Default value to be used if value does not exist in map
* *
* @throws SearchParseException * @throws ParseException
* *
* @return Integer value extracted from settings map * @return Integer value extracted from settings map
*/ */
protected int parseIntegerParam(SearchContext context, @Nullable Map<String, Object> settings, String name, int defaultValue) { protected int parseIntegerParam(@Nullable Map<String, Object> settings, String name, int defaultValue) throws ParseException {
if (settings == null) { if (settings == null) {
return defaultValue; return defaultValue;
} }
@ -186,18 +184,17 @@ public abstract class MovAvgModel {
Object value = settings.get(name); Object value = settings.get(name);
if (value == null) { if (value == null) {
return defaultValue; return defaultValue;
} else if (value instanceof Integer) { } else if (value instanceof Number) {
return (Integer)value; return ((Number) value).intValue();
} }
throw new SearchParseException(context, "Parameter [" + name + "] must be an integer, type `" throw new ParseException("Parameter [" + name + "] must be an integer, type `"
+ value.getClass().getSimpleName() + "` provided instead", null); + value.getClass().getSimpleName() + "` provided instead", 0);
} }
/** /**
* Extracts a boolean from the settings map, otherwise throws an exception * Extracts a boolean from the settings map, otherwise throws an exception
* *
* @param context Search query context
* @param settings Map of settings provided to this model * @param settings Map of settings provided to this model
* @param name Name of parameter we are attempting to extract * @param name Name of parameter we are attempting to extract
* @param defaultValue Default value to be used if value does not exist in map * @param defaultValue Default value to be used if value does not exist in map
@ -206,7 +203,7 @@ public abstract class MovAvgModel {
* *
* @return Boolean value extracted from settings map * @return Boolean value extracted from settings map
*/ */
protected boolean parseBoolParam(SearchContext context, @Nullable Map<String, Object> settings, String name, boolean defaultValue) { protected boolean parseBoolParam(@Nullable Map<String, Object> settings, String name, boolean defaultValue) throws ParseException {
if (settings == null) { if (settings == null) {
return defaultValue; return defaultValue;
} }
@ -218,8 +215,8 @@ public abstract class MovAvgModel {
return (Boolean)value; return (Boolean)value;
} }
throw new SearchParseException(context, "Parameter [" + name + "] must be a boolean, type `" throw new ParseException("Parameter [" + name + "] must be a boolean, type `"
+ value.getClass().getSimpleName() + "` provided instead", null); + value.getClass().getSimpleName() + "` provided instead", 0);
} }
} }

View File

@ -28,6 +28,7 @@ import org.elasticsearch.search.aggregations.pipeline.movavg.MovAvgParser;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -72,7 +73,7 @@ public class SimpleModel extends MovAvgModel {
} }
@Override @Override
public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, SearchContext context, int windowSize) { public MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException {
return new SimpleModel(); return new SimpleModel();
} }
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.search.aggregations.pipeline.moving.avg;
import com.google.common.collect.EvictingQueue; import com.google.common.collect.EvictingQueue;
import org.elasticsearch.search.SearchParseException;
import org.elasticsearch.search.aggregations.pipeline.movavg.models.*; import org.elasticsearch.search.aggregations.pipeline.movavg.models.*;
import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.ElasticsearchTestCase;
@ -28,7 +29,8 @@ import static org.hamcrest.Matchers.equalTo;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays; import java.text.ParseException;
import java.util.*;
public class MovAvgUnitTests extends ElasticsearchTestCase { public class MovAvgUnitTests extends ElasticsearchTestCase {
@ -583,4 +585,50 @@ public class MovAvgUnitTests extends ElasticsearchTestCase {
} }
} }
@Test
public void testNumericValidation() {
List<MovAvgModel.AbstractModelParser> parsers = new ArrayList<>(5);
// Simple and Linear don't have any settings to test
parsers.add(new EwmaModel.SingleExpModelParser());
parsers.add(new HoltWintersModel.HoltWintersModelParser());
parsers.add(new HoltLinearModel.DoubleExpModelParser());
Object[] values = {(byte)1, 1, 1L, (short)1, (double)1};
Map<String, Object> settings = new HashMap<>(2);
for (MovAvgModel.AbstractModelParser parser : parsers) {
for (Object v : values) {
settings.put("alpha", v);
settings.put("beta", v);
settings.put("gamma", v);
try {
parser.parse(settings, "pipeline", 10);
} catch (ParseException e) {
fail(parser.getName() + " parser should not have thrown SearchParseException while parsing [" +
v.getClass().getSimpleName() +"]");
}
}
}
for (MovAvgModel.AbstractModelParser parser : parsers) {
settings.put("alpha", "abc");
settings.put("beta", "abc");
settings.put("gamma", "abc");
try {
parser.parse(settings, "pipeline", 10);
} catch (ParseException e) {
//all good
continue;
}
fail(parser.getName() + " parser should have thrown SearchParseException while parsing [String]");
}
}
} }