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 }