blob: a582956f26f0d2abe01eae31bdeef50416b4e604 [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.hadoop.yarn.server.timelineservice.reader;
import java.util.Deque;
import java.util.LinkedList;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.classification.InterfaceStability.Unstable;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineCompareFilter;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineCompareOp;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineFilter;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineFilterList;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineFilterList.Operator;
/**
* Abstract class for parsing compare expressions.
* Compare expressions are of the form :
* (<key> <compareop> <value>) <op> (<key
* > <compareop> <value>)
* compareop is used to compare value of a the specified key in the backend
* storage. compareop can be :
* 1. eq - Equals
* 2. ne - Not equals (matches if key does not exist)
* 3. ene - Exists and not equals (key must exist for match to occur)
* 4. lt - Less than
* 5. gt - Greater than
* 6. le - Less than or equals
* 7. ge - Greater than or equals
* compareop's supported would depend on implementation. For instance, all
* the above compareops' will be supported for metric filters but only eq,ne and
* ene would be supported for KV filters like config/info filters.
*
* op is a logical operator and can be either AND or OR.
*
* The way values will be interpreted would also depend on implementation
*
* A typical compare expression would look as under:
* ((key1 eq val1 OR key2 ne val2) AND (key5 gt val45))
*/
@Private
@Unstable
abstract class TimelineParserForCompareExpr implements TimelineParser {
private enum ParseState {
PARSING_KEY,
PARSING_VALUE,
PARSING_OP,
PARSING_COMPAREOP
}
// Main expression.
private final String expr;
// Expression in lower case.
private final String exprInLowerCase;
private final String exprName;
private int offset = 0;
private int kvStartOffset = 0;
private final int exprLength;
private ParseState currentParseState = ParseState.PARSING_KEY;
// Linked list implemented as a stack.
private Deque<TimelineFilterList> filterListStack = new LinkedList<>();
private TimelineFilter currentFilter = null;
private TimelineFilterList filterList = null;
public TimelineParserForCompareExpr(String expression, String name) {
if (expression != null) {
expr = expression.trim();
exprLength = expr.length();
exprInLowerCase = expr.toLowerCase();
} else {
expr = null;
exprInLowerCase = null;
exprLength = 0;
}
this.exprName = name;
}
protected TimelineFilter getCurrentFilter() {
return currentFilter;
}
protected TimelineFilter getFilterList() {
return filterList;
}
protected abstract TimelineFilter createFilter();
protected abstract Object parseValue(String strValue)
throws TimelineParseException;
protected abstract void setCompareOpToCurrentFilter(
TimelineCompareOp compareOp, boolean keyMustExistFlag)
throws TimelineParseException;
protected abstract void setValueToCurrentFilter(Object value);
private void handleSpaceChar() throws TimelineParseException {
if (currentParseState == ParseState.PARSING_KEY ||
currentParseState == ParseState.PARSING_VALUE) {
if (kvStartOffset == offset) {
kvStartOffset++;
offset++;
return;
}
String str = expr.substring(kvStartOffset, offset);
if (currentParseState == ParseState.PARSING_KEY) {
if (currentFilter == null) {
currentFilter = createFilter();
}
((TimelineCompareFilter)currentFilter).setKey(str);
currentParseState = ParseState.PARSING_COMPAREOP;
} else if (currentParseState == ParseState.PARSING_VALUE) {
if (currentFilter != null) {
setValueToCurrentFilter(parseValue(str));
}
currentParseState = ParseState.PARSING_OP;
}
}
offset++;
}
private void handleOpeningBracketChar() throws TimelineParseException {
if (currentParseState != ParseState.PARSING_KEY) {
throw new TimelineParseException("Encountered unexpected opening " +
"bracket while parsing " + exprName + ".");
}
offset++;
kvStartOffset = offset;
filterListStack.push(filterList);
filterList = null;
}
private void handleClosingBracketChar() throws TimelineParseException {
if (currentParseState != ParseState.PARSING_VALUE &&
currentParseState != ParseState.PARSING_OP) {
throw new TimelineParseException("Encountered unexpected closing " +
"bracket while parsing " + exprName + ".");
}
if (!filterListStack.isEmpty()) {
if (currentParseState == ParseState.PARSING_VALUE) {
setValueToCurrentFilter(
parseValue(expr.substring(kvStartOffset, offset)));
currentParseState = ParseState.PARSING_OP;
}
if (currentFilter != null) {
filterList.addFilter(currentFilter);
}
// As bracket is closing, pop the filter list from top of the stack and
// combine it with current filter list.
TimelineFilterList fList = filterListStack.pop();
if (fList != null) {
fList.addFilter(filterList);
filterList = fList;
}
currentFilter = null;
offset++;
kvStartOffset = offset;
} else {
throw new TimelineParseException("Encountered unexpected closing " +
"bracket while parsing " + exprName + ".");
}
}
private void parseCompareOp() throws TimelineParseException {
if (offset + 2 >= exprLength) {
throw new TimelineParseException("Compare op cannot be parsed for " +
exprName + ".");
}
TimelineCompareOp compareOp = null;
boolean keyExistFlag = true;
if (expr.charAt(offset + 2) == TimelineParseConstants.SPACE_CHAR) {
if (exprInLowerCase.startsWith("eq", offset)) {
compareOp = TimelineCompareOp.EQUAL;
} else if (exprInLowerCase.startsWith("ne", offset)) {
compareOp = TimelineCompareOp.NOT_EQUAL;
keyExistFlag = false;
} else if (exprInLowerCase.startsWith("lt", offset)) {
compareOp = TimelineCompareOp.LESS_THAN;
} else if (exprInLowerCase.startsWith("le", offset)) {
compareOp = TimelineCompareOp.LESS_OR_EQUAL;
} else if (exprInLowerCase.startsWith("gt", offset)) {
compareOp = TimelineCompareOp.GREATER_THAN;
} else if (exprInLowerCase.startsWith("ge", offset)) {
compareOp = TimelineCompareOp.GREATER_OR_EQUAL;
}
offset = offset + 3;
} else if (exprInLowerCase.startsWith("ene ", offset)) {
// Not equal but key should be present.
compareOp = TimelineCompareOp.NOT_EQUAL;
offset = offset + 4;
}
if (compareOp == null) {
throw new TimelineParseException("Compare op cannot be parsed for " +
exprName + ".");
}
setCompareOpToCurrentFilter(compareOp, keyExistFlag);
kvStartOffset = offset;
currentParseState = ParseState.PARSING_VALUE;
}
private void parseOp(boolean closingBracket) throws TimelineParseException {
Operator operator = null;
if (exprInLowerCase.startsWith("or ", offset)) {
operator = Operator.OR;
offset = offset + 3;
} else if (exprInLowerCase.startsWith("and ", offset)) {
operator = Operator.AND;
offset = offset + 4;
}
if (operator == null) {
throw new TimelineParseException("Operator cannot be parsed for " +
exprName + ".");
}
if (filterList == null) {
filterList = new TimelineFilterList(operator);
}
if (currentFilter != null) {
filterList.addFilter(currentFilter);
}
if (closingBracket || filterList.getOperator() != operator) {
filterList = new TimelineFilterList(operator, filterList);
}
currentFilter = null;
kvStartOffset = offset;
currentParseState = ParseState.PARSING_KEY;
}
@Override
public TimelineFilterList parse() throws TimelineParseException {
if (expr == null || exprLength == 0) {
return null;
}
boolean closingBracket = false;
while (offset < exprLength) {
char offsetChar = expr.charAt(offset);
switch(offsetChar) {
case TimelineParseConstants.SPACE_CHAR:
handleSpaceChar();
break;
case TimelineParseConstants.OPENING_BRACKET_CHAR:
handleOpeningBracketChar();
break;
case TimelineParseConstants.CLOSING_BRACKET_CHAR:
handleClosingBracketChar();
closingBracket = true;
break;
default: // other characters.
// Parse based on state.
if (currentParseState == ParseState.PARSING_COMPAREOP) {
parseCompareOp();
} else if (currentParseState == ParseState.PARSING_OP) {
parseOp(closingBracket);
closingBracket = false;
} else {
// Might be a key or value. Move ahead.
offset++;
}
break;
}
}
if (!filterListStack.isEmpty()) {
filterListStack.clear();
throw new TimelineParseException("Encountered improper brackets while " +
"parsing " + exprName + ".");
}
if (currentParseState == ParseState.PARSING_VALUE) {
setValueToCurrentFilter(
parseValue(expr.substring(kvStartOffset, offset)));
}
if (filterList == null || filterList.getFilterList().isEmpty()) {
if (currentFilter == null) {
throw new TimelineParseException(
"Invalid expression provided for " + exprName);
} else {
filterList = new TimelineFilterList(currentFilter);
}
} else if (currentFilter != null) {
filterList.addFilter(currentFilter);
}
return filterList;
}
@Override
public void close() {
if (filterListStack != null) {
filterListStack.clear();
}
filterList = null;
currentFilter = null;
}
}