001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.language.simple;
018
019 import java.util.ArrayList;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.regex.Matcher;
023 import java.util.regex.Pattern;
024
025 import org.apache.camel.Exchange;
026 import org.apache.camel.Expression;
027 import org.apache.camel.IsSingleton;
028 import org.apache.camel.Predicate;
029 import org.apache.camel.builder.ExpressionBuilder;
030 import org.apache.camel.builder.PredicateBuilder;
031 import org.apache.camel.builder.ValueBuilder;
032 import org.apache.camel.spi.Language;
033 import org.apache.camel.util.ObjectHelper;
034 import org.apache.commons.logging.Log;
035 import org.apache.commons.logging.LogFactory;
036 import static org.apache.camel.language.simple.SimpleLangaugeOperator.*;
037
038 /**
039 * Abstract base class for Simple languages.
040 */
041 public abstract class SimpleLanguageSupport implements Language, IsSingleton {
042
043 // this is a regex for a given group in a simple expression that uses operators
044 protected static final String GROUP_PATTERN =
045 "\\$\\{(\\S+)\\}\\s+(==|>|>=|<|<=|!=|contains|not contains|regex|not regex|in|not in|is|not is|range|not range)\\s+('.*?'|\\S+)";
046
047 // this is the operator reg ex pattern used to match if a given expression is operator based or not
048 protected static final Pattern PATTERN = Pattern.compile("^(" + GROUP_PATTERN + ")(\\s+(and|or)\\s+(" + GROUP_PATTERN + "))?$");
049
050 // this is special for the range operator where you define the range as from..to (where from and to are numbers)
051 protected static final Pattern RANGE_PATTERN = Pattern.compile("^(\\d+)(\\.\\.)(\\d+)$");
052 protected final Log log = LogFactory.getLog(getClass());
053
054 public Predicate createPredicate(String expression) {
055 return PredicateBuilder.toPredicate(createExpression(expression));
056 }
057
058 public Expression createExpression(String expression) {
059 Matcher matcher = PATTERN.matcher(expression);
060 if (matcher.matches()) {
061 if (log.isDebugEnabled()) {
062 log.debug("Expression is evaluated as simple expression wiht operator: " + expression);
063 }
064 return createOperatorExpression(matcher, expression);
065 } else if (expression.indexOf("${") >= 0) {
066 if (log.isDebugEnabled()) {
067 log.debug("Expression is evaluated as simple (strict) expression: " + expression);
068 }
069 return createComplexConcatExpression(expression);
070 } else {
071 if (log.isDebugEnabled()) {
072 log.debug("Expression is evaluated as simple (non strict) expression: " + expression);
073 }
074 return createSimpleExpression(expression, false);
075 }
076 }
077
078 private Expression createOperatorExpression(final Matcher matcher, final String expression) {
079 int groupCount = matcher.groupCount();
080
081 if (log.isTraceEnabled()) {
082 log.trace("Matcher expression: " + expression);
083 log.trace("Matcher group count: " + groupCount);
084 for (int i = 0; i < matcher.groupCount() + 1; i++) {
085 String group = matcher.group(i);
086 if (log.isTraceEnabled()) {
087 log.trace("Matcher group #" + i + ": " + group);
088 }
089 }
090 }
091
092 // a simple expression with operator can either be a single group or a dual group
093 String operatorText = matcher.group(6);
094 if (operatorText == null) {
095 // single group
096 return doCreateOperatorExpression(expression, matcher.group(2), matcher.group(3), matcher.group(4));
097 } else {
098 // dual group with an and/or operator between the two groups
099 final Expression first = doCreateOperatorExpression(expression, matcher.group(2), matcher.group(3), matcher.group(4));
100 final SimpleLangaugeOperator operator = asOperator(operatorText);
101 final Expression last = doCreateOperatorExpression(expression, matcher.group(8), matcher.group(9), matcher.group(10));
102
103 // create a compound predicate to combine the two groups with the operator
104 final Predicate compoundPredicate;
105 if (operator == AND) {
106 compoundPredicate = PredicateBuilder.and(PredicateBuilder.toPredicate(first), PredicateBuilder.toPredicate(last));
107 } else if (operator == OR) {
108 compoundPredicate = PredicateBuilder.or(PredicateBuilder.toPredicate(first), PredicateBuilder.toPredicate(last));
109 } else {
110 throw new IllegalArgumentException("Syntax error in expression: " + expression
111 + ". Expected operator as either and/or but was: " + operator);
112 }
113
114 // return the expression that evaluates this expression
115 return new Expression() {
116 public <T> T evaluate(Exchange exchange, Class<T> type) {
117 boolean matches = compoundPredicate.matches(exchange);
118 return exchange.getContext().getTypeConverter().convertTo(type, matches);
119 }
120
121 @Override
122 public String toString() {
123 return first + " " + operator + " " + last;
124 }
125 };
126 }
127 }
128
129 private Expression doCreateOperatorExpression(final String expression, final String leftText,
130 final String operatorText, final String rightText) {
131 // left value is always a simple expression
132 final Expression left = createSimpleExpression(leftText, true);
133 final SimpleLangaugeOperator operator = asOperator(operatorText);
134
135 // the right hand side expression can either be a constant expression with or without enclosing ' '
136 // or another simple expression using ${ } placeholders
137 final Expression right;
138 final Boolean isNull;
139 // special null handling
140 if ("null".equals(rightText) || "'null'".equals(rightText)) {
141 isNull = Boolean.TRUE;
142 right = createSimpleOrConstantExpression(null);
143 } else {
144 isNull = Boolean.FALSE;
145 right = createSimpleOrConstantExpression(rightText);
146 }
147
148 return new Expression() {
149 public <T> T evaluate(Exchange exchange, Class<T> type) {
150 Predicate predicate = null;
151
152 if (operator == EQ && isNull) {
153 // special for EQ null
154 predicate = PredicateBuilder.isNull(left);
155 } else if (operator == NOT && isNull) {
156 // special for not EQ null
157 predicate = PredicateBuilder.isNotNull(left);
158 } else if (operator == EQ) {
159 predicate = PredicateBuilder.isEqualTo(left, right);
160 } else if (operator == GT) {
161 predicate = PredicateBuilder.isGreaterThan(left, right);
162 } else if (operator == GTE) {
163 predicate = PredicateBuilder.isGreaterThanOrEqualTo(left, right);
164 } else if (operator == LT) {
165 predicate = PredicateBuilder.isLessThan(left, right);
166 } else if (operator == LTE) {
167 predicate = PredicateBuilder.isLessThanOrEqualTo(left, right);
168 } else if (operator == NOT) {
169 predicate = PredicateBuilder.isNotEqualTo(left, right);
170 } else if (operator == CONTAINS || operator == NOT_CONTAINS) {
171 predicate = PredicateBuilder.contains(left, right);
172 if (operator == NOT_CONTAINS) {
173 predicate = PredicateBuilder.not(predicate);
174 }
175 } else if (operator == REGEX || operator == NOT_REGEX) {
176 // reg ex should use String pattern, so we evalute the right hand side as a String
177 predicate = PredicateBuilder.regex(left, right.evaluate(exchange, String.class));
178 if (operator == NOT_REGEX) {
179 predicate = PredicateBuilder.not(predicate);
180 }
181 } else if (operator == IN || operator == NOT_IN) {
182 // okay the in operator is a bit more complex as we need to build a list of values
183 // from the right handside expression.
184 // each element on the right handside must be separated by comma (default for create iterator)
185 Iterator it = ObjectHelper.createIterator(right.evaluate(exchange, Object.class));
186 List<Object> values = new ArrayList<Object>();
187 while (it.hasNext()) {
188 values.add(it.next());
189 }
190 // then reuse value builder to create the in predicate with the list of values
191 ValueBuilder vb = new ValueBuilder(left);
192 predicate = vb.in(values.toArray());
193 if (operator == NOT_IN) {
194 predicate = PredicateBuilder.not(predicate);
195 }
196 } else if (operator == IS || operator == NOT_IS) {
197 String name = right.evaluate(exchange, String.class);
198 Class rightType = exchange.getContext().getClassResolver().resolveClass(name);
199 if (rightType == null) {
200 // prefix class name with java.lang. so people can use String as shorthand
201 rightType = exchange.getContext().getClassResolver().resolveClass("java.lang." + name);
202 }
203 if (rightType == null) {
204 throw new IllegalArgumentException("Syntax error in is operator: " + expression
205 + " cannot find class with name: " + name);
206 }
207 predicate = PredicateBuilder.isInstanceOf(left, rightType);
208 if (operator == NOT_IS) {
209 predicate = PredicateBuilder.not(predicate);
210 }
211 } else if (operator == RANGE || operator == NOT_RANGE) {
212 String range = right.evaluate(exchange, String.class);
213 Matcher matcher = RANGE_PATTERN.matcher(range);
214 if (matcher.matches()) {
215 // wrap as constant expression for the from and to values
216 Expression from = ExpressionBuilder.constantExpression(matcher.group(1));
217 Expression to = ExpressionBuilder.constantExpression(matcher.group(3));
218
219 // build a compound predicate for the range
220 predicate = PredicateBuilder.isGreaterThanOrEqualTo(left, from);
221 predicate = PredicateBuilder.and(predicate, PredicateBuilder.isLessThanOrEqualTo(left, to));
222 } else {
223 throw new IllegalArgumentException("Syntax error in range operator: " + expression + " is not valid."
224 + " Valid syntax: from..to (where from and to are numbers).");
225 }
226 if (operator == NOT_RANGE) {
227 predicate = PredicateBuilder.not(predicate);
228 }
229 }
230
231 if (predicate == null) {
232 throw new IllegalArgumentException("Unsupported operator: " + operator + " for expression: " + expression);
233 }
234
235 boolean matches = predicate.matches(exchange);
236 return exchange.getContext().getTypeConverter().convertTo(type, matches);
237 }
238
239 @Override
240 public String toString() {
241 return left + " " + operator + " " + right;
242 }
243 };
244 }
245
246 protected Expression createComplexConcatExpression(String expression) {
247 List<Expression> results = new ArrayList<Expression>();
248
249 int pivot = 0;
250 int size = expression.length();
251 while (pivot < size) {
252 int idx = expression.indexOf("${", pivot);
253 if (idx < 0) {
254 results.add(createConstantExpression(expression, pivot, size));
255 break;
256 } else {
257 if (pivot < idx) {
258 results.add(createConstantExpression(expression, pivot, idx));
259 }
260 pivot = idx + 2;
261 int endIdx = expression.indexOf('}', pivot);
262 if (endIdx < 0) {
263 throw new IllegalArgumentException("Expecting } but found end of string for simple expression: " + expression);
264 }
265 String simpleText = expression.substring(pivot, endIdx);
266
267 Expression simpleExpression = createSimpleExpression(simpleText, true);
268 results.add(simpleExpression);
269 pivot = endIdx + 1;
270 }
271 }
272 return ExpressionBuilder.concatExpression(results, expression);
273 }
274
275 protected Expression createSimpleOrConstantExpression(String text) {
276 if (text != null) {
277 String simple = ObjectHelper.between(text, "${", "}");
278 if (simple != null) {
279 return createSimpleExpression(simple, true);
280 }
281
282 simple = ObjectHelper.between(text, "'", "'");
283 if (simple != null) {
284 return createConstantExpression(simple);
285 }
286 }
287
288 return createConstantExpression(text);
289 }
290
291 protected Expression createConstantExpression(String expression, int start, int end) {
292 return ExpressionBuilder.constantExpression(expression.substring(start, end));
293 }
294
295 protected Expression createConstantExpression(String expression) {
296 return ExpressionBuilder.constantExpression(expression);
297 }
298
299 /**
300 * Creates the simple expression based on the extracted content from the ${ } place holders
301 *
302 * @param expression the content between ${ and }
303 * @param strict whether it is strict mode or not, if strict it will throw a
304 * {@link org.apache.camel.ExpressionIllegalSyntaxException} if the expression was not known.
305 * Set to <tt>false</tt> to support constant expressions
306 * @return the expression
307 */
308 protected abstract Expression createSimpleExpression(String expression, boolean strict);
309
310 protected String ifStartsWithReturnRemainder(String prefix, String text) {
311 if (text.startsWith(prefix)) {
312 String remainder = text.substring(prefix.length());
313 if (remainder.length() > 0) {
314 return remainder;
315 }
316 }
317 return null;
318 }
319 }