blob: 7e1a71e1d49cdcb6370c7307bb2a34e1b22853ca [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.nifi.attribute.expression.language;
import org.antlr.runtime.tree.Tree;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.attribute.expression.language.Query.Range;
import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
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.flowfile.FlowFile;
import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.registry.VariableRegistry;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static java.lang.Double.NEGATIVE_INFINITY;
import static java.lang.Double.NaN;
import static java.lang.Double.POSITIVE_INFINITY;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TestQuery {
// Address book JsonPath constants
public static final String ADDRESS_BOOK_JSON_PATH_FIRST_NAME = "${json:jsonPath('$.firstName')}";
public static final String ADDRESS_BOOK_JSON_PATH_LAST_NAME = "${json:jsonPath('$.lastName')}";
public static final String ADDRESS_BOOK_JSON_PATH_AGE = "${json:jsonPath('$.age')}";
public static final String ADDRESS_BOOK_JSON_PATH_VOTER = "${json:jsonPath('$.voter')}";
public static final String ADDRESS_BOOK_JSON_PATH_ADDRESS_POSTAL_CODE = "${json:jsonPath('$.address.postalCode')}";
public static final String ADDRESS_BOOK_JSON_PATH_PHONE_NUMBERS_TYPE_HOME_NUMBER = "${json:jsonPath(\"$.phoneNumbers[?(@.type=='home')].number\")}";
public static final String ADDRESS_BOOK_JSON_PATH_PHONE_NUMBERS_TYPE_OFFICE_NUMBER = "${json:jsonPath(\"$.phoneNumbers[?(@.type=='office')].number\")}";
public static final String ADDRESS_BOOK_JSON_PATH_HEIGHT = "${json:jsonPath('$.height')}";
public static final String ADDRESS_BOOK_JSON_PATH_EMPTY = "";
private static final List<String> phoneBookAttributes = Arrays.asList(
ADDRESS_BOOK_JSON_PATH_FIRST_NAME,
ADDRESS_BOOK_JSON_PATH_LAST_NAME,
ADDRESS_BOOK_JSON_PATH_AGE,
ADDRESS_BOOK_JSON_PATH_VOTER,
ADDRESS_BOOK_JSON_PATH_ADDRESS_POSTAL_CODE,
ADDRESS_BOOK_JSON_PATH_PHONE_NUMBERS_TYPE_HOME_NUMBER,
ADDRESS_BOOK_JSON_PATH_PHONE_NUMBERS_TYPE_OFFICE_NUMBER
);
@Test
public void testCompilation() {
assertInvalid("${attr:uuid()}");
assertInvalid("${attr:indexOf(length())}");
assertValid("${UUID()}");
assertInvalid("${UUID():nextInt()}");
assertValid("${attr:UUID3('94c09378-43a6-11ea-8bcc-acde48001122')}");
assertValid("${attr:UUID5('94c09378-43a6-11ea-8bcc-acde48001122')}");
assertInvalid("${UUID3('94c09378-43a6-11ea-8bcc-acde48001122', attr)}");
assertInvalid("${UUID5('94c09378-43a6-11ea-8bcc-acde48001122', attr)}");
assertValid("${nextInt()}");
assertValid("${now():format('yyyy/MM/dd')}");
assertInvalid("${attr:times(3)}");
assertValid("${attr:toNumber():multiply(3)}");
assertValid("${hostname()}");
assertValid("${literal(3)}");
assertValid("${random()}");
assertValid("${getStateValue('the_count')}");
assertValid("${attr:padLeft(10, '#')}");
assertValid("${attr:padRight(10, '#')}");
assertValid("${attr:padLeft(10)}");
assertValid("${attr:padRight(10)}");
// left here because it's convenient for looking at the output
//System.out.println(Query.compile("").evaluate(null));
}
@Test
public void testPrepareWithEscapeChar() {
final Map<String, String> variables = Collections.singletonMap("foo", "bar");
assertEquals("bar${foo}$bar", Query.prepare("${foo}$${foo}$$${foo}").evaluateExpressions(new StandardEvaluationContext(variables), null));
final PreparedQuery onlyEscapedQuery = Query.prepare("$${foo}");
final String onlyEscapedEvaluated = onlyEscapedQuery.evaluateExpressions(new StandardEvaluationContext(variables), null);
assertEquals("${foo}", onlyEscapedEvaluated);
final PreparedQuery mixedQuery = Query.prepare("${foo}$${foo}");
final String mixedEvaluated = mixedQuery.evaluateExpressions(new StandardEvaluationContext(variables), null);
assertEquals("bar${foo}", mixedEvaluated);
}
private void assertValid(final String query) {
try {
Query.compile(query);
} catch (final Exception e) {
e.printStackTrace();
Assert.fail("Expected query to be valid, but it failed to compile due to " + e);
}
}
private void assertInvalid(final String query) {
try {
Query.compile(query);
Assert.fail("Expected query to be invalid, but it did compile");
} catch (final Exception e) {
}
}
@Test
public void testIsValidExpression() {
Query.validateExpression("${abc:substring(${xyz:length()})}", false);
Query.isValidExpression("${now():format('yyyy-MM-dd')}");
try {
Query.validateExpression("$${attr}", false);
Assert.fail("invalid query validated");
} catch (final AttributeExpressionLanguageParsingException e) {
}
Query.validateExpression("$${attr}", true);
Query.validateExpression("${filename:startsWith('T8MTXBC')\n"
+ ":or( ${filename:startsWith('C4QXABC')} )\n"
+ ":or( ${filename:startsWith('U6CXEBC')} )"
+ ":or( ${filename:startsWith('KYM3ABC')} )}", false);
}
@Test
public void testStringEL() {
final Map<String, String> attrs = new HashMap<>();
attrs.put("employee.gender", "male ");
attrs.put("employee.name", "Harry Potter");
attrs.put("id", "1234");
attrs.put("sql.query", "SELECT * FROM table WHERE ID = ${id}");
String query = "${sql.query:evaluateELString()}";
String query1 = "${employee.name:evaluateELString()}";
String query2 = "${employee.name:evaluateELString():toUpper()}";
String query3 = "${employee.gender:trim():evaluateELString()}";
verifyEquals(query, attrs, "SELECT * FROM table WHERE ID = 1234");
verifyEquals(query1, attrs, "Harry Potter");
verifyEquals(query2, attrs, "HARRY POTTER");
verifyEquals(query3, attrs, "male");
}
@Test
public void testCompileEmbedded() {
final String expression = "${x:equals( ${y} )}";
final Query query = Query.compile(expression);
final Tree tree = query.getTree();
System.out.println(printTree(tree));
final Map<String, String> attributes = new HashMap<>();
attributes.put("x", "x");
attributes.put("y", "x");
final String result = Query.evaluateExpressions(expression, attributes, null);
assertEquals("true", result);
Query.validateExpression(expression, false);
}
private String printTree(final Tree tree) {
final StringBuilder sb = new StringBuilder();
printTree(tree, 0, sb);
return sb.toString();
}
private void printTree(final Tree tree, final int spaces, final StringBuilder sb) {
for (int i = 0; i < spaces; i++) {
sb.append(" ");
}
if (tree.getText().trim().isEmpty()) {
sb.append(tree.toString()).append("\n");
} else {
sb.append(tree.getText()).append("\n");
}
for (int i = 0; i < tree.getChildCount(); i++) {
printTree(tree.getChild(i), spaces + 2, sb);
}
}
@Test
public void testEscape() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "xyz");
attributes.put("xyz", "hello");
attributes.put("$xyz", "good-bye");
attributes.put("${abc}", "good-bye");
assertEquals("$$xyz", evaluateQueryForEscape("$$$$${abc}", attributes));
assertEquals("xyz", evaluateQueryForEscape("${abc}", attributes));
assertEquals("${abc}", evaluateQueryForEscape("$${abc}", attributes));
assertEquals("$xyz", evaluateQueryForEscape("$$${abc}", attributes));
assertEquals("$${abc}", evaluateQueryForEscape("$$$${abc}", attributes));
assertEquals( "Unescaped $$${5 because no closing brace", evaluateQueryForEscape("Unescaped $$${5 because no closing brace", attributes));
assertEquals( "Unescaped $ because no closing brace", evaluateQueryForEscape("Unescaped $$${'5'} because no closing brace", attributes));
assertEquals("I owe you $5", evaluateQueryForEscape("I owe you $5", attributes));
assertEquals("You owe me $$5 too", evaluateQueryForEscape("You owe me $$5 too", attributes));
assertEquals("Unescaped $$${5 because no closing brace", evaluateQueryForEscape("Unescaped $$${5 because no closing brace", attributes));
assertEquals("xyz owes me $5", evaluateQueryForEscape("${abc} owes me $5", attributes));
assertEquals("xyz owes me ${5", evaluateQueryForEscape("${abc} owes me ${5", attributes));
assertEquals("xyz owes me ", evaluateQueryForEscape("${abc} owes me ${'5'}", attributes));
assertEquals("xyz owes me $", evaluateQueryForEscape("${abc} owes me $$${'5'}", attributes));
assertEquals("SNAP$$$$$$$$$", evaluateQueryForEscape("${literal('SNAP$$$$$$$$$')}", attributes));
assertEquals("SNAP$$", evaluateQueryForEscape("${literal('SNAP$$')}", attributes));
assertEquals("hello", evaluateQueryForEscape("${${abc}}", attributes));
assertEquals("good-bye", evaluateQueryForEscape("${'$$${abc}'}", attributes));
assertEquals("good-bye", evaluateQueryForEscape("${'$xyz'}", attributes));
}
@Test
public void testWithBackSlashes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("x", "C:\\test\\1.txt");
attributes.put("y", "y\ny");
final String query = "${x:substringAfterLast( '/' ):substringAfterLast( '\\\\' )}";
verifyEquals(query, attributes, "1.txt");
attributes.put("x", "C:/test/1.txt");
verifyEquals(query, attributes, "1.txt");
verifyEquals("${y:equals('y\\ny')}", attributes, Boolean.TRUE);
}
@Test
public void testWithTicksOutside() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "My Value");
assertEquals(1, Query.extractExpressionRanges("\"${attr}").size());
assertEquals(1, Query.extractExpressionRanges("'${attr}").size());
assertEquals(1, Query.extractExpressionRanges("'${attr}'").size());
assertEquals(1, Query.extractExpressionRanges("${attr}").size());
assertEquals("'My Value'", Query.evaluateExpressions("'${attr}'", attributes, null));
assertEquals("'My Value", Query.evaluateExpressions("'${attr}", attributes, null));
}
@Test
public void testDateToNumber() {
final Query query = Query.compile("${dateTime:toDate('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York'):toNumber()}");
final Map<String, String> attributes = new HashMap<>();
attributes.put("dateTime", "2013/11/18 10:22:27.678");
final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
assertEquals(ResultType.WHOLE_NUMBER, result.getResultType());
assertEquals(1384788147678L, result.getValue());
}
@Test
public void testAddOneDayToDate() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("dateTime", "2013/11/18 10:22:27.678");
verifyEquals("${dateTime:toDate('yyyy/MM/dd HH:mm:ss.SSS'):toNumber():plus(86400000):toDate():format('yyyy/MM/dd HH:mm:ss.SSS')}", attributes, "2013/11/19 10:22:27.678");
verifyEquals("${dateTime:toDate('yyyy/MM/dd HH:mm:ss.SSS'):plus(86400000):format('yyyy/MM/dd HH:mm:ss.SSS')}", attributes, "2013/11/19 10:22:27.678");
}
@Test
@Ignore("Requires specific locale")
public void implicitDateConversion() {
final Date date = new Date();
final Query query = Query.compile("${dateTime:format('yyyy/MM/dd HH:mm:ss.SSS')}");
final Map<String, String> attributes = new HashMap<>();
attributes.put("dateTime", date.toString());
// the date.toString() above will end up truncating the milliseconds. So remove millis from the Date before
// formatting it
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS", Locale.US);
final long millis = date.getTime() % 1000L;
final Date roundedToNearestSecond = new Date(date.getTime() - millis);
final String formatted = sdf.format(roundedToNearestSecond);
final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
assertEquals(ResultType.STRING, result.getResultType());
assertEquals(formatted, result.getValue());
}
@Test
public void testEmbeddedExpressionsAndQuotes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("x", "abc");
attributes.put("a", "abc");
verifyEquals("${x:equals(${a})}", attributes, true);
Query.validateExpression("${x:equals('${a}')}", false);
assertEquals("true", Query.evaluateExpressions("${x:equals('${a}')}", attributes, null));
Query.validateExpression("${x:equals(\"${a}\")}", false);
assertEquals("true", Query.evaluateExpressions("${x:equals(\"${a}\")}", attributes, null));
}
@Test
public void testParameterReference() {
final Map<String, String> attributes = Collections.emptyMap();
final Map<String, String> stateValues = Collections.emptyMap();
final Map<String, String> parameters = new HashMap<>();
parameters.put("test", "unit");
verifyEquals("${#{test}}", attributes, stateValues, parameters,"unit");
verifyEquals("${#{test}:append(' - '):append(#{test})}", attributes, stateValues, parameters,"unit - unit");
}
@Test
public void testParameterReferenceWithSpace() {
final Map<String, String> attributes = Collections.emptyMap();
final Map<String, String> stateValues = Collections.emptyMap();
final Map<String, String> parameters = new HashMap<>();
parameters.put("test param", "unit");
final Query query = Query.compile("${'#{test param}'}");
verifyEquals("${#{'test param'}}", attributes, stateValues, parameters,"unit");
verifyEquals("${#{'test param'}:append(' - '):append(#{'test param'})}", attributes, stateValues, parameters,"unit - unit");
verifyEquals("${#{\"test param\"}}", attributes, stateValues, parameters,"unit");
}
@Test
public void testJsonPath() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"", "${json:jsonPathDelete('$.missingpath')}", "");
verifyEquals("${json:jsonPath('$.missingpath')}", attributes, "");
try {
verifyEquals("${json:jsonPath('$..')}", attributes, "");
Assert.fail("Did not detect bad JSON path expression");
} catch (final AttributeExpressionLanguageException e) {
}
try {
verifyEquals("${missing:jsonPath('$.firstName')}", attributes, "");
Assert.fail("Did not detect empty JSON document");
} catch (AttributeExpressionLanguageException e) {
}
attributes.put("invalid", "[}");
try {
verifyEquals("${invlaid:jsonPath('$.firstName')}", attributes, "John");
Assert.fail("Did not detect invalid JSON document");
} catch (AttributeExpressionLanguageException e) {
}
}
private void verifyAddressBookAttributes(String originalAddressBook, Map<String,String> attributes, String updatedAttribute, Object updatedValue) {
if (StringUtils.isBlank(attributes.get("json"))) {
throw new IllegalArgumentException("original Json attributes is empty");
}
Map<String, String> originalAttributes = new HashMap<>();
originalAttributes.put("json", originalAddressBook);
phoneBookAttributes.stream()
.filter(currentAttribute -> !currentAttribute.equals(updatedAttribute))
.forEach(currentAttribute -> {
String expected = Query.evaluateExpressions(currentAttribute, originalAttributes, null, null, ParameterLookup.EMPTY);
verifyEquals(currentAttribute, attributes, expected);
}
);
if (! ADDRESS_BOOK_JSON_PATH_EMPTY.equals(updatedAttribute) ) {
verifyEquals(updatedAttribute, attributes, updatedValue);
}
}
private Map<String,String> verifyJsonPathExpressions(String targetAttribute, Object originalValue, String updateExpression, Object updatedValue) throws IOException {
final Map<String, String> attributes = new HashMap<>();
String addressBook = getResourceAsString("/json/address-book.json");
attributes.put("json", addressBook);
if ( ! ADDRESS_BOOK_JSON_PATH_EMPTY.equals(targetAttribute) ) {
verifyEquals(targetAttribute, attributes, originalValue);
}
String addressBookAfterUpdate = Query.evaluateExpressions(updateExpression, attributes, ParameterLookup.EMPTY);
attributes.clear();
attributes.put("json", addressBookAfterUpdate);
verifyAddressBookAttributes(addressBook, attributes, targetAttribute, updatedValue);
return attributes;
}
@Test
public void testJsonPathDeleteFirstNameAttribute() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_FIRST_NAME,
"John",
"${json:jsonPathDelete('$.firstName')}",
""
);
}
@Test
public void testJsonPathDeleteMissingPath() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathDelete('$.missing-path')}",
"");
}
@Test
public void testJsonPathDeleteHomePhoneNumber() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_PHONE_NUMBERS_TYPE_HOME_NUMBER,
"212 555-1234",
"${json:jsonPathDelete(\"$.phoneNumbers[?(@.type=='home')]\")}",
"[]");
}
@Test
public void testJsonPathSetFirstNameAttribute() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_FIRST_NAME,
"John",
"${json:jsonPathSet('$.firstName', 'James')}",
"James");
}
@Test
public void testJsonPathSetAgeWholeNumberAttribute() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_AGE,
"25",
"${json:jsonPathSet('$.age', '35')}",
"35");
}
@Test
public void testJsonPathSetVoterBooleanAttribute() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_VOTER,
"true",
"${json:jsonPathSet('$.voter', false)}",
"false");
}
@Test
public void testJsonPathSetHeightNumberAttribute() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_HEIGHT,
"6.1",
"${json:jsonPathSet('$.height', 5.9)}",
"5.9");
}
@Test
public void testJsonPathSetMissingPathAttribute() throws IOException {
verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathSet('$.missing-path', 5.9)}",
"");
}
@Test
public void testJsonPathAddNicknameJimmy() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathAdd('$.nicknames', 'Jimmy')}",
"");
verifyEquals("${json:jsonPath('$.nicknames')}", attributes, "Jimmy");
}
@Test
public void testJsonPathAddNicknameJimmyAtNonexistantPath() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathAdd('$.missing-path', 'Jimmy')}",
"");
verifyEquals("${json:jsonPath('$.missing-path')}", attributes, "");
}
@Test(expected=IllegalArgumentException.class)
public void testJsonPathAddNicknameJimmyAtNonArray() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathAdd('$.firstName', 'Jimmy')}",
"");
}
@Test
public void testJsonPathPutRootLevelMiddlenameTuron() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathPut('$','middlename','Turon')}",
"");
verifyEquals("${json:jsonPath('$.middlename')}", attributes, "Turon");
}
@Test
public void testJsonPathPutCountryToMap() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathPut('$.address','country','US')}",
"");
verifyEquals("${json:jsonPath('$.address.country')}", attributes, "US");
}
@Test
public void testJsonPathPutElementToArray() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathPut('$.phoneNumbers[1]', 'backup', '212-555-1212')}",
"");
verifyEquals("${json:jsonPath('$.phoneNumbers[1].backup')}", attributes, "212-555-1212");
}
@Test
public void testJsonPathPutOverwriteFirstNameToJimmy() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_FIRST_NAME,
"John",
"${json:jsonPathPut('$','firstName','Jimmy')}",
"Jimmy");
}
@Test
public void testEmbeddedExpressionsAndQuotesWithProperties() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("x", "abc");
attributes.put("a", "abc");
verifyEquals("${x:equals(${a})}", attributes, true);
Query.validateExpression("${x:equals('${a}')}", false);
assertEquals("true", Query.evaluateExpressions("${x:equals('${a}')}", attributes, null));
Query.validateExpression("${x:equals(\"${a}\")}", false);
assertEquals("true", Query.evaluateExpressions("${x:equals(\"${a}\")}", attributes, null));
}
@Test
public void testJoin() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("a.a", "a");
attributes.put("a.b", "b");
attributes.put("a.c", "c");
verifyEquals("${allAttributes( 'a.a', 'a.b', 'a.c' ):join(', ')}", attributes, "a, b, c");
verifyEquals("${x:join(', ')}", attributes, "");
verifyEquals("${a.a:join(', ')}", attributes, "a");
verifyEquals("${allAttributes( 'x', 'y' ):join(',')}", attributes, ",");
}
@Test(expected = AttributeExpressionLanguageException.class)
public void testCannotCombineWithNonReducingFunction() {
Query.compile("${allAttributes( 'a.1' ):plus(1)}");
}
@Test
public void testIsEmpty() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("a", "a");
attributes.put("b", "");
attributes.put("c", " \n");
verifyEquals("${a:isEmpty()}", attributes, false);
verifyEquals("${b:isEmpty()}", attributes, true);
verifyEquals("${c:isEmpty()}", attributes, true);
verifyEquals("${d:isEmpty()}", attributes, true);
}
@Test
public void testReplaceEmpty() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("a", "a");
attributes.put("b", "");
attributes.put("c", " \n");
verifyEquals("${a:replaceEmpty('c')}", attributes, "a");
verifyEquals("${b:replaceEmpty('c')}", attributes, "c");
verifyEquals("${c:replaceEmpty('c')}", attributes, "c");
verifyEquals("${d:replaceEmpty('c')}", attributes, "c");
}
@Test
public void testCount() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("a", "a");
attributes.put("b", "abc");
attributes.put("c", " \n");
attributes.put("n1", "111");
attributes.put("n2", "222");
attributes.put("n3", "333333");
verifyEquals("${allMatchingAttributes( '.*' ):count()}", attributes, 6L);
verifyEquals("${allMatchingAttributes( '.*' ):length():gt(2):count()}", attributes, 5L);
verifyEquals("${allMatchingAttributes( 'n.*' ):plus(1):count()}", attributes, 3L);
}
@Test
public void testCurlyBracesInQuotes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "My Valuee");
assertEquals("Val", evaluateQueryForEscape("${attr:replaceAll('My (Val)ue{1,2}', '$1')}", attributes));
assertEquals("Val", evaluateQueryForEscape("${attr:replaceAll(\"My (Val)ue{1,2}\", '$1')}", attributes));
}
@SuppressWarnings("unchecked")
private String evaluateQueryForEscape(final String queryString, final Map<String, String> attributes) {
final FlowFile mockFlowFile = Mockito.mock(FlowFile.class);
Mockito.when(mockFlowFile.getAttributes()).thenReturn(attributes);
Mockito.when(mockFlowFile.getId()).thenReturn(1L);
Mockito.when(mockFlowFile.getEntryDate()).thenReturn(System.currentTimeMillis());
Mockito.when(mockFlowFile.getSize()).thenReturn(1L);
Mockito.when(mockFlowFile.getLineageStartDate()).thenReturn(System.currentTimeMillis());
final ValueLookup lookup = new ValueLookup(VariableRegistry.EMPTY_REGISTRY, mockFlowFile);
return Query.evaluateExpressions(queryString, lookup, ParameterLookup.EMPTY);
}
@Test
public void testGetAttributeValue() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "My Value");
verifyEquals("${attr}", attributes, "My Value");
}
@Test
public void testGetAttributeValueEmbedded() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "XX ");
attributes.put("XX", "My Value");
verifyEquals("${${attr:trim()}}", attributes, "My Value");
}
@Test
public void testSimpleSubstring() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "My Value");
verifyEquals("${attr:substring(2, 5)}", attributes, " Va");
}
@Test
public void testCallToFunctionWithSubjectResultOfAnotherFunctionCall() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", " My Value ");
verifyEquals("${attr:trim():substring(2, 5)}", attributes, " Va");
}
@Test
public void testProblematic1() {
// There was a bug that prevented this expression from compiling. This test just verifies that it now compiles.
final String queryString = "${xx:append( \"120101\" ):toDate( 'yyMMddHHmmss' ):format( \"yy-MM-dd’T’HH:mm:ss\") }";
Query.compile(queryString);
}
@Test
public void testEquals() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", " XX ");
verifyEquals("${attr:trim():equals('XX')}", attributes, true);
}
@Test
public void testDeeplyEmbedded() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("x", "false");
attributes.put("abc", "a");
attributes.put("a", "a");
verifyEquals("${x:or( ${${abc}:length():equals(1)} )}", attributes, true);
}
@Test
public void testExtractExpressionRanges() {
assertEquals(29, Query.extractExpressionRanges("${hello:equals( $${goodbye} )}").get(0).getEnd());
List<Range> ranges = Query.extractExpressionRanges("hello");
assertTrue(ranges.isEmpty());
ranges = Query.extractExpressionRanges("${hello");
assertTrue(ranges.isEmpty());
ranges = Query.extractExpressionRanges("hello}");
assertTrue(ranges.isEmpty());
ranges = Query.extractExpressionRanges("$${hello");
assertTrue(ranges.isEmpty());
ranges = Query.extractExpressionRanges("$he{ll}o");
assertTrue(ranges.isEmpty());
ranges = Query.extractExpressionRanges("${hello}");
assertEquals(1, ranges.size());
Range range = ranges.get(0);
assertEquals(0, range.getStart());
assertEquals(7, range.getEnd());
ranges = Query.extractExpressionRanges("${hello:equals( ${goodbye} )}");
assertEquals(1, ranges.size());
range = ranges.get(0);
assertEquals(0, range.getStart());
assertEquals(28, range.getEnd());
ranges = Query.extractExpressionRanges("${hello:equals( $${goodbye} )}");
assertEquals(1, ranges.size());
range = ranges.get(0);
assertEquals(0, range.getStart());
assertEquals(29, range.getEnd());
ranges = Query.extractExpressionRanges("${hello:equals( $${goodbye} )} or just hi, ${bob:or(${jerry})}");
assertEquals(2, ranges.size());
range = ranges.get(0);
assertEquals(0, range.getStart());
assertEquals(29, range.getEnd());
range = ranges.get(1);
assertEquals(43, range.getStart());
assertEquals(61, range.getEnd());
ranges = Query.extractExpressionRanges("${hello:equals( ${goodbye} )} or just hi, ${bob}, are you ${bob.age:toNumber()} yet? $$$${bob}");
assertEquals(3, ranges.size());
range = ranges.get(0);
assertEquals(0, range.getStart());
assertEquals(28, range.getEnd());
range = ranges.get(1);
assertEquals(42, range.getStart());
assertEquals(47, range.getEnd());
range = ranges.get(2);
assertEquals(58, range.getStart());
assertEquals(78, range.getEnd());
ranges = Query.extractExpressionRanges("${x:matches( '.{4}' )}");
assertEquals(1, ranges.size());
range = ranges.get(0);
assertEquals(0, range.getStart());
assertEquals(21, range.getEnd());
}
@Test
public void testExtractExpressionTypes() {
final List<ResultType> types = Query.extractResultTypes("${hello:equals( ${goodbye} )} or just hi, ${bob}, are you ${bob.age:toNumber()} yet? $$$${bob}");
assertEquals(3, types.size());
assertEquals(ResultType.BOOLEAN, types.get(0));
assertEquals(ResultType.STRING, types.get(1));
assertEquals(ResultType.WHOLE_NUMBER, types.get(2));
}
@Test
public void testEqualsEmbedded() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("x", "hello");
attributes.put("y", "good-bye");
verifyEquals("${x:equals( ${y} )}", attributes, false);
attributes.put("y", "hello");
verifyEquals("${x:equals( ${y} )}", attributes, true);
attributes.put("x", "4");
attributes.put("y", "3");
attributes.put("z", "1");
attributes.put("h", "100");
verifyEquals("${x:toNumber():lt( ${y:toNumber():plus( ${h:toNumber()} )} )}", attributes, true);
verifyEquals("${h:toNumber():ge( ${y:toNumber():plus( ${z:toNumber()} )} )}", attributes, true);
verifyEquals("${x:toNumber():equals( ${y:toNumber():plus( ${z:toNumber()} )} )}", attributes, true);
attributes.put("x", "88");
verifyEquals("${x:toNumber():gt( ${y:toNumber():plus( ${z:toNumber()} )} )}", attributes, true);
attributes.put("y", "88");
assertEquals("true", Query.evaluateExpressions("${x:equals( '${y}' )}", attributes, null));
}
@Test
public void testComplicatedEmbeddedExpressions() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("fox", "quick, brown");
attributes.put("dog", "lazy");
verifyEquals("${fox:substring( ${ 'dog' :substring(2):length()}, 5 ):equals( 'ick' )}", attributes, true);
verifyEquals("${fox:substring( ${ 'dog' :substring(2):length()}, 5 ):equals( 'ick' )}", attributes, true);
}
@Test
public void testQuotingQuotes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("xx", "say 'hi'");
String query = "${xx:replaceAll( \"'.*'\", '\\\"hello\\\"' )}";
verifyEquals(query, attributes, "say \"hello\"");
query = "${xx:replace( \"'\", '\"')}";
verifyEquals(query, attributes, "say \"hi\"");
query = "${xx:replace( '\\'', '\"')}";
System.out.println(query);
verifyEquals(query, attributes, "say \"hi\"");
}
@Test
public void testDoubleQuotesWithinSingleQuotes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("xx", "say 'hi'");
final String query = "${xx:replace( \"'hi'\", '\\\"hello\\\"' )}";
System.out.println(query);
verifyEquals(query, attributes, "say \"hello\"");
}
@Test
public void testEscapeQuotes() {
final long timestamp = 1403620278642L;
final Map<String, String> attributes = new HashMap<>();
attributes.put("date", String.valueOf(timestamp));
final String format = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
final String query = "startDateTime=\"${date:toNumber():toDate():format(\"" + format + "\")}\"";
final String result = Query.evaluateExpressions(query, attributes, null);
final String expectedTime = new SimpleDateFormat(format, Locale.US).format(timestamp);
assertEquals("startDateTime=\"" + expectedTime + "\"", result);
final List<Range> ranges = Query.extractExpressionRanges(query);
assertEquals(1, ranges.size());
}
@Test
public void testDateConversion() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("date", "1403620278642");
verifyEquals("${date:format('yyyy')}", attributes, "2014");
verifyEquals("${date:toDate():format('yyyy')}", attributes, "2014");
verifyEquals("${date:toNumber():format('yyyy')}", attributes, "2014");
verifyEquals("${date:toNumber():toDate():format('yyyy')}", attributes, "2014");
verifyEquals("${date:toDate():toNumber():format('yyyy')}", attributes, "2014");
verifyEquals("${date:toDate():toNumber():toDate():toNumber():toDate():toNumber():format('yyyy')}", attributes, "2014");
}
@Test
public void testSingleLetterAttribute() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("A", "0123456789");
verifyEquals("${A}", attributes, "0123456789");
verifyEquals("${'A'}", attributes, "0123456789");
}
@Test
public void testImplicitConversions() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("A", "0123456789");
attributes.put("b", "true");
attributes.put("c", "false");
attributes.put("d", "Quick Brown Fox");
attributes.put("F", "-48");
attributes.put("n", "2014/04/04 00:00:00");
final Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2014);
cal.set(Calendar.MONTH, 3);
cal.set(Calendar.DAY_OF_MONTH, 4);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 45);
final String dateString = cal.getTime().toString();
attributes.put("z", dateString);
verifyEquals("${A:plus(4)}", attributes, 123456793L);
verifyEquals("${A:plus( ${F} )}", attributes, 123456741L);
verifyEquals("${F:lt( ${A} )}", attributes, true);
verifyEquals("${A:substring(2,3):plus(21):substring(1,2):plus(0)}", attributes, 3L);
verifyEquals("${n:format( 'yyyy' )}", attributes, "2014");
verifyEquals("${z:format( 'yyyy' )}", attributes, "2014");
attributes.put("n", "2014/04/04 00:00:00.045");
verifyEquals("${n:format( 'yyyy' ):append(','):append( ${n:format( 'SSS' )} )}", attributes, "2014,045");
}
@Test
public void testNewLinesAndTabsInQuery() {
final String query = "${ abc:equals('abc'):or( \n\t${xx:isNull()}\n) }";
assertEquals(ResultType.BOOLEAN, Query.getResultType(query));
Query.validateExpression(query, false);
assertEquals("true", Query.evaluateExpressions(query, Collections.emptyMap(), ParameterLookup.EMPTY));
}
@Test
public void testAttributeReferencesWithWhiteSpace() {
final Map<String, String> attrs = new HashMap<>();
attrs.put("a b c,d", "abc");
final String query = "${ 'a b c,d':equals('abc') }";
verifyEquals(query, attrs, true);
}
@Test
public void testComments() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "xyz");
final String expression
= "# hello, world\n"
+ "${# ref attr\n"
+ "\t"
+ "abc"
+ "\t"
+ "#end ref attr\n"
+ "}";
Query query = Query.compile(expression);
QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
assertEquals(ResultType.STRING, result.getResultType());
assertEquals("xyz", result.getValue());
query = Query.compile("${abc:append('# hello') #good-bye \n}");
result = query.evaluate(new StandardEvaluationContext(attributes));
assertEquals(ResultType.STRING, result.getResultType());
assertEquals("xyz# hello", result.getValue());
}
@Test
public void testAppendPrepend() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "XX");
attributes.put("YXXX", "bingo");
verifyEquals("${${attr:append('X'):prepend('Y')}}", attributes, "bingo");
}
@Test
public void testIsNull() {
final Map<String, String> attributes = new HashMap<>();
verifyEquals("${attr:isNull()}", attributes, true);
}
@Test
public void testNotNull() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "");
verifyEquals("${attr:notNull()}", attributes, true);
}
@Test
public void testIsNullOrLengthEquals0() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "");
attributes.put("xyz", "xyz");
attributes.put("xx", " ");
verifyEquals("${abc:isNull():or( ${abc:length():equals(0)} )}", attributes, true);
verifyEquals("${xyz:isNull():or( ${xyz:length():equals(0)} )}", attributes, false);
verifyEquals("${none:isNull():or( ${none:length():equals(0)} )}", attributes, true);
verifyEquals("${xx:isNull():or( ${xx:trim():length():equals(0)} )}", attributes, true);
}
@Test
public void testReplaceNull() {
final Map<String, String> attributes = new HashMap<>();
verifyEquals("${attr:replaceNull('hello')}", attributes, "hello");
}
@Test
public void testReplace() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
verifyEquals("${attr:replace('hell', 'yell')}", attributes, "yello");
}
@Test
public void testReplaceAll() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
attributes.put("xyz", "00-00TEST.2014_01_01_000000_value");
verifyEquals("${xyz:replaceAll(\"^([^.]+)\\.([0-9]{4})_([0-9]{2})_([0-9]{2}).*$\", \"$3\")}", attributes, "01");
verifyEquals("${attr:replaceAll('l+', 'r')}", attributes, "hero");
attributes.clear();
attributes.put("filename1", "abc.gz");
attributes.put("filename2", "abc.g");
attributes.put("filename3", "abc.gz.gz");
attributes.put("filename4", "abc.gz.g");
attributes.put("abc", "hello world");
verifyEquals("${filename3:replaceAll('\\\\\\.gz$', '')}", attributes, "abc.gz.gz");
verifyEquals("${filename3:replaceAll('\\\\\\\\.gz$', '')}", attributes, "abc.gz.gz");
verifyEquals("${filename1:replaceAll('\\.gz$', '')}", attributes, "abc");
verifyEquals("${filename2:replaceAll('\\.gz$', '')}", attributes, "abc.g");
verifyEquals("${filename4:replaceAll('\\\\.gz$', '')}", attributes, "abc.gz.g");
verifyEquals("${abc:replaceAll( 'lo wor(ld)', '$0')}", attributes, "hello world");
verifyEquals("${abc:replaceAll( 'he(llo) world', '$1')}", attributes, "llo");
verifyEquals("${abc:replaceAll( 'xx', '$0')}", attributes, "hello world");
verifyEquals("${abc:replaceAll( '(xx)', '$1')}", attributes, "hello world");
verifyEquals("${abc:replaceAll( 'lo wor(ld)', '$1')}", attributes, "helld");
}
@Test
public void testReplaceAllWithOddNumberOfBackslashPairs() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename", "C:\\temp\\.txt");
verifyEquals("${filename:replace('\\\\', '/')}", attributes, "C:/temp/.txt");
verifyEquals("${filename:replaceAll('\\\\\\\\', '/')}", attributes, "C:/temp/.txt");
verifyEquals("${filename:replaceAll('\\\\\\.txt$', '')}", attributes, "C:\\temp");
}
@Test
public void testReplaceAllWithMatchingGroup() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
verifyEquals("${attr:replaceAll('.*?(l+).*', '$1')}", attributes, "ll");
}
@Test
public void testMathWholeNumberOperations() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("one", "1");
attributes.put("two", "2");
attributes.put("three", "3");
attributes.put("four", "4");
attributes.put("five", "5");
attributes.put("hundred", "100");
verifyEquals("${hundred:toNumber():multiply(${two}):divide(${three}):plus(${one}):mod(${five})}", attributes, 2L);
}
@Test
public void testMathDecimalOperations() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("first", "1.5");
attributes.put("second", "12.3");
attributes.put("third", "3");
attributes.put("fourth", "4.201");
attributes.put("fifth", "5.1");
attributes.put("hundred", "100");
// The expected resulted is calculated instead of a set number due to the inaccuracy of double arithmetic
verifyEquals("${hundred:toNumber():multiply(${second}):divide(${third}):plus(${first}):mod(${fifth})}", attributes, (((100 * 12.3) / 3) + 1.5) %5.1);
}
@Test
public void testMathResultInterpretation() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("ten", "10.1");
attributes.put("two", "2.2");
// The expected resulted is calculated instead of a set number due to the inaccuracy of double arithmetic
verifyEquals("${ten:divide(${two:plus(3)}):toNumber()}", attributes, (Double.valueOf(10.1 / (2.2 + 3)).longValue()));
// The expected resulted is calculated instead of a set number due to the inaccuracy of double arithmetic
verifyEquals("${ten:divide(${two:plus(3)}):toDecimal()}", attributes, (10.1 / (2.2 + 3)));
// The expected resulted is calculated instead of a set number due to the inaccuracy of double arithmetic
verifyEquals("${ten:divide(${two:plus(3.1)}):toDecimal()}", attributes, (10.1 / (2.2 + 3.1)));
verifyEquals("${ten:divide(${two:plus(3)}):toDate():format(\"SSS\")}", attributes, "001");
}
@Test
public void testMathFunction() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("one", "1");
attributes.put("two", "2");
attributes.put("oneDecimal", "1.5");
attributes.put("twoDecimal", "2.3");
attributes.put("negative", "-64");
attributes.put("negativeDecimal", "-64.1");
// Test that errors relating to not finding methods are properly handled
try {
verifyEquals("${math('rand'):toNumber()}", attributes, 0L);
fail();
} catch (AttributeExpressionLanguageException expected) {
assertEquals("Cannot evaluate 'math' function because no subjectless method was found with the name:'rand'", expected.getMessage());
}
try {
verifyEquals("${negativeDecimal:math('absolute')}", attributes, 0L);
fail();
} catch (AttributeExpressionLanguageException expected) {
assertEquals("Cannot evaluate 'math' function because no method was found matching the passed parameters: name:'absolute', one argument of type: 'double'", expected.getMessage());
}
try {
verifyEquals("${oneDecimal:math('power', ${two:toDecimal()})}", attributes, 0L);
fail();
} catch (AttributeExpressionLanguageException expected) {
assertEquals("Cannot evaluate 'math' function because no method was found matching the passed parameters: name:'power', " +
"first argument type: 'double', second argument type: 'double'", expected.getMessage());
}
try {
verifyEquals("${oneDecimal:math('power', ${two})}", attributes, 0L);
fail();
} catch (AttributeExpressionLanguageException expected) {
assertEquals("Cannot evaluate 'math' function because no method was found matching the passed parameters: name:'power', " +
"first argument type: 'double', second argument type: 'long'", expected.getMessage());
}
// Can only verify that it runs. ToNumber() will verify that it produced a number greater than or equal to 0.0 and less than 1.0
verifyEquals("${math('random'):toNumber()}", attributes, 0L);
verifyEquals("${negative:math('abs')}", attributes, 64L);
verifyEquals("${negativeDecimal:math('abs')}", attributes, 64.1D);
verifyEquals("${negative:math('max', ${two})}", attributes, 2L);
verifyEquals("${negativeDecimal:math('max', ${twoDecimal})}", attributes, 2.3D);
verifyEquals("${oneDecimal:math('pow', ${two:toDecimal()})}", attributes, Math.pow(1.5,2));
verifyEquals("${oneDecimal:math('scalb', ${two})}", attributes, Math.scalb(1.5,2));
verifyEquals("${negative:math('abs'):toDecimal():math('cbrt'):math('max', ${two:toDecimal():math('pow',${oneDecimal}):mod(${two})})}", attributes,
Math.max(Math.cbrt(Math.abs(-64)), Math.pow(2,1.5)%2));
}
@Test
public void testMathLiteralOperations() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("ten", "10.1");
attributes.put("two", "2.2");
// The expected resulted is calculated instead of a set number due to the inaccuracy of double arithmetic
verifyEquals("${literal(5):toNumber():multiply(${two:plus(1)})}", attributes, 5*3.2);
verifyEquals("${literal(5.5E-1):toDecimal():plus(${literal(.5E1)}):multiply(${two:plus(1)})}", attributes, (0.55+5)*3.2);
}
@Test
public void testIndexOf() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "https://abc.go");
verifyEquals("${attr:indexOf('/')}", attributes, 6L);
}
@Test
public void testDate() {
final Calendar now = Calendar.getInstance();
final int year = now.get(Calendar.YEAR);
final Map<String, String> attributes = new HashMap<>();
attributes.put("entryDate", String.valueOf(now.getTimeInMillis()));
verifyEquals("${entryDate:toNumber():toDate():format('yyyy')}", attributes, String.valueOf(year));
// test for not existing attribute (NIFI-1962)
assertEquals("", Query.evaluateExpressions("${notExistingAtt:toDate()}", attributes, null));
attributes.clear();
attributes.put("month", "3");
attributes.put("day", "4");
attributes.put("year", "2013");
assertEquals("63", Query.evaluateExpressions("${year:append('/'):append(${month}):append('/'):append(${day}):toDate('yyyy/MM/dd'):format('D')}", attributes, null));
assertEquals("63", Query.evaluateExpressions("${year:append('/'):append('${month}'):append('/'):append('${day}'):toDate('yyyy/MM/dd'):format('D')}", attributes, null));
verifyEquals("${year:append('/'):append(${month}):append('/'):append(${day}):toDate('yyyy/MM/dd'):format('D')}", attributes, "63");
}
@Test
public void testAnyAttribute() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "zzz");
attributes.put("xyz", "abc");
verifyEquals("${anyAttribute('abc', 'xyz', 'missingAttr'):substring(1,2):equals('b')}", attributes, true);
verifyEquals("${anyAttribute('abc', 'xyz'):substring(1,2):equals('b')}", attributes, true);
verifyEquals("${anyAttribute('xyz', 'abc'):substring(1,2):equals('b')}", attributes, true);
verifyEquals("${anyAttribute('zz'):substring(1,2):equals('b')}", attributes, false);
verifyEquals("${anyAttribute('abc', 'zz'):isNull()}", attributes, true);
}
@Test
public void testAnyMatchingAttribute() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "zzz");
attributes.put("xyz", "abc");
attributes.put("123.cba", "hello");
verifyEquals("${anyMatchingAttribute('.{2}x', '.{2}z'):substring(1,2):equals('b')}", attributes, true);
verifyEquals("${anyMatchingAttribute('.*'):substring(1,2):equals('b')}", attributes, true);
verifyEquals("${anyMatchingAttribute('x{44}'):substring(1,2):equals('b')}", attributes, false);
verifyEquals("${anyMatchingAttribute('abc'):substring(1,2):equals('b')}", attributes, false);
verifyEquals("${anyMatchingAttribute('xyz'):substring(1,2):equals('b')}", attributes, true);
verifyEquals("${anyMatchingAttribute('xyz'):notNull()}", attributes, true);
verifyEquals("${anyMatchingAttribute('xyz'):isNull()}", attributes, false);
verifyEquals("${anyMatchingAttribute('xxxxxxxxx'):notNull()}", attributes, false);
verifyEquals("${anyMatchingAttribute('123\\.c.*'):matches('hello')}", attributes, true);
verifyEquals("${anyMatchingAttribute('123\\.c.*|a.c'):matches('zzz')}", attributes, true);
}
@Test
public void testAnyDelineatedValue() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "a,b,c");
attributes.put("xyz", "abc");
final String query = "${anyDelineatedValue('${abc}', ','):equals('b')}";
assertEquals(ResultType.BOOLEAN, Query.getResultType(query));
assertEquals("true", Query.evaluateExpressions(query, attributes, null));
assertEquals("true", Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):equals('a')}", attributes, null));
assertEquals("true", Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):equals('c')}", attributes, null));
assertEquals("false", Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):equals('d')}", attributes, null));
verifyEquals("${anyDelineatedValue(${abc}, ','):equals('b')}", attributes, true);
verifyEquals("${anyDelineatedValue(${abc}, ','):equals('a')}", attributes, true);
verifyEquals("${anyDelineatedValue(${abc}, ','):equals('c')}", attributes, true);
verifyEquals("${anyDelineatedValue(${abc}, ','):equals('d')}", attributes, false);
}
@Test
public void testNestedAnyDelineatedValueOr() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "a,b,c");
attributes.put("xyz", "x");
// Assert each part separately.
assertEquals("true", Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):equals('c')}",
attributes, null));
assertEquals("false", Query.evaluateExpressions("${anyDelineatedValue('${xyz}', ','):equals('z')}",
attributes, null));
// Combine them with 'or'.
assertEquals("true", Query.evaluateExpressions(
"${anyDelineatedValue('${abc}', ','):equals('c'):or(${anyDelineatedValue('${xyz}', ','):equals('z')})}",
attributes, null));
}
@Test
public void testNestedAnyDelineatedValueAnd() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "2,0,1,3");
attributes.put("xyz", "x,y,z");
// Assert each part separately.
assertEquals("true", Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):gt('2')}",
attributes, null));
assertEquals("true", Query.evaluateExpressions("${anyDelineatedValue('${xyz}', ','):equals('z')}",
attributes, null));
// Combine them with 'and'.
assertEquals("true", Query.evaluateExpressions(
"${anyDelineatedValue('${abc}', ','):gt('2'):and(${anyDelineatedValue('${xyz}', ','):equals('z')})}",
attributes, null));
}
@Test
public void testAllDelineatedValues() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "a,b,c");
attributes.put("xyz", "abc");
final String query = "${allDelineatedValues('${abc}', ','):matches('[abc]')}";
assertEquals(ResultType.BOOLEAN, Query.getResultType(query));
assertEquals("true", Query.evaluateExpressions(query, attributes, null));
assertEquals("true", Query.evaluateExpressions(query, attributes, null));
assertEquals("false", Query.evaluateExpressions("${allDelineatedValues('${abc}', ','):matches('[abd]')}", attributes, null));
assertEquals("false", Query.evaluateExpressions("${allDelineatedValues('${abc}', ','):equals('a'):not()}", attributes, null));
verifyEquals("${allDelineatedValues(${abc}, ','):matches('[abc]')}", attributes, true);
verifyEquals("${allDelineatedValues(${abc}, ','):matches('[abd]')}", attributes, false);
verifyEquals("${allDelineatedValues(${abc}, ','):equals('a'):not()}", attributes, false);
}
@Test
public void testAllDelineatedValuesCount() {
final Map<String, String> attributes = new HashMap<>();
final String query = "${allDelineatedValues('${test}', '/'):count()}";
attributes.put("test", "/my/path");
assertEquals(ResultType.WHOLE_NUMBER, Query.getResultType(query));
assertEquals("3", Query.evaluateExpressions(query, attributes, null));
assertEquals("", Query.evaluateExpressions("${test:getDelimitedField(1, '/')}", attributes, null));
assertEquals("my", Query.evaluateExpressions("${test:getDelimitedField(2, '/')}", attributes, null));
assertEquals("path", Query.evaluateExpressions("${test:getDelimitedField(3, '/')}", attributes, null));
attributes.put("test", "this/is/my/path");
assertEquals(ResultType.WHOLE_NUMBER, Query.getResultType(query));
assertEquals("4", Query.evaluateExpressions(query, attributes, null));
assertEquals("this", Query.evaluateExpressions("${test:getDelimitedField(1, '/')}", attributes, null));
assertEquals("is", Query.evaluateExpressions("${test:getDelimitedField(2, '/')}", attributes, null));
assertEquals("my", Query.evaluateExpressions("${test:getDelimitedField(3, '/')}", attributes, null));
assertEquals("path", Query.evaluateExpressions("${test:getDelimitedField(4, '/')}", attributes, null));
attributes.put("test", "/");
assertEquals(ResultType.WHOLE_NUMBER, Query.getResultType(query));
assertEquals("0", Query.evaluateExpressions(query, attributes, null));
attributes.put("test", "path/");
assertEquals(ResultType.WHOLE_NUMBER, Query.getResultType(query));
assertEquals("1", Query.evaluateExpressions(query, attributes, null));
assertEquals("path", Query.evaluateExpressions("${test:getDelimitedField(1, '/')}", attributes, null));
}
@Test
public void testAllAttributes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "1234");
attributes.put("xyz", "4132");
attributes.put("hello", "world!");
verifyEquals("${allAttributes('abc', 'xyz'):matches('\\d+')}", attributes, true);
verifyEquals("${allAttributes('abc', 'xyz'):toNumber():lt(99999)}", attributes, true);
verifyEquals("${allAttributes('abc', 'hello'):length():gt(3)}", attributes, true);
verifyEquals("${allAttributes('abc', 'hello'):length():equals(4)}", attributes, false);
verifyEquals("${allAttributes('abc', 'xyz'):length():equals(4)}", attributes, true);
verifyEquals("${allAttributes('abc', 'xyz', 'other'):isNull()}", attributes, false);
try {
Query.compile("${allAttributes('#ah'):equals('hello')");
Assert.fail("Was able to compile with allAttributes and an invalid attribute name");
} catch (final AttributeExpressionLanguageParsingException e) {
// expected behavior
}
}
@Test
public void testMathWholeNumberOperators() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "1234");
attributes.put("xyz", "4132");
attributes.put("hello", "world!");
verifyEquals("${xyz:toNumber():gt( ${abc:toNumber()} )}", attributes, true);
}
@Test
public void testMathDecimalOperators() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("one", "1.1");
attributes.put("two", "2.2");
attributes.put("one_2", "1.1");
verifyEquals("${one:lt(${two})}", attributes, true);
verifyEquals("${one:lt(${one_2})}", attributes, false);
verifyEquals("${two:lt(${one})}", attributes, false);
verifyEquals("${one:le(${two})}", attributes, true);
verifyEquals("${one:le(${one_2})}", attributes, true);
verifyEquals("${two:le(${one_2})}", attributes, false);
verifyEquals("${one:ge(${two})}", attributes, false);
verifyEquals("${one:ge(${one_2})}", attributes, true);
verifyEquals("${two:ge(${one_2})}", attributes, true);
verifyEquals("${one:gt(${two})}", attributes, false);
verifyEquals("${one:gt(${one_2})}", attributes, false);
verifyEquals("${two:gt(${one})}", attributes, true);
}
@Test
public void testMathNumberDecimalConversion() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("xyz", "1.332");
attributes.put("hello", "world!");
verifyEquals("${xyz:toNumber()}", attributes, 1L);
attributes.put("xyz", "2");
attributes.put("hello", "world!");
verifyEquals("${xyz:toDecimal()}", attributes, 2D);
}
@Test
public void testLiteral() {
final Map<String, String> attributes = new HashMap<>();
verifyEquals("${literal(5)}", attributes, "5");
verifyEquals("${literal(\"5\")}", attributes, "5");
verifyEquals("${literal(5):toNumber()}", attributes, 5L);
verifyEquals("${literal(5):toDecimal()}", attributes, 5D);
verifyEquals("${literal(\"5.5\")}", attributes, "5.5");
verifyEquals("${literal(5.5):toNumber()}", attributes, 5L);
verifyEquals("${literal(-5.5):toNumber()}", attributes, -5L);
verifyEquals("${literal(+5.5):toNumber()}", attributes, 5L);
verifyEquals("${literal(5.5):toDecimal()}", attributes, 5.5D);
verifyEquals("${literal(-5.5):toDecimal()}", attributes, -5.5D);
verifyEquals("${literal(+5.5):toDecimal()}", attributes, 5.5D);
verifyEquals("${literal('0xF.Fp10'):toDecimal()}", attributes, 0xF.Fp10D);
verifyEquals("${literal('0x1234567890ABCDEF'):toNumber()}", attributes, 0x1234567890ABCDEFL);
verifyEquals("${literal('-0x1234567890ABCDEF'):toNumber()}", attributes, -0x1234567890ABCDEFL);
verifyEquals("${literal('-0x1234567890abcdef'):toNumber()}", attributes, -0x1234567890abcdefL);
verifyEquals("${literal('0x1234567890abcdef'):toNumber()}", attributes, 0x1234567890abcdefL);
}
@Test
public void testDecimalParsing() {
final Map<String, String> attributes = new HashMap<>();
// Test decimal format X.X
verifyEquals("${literal(5.5):toDecimal()}", attributes, 5.5D);
verifyEquals("${literal(-12.5):toDecimal()}", attributes, -12.5D);
verifyEquals("${literal(+12.5):toDecimal()}", attributes, 12.5D);
// Test decimal format X.XEX with positive exponent
verifyEquals("${literal(-12.5E2):toDecimal()}", attributes, -12.5E2D);
verifyEquals("${literal(-12.5e2):toDecimal()}", attributes, -12.5e2D);
verifyEquals("${literal(-12.5e+2):toDecimal()}", attributes, -12.5e+2D);
verifyEquals("${literal(12.5E+2):toDecimal()}", attributes, 12.5E+2D);
verifyEquals("${literal(+12.5e+2):toDecimal()}", attributes, +12.5e+2D);
verifyEquals("${literal(+12.5E2):toDecimal()}", attributes, +12.5E2D);
verifyEquals("${literal(-12.5e2):toDecimal()}", attributes, -12.5e2D);
verifyEquals("${literal(12.5E2):toDecimal()}", attributes, 12.5E2D);
verifyEquals("${literal(+12.5e2):toDecimal()}", attributes, +12.5e2D);
// Test decimal format X.XEX with negative exponent
verifyEquals("${literal(-12.5E-2):toDecimal()}", attributes, -12.5E-2D);
verifyEquals("${literal(12.5E-2):toDecimal()}", attributes, 12.5E-2D);
verifyEquals("${literal(+12.5e-2):toDecimal()}", attributes, +12.5e-2D);
// Test decimal format .X
verifyEquals("${literal(.5):toDecimal()}", attributes, .5D);
verifyEquals("${literal(.5):toDecimal()}", attributes, .5D);
verifyEquals("${literal(-.5):toDecimal()}", attributes, -0.5D);
verifyEquals("${literal(+.5):toDecimal()}", attributes, .5D);
// Test decimal format .XEX with positive exponent
verifyEquals("${literal(-.5E2):toDecimal()}", attributes, -.5E2D);
verifyEquals("${literal(-.5E2):toDecimal()}", attributes, -.5E2D);
verifyEquals("${literal(-.5e+2):toDecimal()}", attributes, -.5e+2D);
verifyEquals("${literal(.5E+2):toDecimal()}", attributes, .5E+2D);
verifyEquals("${literal(+.5e+2):toDecimal()}", attributes, +.5e+2D);
verifyEquals("${literal(+.5E2):toDecimal()}", attributes, +.5E2D);
verifyEquals("${literal(-.5e2):toDecimal()}", attributes, -.5e2D);
verifyEquals("${literal(.5E2):toDecimal()}", attributes, .5E2D);
verifyEquals("${literal(+.5e2):toDecimal()}", attributes, +.5e2D);
// Test decimal format .XEX with negative exponent
verifyEquals("${literal(-.5E-2):toDecimal()}", attributes, -.5E-2D);
verifyEquals("${literal(.5e-2):toDecimal()}", attributes, .5e-2D);
verifyEquals("${literal(+.5E-2):toDecimal()}", attributes, +.5E-2D);
// Verify allowed values
verifyEquals("${literal(9876543210.0123456789e123):toDecimal()}", attributes, 9876543210.0123456789e123D);
verifyEmpty("${literal('A.1e123'):toDecimal()}", attributes);
verifyEmpty("${literal('0.Ae123'):toDecimal()}", attributes);
verifyEmpty("${literal('0.1eA'):toDecimal()}", attributes);
// --------- Hex format ------//
// Test Hex format X.
verifyEquals("${literal('0xF1.p2'):toDecimal()}", attributes, 0xF1.p2D);
verifyEquals("${literal('+0xF1.P2'):toDecimal()}", attributes, +0xF1.p2D);
verifyEquals("${literal('-0xF1.p2'):toDecimal()}", attributes, -0xF1.p2D);
// Test Hex format X.XEX with positive exponent
verifyEquals("${literal('-0xF1.5Bp2'):toDecimal()}", attributes, -0xF1.5Bp2D);
verifyEquals("${literal('-0xF1.5BP2'):toDecimal()}", attributes, -0xF1.5BP2D);
verifyEquals("${literal('-0xF1.5BP+2'):toDecimal()}", attributes, -0xF1.5Bp+2D);
verifyEquals("${literal('0xF1.5BP+2'):toDecimal()}", attributes, 0xF1.5BP+2D);
verifyEquals("${literal('+0xF1.5Bp+2'):toDecimal()}", attributes, +0xF1.5Bp+2D);
verifyEquals("${literal('+0xF1.5BP2'):toDecimal()}", attributes, +0xF1.5BP2D);
verifyEquals("${literal('-0xF1.5Bp2'):toDecimal()}", attributes, -0xF1.5Bp2D);
verifyEquals("${literal('0xF1.5BP2'):toDecimal()}", attributes, 0xF1.5BP2D);
verifyEquals("${literal('+0xF1.5Bp2'):toDecimal()}", attributes, +0xF1.5Bp2D);
// Test decimal format X.XEX with negative exponent
verifyEquals("${literal('-0xF1.5BP-2'):toDecimal()}", attributes, -0xF1.5BP-2D);
verifyEquals("${literal('0xF1.5BP-2'):toDecimal()}", attributes, 0xF1.5BP-2D);
verifyEquals("${literal('+0xF1.5Bp-2'):toDecimal()}", attributes, +0xF1.5Bp-2D);
// Test decimal format .XEX with positive exponent
verifyEquals("${literal('0x.5BP0'):toDecimal()}", attributes, 0x.5BP0D);
verifyEquals("${literal('-0x.5BP0'):toDecimal()}", attributes, -0x.5BP0D);
verifyEquals("${literal('-0x.5BP+2'):toDecimal()}", attributes, -0x.5BP+2D);
verifyEquals("${literal('0x.5BP+2'):toDecimal()}", attributes, 0x.5BP+2D);
verifyEquals("${literal('+0x.5Bp+2'):toDecimal()}", attributes, +0x.5Bp+2D);
verifyEquals("${literal('+0x.5BP2'):toDecimal()}", attributes, +0x.5BP2D);
verifyEquals("${literal('-0x.5Bp2'):toDecimal()}", attributes, -0x.5Bp2D);
verifyEquals("${literal('0x.5BP2'):toDecimal()}", attributes, 0x.5BP2D);
verifyEquals("${literal('+0x.5Bp+2'):toDecimal()}", attributes, +0x.5Bp2D);
// Test decimal format .XEX with negative exponent
verifyEquals("${literal('-0x.5BP-2'):toDecimal()}", attributes, -0x.5BP-2D);
verifyEquals("${literal('0x.5Bp-2'):toDecimal()}", attributes, 0x.5Bp-2D);
verifyEquals("${literal('+0x.5BP-2'):toDecimal()}", attributes, +0x.5BP-2D);
// Verify allowed values
verifyEquals("${literal('0xFEDCBA9876543210.0123456789ABCDEFp123'):toDecimal()}", attributes, 0xFEDCBA9876543210.0123456789ABCDEFp123D);
verifyEquals("${literal('0xfedcba9876543210.0123456789abcdefp123'):toDecimal()}", attributes, 0xfedcba9876543210.0123456789abcdefp123D);
verifyEmpty("${literal('0xG.1p123'):toDecimal()}", attributes);
verifyEmpty("${literal('0x1.Gp123'):toDecimal()}", attributes);
verifyEmpty("${literal('0x1.1pA'):toDecimal()}", attributes);
verifyEmpty("${literal('0x1.1'):toDecimal()}", attributes);
// Special cases
verifyEquals("${literal('" + Double.toString(POSITIVE_INFINITY) + "'):toDecimal():plus(1):plus(2)}", attributes, POSITIVE_INFINITY);
verifyEquals("${literal('" + Double.toString(NEGATIVE_INFINITY) + "'):toDecimal():plus(1):plus(2)}", attributes, NEGATIVE_INFINITY);
verifyEquals("${literal('" + Double.toString(NaN) + "'):toDecimal():plus(1):plus(2)}", attributes, NaN);
}
@Test
public void testAllMatchingAttributes() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "1234");
attributes.put("xyz", "4132");
attributes.put("hello", "world!");
attributes.put("123.cba", "hell.o");
System.out.println(printTree(Query.compile("${allMatchingAttributes('(abc|xyz)'):matches('\\\\d+')}").getTree()));
verifyEquals("${'123.cba':matches('hell\\.o')}", attributes, true);
verifyEquals("${allMatchingAttributes('123\\.cba'):equals('hell.o')}", attributes, true);
verifyEquals("${allMatchingAttributes('(abc|xyz)'):matches('\\d+')}", attributes, true);
verifyEquals("${allMatchingAttributes('[ax].*'):toNumber():lt(99999)}", attributes, true);
verifyEquals("${allMatchingAttributes('hell.'):length():gt(3)}", attributes, true);
verifyEquals("${allMatchingAttributes('123\\.cba'):equals('no')}", attributes, false);
}
@Test
public void testMatches() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "1234xyz4321");
attributes.put("end", "xyz");
attributes.put("xyz", "4132");
attributes.put("hello", "world!");
attributes.put("dotted", "abc.xyz");
final String evaluated = Query.evaluateExpressions("${abc:matches('1234${end}4321')}", attributes, null);
assertEquals("true", evaluated);
attributes.put("end", "888");
final String secondEvaluation = Query.evaluateExpressions("${abc:matches('1234${end}4321')}", attributes, null);
assertEquals("false", secondEvaluation);
verifyEquals("${dotted:matches('abc\\.xyz')}", attributes, true);
// Test for matches(null)
assertEquals("false", Query.evaluateExpressions("${abc:matches(${not.here})}", attributes, null));
}
@Test
public void testFind() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "1234xyz4321");
attributes.put("end", "xyz");
attributes.put("xyz", "4132");
attributes.put("hello", "world!");
attributes.put("dotted", "abc.xyz");
final String evaluated = Query.evaluateExpressions("${abc:find('1234${end}4321')}", attributes, null);
assertEquals("true", evaluated);
attributes.put("end", "888");
final String secondEvaluation = Query.evaluateExpressions("${abc:find('${end}4321')}", attributes, null);
assertEquals("false", secondEvaluation);
verifyEquals("${dotted:find('\\.')}", attributes, true);
// Test for find(null)
assertEquals("false", Query.evaluateExpressions("${abc:find(${not.here})}", attributes, null));
}
@Test
public void testSubstringAfter() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename", "file-255");
verifyEquals("${filename:substringAfter('')}", attributes, "file-255");
verifyEquals("${filename:substringAfterLast('')}", attributes, "file-255");
verifyEquals("${filename:substringBefore('')}", attributes, "file-255");
verifyEquals("${filename:substringBeforeLast('')}", attributes, "file-255");
verifyEquals("${filename:substringBefore('file')}", attributes, "");
attributes.put("uri", "sftp://some.uri");
verifyEquals("${uri:substringAfter('sftp')}", attributes, "://some.uri");
}
@Test
public void testSubstringAfterLast() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename", "file-file-255");
verifyEquals("${filename:substringAfterLast('file-')}", attributes, "255");
verifyEquals("${filename:substringAfterLast('5')}", attributes, "");
verifyEquals("${filename:substringAfterLast('x')}", attributes, "file-file-255");
}
@Test
public void testSubstringBefore() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("something", "some {} or other");
verifyEquals("${something:substringBefore('}')}", attributes, "some {");
}
@Test
public void testSubstring() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename", "file-255");
verifyEquals("${filename:substring(1, 2)}", attributes, "i");
verifyEquals("${filename:substring(4)}", attributes, "-255");
}
@Test
public void testSubstringOOB() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename", "file-255");
verifyEquals("${filename:substring(10, 20)}", attributes, "");
verifyEquals("${filename:substring(10)}", attributes, "");
verifyEquals("${filename:substring(-2)}", attributes, "");
verifyEquals("${filename:substring(2, -2)}", attributes, "");
}
@Test
public void testToRadix() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename", "file-255");
attributes.put("filename2", "file-99999");
verifyEquals("${filename:substringAfter('-'):toNumber():toRadix(16):toUpper()}", attributes, "FF");
verifyEquals("${filename:substringAfter('-'):toNumber():toRadix(16, 4):toUpper()}", attributes, "00FF");
verifyEquals("${filename:substringAfter('-'):toNumber():toRadix(36, 3):toUpper()}", attributes, "073");
}
@Test
public void testFromRadix() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("test1", "ABCDEF");
attributes.put("test2", "123");
verifyEquals("${test1:fromRadix(16)}", attributes, 0xABCDEFL);
verifyEquals("${test2:fromRadix(4)}", attributes, 27L);
}
@Test
public void testBase64Encode(){
final Map<String, String> attributes = new HashMap<>();
attributes.put("userpass", "admin:admin");
verifyEquals("${userpass:base64Encode()}", attributes, "YWRtaW46YWRtaW4=");
}
@Test
public void testBase64Decode(){
final Map<String, String> attributes = new HashMap<>();
attributes.put("userpassbase64", "YWRtaW46YWRtaW4=");
verifyEquals("${userpassbase64:base64Decode()}", attributes, "admin:admin");
}
@Test
public void testDateFormatConversion() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("blue", "20130917162643");
verifyEquals("${blue:toDate('yyyyMMddHHmmss'):format(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\")}", attributes, "2013/09/17 16:26:43.000Z");
}
@Test
public void testDateFormatConversionWithTimeZone() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("blue", "20130917162643");
verifyEquals("${blue:toDate('yyyyMMddHHmmss', 'GMT'):format(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'GMT')}", attributes, "2013/09/17 16:26:43.000Z");
verifyEquals("${blue:toDate('yyyyMMddHHmmss', 'GMT'):format(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'Europe/Paris')}", attributes, "2013/09/17 18:26:43.000Z");
verifyEquals("${blue:toDate('yyyyMMddHHmmss', 'GMT'):format(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'America/Los_Angeles')}", attributes, "2013/09/17 09:26:43.000Z");
}
@Test
public void testNot() {
verifyEquals("${ab:notNull():not()}", new HashMap<String, String>(), true);
}
@Test
public void testAttributesWithSpaces() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("ab", "abc");
attributes.put("a b", "abc");
verifyEquals("${ab}", attributes, "abc");
verifyEquals("${'a b'}", attributes, "abc");
verifyEquals("${'a b':replaceNull('')}", attributes, "");
}
@Test
public void testOr() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename1", "xabc");
attributes.put("filename2", "yabc");
attributes.put("filename3", "abcxy");
verifyEquals("${filename1:startsWith('x'):or(true)}", attributes, true);
verifyEquals("${filename1:startsWith('x'):or( ${filename1:startsWith('y')} )}", attributes, true);
verifyEquals("${filename2:startsWith('x'):or( ${filename2:startsWith('y')} )}", attributes, true);
verifyEquals("${filename3:startsWith('x'):or( ${filename3:startsWith('y')} )}", attributes, false);
verifyEquals("${filename1:startsWith('x'):or( ${filename2:startsWith('y')} )}", attributes, true);
verifyEquals("${filename2:startsWith('x'):or( ${filename1:startsWith('y')} )}", attributes, false);
}
@Test
public void testAnd() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename1", "xabc");
attributes.put("filename2", "yabc");
attributes.put("filename 3", "abcxy");
verifyEquals("${filename1:startsWith('x'):and(true)}", attributes, true);
verifyEquals("${filename1:startsWith('x') : and( false )}", attributes, false);
verifyEquals("${filename1:startsWith('x'):and( ${filename1:startsWith('y')} )}", attributes, false);
verifyEquals("${filename2:startsWith('x'):and( ${filename2:startsWith('y')} )}", attributes, false);
verifyEquals("${filename3:startsWith('x'):and( ${filename3:startsWith('y')} )}", attributes, false);
verifyEquals("${filename1:startsWith('x'):and( ${filename2:startsWith('y')} )}", attributes, true);
verifyEquals("${filename2:startsWith('x'):and( ${filename1:startsWith('y')} )}", attributes, false);
verifyEquals("${filename1:startsWith('x'):and( ${'filename 3':endsWith('y')} )}", attributes, true);
}
@Test
public void testAndOrNot() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename1", "xabc");
attributes.put("filename2", "yabc");
attributes.put("filename 3", "abcxy");
final String query
= "${"
+ " 'non-existing':notNull():not():and("
+ // true AND (
" ${filename1:startsWith('y')"
+ // false
" :or("
+ // or
" ${ filename1:startsWith('x'):and(false) }"
+ // false
" ):or("
+ // or
" ${ filename2:endsWith('xxxx'):or( ${'filename 3':length():gt(1)} ) }"
+ // true )
" )}"
+ " )"
+ "}";
System.out.println(query);
verifyEquals(query, attributes, true);
}
@Test
public void testAndOrLogicWithAnyAll() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("filename1", "xabc");
attributes.put("filename2", "yabc");
attributes.put("filename 3", "abcxy");
verifyEquals("${anyMatchingAttribute('filename.*'):contains('abc'):and( ${filename2:equals('yabc')} )}", attributes, true);
verifyEquals("${anyMatchingAttribute('filename.*'):contains('abc'):and( ${filename2:equals('xabc')} )}", attributes, false);
verifyEquals("${anyMatchingAttribute('filename.*'):contains('abc'):not():or( ${filename2:equals('yabc')} )}", attributes, true);
verifyEquals("${anyMatchingAttribute('filename.*'):contains('abc'):not():or( ${filename2:equals('xabc')} )}", attributes, false);
}
@Test
public void testKeywords() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("UUID", "123");
verifyEquals("${ 'UUID':toNumber():equals(123) }", attributes, true);
}
@Test
public void testEqualsNumber() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "123");
verifyEquals("${ abc:toNumber():equals(123) }", attributes, true);
}
@Test
public void testIn() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("myEnum", "JOHN");
verifyEquals("${ myEnum:in('PAUL', 'JOHN', 'MIKE') }", attributes, true);
verifyEquals("${ myEnum:in('RED', 'BLUE', 'GREEN') }", attributes, false);
attributes.put("toReplace", "BLUE");
verifyEquals("${ myEnum:in('RED', ${ toReplace:replace('BLUE', 'JOHN') }, 'GREEN') }", attributes, true);
}
@Test
public void testSubjectAsEmbeddedExpressionWithSurroundChars() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("b", "x");
attributes.put("abcxcba", "hello");
final String evaluated = Query.evaluateExpressions("${ 'abc${b}cba':substring(0, 1) }", attributes, null);
assertEquals("h", evaluated);
}
@Test
public void testToNumberFunctionReturnsNumberType() {
assertEquals(ResultType.WHOLE_NUMBER, Query.getResultType("${header.size:toNumber()}"));
}
@Test
public void testRandomFunctionReturnsNumberType() {
assertEquals(ResultType.WHOLE_NUMBER, Query.getResultType("${random()}"));
}
@Test
public void testAnyAttributeEmbedded() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("a1", "test1");
attributes.put("b2", "2test");
attributes.put("c3", "3test3");
final String query = "${a1:equals('test1'):and( ${anyAttribute('a1','b2','c3'):contains('2')})}";
verifyEquals(query, attributes, true);
}
@Test
public void testEvaluateWithinCurlyBraces() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "xyz");
final String query = "{ ${abc} }";
final List<String> expressions = Query.extractExpressions(query);
assertEquals(1, expressions.size());
assertEquals("${abc}", expressions.get(0));
assertEquals("{ xyz }", Query.evaluateExpressions(query, attributes, ParameterLookup.EMPTY));
}
@Test
public void testGetStateValue() {
final Map<String, String> stateValues = new HashMap<>();
stateValues.put("abc", "xyz");
stateValues.put("123", "qwe");
stateValues.put("true", "asd");
stateValues.put("iop", "098");
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "iop");
attributes.put("4321", "123");
attributes.put("false", "bnm");
String query = "${getStateValue('abc')}";
verifyEquals(query, attributes, stateValues, "xyz");
query = "${getStateValue(${'4321':toString()})}";
verifyEquals(query, attributes, stateValues, "qwe");
query = "${getStateValue(${literal(true):toString()})}";
verifyEquals(query, attributes, stateValues, "asd");
query = "${getStateValue(${abc}):equals('098')}";
verifyEquals(query, attributes, stateValues, true);
}
@Test
public void testLiteralFunction() {
final Map<String, String> attrs = Collections.<String, String>emptyMap();
verifyEquals("${literal(2):gt(1)}", attrs, true);
verifyEquals("${literal('hello'):substring(0, 1):equals('h')}", attrs, true);
}
@Test
public void testRandomFunction() {
final Map<String, String> attrs = Collections.<String, String>emptyMap();
final Long negOne = Long.valueOf(-1L);
final HashSet<Long> results = new HashSet<>(100);
for (int i = 0; i < results.size(); i++) {
long result = (Long) getResult("${random()}", attrs).getValue();
assertThat("random", result, greaterThan(negOne));
assertEquals("duplicate random", true, results.add(result));
}
}
QueryResult<?> getResult(String expr, Map<String, String> attrs) {
final Query query = Query.compile(expr);
final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attrs));
return result;
}
@Test
public void testFunctionAfterReduce() {
// Cannot call gt(2) after count() because count() is a 'reducing function'
// and must be the last function in an expression.
assertFalse(Query.isValidExpression("${allMatchingAttributes('a.*'):contains('2'):count():gt(2)}"));
final Map<String, String> attributes = new HashMap<>();
attributes.put("a.1", "245");
attributes.put("a.2", "123");
attributes.put("a.3", "732");
attributes.put("a.4", "343");
attributes.put("a.5", "553");
final String endsWithCount = "${allMatchingAttributes('a.*'):contains('2'):count()}";
assertTrue(Query.isValidExpression(endsWithCount));
verifyEquals(endsWithCount, attributes, 3L);
// in order to check if value is greater than 2, need to first evaluate the
// 'aggregate' and 'reducing' functions as an inner expression. Then we can
// use the literal() function to make the result of the inner expression the subject
// of the function gt()
final String usingLiteral = "${literal(" + endsWithCount + "):gt(2)}";
assertTrue(Query.isValidExpression(usingLiteral));
verifyEquals(usingLiteral, attributes, true);
attributes.clear();
attributes.put("a1", "123");
attributes.put("a2", "321");
verifyEquals("${allMatchingAttributes('a.*'):contains('2')}", attributes, true);
verifyEquals("${allMatchingAttributes('a.*'):contains('2'):toUpper():equals('TRUE')}", attributes, true);
verifyEquals("${allMatchingAttributes('a.*'):contains('2'):equals('true'):and( ${literal(true)} )}", attributes, true);
}
@Test
public void testGetDelimitedField() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("line", "Name, Age, Title");
// Test "simple" case - comma separated with no quoted or escaped text
verifyEquals("${line:getDelimitedField(1)}", attributes, "Name");
verifyEquals("${line:getDelimitedField(1, ',')}", attributes, "Name");
verifyEquals("${line:getDelimitedField(1, ',', '\"')}", attributes, "Name");
verifyEquals("${line:getDelimitedField(1, ',', '\"', '\\\\')}", attributes, "Name");
verifyEquals("${line:getDelimitedField(2)}", attributes, " Age");
verifyEquals("${line:getDelimitedField(2, ',')}", attributes, " Age");
verifyEquals("${line:getDelimitedField(2, ',', '\"')}", attributes, " Age");
verifyEquals("${line:getDelimitedField(2, ',', '\"', '\\\\')}", attributes, " Age");
verifyEquals("${line:getDelimitedField(3)}", attributes, " Title");
verifyEquals("${line:getDelimitedField(3, ',')}", attributes, " Title");
verifyEquals("${line:getDelimitedField(3, ',', '\"')}", attributes, " Title");
verifyEquals("${line:getDelimitedField(3, ',', '\"', '\\\\')}", attributes, " Title");
// test with a space in column
attributes.put("line", "First Name, Age, Title");
verifyEquals("${line:getDelimitedField(1)}", attributes, "First Name");
verifyEquals("${line:getDelimitedField(1, ',')}", attributes, "First Name");
verifyEquals("${line:getDelimitedField(1, ',', '\"')}", attributes, "First Name");
verifyEquals("${line:getDelimitedField(1, ',', '\"', '\\\\')}", attributes, "First Name");
// test quoted value
attributes.put("line", "\"Name (Last, First)\", Age, Title");
verifyEquals("${line:getDelimitedField(1)}", attributes, "\"Name (Last, First)\"");
verifyEquals("${line:getDelimitedField(1, ',')}", attributes, "\"Name (Last, First)\"");
verifyEquals("${line:getDelimitedField(1, ',', '\"')}", attributes, "\"Name (Last, First)\"");
verifyEquals("${line:getDelimitedField(1, ',', '\"', '\\\\')}", attributes, "\"Name (Last, First)\"");
// test non-standard quote char
attributes.put("line", "_Name (Last, First)_, Age, Title");
verifyEquals("${line:getDelimitedField(1)}", attributes, "_Name (Last");
verifyEquals("${line:getDelimitedField(1, ',', '_')}", attributes, "_Name (Last, First)_");
// test escape char
attributes.put("line", "Name (Last\\, First), Age, Title");
verifyEquals("${line:getDelimitedField(1)}", attributes, "Name (Last\\, First)");
attributes.put("line", "Name (Last__, First), Age, Title");
verifyEquals("${line:getDelimitedField(1, ',', '\"', '_')}", attributes, "Name (Last__");
attributes.put("line", "Name (Last_, First), Age, Title");
verifyEquals("${line:getDelimitedField(1, ',', '\"', '_')}", attributes, "Name (Last_, First)");
// test escape for enclosing chars
attributes.put("line", "\\\"Name (Last, First), Age, Title");
verifyEquals("${line:getDelimitedField(1)}", attributes, "\\\"Name (Last");
// get non existing field
attributes.put("line", "Name, Age, Title");
verifyEquals("${line:getDelimitedField(12)}", attributes, "");
// test escape char within quotes
attributes.put("line", "col 1, col 2, \"The First, Second, and \\\"Last\\\" Column\", Last");
verifyEquals("${line:getDelimitedField(3):trim()}", attributes, "\"The First, Second, and \\\"Last\\\" Column\"");
// test stripping chars
attributes.put("line", "col 1, col 2, \"The First, Second, and \\\"Last\\\" Column\", Last");
verifyEquals("${line:getDelimitedField(3, ',', '\"', '\\\\', true):trim()}", attributes, "The First, Second, and \"Last\" Column");
attributes.put("line", "\"Jacobson, John\", 32, Mr.");
verifyEquals("${line:getDelimitedField(2)}", attributes, " 32");
}
@Test
public void testEscapeFunctions() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("string", "making air \"QUOTES\".");
verifyEquals("${string:escapeJson()}", attributes, "making air \\\"QUOTES\\\".");
attributes.put("string", "M & M");
verifyEquals("${string:escapeXml()}", attributes, "M &amp; M");
attributes.put("string", "making air \"QUOTES\".");
verifyEquals("${string:escapeCsv()}", attributes, "\"making air \"\"QUOTES\"\".\"");
attributes.put("string", "special ¡");
verifyEquals("${string:escapeHtml3()}", attributes, "special &iexcl;");
attributes.put("string", "special ♣");
verifyEquals("${string:escapeHtml4()}", attributes, "special &clubs;");
}
@Test
public void testUnescapeFunctions() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("string", "making air \\\"QUOTES\\\".");
verifyEquals("${string:unescapeJson()}", attributes, "making air \"QUOTES\".");
attributes.put("string", "M &amp; M");
verifyEquals("${string:unescapeXml()}", attributes, "M & M");
attributes.put("string", "\"making air \"\"QUOTES\"\".\"");
verifyEquals("${string:unescapeCsv()}", attributes, "making air \"QUOTES\".");
attributes.put("string", "special &iexcl;");
verifyEquals("${string:unescapeHtml3()}", attributes, "special ¡");
attributes.put("string", "special &clubs;");
verifyEquals("${string:unescapeHtml4()}", attributes, "special ♣");
}
@Test
public void testIfElse() {
final Map<String, String> attributes = new HashMap<>();
verifyEquals("${attr:isNull():ifElse('a', 'b')}", attributes, "a");
verifyEquals("${attr:ifElse('a', 'b')}", attributes, "b");
attributes.put("attr", "hello");
verifyEquals("${attr:isNull():ifElse('a', 'b')}", attributes, "b");
verifyEquals("${attr:ifElse('a', 'b')}", attributes, "b");
attributes.put("attr", "true");
verifyEquals("${attr:ifElse('a', 'b')}", attributes, "a");
verifyEquals("${attr2:isNull():ifElse('a', 'b')}", attributes, "a");
verifyEquals("${literal(true):ifElse('a', 'b')}", attributes, "a");
verifyEquals("${literal(true):ifElse(false, 'b')}", attributes, "false");
}
@Test
public void testHash() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("str_attr", "string value");
attributes.put("nbr_attr", "10");
verifyEquals("${literal('john'):hash('MD5')}", attributes, "527bd5b5d689e2c32ae974c6229ff785");
verifyEquals("${str_attr:hash('MD5')}", attributes, "64e58419496c7248b4ef25731f88b8c3");
verifyEquals("${str_attr:hash('SHA-1')}", attributes, "34990db823e7bb2b47278a7fbf08c62d9e8e4307");
verifyEquals("${str_attr:hash('SHA-256')}", attributes, "9b6a1a9167a5caf3f5948413faa89e0ec0de89e12bef55327442e60dcc0e8c9b");
verifyEquals("${nbr_attr:toNumber():hash('MD5')}", attributes, "d3d9446802a44259755d38e6d163e820");
verifyEquals("${nbr_attr:hash('MD5')}", attributes, "d3d9446802a44259755d38e6d163e820");
}
@Test(expected = AttributeExpressionLanguageException.class)
public void testHashFailure() {
final Map<String, String> attributes = new HashMap<>();
verifyEquals("${literal('john'):hash('NOT_A_ALGO')}", attributes, "527bd5b5d689e2c32ae974c6229ff785");
}
@Test
public void testThread() {
final Map<String, String> attributes = new HashMap<>();
verifyEquals("${thread()}", attributes, "main");
}
@Test
public void testPadLeft() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
attributes.put("emptyString", "");
attributes.put("nullString", null);
verifyEquals("${attr:padLeft(10, '@')}", attributes, "@@@@@hello");
verifyEquals("${attr:padLeft(10)}", attributes, "_____hello");
verifyEquals("${attr:padLeft(10, \"xy\")}", attributes, "xyxyxhello");
verifyEquals("${attr:padLeft(10, \"aVeryLongPaddingString\")}", attributes, "aVeryhello");
verifyEquals("${attr:padLeft(1, \"a\")}", attributes, "hello");
verifyEquals("${attr:padLeft(-10, \"a\")}", attributes, "hello");
verifyEquals("${emptyString:padLeft(10, '@')}", attributes, "@@@@@@@@@@");
verifyEquals("${attr:padLeft(9999999999, \"abc\")}", attributes, "hello");
verifyEmpty("${nonExistingAttr:padLeft(10, \"abc\")}", attributes);
verifyEmpty("${nullString:padLeft(10, \"@\")}", attributes);
}
@Test
public void testPadRight() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
attributes.put("emptyString", "");
attributes.put("nullString", null);
verifyEquals("${attr:padRight(10, '@')}", attributes, "hello@@@@@");
verifyEquals("${attr:padRight(10)}", attributes, "hello_____");
verifyEquals("${attr:padRight(10, \"xy\")}", attributes, "helloxyxyx");
verifyEquals("${attr:padRight(10, \"aVeryLongPaddingString\")}", attributes, "helloaVery");
verifyEquals("${attr:padRight(1, \"a\")}", attributes, "hello");
verifyEquals("${attr:padRight(-10, \"a\")}", attributes, "hello");
verifyEquals("${emptyString:padRight(10, '@')}", attributes, "@@@@@@@@@@");
verifyEquals("${attr:padRight(9999999999, \"abc\")}", attributes, "hello");
verifyEmpty("${nonExistingAttr:padRight(10, \"abc\")}", attributes);
verifyEmpty("${nullString:padRight(10, \"@\")}", attributes);
}
@Test
public void testRepeat() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("str", "abc");
verifyEquals("${not_exist:repeat(1, 2)}", attributes, "");
verifyEquals("${str:repeat(1, 1)}", attributes, "abc");
// Custom verify because the result could be one of multiple options
String multipleResultExpression = "${str:repeat(1, 3)}";
String multipleResultExpectedResult1 = "abc";
String multipleResultExpectedResult2 = "abcabc";
String multipleResultExpectedResult3 = "abcabcabc";
List<String> multipleResultExpectedResults = Arrays.asList(multipleResultExpectedResult1, multipleResultExpectedResult2, multipleResultExpectedResult3);
Query.validateExpression(multipleResultExpression, false);
final String actualResult = Query.evaluateExpressions(multipleResultExpression, attributes, null, null, ParameterLookup.EMPTY);
assertTrue(multipleResultExpectedResults.contains(actualResult));
verifyEquals("${str:repeat(4)}", attributes, "abcabcabcabc");
try {
verifyEquals("${str:repeat(-1)}", attributes, "");
fail("Should have failed on numRepeats < 0");
} catch(AttributeExpressionLanguageException aele) {
// Do nothing, it is expected
}
try {
verifyEquals("${str:repeat(0)}", attributes, "");
fail("Should have failed on numRepeats = 0");
} catch(AttributeExpressionLanguageException aele) {
// Do nothing, it is expected
}
try {
verifyEquals("${str:repeat(2,1)}", attributes, "");
fail("Should have failed on minRepeats > maxRepeats");
} catch(AttributeExpressionLanguageException aele) {
// Do nothing, it is expected
}
}
@Test
public void testUuidsWithNamespace() {
// Testing a lot of cases here b/c it's a custom UUID3/5 implementation
final Map<String, String> attributes = new HashMap<>();
attributes.put("myattr0", "u5IkOYFFvYioYBJSNI2XNjPaVoRjXYnr");
attributes.put("myattr1", "mSDgSKQrY67QCTPatV5qHrZa4oUQ2wEX");
attributes.put("myattr2", "u6jH7pF8iAqwjr42i3r5DubdNcgwqEaX");
attributes.put("myattr3", "9eDG1KbqvHrtIMSmvH44t0K7fHXs7xtz");
attributes.put("myattr4", "QeAUDsMYoHJHLsy1BPPSmQWKhCKvwEpj");
attributes.put("myattr5", "U5Cw4b79SW1YiB5Va3DfUMI9y4iJwnVS");
attributes.put("myattr6", "Ig51Jl3EtwaKlVo9MnDSDdJSlXMgZ1It");
attributes.put("myattr7", "F2iLLLHXgliEpIDwJ4JcqeWBVi70cHS6");
attributes.put("myattr8", "1BFShkKLOcjwn1GMsyO4Fmb0iNTVt2Tf");
attributes.put("myattr9", "WxiyO8Gzw0jQnBlYeZMcdNTwCWJe5MNg");
attributes.put("myattr10", null);
// Version 3s
verifyEquals("${myattr0:UUID3('b9e81de3-7047-4b5e-a822-8fff5b49f808')}", attributes, "7ab88cc4-7748-3214-812a-1bc4500a911a");
verifyEquals("${myattr1:UUID3('341857cc-c5f3-4f76-b336-169f81e9dc7a')}", attributes, "d788c2df-95e1-33aa-a548-9be42f222909");
verifyEquals("${myattr2:UUID3('27e35966-52c9-48ba-bc91-3894a2f164d8')}", attributes, "e960f7af-5eec-3298-8512-e3933836bd5a");
verifyEquals("${myattr3:UUID3('1aef683a-2c0b-4f0e-9287-792361873e8f')}", attributes, "bf1727d8-93d3-3550-9071-78c8686f30c3");
verifyEquals("${myattr4:UUID3('5f15efac-e274-42b1-8d0f-15c2c97acb7d')}", attributes, "9e68a780-090d-30a9-903c-22cf7eb5c511");
verifyEquals("${myattr5:UUID3('ebd71811-fd78-4929-856b-4cec7a38d666')}", attributes, "a2a4b1b5-d93f-3656-be0d-f1db281060c1");
verifyEquals("${myattr6:UUID3('7b1bce89-f12b-4b56-afb8-f9b0a1334926')}", attributes, "8eea2153-d42e-3f63-892c-33ff7f0be389");
verifyEquals("${myattr7:UUID3('fe085c56-95e2-4cf8-8612-ba878ed35f0b')}", attributes, "3cd6470f-5432-3599-82ce-1f2b22adcec6");
verifyEquals("${myattr8:UUID3('2be146a5-f54e-4ca1-a10d-d219e9fc6c6f')}", attributes, "c3ed8ced-b32f-39da-a7ea-75934f419446");
verifyEquals("${myattr9:UUID3('4939d5dd-51c1-4d0e-badd-77fa7c7eebc1')}", attributes, "6507198b-f565-3196-9123-6f946f8c53bc");
verifyEmpty("${myattr10:UUID3('4939d5dd-51c1-4d0e-badd-77fa7c7eebc1')}", attributes);
verifyEmpty("${myattr11:UUID3('4939d5dd-51c1-4d0e-badd-77fa7c7eebc1')}", attributes);
verifyEquals("${myattr9:UUID3(${myattr11})}", attributes, "f2d25da2-cc06-34de-80a3-cf64aff82020");
// Version 5s
verifyEquals("${myattr0:UUID5('245b55a8-397d-4480-a41e-16603c8cf9ad')}", attributes, "74f6dc12-6d84-500c-9583-e9fed79912ea");
verifyEquals("${myattr1:UUID5('45089bfa-f5eb-40e3-bc02-4270ccb8ef34')}", attributes, "7b197702-0ed0-5494-9f61-417e26010308");
verifyEquals("${myattr2:UUID5('49861367-c791-4d6d-987e-fe994b2ee4b7')}", attributes, "7b38b455-a0d6-53bd-a0fa-d0f3bf4e7399");
verifyEquals("${myattr3:UUID5('1142b2d9-e434-4931-b1a5-6dbf363aa9cf')}", attributes, "cd13422e-b030-547b-807b-a868e9282eab");
verifyEquals("${myattr4:UUID5('967190d3-b4ba-4ef3-a8e6-3b8bf2d3f1d8')}", attributes, "e4f1ef89-0d25-55cd-bc4b-1904813c3137");
verifyEquals("${myattr5:UUID5('2942f01d-82df-40ee-b1fd-476542160b7c')}", attributes, "a0415b30-5ef9-5530-93a2-fd20f4262d68");
verifyEquals("${myattr6:UUID5('3a47c04b-7cea-4c95-a379-018e64c701c5')}", attributes, "e1931aad-30e8-5283-8505-394f0d08b181");
verifyEquals("${myattr7:UUID5('6f78ce33-4186-46c0-ae05-15f8c78024cf')}", attributes, "a5c80e26-88d6-5de9-9234-66a050f4d940");
verifyEquals("${myattr8:UUID5('b85962a8-6614-49f4-8fdd-a984cf35144e')}", attributes, "6e33ce29-d3f0-59ee-a864-c05ec4d4300d");
verifyEquals("${myattr9:UUID5('5b6da974-4eca-4c17-bbd2-3b59a1b40bee')}", attributes, "2e2e846c-1cbc-54b2-96f7-5a66d246126f");
verifyEmpty("${myattr10:UUID5('4939d5dd-51c1-4d0e-badd-77fa7c7eebc1')}", attributes);
verifyEmpty("${myattr11:UUID5('4939d5dd-51c1-4d0e-badd-77fa7c7eebc1')}", attributes);
verifyEquals("${myattr9:UUID5(${myattr11})}", attributes, "0231a7bf-7bbe-5a0c-8bbd-9c7bc2e95071");
// Make sure it works using the UUID() expression for the namespace
verifyEquals("${myattr0:UUID3(${UUID()}):length()}", attributes, 36L);
verifyEquals("${myattr0:UUID5(${UUID()}):length()}", attributes, 36L);
}
private void verifyEquals(final String expression, final Map<String, String> attributes, final Object expectedResult) {
verifyEquals(expression,attributes, null, ParameterLookup.EMPTY, expectedResult);
}
private void verifyEquals(final String expression, final Map<String, String> attributes, final Map<String, String> stateValues, final Object expectedResult) {
verifyEquals(expression, attributes, stateValues, ParameterLookup.EMPTY, expectedResult);
}
private void verifyEquals(final String expression, final Map<String, String> attributes, final Map<String, String> stateValues, final Map<String, String> parameters,
final Object expectedResult) {
verifyEquals(expression, attributes, stateValues, new MapParameterLookup(parameters), expectedResult);
}
private void verifyEquals(final String expression, final Map<String, String> attributes, final Map<String, String> stateValues, final ParameterLookup parameterLookup,
final Object expectedResult) {
Query.validateExpression(expression, false);
assertEquals(String.valueOf(expectedResult), Query.evaluateExpressions(expression, attributes, null, stateValues, parameterLookup));
final Query query = Query.compile(expression);
final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes, stateValues, parameterLookup));
if (expectedResult instanceof Long) {
if (ResultType.NUMBER.equals(result.getResultType())) {
final Number resultNumber = ((NumberQueryResult) result).getValue();
assertTrue(resultNumber instanceof Long);
} else {
assertEquals(ResultType.WHOLE_NUMBER, result.getResultType());
}
} else if(expectedResult instanceof Double) {
if (ResultType.NUMBER.equals(result.getResultType())) {
final Number resultNumber = ((NumberQueryResult) result).getValue();
assertTrue(resultNumber instanceof Double);
} else {
assertEquals(ResultType.DECIMAL, result.getResultType());
}
} else if (expectedResult instanceof Boolean) {
assertEquals(ResultType.BOOLEAN, result.getResultType());
} else {
assertEquals(ResultType.STRING, result.getResultType());
}
assertEquals(expectedResult, result.getValue());
}
private void verifyEmpty(final String expression, final Map<String, String> attributes) {
Query.validateExpression(expression, false);
assertEquals(String.valueOf(""), Query.evaluateExpressions(expression, attributes, null));
}
private String getResourceAsString(String resourceName) throws IOException {
try (final Reader reader = new InputStreamReader(new BufferedInputStream(getClass().getResourceAsStream(resourceName)))) {
int n = 0;
char[] buf = new char[1024];
StringBuilder sb = new StringBuilder();
while (n != -1) {
try {
n = reader.read(buf, 0, buf.length);
} catch (IOException e) {
throw new RuntimeException("failed to read resource", e);
}
if (n > 0) {
sb.append(buf, 0, n);
}
}
return sb.toString();
}
}
private static class MapParameterLookup implements ParameterLookup {
private final Map<String, String> parameters;
public MapParameterLookup(final Map<String, String> parameters) {
this.parameters = parameters;
}
@Override
public Optional<Parameter> getParameter(final String parameterName) {
final String value = parameters.get(parameterName);
if (value == null) {
return Optional.empty();
}
return Optional.of(new Parameter(new ParameterDescriptor.Builder().name(parameterName).build(), value));
}
@Override
public boolean isEmpty() {
return parameters.isEmpty();
}
@Override
public long getVersion() {
return 0;
}
}
}