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    }