blob: 0ebd919691ba1bbd371632054970162f105a8fc7 [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.camel.language.simple;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.IsSingleton;
import org.apache.camel.Predicate;
import org.apache.camel.builder.ExpressionBuilder;
import org.apache.camel.builder.PredicateBuilder;
import org.apache.camel.builder.ValueBuilder;
import org.apache.camel.spi.Language;
import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.language.simple.SimpleLanguageOperator.*;
/**
* Abstract base class for Simple languages.
*/
public abstract class SimpleLanguageSupport implements Language, IsSingleton {
// this is a regex for a given group in a simple expression that uses operators
protected static final String OPERATOR_REGEX =
"\\$\\{(\\S+)\\}\\s+(==|>|>=|<|<=|!=|contains|not contains|regex|not regex|in|not in|is|not is|range|not range)\\s+('.*?'|\\S+)";
// this is the operator reg ex pattern used to match if a given expression is operator based or not
protected static final Pattern OPERATOR_PATTERN = Pattern.compile(OPERATOR_REGEX);
// two specialized pattern for matching/finding multiple expressions combined using multiple and|or operators
protected static final Pattern ANDOR_PATTERN = Pattern.compile("\\s+(and|or)\\s+" + OPERATOR_REGEX);
protected static final Pattern START_ANDOR_PATTERN = Pattern.compile("^" + OPERATOR_REGEX + "\\s+(and|or)\\s+.*$");
// this is special for the range operator where you define the range as from..to (where from and to are numbers)
protected static final Pattern RANGE_PATTERN = Pattern.compile("^(\\d+)(\\.\\.)(\\d+)$");
protected final Logger log = LoggerFactory.getLogger(getClass());
/**
* A holder class to hold an operator and the expression.
* <p/>
* This is used for expression with multiple expressions grouped using and/or operators
*/
private final class ExpressionGroup extends KeyValueHolder<SimpleLanguageOperator, Expression> {
public ExpressionGroup(SimpleLanguageOperator key, Expression value) {
super(key, value);
}
@Override
public String toString() {
return getKey() + " " + getValue();
}
}
public Predicate createPredicate(String expression) {
return PredicateBuilder.toPredicate(createExpression(expression));
}
public Expression createExpression(String expression) {
Matcher matcher = OPERATOR_PATTERN.matcher(expression);
Matcher startMatcher = START_ANDOR_PATTERN.matcher(expression);
if (matcher.matches() || startMatcher.matches()) {
log.debug("Expression is evaluated as simple (with operator) expression: {}", expression);
return createOperatorExpression(matcher, startMatcher, expression);
} else if (SimpleLanguage.hasStartToken(expression)) {
log.debug("Expression is evaluated as simple (strict) expression: {}", expression);
return createComplexConcatExpression(expression);
} else {
log.debug("Expression is evaluated as simple (non strict) expression: {}", expression);
return createSimpleExpression(expression, false);
}
}
private Expression createOperatorExpression(Matcher matcher, Matcher startMatcher, String expression) {
Expression answer = null;
if (startMatcher.matches()) {
answer = doCreateOperatorExpression(expression, startMatcher.group(1), startMatcher.group(2), startMatcher.group(3));
} else if (matcher.matches()) {
answer = doCreateOperatorExpression(expression, matcher.group(1), matcher.group(2), matcher.group(3));
}
// append any additional operators
answer = appendAdditionalOperatorExpressions(answer, expression);
return answer;
}
private Expression appendAdditionalOperatorExpressions(final Expression answer, final String expression) {
Matcher matcher = ANDOR_PATTERN.matcher(expression);
// now go through the and/or and append those sub expressions
final List<ExpressionGroup> expressions = new ArrayList<ExpressionGroup>();
while (matcher.find()) {
dumpMatcher(matcher);
// we only support AND/OR operator between expression groups
final SimpleLanguageOperator operator = asOperator(matcher.group(1));
if (operator != AND && operator != OR) {
throw new IllegalArgumentException("Syntax error in expression: " + expression
+ ". Expected operator as either and/or but was: " + operator);
}
final Expression exp = doCreateOperatorExpression(expression, matcher.group(2), matcher.group(3), matcher.group(4));
// add this group
expressions.add(new ExpressionGroup(operator, exp));
}
// return the expression that evaluates the entire expression with multiple groups
return new Expression() {
public <T> T evaluate(Exchange exchange, Class<T> type) {
boolean matches = PredicateBuilder.toPredicate(answer).matches(exchange);
for (ExpressionGroup group : expressions) {
boolean result = PredicateBuilder.toPredicate(group.getValue()).matches(exchange);
if (group.getKey() == AND) {
matches &= result;
} else {
matches |= result;
}
}
return exchange.getContext().getTypeConverter().convertTo(type, matches);
}
@Override
public String toString() {
StringBuilder msg = new StringBuilder(answer.toString());
for (ExpressionGroup group : expressions) {
msg.append(" ");
msg.append(group.getKey());
msg.append(" ");
msg.append(group.getValue());
}
return msg.toString();
}
};
}
private void dumpMatcher(Matcher matcher) {
if (log.isTraceEnabled()) {
log.trace("Matcher start: {}", matcher.start());
log.trace("Matcher end: {}", matcher.end());
log.trace("Matcher group: {}", matcher.group());
log.trace("Matcher group count: {}", matcher.groupCount());
for (int i = 0; i < matcher.groupCount() + 1; i++) {
String group = matcher.group(i);
log.trace("Matcher group #{}: {}", i, group);
}
}
}
private Expression doCreateOperatorExpression(final String expression, final String leftText,
final String operatorText, final String rightText) {
// left value is always a simple expression
final Expression left = createSimpleExpression(leftText, true);
final SimpleLanguageOperator operator = asOperator(operatorText);
// the right hand side expression can either be a constant expression with or without enclosing ' '
// or another simple expression using ${ } placeholders
final Expression right;
final Boolean isNull;
// special null handling
if ("null".equals(rightText) || "'null'".equals(rightText)) {
isNull = Boolean.TRUE;
right = createSimpleOrConstantExpression(null);
} else {
isNull = Boolean.FALSE;
right = createSimpleOrConstantExpression(rightText);
}
return new Expression() {
public <T> T evaluate(Exchange exchange, Class<T> type) {
Predicate predicate = null;
if (operator == EQ && isNull) {
// special for EQ null
predicate = PredicateBuilder.isNull(left);
} else if (operator == NOT && isNull) {
// special for not EQ null
predicate = PredicateBuilder.isNotNull(left);
} else if (operator == EQ) {
predicate = PredicateBuilder.isEqualTo(left, right);
} else if (operator == GT) {
predicate = PredicateBuilder.isGreaterThan(left, right);
} else if (operator == GTE) {
predicate = PredicateBuilder.isGreaterThanOrEqualTo(left, right);
} else if (operator == LT) {
predicate = PredicateBuilder.isLessThan(left, right);
} else if (operator == LTE) {
predicate = PredicateBuilder.isLessThanOrEqualTo(left, right);
} else if (operator == NOT) {
predicate = PredicateBuilder.isNotEqualTo(left, right);
} else if (operator == CONTAINS || operator == NOT_CONTAINS) {
predicate = PredicateBuilder.contains(left, right);
if (operator == NOT_CONTAINS) {
predicate = PredicateBuilder.not(predicate);
}
} else if (operator == REGEX || operator == NOT_REGEX) {
// reg ex should use String pattern, so we evaluate the right hand side as a String
predicate = PredicateBuilder.regex(left, right.evaluate(exchange, String.class));
if (operator == NOT_REGEX) {
predicate = PredicateBuilder.not(predicate);
}
} else if (operator == IN || operator == NOT_IN) {
// okay the in operator is a bit more complex as we need to build a list of values
// from the right handside expression.
// each element on the right handside must be separated by comma (default for create iterator)
Iterator<Object> it = ObjectHelper.createIterator(right.evaluate(exchange, Object.class));
List<Object> values = new ArrayList<Object>();
while (it.hasNext()) {
values.add(it.next());
}
// then reuse value builder to create the in predicate with the list of values
ValueBuilder vb = new ValueBuilder(left);
predicate = vb.in(values.toArray());
if (operator == NOT_IN) {
predicate = PredicateBuilder.not(predicate);
}
} else if (operator == IS || operator == NOT_IS) {
String name = right.evaluate(exchange, String.class);
if (name == null) {
throw new IllegalArgumentException("Syntax error in " + operatorText + " operator: " + expression
+ " cannot be null. It must be a class type.");
}
Class<?> rightType = exchange.getContext().getClassResolver().resolveClass(name);
if (rightType == null) {
throw new IllegalArgumentException("Syntax error in " + operatorText + " operator: " + expression
+ " cannot find class with name: " + name);
}
predicate = PredicateBuilder.isInstanceOf(left, rightType);
if (operator == NOT_IS) {
predicate = PredicateBuilder.not(predicate);
}
} else if (operator == RANGE || operator == NOT_RANGE) {
String range = right.evaluate(exchange, String.class);
Matcher matcher = RANGE_PATTERN.matcher(range);
if (matcher.matches()) {
// wrap as constant expression for the from and to values
Expression from = ExpressionBuilder.constantExpression(matcher.group(1));
Expression to = ExpressionBuilder.constantExpression(matcher.group(3));
// build a compound predicate for the range
predicate = PredicateBuilder.isGreaterThanOrEqualTo(left, from);
predicate = PredicateBuilder.and(predicate, PredicateBuilder.isLessThanOrEqualTo(left, to));
} else {
throw new IllegalArgumentException("Syntax error in " + operatorText + " operator: " + expression
+ " is not valid. Valid syntax:from..to(where from and to are numbers).");
}
if (operator == NOT_RANGE) {
predicate = PredicateBuilder.not(predicate);
}
}
if (predicate == null) {
throw new IllegalArgumentException("Unsupported operator: " + operatorText + " for expression: " + expression);
}
boolean matches = predicate.matches(exchange);
return exchange.getContext().getTypeConverter().convertTo(type, matches);
}
@Override
public String toString() {
return left + " " + operator + " " + right;
}
};
}
protected Expression createComplexConcatExpression(String expression) {
List<Expression> results = new ArrayList<Expression>();
int pivot = 0;
int size = expression.length();
while (pivot < size) {
// look for start tokens
int delta = 2;
int idx = expression.indexOf("${", pivot);
if (idx < 0) {
idx = expression.indexOf("$simple{", pivot);
delta = 8;
}
if (idx < 0) {
results.add(createConstantExpression(expression, pivot, size));
break;
} else {
if (pivot < idx) {
results.add(createConstantExpression(expression, pivot, idx));
}
pivot = idx + delta;
int endIdx = expression.indexOf('}', pivot);
if (endIdx < 0) {
throw new IllegalArgumentException("Expecting } but found end of string for simple expression: " + expression);
}
String simpleText = expression.substring(pivot, endIdx);
Expression simpleExpression = createSimpleExpression(simpleText, true);
results.add(simpleExpression);
pivot = endIdx + 1;
}
}
// only concat if there is more than one expression
if (results.size() > 1) {
return ExpressionBuilder.concatExpression(results, expression);
} else if (results.size() == 1) {
return results.get(0);
} else {
return null;
}
}
protected Expression createSimpleOrConstantExpression(String text) {
if (text != null) {
String simple = ObjectHelper.between(text, "${", "}");
if (simple == null) {
simple = ObjectHelper.between(text, "$simple{", "}");
}
if (simple != null) {
return createSimpleExpression(simple, true);
}
simple = ObjectHelper.between(text, "'", "'");
if (simple != null) {
return createConstantExpression(simple);
}
}
return createConstantExpression(text);
}
protected Expression createConstantExpression(String expression, int start, int end) {
return ExpressionBuilder.constantExpression(expression.substring(start, end));
}
protected Expression createConstantExpression(String expression) {
return ExpressionBuilder.constantExpression(expression);
}
/**
* Creates the simple expression based on the extracted content from the ${ } place holders
*
* @param expression the content between ${ and }
* @param strict whether it is strict mode or not, if strict it will throw a
* {@link org.apache.camel.ExpressionIllegalSyntaxException} if the expression was not known.
* Set to <tt>false</tt> to support constant expressions
* @return the expression
*/
protected abstract Expression createSimpleExpression(String expression, boolean strict);
protected String ifStartsWithReturnRemainder(String prefix, String text) {
if (text.startsWith(prefix)) {
String remainder = text.substring(prefix.length());
if (remainder.length() > 0) {
return remainder;
}
}
return null;
}
}