| /* |
| * 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.nifi.attribute.expression.language; |
| |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionLexer; |
| import org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser; |
| import org.apache.nifi.attribute.expression.language.evaluation.BooleanEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.DateEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.Evaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.NumberEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.QueryResult; |
| import org.apache.nifi.attribute.expression.language.evaluation.StringEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.cast.BooleanCastEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.cast.DateCastEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.cast.NumberCastEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.cast.StringCastEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.AndEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.AppendEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.AttributeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ContainsEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.DateToNumberEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.DivideEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.EndsWithEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.EqualsEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.EqualsIgnoreCaseEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.FindEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.FormatEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.GetDelimitedFieldEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.GreaterThanEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.GreaterThanOrEqualEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.HostnameEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.IPEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.IndexOfEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.IsEmptyEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.IsNullEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.LastIndexOfEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.LengthEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.LessThanEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.LessThanOrEqualEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.MatchesEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.MinusEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ModEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.MultiplyEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.NotEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.NotNullEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.NowEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.NumberToDateEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.OneUpSequenceEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.OrEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.PlusEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.PrependEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceAllEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceEmptyEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceNullEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.StartsWithEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.StringToDateEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringAfterEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringAfterLastEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringBeforeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringBeforeLastEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ToLowerEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ToNumberEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ToRadixEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ToStringEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.ToUpperEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.TrimEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.UrlDecodeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.UrlEncodeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.functions.UuidEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.literals.BooleanLiteralEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.literals.NumberLiteralEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.literals.StringLiteralEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.literals.ToLiteralEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.reduce.CountEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.reduce.JoinEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.reduce.ReduceEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.AllAttributesEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.AnyAttributeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.DelineatedAttributeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.IteratingEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.MultiAttributeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.MultiMatchAttributeEvaluator; |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.MultiNamedAttributeEvaluator; |
| import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException; |
| import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageParsingException; |
| import org.apache.nifi.expression.AttributeExpression.ResultType; |
| import org.apache.nifi.expression.AttributeValueDecorator; |
| import org.apache.nifi.flowfile.FlowFile; |
| import org.apache.nifi.processor.exception.ProcessException; |
| import org.antlr.runtime.ANTLRStringStream; |
| import org.antlr.runtime.CharStream; |
| import org.antlr.runtime.CommonTokenStream; |
| import org.antlr.runtime.tree.Tree; |
| |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_ATTRIBUTES; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_DELINEATED_VALUES; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_MATCHING_ATTRIBUTES; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.AND; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_ATTRIBUTE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_DELINEATED_VALUE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_MATCHING_ATTRIBUTE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.APPEND; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ATTRIBUTE_REFERENCE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ATTR_NAME; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.CONTAINS; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.COUNT; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.DIVIDE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ENDS_WITH; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EQUALS; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EQUALS_IGNORE_CASE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EXPRESSION; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FALSE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FIND; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FORMAT; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_DELIMITED_FIELD; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GREATER_THAN; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GREATER_THAN_OR_EQUAL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.HOSTNAME; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.INDEX_OF; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IP; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_EMPTY; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_NULL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JOIN; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LAST_INDEX_OF; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LENGTH; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LESS_THAN; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LESS_THAN_OR_EQUAL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MATCHES; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MINUS; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MOD; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MULTIPLY; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MULTI_ATTRIBUTE_REFERENCE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NEXT_INT; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT_NULL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOW; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NUMBER; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.OR; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PLUS; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PREPEND; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_ALL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_EMPTY; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_NULL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.STARTS_WITH; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.STRING_LITERAL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_AFTER; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_AFTER_LAST; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_BEFORE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_BEFORE_LAST; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DATE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LITERAL; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LOWER; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_NUMBER; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_RADIX; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_STRING; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_UPPER; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TRIM; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TRUE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.URL_DECODE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.URL_ENCODE; |
| import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UUID; |
| |
| import org.apache.nifi.attribute.expression.language.evaluation.selection.MappingEvaluator; |
| |
| /** |
| * Class used for creating and evaluating NiFi Expression Language. Once a Query |
| * has been created, it may be evaluated using the evaluate methods exactly |
| * once. |
| */ |
| public class Query { |
| |
| private final String query; |
| private final Tree tree; |
| private final Evaluator<?> evaluator; |
| private final AtomicBoolean evaluated = new AtomicBoolean(false); |
| |
| private Query(final String query, final Tree tree, final Evaluator<?> evaluator) { |
| this.query = query; |
| this.tree = tree; |
| this.evaluator = evaluator; |
| } |
| |
| public static boolean isValidExpression(final String value) { |
| try { |
| validateExpression(value, false); |
| return true; |
| } catch (final AttributeExpressionLanguageParsingException | ProcessException e) { |
| return false; |
| } |
| } |
| |
| public static ResultType getResultType(final String value) throws AttributeExpressionLanguageParsingException { |
| return Query.compile(value).getResultType(); |
| } |
| |
| public static List<ResultType> extractResultTypes(final String value) throws AttributeExpressionLanguageParsingException { |
| final List<ResultType> types = new ArrayList<>(); |
| |
| for (final Range range : extractExpressionRanges(value)) { |
| final String text = value.substring(range.getStart(), range.getEnd() + 1); |
| types.add(getResultType(text)); |
| } |
| |
| return types; |
| } |
| |
| public static List<String> extractExpressions(final String value) throws AttributeExpressionLanguageParsingException { |
| final List<String> expressions = new ArrayList<>(); |
| |
| for (final Range range : extractExpressionRanges(value)) { |
| expressions.add(value.substring(range.getStart(), range.getEnd() + 1)); |
| } |
| |
| return expressions; |
| } |
| |
| public static List<Range> extractExpressionRanges(final String value) throws AttributeExpressionLanguageParsingException { |
| final List<Range> ranges = new ArrayList<>(); |
| char lastChar = 0; |
| int embeddedCount = 0; |
| int expressionStart = -1; |
| boolean oddDollarCount = false; |
| int backslashCount = 0; |
| |
| charLoop: |
| for (int i = 0; i < value.length(); i++) { |
| final char c = value.charAt(i); |
| |
| if (expressionStart > -1 && (c == '\'' || c == '"') && (lastChar != '\\' || backslashCount % 2 == 0)) { |
| final int endQuoteIndex = findEndQuoteChar(value, i); |
| if (endQuoteIndex < 0) { |
| break charLoop; |
| } |
| |
| i = endQuoteIndex; |
| continue; |
| } |
| |
| if (c == '{') { |
| if (oddDollarCount && lastChar == '$') { |
| if (embeddedCount == 0) { |
| expressionStart = i - 1; |
| } |
| } |
| |
| // Keep track of the number of opening curly braces that we are embedded within, |
| // if we are within an Expression. If we are outside of an Expression, we can just ignore |
| // curly braces. This allows us to ignore the first character if the value is something |
| // like: { ${abc} } |
| // However, we will count the curly braces if we have something like: ${ $${abc} } |
| if (expressionStart > -1) { |
| embeddedCount++; |
| } |
| } else if (c == '}') { |
| if (embeddedCount <= 0) { |
| continue; |
| } |
| |
| if (--embeddedCount == 0) { |
| if (expressionStart > -1) { |
| // ended expression. Add a new range. |
| final Range range = new Range(expressionStart, i); |
| ranges.add(range); |
| } |
| |
| expressionStart = -1; |
| } |
| } else if (c == '$') { |
| oddDollarCount = !oddDollarCount; |
| } else if (c == '\\') { |
| backslashCount++; |
| } else { |
| oddDollarCount = false; |
| } |
| |
| lastChar = c; |
| } |
| |
| return ranges; |
| } |
| |
| /** |
| * @param value expression to validate |
| * @param allowSurroundingCharacters whether to allow surrounding chars |
| * @throws AttributeExpressionLanguageParsingException if problems parsing given expression |
| */ |
| public static void validateExpression(final String value, final boolean allowSurroundingCharacters) throws AttributeExpressionLanguageParsingException { |
| if (!allowSurroundingCharacters) { |
| final List<Range> ranges = extractExpressionRanges(value); |
| if (ranges.size() > 1) { |
| throw new AttributeExpressionLanguageParsingException("Found multiple Expressions but expected only 1"); |
| } |
| |
| if (ranges.isEmpty()) { |
| throw new AttributeExpressionLanguageParsingException("No Expressions found"); |
| } |
| |
| final Range range = ranges.get(0); |
| final String expression = value.substring(range.getStart(), range.getEnd() + 1); |
| Query.compile(expression); |
| |
| if (range.getStart() > 0 || range.getEnd() < value.length() - 1) { |
| throw new AttributeExpressionLanguageParsingException("Found characters outside of Expression"); |
| } |
| } else { |
| for (final Range range : extractExpressionRanges(value)) { |
| final String expression = value.substring(range.getStart(), range.getEnd() + 1); |
| Query.compile(expression); |
| } |
| } |
| } |
| |
| static int findEndQuoteChar(final String value, final int quoteStart) { |
| final char quoteChar = value.charAt(quoteStart); |
| |
| int backslashCount = 0; |
| char lastChar = 0; |
| for (int i = quoteStart + 1; i < value.length(); i++) { |
| final char c = value.charAt(i); |
| |
| if (c == '\\') { |
| backslashCount++; |
| } else if (c == quoteChar && (backslashCount % 2 == 0 || lastChar != '\\')) { |
| return i; |
| } |
| |
| lastChar = c; |
| } |
| |
| return -1; |
| } |
| |
| static String evaluateExpression(final Tree tree, final String queryText, final Map<String, String> expressionMap, final AttributeValueDecorator decorator) throws ProcessException { |
| final Object evaluated = Query.fromTree(tree, queryText).evaluate(expressionMap).getValue(); |
| if (evaluated == null) { |
| return null; |
| } |
| |
| final String value = evaluated.toString(); |
| final String escaped = value.replace("$$", "$"); |
| return decorator == null ? escaped : decorator.decorate(escaped); |
| } |
| |
| static String evaluateExpressions(final String rawValue, Map<String, String> expressionMap) throws ProcessException { |
| return evaluateExpressions(rawValue, expressionMap, null); |
| } |
| |
| static String evaluateExpressions(final String rawValue) throws ProcessException { |
| return evaluateExpressions(rawValue, createExpressionMap(null), null); |
| } |
| |
| static String evaluateExpressions(final String rawValue, final FlowFile flowFile) throws ProcessException { |
| return evaluateExpressions(rawValue, createExpressionMap(flowFile), null); |
| } |
| |
| static String evaluateExpressions(final String rawValue, Map<String, String> expressionMap, final AttributeValueDecorator decorator) throws ProcessException { |
| return Query.prepare(rawValue).evaluateExpressions(expressionMap, decorator); |
| } |
| |
| public static String evaluateExpressions(final String rawValue, final FlowFile flowFile, final AttributeValueDecorator decorator) throws ProcessException { |
| if (rawValue == null) { |
| return null; |
| } |
| |
| final Map<String, String> expressionMap = createExpressionMap(flowFile); |
| return evaluateExpressions(rawValue, expressionMap, decorator); |
| } |
| |
| private static Evaluator<?> getRootSubjectEvaluator(final Evaluator<?> evaluator) { |
| if (evaluator == null) { |
| return null; |
| } |
| |
| final Evaluator<?> subject = evaluator.getSubjectEvaluator(); |
| if (subject == null) { |
| return evaluator; |
| } |
| |
| return getRootSubjectEvaluator(subject); |
| } |
| |
| /** |
| * Un-escapes ${...} patterns that were escaped |
| * |
| * @param value to un-escape |
| * @return un-escaped value |
| */ |
| public static String unescape(final String value) { |
| return value.replaceAll("\\$\\$(?=\\$*\\{.*?\\})", "\\$"); |
| } |
| |
| static Map<String, String> createExpressionMap(final FlowFile flowFile) { |
| return createExpressionMap(flowFile, null); |
| } |
| |
| static Map<String, String> createExpressionMap(final FlowFile flowFile, final Map<String, String> additionalAttributes) { |
| final Map<String, String> attributeMap = flowFile == null ? Collections.<String, String> emptyMap() : flowFile.getAttributes(); |
| final Map<String, String> additionalOrEmpty = additionalAttributes == null ? Collections.<String, String> emptyMap() : additionalAttributes; |
| final Map<String, String> envMap = System.getenv(); |
| final Map<?, ?> sysProps = System.getProperties(); |
| |
| final Map<String, String> flowFileProps = new HashMap<>(); |
| if (flowFile != null) { |
| flowFileProps.put("flowFileId", String.valueOf(flowFile.getId())); |
| flowFileProps.put("fileSize", String.valueOf(flowFile.getSize())); |
| flowFileProps.put("entryDate", String.valueOf(flowFile.getEntryDate())); |
| flowFileProps.put("lineageStartDate", String.valueOf(flowFile.getLineageStartDate())); |
| } |
| |
| return wrap(additionalOrEmpty, attributeMap, flowFileProps, envMap, sysProps); |
| } |
| |
| private static Map<String, String> wrap(final Map<String, String> additional, final Map<String, String> attributes, final Map<String, String> flowFileProps, |
| final Map<String, String> env, final Map<?, ?> sysProps) { |
| @SuppressWarnings("rawtypes") |
| final Map[] maps = new Map[] {additional, attributes, flowFileProps, env, sysProps}; |
| |
| return new Map<String, String>() { |
| @Override |
| public int size() { |
| int size = 0; |
| for (final Map<?, ?> map : maps) { |
| size += map.size(); |
| } |
| return size; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| for (final Map<?, ?> map : maps) { |
| if (!map.isEmpty()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean containsKey(final Object key) { |
| if (key == null) { |
| return false; |
| } |
| if (!(key instanceof String)) { |
| return false; |
| } |
| |
| for (final Map<?, ?> map : maps) { |
| if (map.containsKey(key)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean containsValue(final Object value) { |
| for (final Map<?, ?> map : maps) { |
| if (map.containsValue(value)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| @SuppressWarnings("rawtypes") |
| public String get(final Object key) { |
| if (key == null) { |
| throw new IllegalArgumentException("Null Keys are not allowed"); |
| } |
| if (!(key instanceof String)) { |
| return null; |
| } |
| |
| for (final Map map : maps) { |
| final Object val = map.get(key); |
| if (val != null) { |
| return String.valueOf(val); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String put(String key, String value) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public String remove(final Object key) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void putAll(final Map<? extends String, ? extends String> m) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void clear() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public Set<String> keySet() { |
| final Set<String> keySet = new HashSet<>(); |
| for (final Map map : maps) { |
| keySet.addAll(map.keySet()); |
| } |
| return keySet; |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public Collection<String> values() { |
| final Set<String> values = new HashSet<>(); |
| for (final Map map : maps) { |
| values.addAll(map.values()); |
| } |
| return values; |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public Set<java.util.Map.Entry<String, String>> entrySet() { |
| final Set<java.util.Map.Entry<String, String>> entrySet = new HashSet<>(); |
| for (final Map map : maps) { |
| entrySet.addAll(map.entrySet()); |
| } |
| return entrySet; |
| } |
| |
| }; |
| } |
| |
| public static Query fromTree(final Tree tree, final String text) { |
| return new Query(text, tree, buildEvaluator(tree)); |
| } |
| |
| public static Tree compileTree(final String query) throws AttributeExpressionLanguageParsingException { |
| try { |
| final CommonTokenStream lexerTokenStream = createTokenStream(query); |
| final AttributeExpressionParser parser = new AttributeExpressionParser(lexerTokenStream); |
| final Tree ast = (Tree) parser.query().getTree(); |
| final Tree tree = ast.getChild(0); |
| |
| // ensure that we are able to build the evaluators, so that we validate syntax |
| final Evaluator<?> evaluator = buildEvaluator(tree); |
| verifyMappingEvaluatorReduced(evaluator); |
| return tree; |
| } catch (final AttributeExpressionLanguageParsingException e) { |
| throw e; |
| } catch (final Exception e) { |
| throw new AttributeExpressionLanguageParsingException(e); |
| } |
| } |
| |
| public static PreparedQuery prepare(final String query) throws AttributeExpressionLanguageParsingException { |
| if (query == null) { |
| return new EmptyPreparedQuery(null); |
| } |
| |
| final List<Range> ranges = extractExpressionRanges(query); |
| |
| if (ranges.isEmpty()) { |
| return new EmptyPreparedQuery(query.replace("$$", "$")); |
| } |
| |
| try { |
| final List<String> substrings = new ArrayList<>(); |
| final Map<String, Tree> trees = new HashMap<>(); |
| |
| int lastIndex = 0; |
| for (final Range range : ranges) { |
| if (range.getStart() > lastIndex) { |
| substrings.add(query.substring(lastIndex, range.getStart()).replace("$$", "$")); |
| lastIndex = range.getEnd() + 1; |
| } |
| |
| final String treeText = query.substring(range.getStart(), range.getEnd() + 1).replace("$$", "$"); |
| substrings.add(treeText); |
| trees.put(treeText, Query.compileTree(treeText)); |
| lastIndex = range.getEnd() + 1; |
| } |
| |
| final Range lastRange = ranges.get(ranges.size() - 1); |
| if (lastRange.getEnd() + 1 < query.length()) { |
| final String treeText = query.substring(lastRange.getEnd() + 1).replace("$$", "$"); |
| substrings.add(treeText); |
| } |
| |
| return new StandardPreparedQuery(substrings, trees); |
| } catch (final AttributeExpressionLanguageParsingException e) { |
| return new InvalidPreparedQuery(query, e.getMessage()); |
| } |
| } |
| |
| public static Query compile(final String query) throws AttributeExpressionLanguageParsingException { |
| try { |
| final CommonTokenStream lexerTokenStream = createTokenStream(query); |
| final AttributeExpressionParser parser = new AttributeExpressionParser(lexerTokenStream); |
| final Tree ast = (Tree) parser.query().getTree(); |
| final Tree tree = ast.getChild(0); |
| |
| final Evaluator<?> evaluator = buildEvaluator(tree); |
| verifyMappingEvaluatorReduced(evaluator); |
| |
| return new Query(query, tree, evaluator); |
| } catch (final AttributeExpressionLanguageParsingException e) { |
| throw e; |
| } catch (final Exception e) { |
| throw new AttributeExpressionLanguageParsingException(e); |
| } |
| } |
| |
| private static void verifyMappingEvaluatorReduced(final Evaluator<?> evaluator) { |
| final Evaluator<?> rightMostEvaluator; |
| if (evaluator instanceof IteratingEvaluator) { |
| rightMostEvaluator = ((IteratingEvaluator<?>) evaluator).getLogicEvaluator(); |
| } else { |
| rightMostEvaluator = evaluator; |
| } |
| |
| Evaluator<?> eval = rightMostEvaluator.getSubjectEvaluator(); |
| Evaluator<?> lastEval = rightMostEvaluator; |
| while (eval != null) { |
| if (eval instanceof ReduceEvaluator) { |
| throw new AttributeExpressionLanguageParsingException("Expression attempts to call function '" + lastEval.getToken() + "' on the result of '" + eval.getToken() + |
| "'. This is not allowed. Instead, use \"${literal( ${<embedded expression>} ):" + lastEval.getToken() + "(...)}\""); |
| } |
| |
| lastEval = eval; |
| eval = eval.getSubjectEvaluator(); |
| } |
| |
| // if the result type of the evaluator is BOOLEAN, then it will always |
| // be reduced when evaluator. |
| final ResultType resultType = evaluator.getResultType(); |
| if (resultType == ResultType.BOOLEAN) { |
| return; |
| } |
| |
| final Evaluator<?> rootEvaluator = getRootSubjectEvaluator(evaluator); |
| if (rootEvaluator != null && rootEvaluator instanceof MultiAttributeEvaluator) { |
| final MultiAttributeEvaluator multiAttrEval = (MultiAttributeEvaluator) rootEvaluator; |
| switch (multiAttrEval.getEvaluationType()) { |
| case ALL_ATTRIBUTES: |
| case ALL_MATCHING_ATTRIBUTES: |
| case ALL_DELINEATED_VALUES: { |
| if (!(evaluator instanceof ReduceEvaluator)) { |
| throw new AttributeExpressionLanguageParsingException("Cannot evaluate expression because it attempts to reference multiple attributes but does not use a reducing function"); |
| } |
| break; |
| } |
| default: |
| throw new AttributeExpressionLanguageParsingException("Cannot evaluate expression because it attempts to reference multiple attributes but does not use a reducing function"); |
| } |
| } |
| } |
| |
| private static CommonTokenStream createTokenStream(final String expression) throws AttributeExpressionLanguageParsingException { |
| final CharStream input = new ANTLRStringStream(expression); |
| final AttributeExpressionLexer lexer = new AttributeExpressionLexer(input); |
| return new CommonTokenStream(lexer); |
| } |
| |
| public ResultType getResultType() { |
| return evaluator.getResultType(); |
| } |
| |
| QueryResult<?> evaluate() { |
| return evaluate(createExpressionMap(null)); |
| } |
| |
| QueryResult<?> evaluate(final FlowFile flowFile) { |
| return evaluate(createExpressionMap(flowFile)); |
| } |
| |
| QueryResult<?> evaluate(final Map<String, String> attributes) { |
| if (evaluated.getAndSet(true)) { |
| throw new IllegalStateException("A Query cannot be evaluated more than once"); |
| } |
| |
| return evaluator.evaluate(attributes); |
| } |
| |
| Tree getTree() { |
| return this.tree; |
| } |
| |
| @Override |
| public String toString() { |
| return "Query [" + query + "]"; |
| } |
| |
| private static Evaluator<String> newStringLiteralEvaluator(final String literalValue) { |
| if (literalValue == null || literalValue.length() < 2) { |
| return new StringLiteralEvaluator(literalValue); |
| } |
| |
| final List<Range> ranges = extractExpressionRanges(literalValue); |
| if (ranges.isEmpty()) { |
| return new StringLiteralEvaluator(literalValue); |
| } |
| |
| final List<Evaluator<?>> evaluators = new ArrayList<>(); |
| |
| int lastIndex = 0; |
| for (final Range range : ranges) { |
| if (range.getStart() > lastIndex) { |
| evaluators.add(newStringLiteralEvaluator(literalValue.substring(lastIndex, range.getStart()))); |
| } |
| |
| final String treeText = literalValue.substring(range.getStart(), range.getEnd() + 1); |
| evaluators.add(buildEvaluator(compileTree(treeText))); |
| lastIndex = range.getEnd() + 1; |
| } |
| |
| final Range lastRange = ranges.get(ranges.size() - 1); |
| if (lastRange.getEnd() + 1 < literalValue.length()) { |
| final String treeText = literalValue.substring(lastRange.getEnd() + 1); |
| evaluators.add(newStringLiteralEvaluator(treeText)); |
| } |
| |
| if (evaluators.size() == 1) { |
| return toStringEvaluator(evaluators.get(0)); |
| } |
| |
| Evaluator<String> lastEvaluator = toStringEvaluator(evaluators.get(0)); |
| for (int i = 1; i < evaluators.size(); i++) { |
| lastEvaluator = new AppendEvaluator(lastEvaluator, toStringEvaluator(evaluators.get(i))); |
| } |
| |
| return lastEvaluator; |
| } |
| |
| private static Evaluator<?> buildEvaluator(final Tree tree) { |
| switch (tree.getType()) { |
| case EXPRESSION: { |
| return buildExpressionEvaluator(tree); |
| } |
| case ATTRIBUTE_REFERENCE: { |
| final Evaluator<?> childEvaluator = buildEvaluator(tree.getChild(0)); |
| if (childEvaluator instanceof MultiAttributeEvaluator) { |
| return childEvaluator; |
| } |
| return new AttributeEvaluator(toStringEvaluator(childEvaluator)); |
| } |
| case MULTI_ATTRIBUTE_REFERENCE: { |
| |
| final Tree functionTypeTree = tree.getChild(0); |
| final int multiAttrType = functionTypeTree.getType(); |
| if (multiAttrType == ANY_DELINEATED_VALUE || multiAttrType == ALL_DELINEATED_VALUES) { |
| final Evaluator<String> delineatedValueEvaluator = toStringEvaluator(buildEvaluator(tree.getChild(1))); |
| final Evaluator<String> delimiterEvaluator = toStringEvaluator(buildEvaluator(tree.getChild(2))); |
| |
| return new DelineatedAttributeEvaluator(delineatedValueEvaluator, delimiterEvaluator, multiAttrType); |
| } |
| |
| final List<String> attributeNames = new ArrayList<>(); |
| for (int i = 1; i < tree.getChildCount(); i++) { // skip the first child because that's the name of the multi-attribute function |
| attributeNames.add(newStringLiteralEvaluator(tree.getChild(i).getText()).evaluate(null).getValue()); |
| } |
| |
| switch (multiAttrType) { |
| case ALL_ATTRIBUTES: |
| for (final String attributeName : attributeNames) { |
| try { |
| FlowFile.KeyValidator.validateKey(attributeName); |
| } catch (final IllegalArgumentException iae) { |
| throw new AttributeExpressionLanguageParsingException("Invalid Attribute Name: " + attributeName + ". " + iae.getMessage()); |
| } |
| } |
| |
| return new MultiNamedAttributeEvaluator(attributeNames, ALL_ATTRIBUTES); |
| case ALL_MATCHING_ATTRIBUTES: |
| return new MultiMatchAttributeEvaluator(attributeNames, ALL_MATCHING_ATTRIBUTES); |
| case ANY_ATTRIBUTE: |
| for (final String attributeName : attributeNames) { |
| try { |
| FlowFile.KeyValidator.validateKey(attributeName); |
| } catch (final IllegalArgumentException iae) { |
| throw new AttributeExpressionLanguageParsingException("Invalid Attribute Name: " + attributeName + ". " + iae.getMessage()); |
| } |
| } |
| |
| return new MultiNamedAttributeEvaluator(attributeNames, ANY_ATTRIBUTE); |
| case ANY_MATCHING_ATTRIBUTE: |
| return new MultiMatchAttributeEvaluator(attributeNames, ANY_MATCHING_ATTRIBUTE); |
| default: |
| throw new AssertionError("Illegal Multi-Attribute Reference: " + functionTypeTree.toString()); |
| } |
| } |
| case ATTR_NAME: { |
| return newStringLiteralEvaluator(tree.getChild(0).getText()); |
| } |
| case NUMBER: { |
| return new NumberLiteralEvaluator(tree.getText()); |
| } |
| case STRING_LITERAL: { |
| return newStringLiteralEvaluator(tree.getText()); |
| } |
| case TRUE: |
| case FALSE: |
| return buildBooleanEvaluator(tree); |
| case UUID: { |
| return new UuidEvaluator(); |
| } |
| case NOW: { |
| return new NowEvaluator(); |
| } |
| case TO_LITERAL: { |
| final Evaluator<?> argEvaluator = buildEvaluator(tree.getChild(0)); |
| return new ToLiteralEvaluator(argEvaluator); |
| } |
| case IP: { |
| try { |
| return new IPEvaluator(); |
| } catch (final UnknownHostException e) { |
| throw new AttributeExpressionLanguageException(e); |
| } |
| } |
| case HOSTNAME: { |
| if (tree.getChildCount() == 0) { |
| try { |
| return new HostnameEvaluator(false); |
| } catch (final UnknownHostException e) { |
| throw new AttributeExpressionLanguageException(e); |
| } |
| } else if (tree.getChildCount() == 1) { |
| final Tree childTree = tree.getChild(0); |
| try { |
| switch (childTree.getType()) { |
| case TRUE: |
| return new HostnameEvaluator(true); |
| case FALSE: |
| return new HostnameEvaluator(false); |
| default: |
| throw new AttributeExpressionLanguageParsingException("Call to hostname() must take 0 or 1 (boolean) parameter"); |
| } |
| } catch (final UnknownHostException e) { |
| throw new AttributeExpressionLanguageException(e); |
| } |
| } else { |
| throw new AttributeExpressionLanguageParsingException("Call to hostname() must take 0 or 1 (boolean) parameter"); |
| } |
| } |
| case NEXT_INT: { |
| return new OneUpSequenceEvaluator(); |
| } |
| default: |
| throw new AttributeExpressionLanguageParsingException("Unexpected token: " + tree.toString()); |
| } |
| } |
| |
| private static <T> Evaluator<T> addToken(final Evaluator<T> evaluator, final String token) { |
| evaluator.setToken(token); |
| return evaluator; |
| } |
| |
| private static Evaluator<Boolean> buildBooleanEvaluator(final Tree tree) { |
| switch (tree.getType()) { |
| case TRUE: |
| return addToken(new BooleanLiteralEvaluator(true), "true"); |
| case FALSE: |
| return addToken(new BooleanLiteralEvaluator(false), "true"); |
| } |
| throw new AttributeExpressionLanguageParsingException("Cannot build Boolean evaluator from tree " + tree.toString()); |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| private static Evaluator<?> buildExpressionEvaluator(final Tree tree) { |
| if (tree.getChildCount() == 0) { |
| throw new AttributeExpressionLanguageParsingException("EXPRESSION tree node has no children"); |
| } |
| |
| final Evaluator<?> evaluator; |
| if (tree.getChildCount() == 1) { |
| evaluator = buildEvaluator(tree.getChild(0)); |
| } else { |
| // we can chain together functions in the form of: |
| // ${x:trim():substring(1,2):trim()} |
| // in this case, the subject of the right-most function is the function to its left; its |
| // subject is the function to its left (the first trim()), and its subject is the value of |
| // the 'x' attribute. We accomplish this logic by iterating over all of the children of the |
| // tree from the right-most child going left-ward. |
| evaluator = buildFunctionExpressionEvaluator(tree, 0); |
| } |
| |
| Evaluator<?> chosenEvaluator = evaluator; |
| final Evaluator<?> rootEvaluator = getRootSubjectEvaluator(evaluator); |
| if (rootEvaluator != null) { |
| if (rootEvaluator instanceof MultiAttributeEvaluator) { |
| final MultiAttributeEvaluator multiAttrEval = (MultiAttributeEvaluator) rootEvaluator; |
| |
| switch (multiAttrEval.getEvaluationType()) { |
| case ANY_ATTRIBUTE: |
| case ANY_MATCHING_ATTRIBUTE: |
| case ANY_DELINEATED_VALUE: |
| chosenEvaluator = new AnyAttributeEvaluator((BooleanEvaluator) evaluator, multiAttrEval); |
| break; |
| case ALL_ATTRIBUTES: |
| case ALL_MATCHING_ATTRIBUTES: |
| case ALL_DELINEATED_VALUES: { |
| final ResultType resultType = evaluator.getResultType(); |
| if (resultType == ResultType.BOOLEAN) { |
| chosenEvaluator = new AllAttributesEvaluator((BooleanEvaluator) evaluator, multiAttrEval); |
| } else if (evaluator instanceof ReduceEvaluator) { |
| chosenEvaluator = new MappingEvaluator((ReduceEvaluator) evaluator, multiAttrEval); |
| } else { |
| throw new AttributeExpressionLanguageException("Cannot evaluate Expression because it attempts to reference multiple attributes but does not use a reducing function"); |
| } |
| break; |
| } |
| } |
| |
| switch (multiAttrEval.getEvaluationType()) { |
| case ANY_ATTRIBUTE: |
| chosenEvaluator.setToken("anyAttribute"); |
| break; |
| case ANY_MATCHING_ATTRIBUTE: |
| chosenEvaluator.setToken("anyMatchingAttribute"); |
| break; |
| case ANY_DELINEATED_VALUE: |
| chosenEvaluator.setToken("anyDelineatedValue"); |
| break; |
| case ALL_ATTRIBUTES: |
| chosenEvaluator.setToken("allAttributes"); |
| break; |
| case ALL_MATCHING_ATTRIBUTES: |
| chosenEvaluator.setToken("allMatchingAttributes"); |
| break; |
| case ALL_DELINEATED_VALUES: |
| chosenEvaluator.setToken("allDelineatedValues"); |
| break; |
| } |
| } |
| } |
| |
| return chosenEvaluator; |
| } |
| |
| private static Evaluator<?> buildFunctionExpressionEvaluator(final Tree tree, final int offset) { |
| if (tree.getChildCount() == 0) { |
| throw new AttributeExpressionLanguageParsingException("EXPRESSION tree node has no children"); |
| } |
| final int firstChildIndex = tree.getChildCount() - offset - 1; |
| if (firstChildIndex == 0) { |
| return buildEvaluator(tree.getChild(0)); |
| } |
| |
| final Tree functionTree = tree.getChild(firstChildIndex); |
| final Evaluator<?> subjectEvaluator = buildFunctionExpressionEvaluator(tree, offset + 1); |
| |
| final Tree functionNameTree = functionTree.getChild(0); |
| final List<Evaluator<?>> argEvaluators = new ArrayList<>(); |
| for (int i = 1; i < functionTree.getChildCount(); i++) { |
| argEvaluators.add(buildEvaluator(functionTree.getChild(i))); |
| } |
| return buildFunctionEvaluator(functionNameTree, subjectEvaluator, argEvaluators); |
| } |
| |
| private static List<Evaluator<?>> verifyArgCount(final List<Evaluator<?>> args, final int count, final String functionName) { |
| if (args.size() != count) { |
| throw new AttributeExpressionLanguageParsingException(functionName + "() function takes " + count + " arguments"); |
| } |
| return args; |
| } |
| |
| private static Evaluator<String> toStringEvaluator(final Evaluator<?> evaluator) { |
| return toStringEvaluator(evaluator, null); |
| } |
| |
| private static Evaluator<String> toStringEvaluator(final Evaluator<?> evaluator, final String location) { |
| if (evaluator.getResultType() == ResultType.STRING) { |
| return (StringEvaluator) evaluator; |
| } |
| |
| return addToken(new StringCastEvaluator(evaluator), evaluator.getToken()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static Evaluator<Boolean> toBooleanEvaluator(final Evaluator<?> evaluator, final String location) { |
| switch (evaluator.getResultType()) { |
| case BOOLEAN: |
| return (Evaluator<Boolean>) evaluator; |
| case STRING: |
| return addToken(new BooleanCastEvaluator((StringEvaluator) evaluator), evaluator.getToken()); |
| default: |
| throw new AttributeExpressionLanguageParsingException("Cannot implicitly convert Data Type " + evaluator.getResultType() + " to " + ResultType.BOOLEAN |
| + (location == null ? "" : " at location [" + location + "]")); |
| } |
| |
| } |
| |
| private static Evaluator<Boolean> toBooleanEvaluator(final Evaluator<?> evaluator) { |
| return toBooleanEvaluator(evaluator, null); |
| } |
| |
| private static Evaluator<Long> toNumberEvaluator(final Evaluator<?> evaluator) { |
| return toNumberEvaluator(evaluator, null); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static Evaluator<Long> toNumberEvaluator(final Evaluator<?> evaluator, final String location) { |
| switch (evaluator.getResultType()) { |
| case NUMBER: |
| return (Evaluator<Long>) evaluator; |
| case STRING: |
| return addToken(new NumberCastEvaluator(evaluator), evaluator.getToken()); |
| case DATE: |
| return addToken(new DateToNumberEvaluator((DateEvaluator) evaluator), evaluator.getToken()); |
| default: |
| throw new AttributeExpressionLanguageParsingException("Cannot implicitly convert Data Type " + evaluator.getResultType() + " to " + ResultType.NUMBER |
| + (location == null ? "" : " at location [" + location + "]")); |
| } |
| } |
| |
| private static DateEvaluator toDateEvaluator(final Evaluator<?> evaluator) { |
| return toDateEvaluator(evaluator, null); |
| } |
| |
| private static DateEvaluator toDateEvaluator(final Evaluator<?> evaluator, final String location) { |
| if (evaluator.getResultType() == ResultType.DATE) { |
| return (DateEvaluator) evaluator; |
| } |
| |
| return new DateCastEvaluator(evaluator); |
| } |
| |
| private static Evaluator<?> buildFunctionEvaluator(final Tree tree, final Evaluator<?> subjectEvaluator, final List<Evaluator<?>> argEvaluators) { |
| switch (tree.getType()) { |
| case TRIM: { |
| verifyArgCount(argEvaluators, 0, "trim"); |
| return addToken(new TrimEvaluator(toStringEvaluator(subjectEvaluator)), "trim"); |
| } |
| case TO_STRING: { |
| verifyArgCount(argEvaluators, 0, "toString"); |
| return addToken(new ToStringEvaluator(subjectEvaluator), "toString"); |
| } |
| case TO_LOWER: { |
| verifyArgCount(argEvaluators, 0, "toLower"); |
| return addToken(new ToLowerEvaluator(toStringEvaluator(subjectEvaluator)), "toLower"); |
| } |
| case TO_UPPER: { |
| verifyArgCount(argEvaluators, 0, "toUpper"); |
| return addToken(new ToUpperEvaluator(toStringEvaluator(subjectEvaluator)), "toUpper"); |
| } |
| case URL_ENCODE: { |
| verifyArgCount(argEvaluators, 0, "urlEncode"); |
| return addToken(new UrlEncodeEvaluator(toStringEvaluator(subjectEvaluator)), "urlEncode"); |
| } |
| case URL_DECODE: { |
| verifyArgCount(argEvaluators, 0, "urlDecode"); |
| return addToken(new UrlDecodeEvaluator(toStringEvaluator(subjectEvaluator)), "urlDecode"); |
| } |
| case SUBSTRING_BEFORE: { |
| verifyArgCount(argEvaluators, 1, "substringBefore"); |
| return addToken(new SubstringBeforeEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to substringBefore")), "substringBefore"); |
| } |
| case SUBSTRING_BEFORE_LAST: { |
| verifyArgCount(argEvaluators, 1, "substringBeforeLast"); |
| return addToken(new SubstringBeforeLastEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to substringBeforeLast")), "substringBeforeLast"); |
| } |
| case SUBSTRING_AFTER: { |
| verifyArgCount(argEvaluators, 1, "substringAfter"); |
| return addToken(new SubstringAfterEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to substringAfter")), "substringAfter"); |
| } |
| case SUBSTRING_AFTER_LAST: { |
| verifyArgCount(argEvaluators, 1, "substringAfterLast"); |
| return addToken(new SubstringAfterLastEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to substringAfterLast")), "substringAfterLast"); |
| } |
| case REPLACE_NULL: { |
| verifyArgCount(argEvaluators, 1, "replaceNull"); |
| return addToken(new ReplaceNullEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to replaceNull")), "replaceNull"); |
| } |
| case REPLACE_EMPTY: { |
| verifyArgCount(argEvaluators, 1, "replaceEmtpy"); |
| return addToken(new ReplaceEmptyEvaluator(toStringEvaluator(subjectEvaluator), toStringEvaluator(argEvaluators.get(0), "first argument to replaceEmpty")), "replaceEmpty"); |
| } |
| case REPLACE: { |
| verifyArgCount(argEvaluators, 2, "replace"); |
| return addToken(new ReplaceEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to replace"), |
| toStringEvaluator(argEvaluators.get(1), "second argument to replace")), "replace"); |
| } |
| case REPLACE_ALL: { |
| verifyArgCount(argEvaluators, 2, "replaceAll"); |
| return addToken(new ReplaceAllEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to replaceAll"), |
| toStringEvaluator(argEvaluators.get(1), "second argument to replaceAll")), "replaceAll"); |
| } |
| case APPEND: { |
| verifyArgCount(argEvaluators, 1, "append"); |
| return addToken(new AppendEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to append")), "append"); |
| } |
| case PREPEND: { |
| verifyArgCount(argEvaluators, 1, "prepend"); |
| return addToken(new PrependEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to prepend")), "prepend"); |
| } |
| case SUBSTRING: { |
| final int numArgs = argEvaluators.size(); |
| if (numArgs == 1) { |
| return addToken(new SubstringEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument to substring")), "substring"); |
| } else if (numArgs == 2) { |
| return addToken(new SubstringEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument to substring"), |
| toNumberEvaluator(argEvaluators.get(1), "second argument to substring")), "substring"); |
| } else { |
| throw new AttributeExpressionLanguageParsingException("substring() function can take either 1 or 2 arguments but cannot take " + numArgs + " arguments"); |
| } |
| } |
| case JOIN: { |
| verifyArgCount(argEvaluators, 1, "join"); |
| return addToken(new JoinEvaluator(toStringEvaluator(subjectEvaluator), toStringEvaluator(argEvaluators.get(0))), "join"); |
| } |
| case COUNT: { |
| verifyArgCount(argEvaluators, 0, "count"); |
| return addToken(new CountEvaluator(subjectEvaluator), "count"); |
| } |
| case IS_NULL: { |
| verifyArgCount(argEvaluators, 0, "isNull"); |
| return addToken(new IsNullEvaluator(toStringEvaluator(subjectEvaluator)), "isNull"); |
| } |
| case IS_EMPTY: { |
| verifyArgCount(argEvaluators, 0, "isEmpty"); |
| return addToken(new IsEmptyEvaluator(toStringEvaluator(subjectEvaluator)), "isEmpty"); |
| } |
| case NOT_NULL: { |
| verifyArgCount(argEvaluators, 0, "notNull"); |
| return addToken(new NotNullEvaluator(toStringEvaluator(subjectEvaluator)), "notNull"); |
| } |
| case STARTS_WITH: { |
| verifyArgCount(argEvaluators, 1, "startsWith"); |
| return addToken(new StartsWithEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to startsWith")), "startsWith"); |
| } |
| case ENDS_WITH: { |
| verifyArgCount(argEvaluators, 1, "endsWith"); |
| return addToken(new EndsWithEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to endsWith")), "endsWith"); |
| } |
| case CONTAINS: { |
| verifyArgCount(argEvaluators, 1, "contains"); |
| return addToken(new ContainsEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to contains")), "contains"); |
| } |
| case FIND: { |
| verifyArgCount(argEvaluators, 1, "find"); |
| return addToken(new FindEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to find")), "find"); |
| } |
| case MATCHES: { |
| verifyArgCount(argEvaluators, 1, "matches"); |
| return addToken(new MatchesEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to matches")), "matches"); |
| } |
| case EQUALS: { |
| verifyArgCount(argEvaluators, 1, "equals"); |
| return addToken(new EqualsEvaluator(subjectEvaluator, argEvaluators.get(0)), "equals"); |
| } |
| case EQUALS_IGNORE_CASE: { |
| verifyArgCount(argEvaluators, 1, "equalsIgnoreCase"); |
| return addToken(new EqualsIgnoreCaseEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to equalsIgnoreCase")), "equalsIgnoreCase"); |
| } |
| case GREATER_THAN: { |
| verifyArgCount(argEvaluators, 1, "gt"); |
| return addToken(new GreaterThanEvaluator(toNumberEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument to gt")), "gt"); |
| } |
| case GREATER_THAN_OR_EQUAL: { |
| verifyArgCount(argEvaluators, 1, "ge"); |
| return addToken(new GreaterThanOrEqualEvaluator(toNumberEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument to ge")), "ge"); |
| } |
| case LESS_THAN: { |
| verifyArgCount(argEvaluators, 1, "lt"); |
| return addToken(new LessThanEvaluator(toNumberEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument to lt")), "lt"); |
| } |
| case LESS_THAN_OR_EQUAL: { |
| verifyArgCount(argEvaluators, 1, "le"); |
| return addToken(new LessThanOrEqualEvaluator(toNumberEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument to le")), "le"); |
| } |
| case LENGTH: { |
| verifyArgCount(argEvaluators, 0, "length"); |
| return addToken(new LengthEvaluator(toStringEvaluator(subjectEvaluator)), "length"); |
| } |
| case TO_DATE: { |
| if (argEvaluators.isEmpty()) { |
| return addToken(new NumberToDateEvaluator(toNumberEvaluator(subjectEvaluator)), "toDate"); |
| } else if (subjectEvaluator.getResultType() == ResultType.STRING) { |
| return addToken(new StringToDateEvaluator(toStringEvaluator(subjectEvaluator), toStringEvaluator(argEvaluators.get(0))), "toDate"); |
| } else { |
| return addToken(new NumberToDateEvaluator(toNumberEvaluator(subjectEvaluator)), "toDate"); |
| } |
| } |
| case TO_NUMBER: { |
| verifyArgCount(argEvaluators, 0, "toNumber"); |
| switch (subjectEvaluator.getResultType()) { |
| case STRING: |
| return addToken(new ToNumberEvaluator((StringEvaluator) subjectEvaluator), "toNumber"); |
| case DATE: |
| return addToken(new DateToNumberEvaluator((DateEvaluator) subjectEvaluator), "toNumber"); |
| default: |
| throw new AttributeExpressionLanguageParsingException(subjectEvaluator + " returns type " + subjectEvaluator.getResultType() + " but expected to get " + ResultType.STRING); |
| } |
| } |
| case TO_RADIX: { |
| if (argEvaluators.size() == 1) { |
| return addToken(new ToRadixEvaluator((NumberEvaluator) subjectEvaluator, toNumberEvaluator(argEvaluators.get(0))), "toRadix"); |
| } else { |
| return addToken(new ToRadixEvaluator((NumberEvaluator) subjectEvaluator, toNumberEvaluator(argEvaluators.get(0)), toNumberEvaluator(argEvaluators.get(1))), "toRadix"); |
| } |
| } |
| case MOD: { |
| return addToken(new ModEvaluator(toNumberEvaluator(subjectEvaluator), toNumberEvaluator(argEvaluators.get(0))), "mod"); |
| } |
| case PLUS: { |
| return addToken(new PlusEvaluator(toNumberEvaluator(subjectEvaluator), toNumberEvaluator(argEvaluators.get(0))), "plus"); |
| } |
| case MINUS: { |
| return addToken(new MinusEvaluator(toNumberEvaluator(subjectEvaluator), toNumberEvaluator(argEvaluators.get(0))), "minus"); |
| } |
| case MULTIPLY: { |
| return addToken(new MultiplyEvaluator(toNumberEvaluator(subjectEvaluator), toNumberEvaluator(argEvaluators.get(0))), "multiply"); |
| } |
| case DIVIDE: { |
| return addToken(new DivideEvaluator(toNumberEvaluator(subjectEvaluator), toNumberEvaluator(argEvaluators.get(0))), "divide"); |
| } |
| case INDEX_OF: { |
| verifyArgCount(argEvaluators, 1, "indexOf"); |
| return addToken(new IndexOfEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to indexOf")), "indexOf"); |
| } |
| case LAST_INDEX_OF: { |
| verifyArgCount(argEvaluators, 1, "lastIndexOf"); |
| return addToken(new LastIndexOfEvaluator(toStringEvaluator(subjectEvaluator), |
| toStringEvaluator(argEvaluators.get(0), "first argument to lastIndexOf")), "lastIndexOf"); |
| } |
| case FORMAT: { |
| return addToken(new FormatEvaluator(toDateEvaluator(subjectEvaluator), toStringEvaluator(argEvaluators.get(0), "first argument of format")), "format"); |
| } |
| case OR: { |
| return addToken(new OrEvaluator(toBooleanEvaluator(subjectEvaluator), toBooleanEvaluator(argEvaluators.get(0))), "or"); |
| } |
| case AND: { |
| return addToken(new AndEvaluator(toBooleanEvaluator(subjectEvaluator), toBooleanEvaluator(argEvaluators.get(0))), "and"); |
| } |
| case NOT: { |
| return addToken(new NotEvaluator(toBooleanEvaluator(subjectEvaluator)), "not"); |
| } |
| case GET_DELIMITED_FIELD: { |
| if (argEvaluators.size() == 1) { |
| // Only a single argument - the index to return. |
| return addToken(new GetDelimitedFieldEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument of getDelimitedField")), "getDelimitedField"); |
| } else if (argEvaluators.size() == 2) { |
| // two arguments - index and delimiter. |
| return addToken(new GetDelimitedFieldEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(1), "second argument of getDelimitedField")), |
| "getDelimitedField"); |
| } else if (argEvaluators.size() == 3) { |
| // 3 arguments - index, delimiter, quote char. |
| return addToken(new GetDelimitedFieldEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(1), "second argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(2), "third argument of getDelimitedField")), |
| "getDelimitedField"); |
| } else if (argEvaluators.size() == 4) { |
| // 4 arguments - index, delimiter, quote char, escape char |
| return addToken(new GetDelimitedFieldEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(1), "second argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(2), "third argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(3), "fourth argument of getDelimitedField")), |
| "getDelimitedField"); |
| } else { |
| // 5 arguments - index, delimiter, quote char, escape char, strip escape/quote chars flag |
| return addToken(new GetDelimitedFieldEvaluator(toStringEvaluator(subjectEvaluator), |
| toNumberEvaluator(argEvaluators.get(0), "first argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(1), "second argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(2), "third argument of getDelimitedField"), |
| toStringEvaluator(argEvaluators.get(3), "fourth argument of getDelimitedField"), |
| toBooleanEvaluator(argEvaluators.get(4), "fifth argument of getDelimitedField")), |
| "getDelimitedField"); |
| } |
| } |
| default: |
| throw new AttributeExpressionLanguageParsingException("Expected a Function-type expression but got " + tree.toString()); |
| } |
| } |
| |
| public static class Range { |
| |
| private final int start; |
| private final int end; |
| |
| public Range(final int start, final int end) { |
| this.start = start; |
| this.end = end; |
| } |
| |
| public int getStart() { |
| return start; |
| } |
| |
| public int getEnd() { |
| return end; |
| } |
| |
| @Override |
| public String toString() { |
| return start + " - " + end; |
| } |
| } |
| } |