blob: 7c1112cedde3642e701a8e5465d99d7c3beee0f4 [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.ObjectHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 GROUP_PATTERN =
"\\$\\{(\\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 PATTERN = Pattern.compile("^(" + GROUP_PATTERN + ")(\\s+(and|or)\\s+(" + GROUP_PATTERN + "))?$");
// 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 Log log = LogFactory.getLog(getClass());
public Predicate createPredicate(String expression) {
return PredicateBuilder.toPredicate(createExpression(expression));
}
public Expression createExpression(String expression) {
Matcher matcher = PATTERN.matcher(expression);
if (matcher.matches()) {
if (log.isDebugEnabled()) {
log.debug("Expression is evaluated as simple expression with operator: " + expression);
}
return createOperatorExpression(matcher, expression);
} else if (expression.indexOf("${") >= 0) {
if (log.isDebugEnabled()) {
log.debug("Expression is evaluated as simple (strict) expression: " + expression);
}
return createComplexConcatExpression(expression);
} else {
if (log.isDebugEnabled()) {
log.debug("Expression is evaluated as simple (non strict) expression: " + expression);
}
return createSimpleExpression(expression, false);
}
}
private Expression createOperatorExpression(final Matcher matcher, final String expression) {
int groupCount = matcher.groupCount();
if (log.isTraceEnabled()) {
log.trace("Matcher expression: " + expression);
log.trace("Matcher group count: " + groupCount);
for (int i = 0; i < matcher.groupCount() + 1; i++) {
String group = matcher.group(i);
if (log.isTraceEnabled()) {
log.trace("Matcher group #" + i + ": " + group);
}
}
}
// a simple expression with operator can either be a single group or a dual group
String operatorText = matcher.group(6);
if (operatorText == null) {
// single group
return doCreateOperatorExpression(expression, matcher.group(2), matcher.group(3), matcher.group(4));
} else {
// dual group with an and/or operator between the two groups
final Expression first = doCreateOperatorExpression(expression, matcher.group(2), matcher.group(3), matcher.group(4));
final SimpleLanguageOperator operator = asOperator(operatorText);
final Expression last = doCreateOperatorExpression(expression, matcher.group(8), matcher.group(9), matcher.group(10));
// create a compound predicate to combine the two groups with the operator
final Predicate compoundPredicate;
if (operator == AND) {
compoundPredicate = PredicateBuilder.and(PredicateBuilder.toPredicate(first), PredicateBuilder.toPredicate(last));
} else if (operator == OR) {
compoundPredicate = PredicateBuilder.or(PredicateBuilder.toPredicate(first), PredicateBuilder.toPredicate(last));
} else {
throw new IllegalArgumentException("Syntax error in expression: " + expression
+ ". Expected operator as either and/or but was: " + operator);
}
// return the expression that evaluates this expression
return new Expression() {
public <T> T evaluate(Exchange exchange, Class<T> type) {
boolean matches = compoundPredicate.matches(exchange);
return exchange.getContext().getTypeConverter().convertTo(type, matches);
}
@Override
public String toString() {
return first + " " + operator + " " + last;
}
};
}
}
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);
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) {
int idx = expression.indexOf("${", pivot);
if (idx < 0) {
results.add(createConstantExpression(expression, pivot, size));
break;
} else {
if (pivot < idx) {
results.add(createConstantExpression(expression, pivot, idx));
}
pivot = idx + 2;
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) {
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;
}
}