blob: eac8664396446826de201ce71b32bccce64025a8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.analytics.statistics;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.BytesRefFieldSource;
import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
import org.apache.lucene.queries.function.valuesource.IntFieldSource;
import org.apache.lucene.queries.function.valuesource.LongFieldSource;
import org.apache.lucene.search.FieldCache;
import org.apache.solr.analytics.expression.ExpressionFactory;
import org.apache.solr.analytics.request.ExpressionRequest;
import org.apache.solr.analytics.util.AnalyticsParams;
import org.apache.solr.analytics.util.AnalyticsParsers;
import org.apache.solr.analytics.util.valuesource.AbsoluteValueDoubleFunction;
import org.apache.solr.analytics.util.valuesource.AddDoubleFunction;
import org.apache.solr.analytics.util.valuesource.ConcatStringFunction;
import org.apache.solr.analytics.util.valuesource.ConstDateSource;
import org.apache.solr.analytics.util.valuesource.ConstDoubleSource;
import org.apache.solr.analytics.util.valuesource.ConstStringSource;
import org.apache.solr.analytics.util.valuesource.DateFieldSource;
import org.apache.solr.analytics.util.valuesource.DateMathFunction;
import org.apache.solr.analytics.util.valuesource.DivDoubleFunction;
import org.apache.solr.analytics.util.valuesource.DualDoubleFunction;
import org.apache.solr.analytics.util.valuesource.FilterFieldSource;
import org.apache.solr.analytics.util.valuesource.LogDoubleFunction;
import org.apache.solr.analytics.util.valuesource.MultiDateFunction;
import org.apache.solr.analytics.util.valuesource.MultiDoubleFunction;
import org.apache.solr.analytics.util.valuesource.MultiplyDoubleFunction;
import org.apache.solr.analytics.util.valuesource.NegateDoubleFunction;
import org.apache.solr.analytics.util.valuesource.PowDoubleFunction;
import org.apache.solr.analytics.util.valuesource.ReverseStringFunction;
import org.apache.solr.analytics.util.valuesource.SingleDoubleFunction;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.StrField;
import org.apache.solr.schema.TrieDateField;
import org.apache.solr.schema.TrieDoubleField;
import org.apache.solr.schema.TrieFloatField;
import org.apache.solr.schema.TrieIntField;
import org.apache.solr.schema.TrieLongField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Supplier;
public class StatsCollectorSupplierFactory {
private static final Logger log = LoggerFactory.getLogger(StatsCollectorSupplierFactory.class);
// FunctionTypes
final static int NUMBER_TYPE = 0;
final static int DATE_TYPE = 1;
final static int STRING_TYPE = 2;
final static int FIELD_TYPE = 3;
final static int FILTER_TYPE = 4;
/**
* Builds a Supplier that will generate identical arrays of new StatsCollectors.
*
* @param schema The Schema being used.
* @param exRequests The expression requests to generate a StatsCollector[] from.
* @return A Supplier that will return an array of new StatsCollector.
*/
@SuppressWarnings("unchecked")
public static Supplier<StatsCollector[]> create(IndexSchema schema, List<ExpressionRequest> exRequests ) {
final Map<String, Set<String>> collectorStats = new TreeMap<>();
final Map<String, Set<Integer>> collectorPercs = new TreeMap<>();
final Map<String, ValueSource> collectorSources = new TreeMap<>();
// Iterate through all expression request to make a list of ValueSource strings
// and statistics that need to be calculated on those ValueSources.
for (ExpressionRequest expRequest : exRequests) {
String statExpression = expRequest.getExpressionString();
Set<String> statistics = getStatistics(statExpression);
if (statistics == null) {
continue;
}
for (String statExp : statistics) {
String stat;
String operands;
try {
stat = statExp.substring(0, statExp.indexOf('(')).trim();
operands = statExp.substring(statExp.indexOf('(')+1, statExp.lastIndexOf(')')).trim();
} catch (Exception e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Unable to parse statistic: ["+statExpression+"]",e);
}
String[] arguments = ExpressionFactory.getArguments(operands);
String source = arguments[0];
if (stat.equals(AnalyticsParams.STAT_PERCENTILE)) {
// The statistic is a percentile, extra parsing is required
if (arguments.length<2) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Too few arguments given for "+stat+"() in ["+statExp+"].");
} else if (arguments.length>2) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Too many arguments given for "+stat+"() in ["+statExp+"].");
}
source = arguments[1];
Set<Integer> percs = collectorPercs.get(source);
if (percs == null) {
percs = new HashSet<>();
collectorPercs.put(source, percs);
}
try {
int perc = Integer.parseInt(arguments[0]);
if (perc>0 && perc<100) {
percs.add(perc);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST,"The percentile in ["+statExp+"] is not between 0 and 100, exculsive.");
}
} catch (NumberFormatException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"\""+arguments[0]+"\" cannot be converted into a percentile.",e);
}
} else if (arguments.length>1) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Too many arguments given for "+stat+"() in ["+statExp+"].");
} else if (arguments.length==0) {
throw new SolrException(ErrorCode.BAD_REQUEST,"No arguments given for "+stat+"() in ["+statExp+"].");
}
// Only unique ValueSources will be made; therefore statistics must be accumulated for
// each ValueSource, even across different expression requests
Set<String> stats = collectorStats.get(source);
if (stats == null) {
stats = new HashSet<>();
collectorStats.put(source, stats);
}
if(AnalyticsParams.STAT_PERCENTILE.equals(stat)) {
stats.add(stat + "_"+ arguments[0]);
} else {
stats.add(stat);
}
}
}
String[] keys = collectorStats.keySet().toArray(new String[0]);
for (String sourceStr : keys) {
// Build one ValueSource for each unique value source string
ValueSource source = buildSourceTree(schema, sourceStr);
if (source == null) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The statistic ["+sourceStr+"] could not be parsed.");
}
String builtString = source.toString();
collectorSources.put(builtString,source);
// Replace the user given string with the correctly built string
if (!builtString.equals(sourceStr)) {
Set<String> stats = collectorStats.remove(sourceStr);
if (stats!=null) {
collectorStats.put(builtString, stats);
}
Set<Integer> percs = collectorPercs.remove(sourceStr);
if (percs!=null) {
collectorPercs.put(builtString, percs);
}
for (ExpressionRequest er : exRequests) {
er.setExpressionString(er.getExpressionString().replace(sourceStr, builtString));
}
}
}
if (collectorSources.size()==0) {
return new Supplier<StatsCollector[]>() {
@Override
public StatsCollector[] get() {
return new StatsCollector[0];
}
};
}
log.info("Stats objects: "+collectorStats.size()+" sr="+collectorSources.size()+" pr="+collectorPercs.size() );
// All information is stored in final arrays so that nothing
// has to be computed when the Supplier's get() method is called.
final Set<String>[] statsArr = collectorStats.values().toArray(new Set[0]);
final ValueSource[] sourceArr = collectorSources.values().toArray(new ValueSource[0]);
final boolean[] uniqueBools = new boolean[statsArr.length];
final boolean[] medianBools = new boolean[statsArr.length];
final boolean[] numericBools = new boolean[statsArr.length];
final boolean[] dateBools = new boolean[statsArr.length];
final double[][] percsArr = new double[statsArr.length][];
final String[][] percsNames = new String[statsArr.length][];
for (int count = 0; count < sourceArr.length; count++) {
uniqueBools[count] = statsArr[count].contains(AnalyticsParams.STAT_UNIQUE);
medianBools[count] = statsArr[count].contains(AnalyticsParams.STAT_MEDIAN);
numericBools[count] = statsArr[count].contains(AnalyticsParams.STAT_SUM)||statsArr[count].contains(AnalyticsParams.STAT_SUM_OF_SQUARES)||statsArr[count].contains(AnalyticsParams.STAT_MEAN)||statsArr[count].contains(AnalyticsParams.STAT_STANDARD_DEVIATION);
dateBools[count] = (sourceArr[count] instanceof DateFieldSource) | (sourceArr[count] instanceof MultiDateFunction) | (sourceArr[count] instanceof ConstDateSource);
Set<Integer> ps = collectorPercs.get(sourceArr[count].toString());
if (ps!=null) {
percsArr[count] = new double[ps.size()];
percsNames[count] = new String[ps.size()];
int percCount = 0;
for (int p : ps) {
percsArr[count][percCount] = p/100.0;
percsNames[count][percCount++] = AnalyticsParams.STAT_PERCENTILE+"_"+p;
}
}
}
// Making the Supplier
return new Supplier<StatsCollector[]>() {
public StatsCollector[] get() {
StatsCollector[] collectors = new StatsCollector[statsArr.length];
for (int count = 0; count < statsArr.length; count++) {
if(numericBools[count]){
StatsCollector sc = new NumericStatsCollector(sourceArr[count], statsArr[count]);
if(uniqueBools[count]) sc = new UniqueStatsCollector(sc);
if(medianBools[count]) sc = new MedianStatsCollector(sc);
if(percsArr[count]!=null) sc = new PercentileStatsCollector(sc,percsArr[count],percsNames[count]);
collectors[count]=sc;
} else if (dateBools[count]) {
StatsCollector sc = new MinMaxStatsCollector(sourceArr[count], statsArr[count]);
if(uniqueBools[count]) sc = new UniqueStatsCollector(sc);
if(medianBools[count]) sc = new DateMedianStatsCollector(sc);
if(percsArr[count]!=null) sc = new PercentileStatsCollector(sc,percsArr[count],percsNames[count]);
collectors[count]=sc;
} else {
StatsCollector sc = new MinMaxStatsCollector(sourceArr[count], statsArr[count]);
if(uniqueBools[count]) sc = new UniqueStatsCollector(sc);
if(medianBools[count]) sc = new MedianStatsCollector(sc);
if(percsArr[count]!=null) sc = new PercentileStatsCollector(sc,percsArr[count],percsNames[count]);
collectors[count]=sc;
}
}
return collectors;
}
};
}
/**
* Finds the set of statistics that must be computed for the expression.
* @param expression The string representation of an expression
* @return The set of statistics (sum, mean, median, etc.) found in the expression
*/
public static Set<String> getStatistics(String expression) {
HashSet<String> set = new HashSet<>();
int firstParen = expression.indexOf('(');
if (firstParen>0) {
String topOperation = expression.substring(0,firstParen).trim();
if (AnalyticsParams.ALL_STAT_SET.contains(topOperation)) {
set.add(expression);
} else if (!(topOperation.equals(AnalyticsParams.CONSTANT_NUMBER)||topOperation.equals(AnalyticsParams.CONSTANT_DATE)||topOperation.equals(AnalyticsParams.CONSTANT_STRING))) {
String operands = expression.substring(firstParen+1, expression.lastIndexOf(')')).trim();
String[] arguments = ExpressionFactory.getArguments(operands);
for (String argument : arguments) {
Set<String> more = getStatistics(argument);
if (more!=null) {
set.addAll(more);
}
}
}
}
if (set.size()==0) {
return null;
}
return set;
}
/**
* Builds a Value Source from a given string
*
* @param schema The schema being used.
* @param expression The string to be turned into an expression.
* @return The completed ValueSource
*/
private static ValueSource buildSourceTree(IndexSchema schema, String expression) {
return buildSourceTree(schema,expression,FIELD_TYPE);
}
/**
* Builds a Value Source from a given string and a given source type
*
* @param schema The schema being used.
* @param expression The string to be turned into an expression.
* @param sourceType The type of source that must be returned.
* @return The completed ValueSource
*/
private static ValueSource buildSourceTree(IndexSchema schema, String expression, int sourceType) {
int expressionType = getSourceType(expression);
if (sourceType != FIELD_TYPE && expressionType != FIELD_TYPE &&
expressionType != FILTER_TYPE && expressionType != sourceType) {
return null;
}
switch (expressionType) {
case NUMBER_TYPE : return buildNumericSource(schema, expression);
case DATE_TYPE : return buildDateSource(schema, expression);
case STRING_TYPE : return buildStringSource(schema, expression);
case FIELD_TYPE : return buildFieldSource(schema, expression, sourceType);
case FILTER_TYPE : return buildFilterSource(schema, expression.substring(expression.indexOf('(')+1,expression.lastIndexOf(')')), sourceType);
default : throw new SolrException(ErrorCode.BAD_REQUEST,expression+" is not a valid operation.");
}
}
/**
* Determines what type of value source the expression represents.
*
* @param expression The expression representing the desired ValueSource
* @return NUMBER_TYPE, DATE_TYPE, STRING_TYPE or -1
*/
private static int getSourceType(String expression) {
int paren = expression.indexOf('(');
if (paren<0) {
return FIELD_TYPE;
}
String operation = expression.substring(0,paren).trim();
if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)) {
return NUMBER_TYPE;
} else if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)) {
return DATE_TYPE;
} else if (AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
return STRING_TYPE;
} else if (operation.equals(AnalyticsParams.FILTER)) {
return FILTER_TYPE;
}
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+operation+"\" in ["+expression+"] is not supported.");
}
/**
* Builds a value source for a given field, making sure that the field fits a given source type.
* @param schema the schema
* @param expressionString The name of the field to build a Field Source from.
* @param sourceType FIELD_TYPE for any type of field, NUMBER_TYPE for numeric fields,
* DATE_TYPE for date fields and STRING_TYPE for string fields.
* @return a value source
*/
private static ValueSource buildFieldSource(IndexSchema schema, String expressionString, int sourceType) {
SchemaField sf;
try {
sf = schema.getField(expressionString);
} catch (SolrException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The field "+expressionString+" does not exist.",e);
}
FieldType type = sf.getType();
if ( type instanceof TrieIntField) {
if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
return null;
}
return new IntFieldSource(expressionString, FieldCache.NUMERIC_UTILS_INT_PARSER) {
public String description() {
return field;
}
};
} else if (type instanceof TrieLongField) {
if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
return null;
}
return new LongFieldSource(expressionString, FieldCache.NUMERIC_UTILS_LONG_PARSER) {
public String description() {
return field;
}
};
} else if (type instanceof TrieFloatField) {
if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
return null;
}
return new FloatFieldSource(expressionString, FieldCache.NUMERIC_UTILS_FLOAT_PARSER) {
public String description() {
return field;
}
};
} else if (type instanceof TrieDoubleField) {
if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
return null;
}
return new DoubleFieldSource(expressionString, FieldCache.NUMERIC_UTILS_DOUBLE_PARSER) {
public String description() {
return field;
}
};
} else if (type instanceof TrieDateField) {
if (sourceType!=DATE_TYPE&&sourceType!=FIELD_TYPE) {
return null;
}
return new DateFieldSource(expressionString, AnalyticsParsers.DEFAULT_DATE_PARSER) {
public String description() {
return field;
}
};
} else if (type instanceof StrField) {
if (sourceType!=STRING_TYPE&&sourceType!=FIELD_TYPE) {
return null;
}
return new BytesRefFieldSource(expressionString) {
public String description() {
return field;
}
};
}
throw new SolrException(ErrorCode.BAD_REQUEST, type.toString()+" is not a supported field type in Solr Analytics.");
}
/**
* Builds a default is missing source that wraps a given source. A missing value is required for all
* non-field value sources.
* @param schema the schema
* @param expressionString The name of the field to build a Field Source from.
* @param sourceType FIELD_TYPE for any type of field, NUMBER_TYPE for numeric fields,
* DATE_TYPE for date fields and STRING_TYPE for string fields.
* @return a value source
*/
@SuppressWarnings("deprecation")
private static ValueSource buildFilterSource(IndexSchema schema, String expressionString, int sourceType) {
String[] arguments = ExpressionFactory.getArguments(expressionString);
if (arguments.length!=2) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Invalid arguments were given for \""+AnalyticsParams.FILTER+"\".");
}
ValueSource delegateSource = buildSourceTree(schema, arguments[0], sourceType);
if (delegateSource==null) {
return null;
}
Object defaultObject;
ValueSource src = delegateSource;
if (delegateSource instanceof FilterFieldSource) {
src = ((FilterFieldSource)delegateSource).getRootSource();
}
if ( src instanceof IntFieldSource) {
try {
defaultObject = new Integer(arguments[1]);
} catch (NumberFormatException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into an integer.",e);
}
} else if ( src instanceof DateFieldSource || src instanceof MultiDateFunction) {
try {
defaultObject = TrieDateField.parseDate(arguments[1]);
} catch (ParseException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a date.",e);
}
} else if ( src instanceof LongFieldSource ) {
try {
defaultObject = new Long(arguments[1]);
} catch (NumberFormatException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a long.",e);
}
} else if ( src instanceof FloatFieldSource ) {
try {
defaultObject = new Float(arguments[1]);
} catch (NumberFormatException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a float.",e);
}
} else if ( src instanceof DoubleFieldSource || src instanceof SingleDoubleFunction ||
src instanceof DualDoubleFunction|| src instanceof MultiDoubleFunction) {
try {
defaultObject = new Double(arguments[1]);
} catch (NumberFormatException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a double.",e);
}
} else {
defaultObject = arguments[1];
}
return new FilterFieldSource(delegateSource,defaultObject);
}
/**
* Recursively parses and breaks down the expression string to build a numeric ValueSource.
*
* @param schema The schema to pull fields from.
* @param expressionString The expression string to build a ValueSource from.
* @return The value source represented by the given expressionString
*/
private static ValueSource buildNumericSource(IndexSchema schema, String expressionString) {
int paren = expressionString.indexOf('(');
String[] arguments;
String operands;
if (paren<0) {
return buildFieldSource(schema,expressionString,NUMBER_TYPE);
} else {
try {
operands = expressionString.substring(paren+1, expressionString.lastIndexOf(')')).trim();
} catch (Exception e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Missing closing parenthesis in ["+expressionString+"]");
}
arguments = ExpressionFactory.getArguments(operands);
}
String operation = expressionString.substring(0, paren).trim();
if (operation.equals(AnalyticsParams.CONSTANT_NUMBER)) {
if (arguments.length!=1) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The constant number declaration ["+expressionString+"] does not have exactly 1 argument.");
}
return new ConstDoubleSource(Double.parseDouble(arguments[0]));
} else if (operation.equals(AnalyticsParams.NEGATE)) {
if (arguments.length!=1) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The negate operation ["+expressionString+"] does not have exactly 1 argument.");
}
ValueSource argSource = buildNumericSource(schema, arguments[0]);
if (argSource==null) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+AnalyticsParams.NEGATE+"\" requires a numeric field or operation as argument. \""+arguments[0]+"\" is not a numeric field or operation.");
}
return new NegateDoubleFunction(argSource);
} else if (operation.equals(AnalyticsParams.ABSOLUTE_VALUE)) {
if (arguments.length!=1) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The absolute value operation ["+expressionString+"] does not have exactly 1 argument.");
}
ValueSource argSource = buildNumericSource(schema, arguments[0]);
if (argSource==null) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+AnalyticsParams.NEGATE+"\" requires a numeric field or operation as argument. \""+arguments[0]+"\" is not a numeric field or operation.");
}
return new AbsoluteValueDoubleFunction(argSource);
} else if (operation.equals(AnalyticsParams.FILTER)) {
return buildFilterSource(schema, operands, NUMBER_TYPE);
}
List<ValueSource> subExpressions = new ArrayList<>();
for (String argument : arguments) {
ValueSource argSource = buildNumericSource(schema, argument);
if (argSource == null) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+operation+"\" requires numeric fields or operations as arguments. \""+argument+"\" is not a numeric field or operation.");
}
subExpressions.add(argSource);
}
if (operation.equals(AnalyticsParams.ADD)) {
return new AddDoubleFunction(subExpressions.toArray(new ValueSource[0]));
} else if (operation.equals(AnalyticsParams.MULTIPLY)) {
return new MultiplyDoubleFunction(subExpressions.toArray(new ValueSource[0]));
} else if (operation.equals(AnalyticsParams.DIVIDE)) {
if (subExpressions.size()!=2) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The divide operation ["+expressionString+"] does not have exactly 2 arguments.");
}
return new DivDoubleFunction(subExpressions.get(0),subExpressions.get(1));
} else if (operation.equals(AnalyticsParams.POWER)) {
if (subExpressions.size()!=2) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The power operation ["+expressionString+"] does not have exactly 2 arguments.");
}
return new PowDoubleFunction(subExpressions.get(0),subExpressions.get(1));
} else if (operation.equals(AnalyticsParams.LOG)) {
if (subExpressions.size()!=2) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The log operation ["+expressionString+"] does not have exactly 2 arguments.");
}
return new LogDoubleFunction(subExpressions.get(0), subExpressions.get(1));
}
if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)||AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
return null;
}
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation ["+expressionString+"] is not supported.");
}
/**
* Recursively parses and breaks down the expression string to build a date ValueSource.
*
* @param schema The schema to pull fields from.
* @param expressionString The expression string to build a ValueSource from.
* @return The value source represented by the given expressionString
*/
@SuppressWarnings("deprecation")
private static ValueSource buildDateSource(IndexSchema schema, String expressionString) {
int paren = expressionString.indexOf('(');
String[] arguments;
if (paren<0) {
return buildFieldSource(schema, expressionString, DATE_TYPE);
} else {
arguments = ExpressionFactory.getArguments(expressionString.substring(paren+1, expressionString.lastIndexOf(')')).trim());
}
String operands = arguments[0];
String operation = expressionString.substring(0, paren).trim();
if (operation.equals(AnalyticsParams.CONSTANT_DATE)) {
if (arguments.length!=1) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The constant date declaration ["+expressionString+"] does not have exactly 1 argument.");
}
try {
return new ConstDateSource(TrieDateField.parseDate(operands));
} catch (ParseException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"The constant "+operands+" cannot be converted into a date.",e);
}
} else if (operation.equals(AnalyticsParams.FILTER)) {
return buildFilterSource(schema, operands, DATE_TYPE);
}
if (operation.equals(AnalyticsParams.DATE_MATH)) {
List<ValueSource> subExpressions = new ArrayList<>();
boolean first = true;
for (String argument : arguments) {
ValueSource argSource;
if (first) {
first = false;
argSource = buildDateSource(schema, argument);
if (argSource == null) {
throw new SolrException(ErrorCode.BAD_REQUEST,"\""+AnalyticsParams.DATE_MATH+"\" requires the first argument be a date operation or field. ["+argument+"] is not a date operation or field.");
}
} else {
argSource = buildStringSource(schema, argument);
if (argSource == null) {
throw new SolrException(ErrorCode.BAD_REQUEST,"\""+AnalyticsParams.DATE_MATH+"\" requires that all arguments except the first be string operations. ["+argument+"] is not a string operation.");
}
}
subExpressions.add(argSource);
}
return new DateMathFunction(subExpressions.toArray(new ValueSource[0]));
}
if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)||AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
return null;
}
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation ["+expressionString+"] is not supported.");
}
/**
* Recursively parses and breaks down the expression string to build a string ValueSource.
*
* @param schema The schema to pull fields from.
* @param expressionString The expression string to build a ValueSource from.
* @return The value source represented by the given expressionString
*/
private static ValueSource buildStringSource(IndexSchema schema, String expressionString) {
int paren = expressionString.indexOf('(');
String[] arguments;
if (paren<0) {
return buildFieldSource(schema, expressionString, FIELD_TYPE);
} else {
arguments = ExpressionFactory.getArguments(expressionString.substring(paren+1, expressionString.lastIndexOf(')')).trim());
}
String operands = arguments[0];
String operation = expressionString.substring(0, paren).trim();
if (operation.equals(AnalyticsParams.CONSTANT_STRING)) {
operands = expressionString.substring(paren+1, expressionString.lastIndexOf(')'));
return new ConstStringSource(operands);
} else if (operation.equals(AnalyticsParams.FILTER)) {
return buildFilterSource(schema,operands,FIELD_TYPE);
} else if (operation.equals(AnalyticsParams.REVERSE)) {
if (arguments.length!=1) {
throw new SolrException(ErrorCode.BAD_REQUEST,"\""+AnalyticsParams.REVERSE+"\" requires exactly one argument. The number of arguments in "+expressionString+" is not 1.");
}
return new ReverseStringFunction(buildStringSource(schema, operands));
}
List<ValueSource> subExpressions = new ArrayList<>();
for (String argument : arguments) {
subExpressions.add(buildSourceTree(schema, argument));
}
if (operation.equals(AnalyticsParams.CONCATENATE)) {
return new ConcatStringFunction(subExpressions.toArray(new ValueSource[0]));
}
if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)) {
return buildNumericSource(schema, expressionString);
} else if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)) {
return buildDateSource(schema, expressionString);
}
throw new SolrException(ErrorCode.BAD_REQUEST,"The operation ["+expressionString+"] is not supported.");
}
}