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.activemq.filter;
018    
019    import java.util.HashSet;
020    import java.util.List;
021    import java.util.Set;
022    import java.util.regex.Pattern;
023    
024    import javax.jms.JMSException;
025    
026    /**
027     * A filter performing a comparison of two objects
028     *
029     *
030     */
031    public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression {
032    
033        public static final ThreadLocal<Boolean> CONVERT_STRING_EXPRESSIONS = new ThreadLocal<Boolean>();
034    
035        boolean convertStringExpressions = false;
036        private static final Set<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>();
037    
038        /**
039         * @param left
040         * @param right
041         */
042        public ComparisonExpression(Expression left, Expression right) {
043            super(left, right);
044            convertStringExpressions = CONVERT_STRING_EXPRESSIONS.get()!=null;
045        }
046    
047        public static BooleanExpression createBetween(Expression value, Expression left, Expression right) {
048            return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
049        }
050    
051        public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) {
052            return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
053        }
054    
055        static {
056            REGEXP_CONTROL_CHARS.add(Character.valueOf('.'));
057            REGEXP_CONTROL_CHARS.add(Character.valueOf('\\'));
058            REGEXP_CONTROL_CHARS.add(Character.valueOf('['));
059            REGEXP_CONTROL_CHARS.add(Character.valueOf(']'));
060            REGEXP_CONTROL_CHARS.add(Character.valueOf('^'));
061            REGEXP_CONTROL_CHARS.add(Character.valueOf('$'));
062            REGEXP_CONTROL_CHARS.add(Character.valueOf('?'));
063            REGEXP_CONTROL_CHARS.add(Character.valueOf('*'));
064            REGEXP_CONTROL_CHARS.add(Character.valueOf('+'));
065            REGEXP_CONTROL_CHARS.add(Character.valueOf('{'));
066            REGEXP_CONTROL_CHARS.add(Character.valueOf('}'));
067            REGEXP_CONTROL_CHARS.add(Character.valueOf('|'));
068            REGEXP_CONTROL_CHARS.add(Character.valueOf('('));
069            REGEXP_CONTROL_CHARS.add(Character.valueOf(')'));
070            REGEXP_CONTROL_CHARS.add(Character.valueOf(':'));
071            REGEXP_CONTROL_CHARS.add(Character.valueOf('&'));
072            REGEXP_CONTROL_CHARS.add(Character.valueOf('<'));
073            REGEXP_CONTROL_CHARS.add(Character.valueOf('>'));
074            REGEXP_CONTROL_CHARS.add(Character.valueOf('='));
075            REGEXP_CONTROL_CHARS.add(Character.valueOf('!'));
076        }
077    
078        static class LikeExpression extends UnaryExpression implements BooleanExpression {
079    
080            Pattern likePattern;
081    
082            /**
083             */
084            public LikeExpression(Expression right, String like, int escape) {
085                super(right);
086    
087                StringBuffer regexp = new StringBuffer(like.length() * 2);
088                regexp.append("\\A"); // The beginning of the input
089                for (int i = 0; i < like.length(); i++) {
090                    char c = like.charAt(i);
091                    if (escape == (0xFFFF & c)) {
092                        i++;
093                        if (i >= like.length()) {
094                            // nothing left to escape...
095                            break;
096                        }
097    
098                        char t = like.charAt(i);
099                        regexp.append("\\x");
100                        regexp.append(Integer.toHexString(0xFFFF & t));
101                    } else if (c == '%') {
102                        regexp.append(".*?"); // Do a non-greedy match
103                    } else if (c == '_') {
104                        regexp.append("."); // match one
105                    } else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) {
106                        regexp.append("\\x");
107                        regexp.append(Integer.toHexString(0xFFFF & c));
108                    } else {
109                        regexp.append(c);
110                    }
111                }
112                regexp.append("\\z"); // The end of the input
113    
114                likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
115            }
116    
117            /**
118             * @see org.apache.activemq.filter.UnaryExpression#getExpressionSymbol()
119             */
120            public String getExpressionSymbol() {
121                return "LIKE";
122            }
123    
124            /**
125             * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext)
126             */
127            public Object evaluate(MessageEvaluationContext message) throws JMSException {
128    
129                Object rv = this.getRight().evaluate(message);
130    
131                if (rv == null) {
132                    return null;
133                }
134    
135                if (!(rv instanceof String)) {
136                    return Boolean.FALSE;
137                    // throw new RuntimeException("LIKE can only operate on String
138                    // identifiers. LIKE attemped on: '" + rv.getClass());
139                }
140    
141                return likePattern.matcher((String)rv).matches() ? Boolean.TRUE : Boolean.FALSE;
142            }
143    
144            public boolean matches(MessageEvaluationContext message) throws JMSException {
145                Object object = evaluate(message);
146                return object != null && object == Boolean.TRUE;
147            }
148        }
149    
150        public static BooleanExpression createLike(Expression left, String right, String escape) {
151            if (escape != null && escape.length() != 1) {
152                throw new RuntimeException("The ESCAPE string litteral is invalid.  It can only be one character.  Litteral used: " + escape);
153            }
154            int c = -1;
155            if (escape != null) {
156                c = 0xFFFF & escape.charAt(0);
157            }
158    
159            return new LikeExpression(left, right, c);
160        }
161    
162        public static BooleanExpression createNotLike(Expression left, String right, String escape) {
163            return UnaryExpression.createNOT(createLike(left, right, escape));
164        }
165    
166        @SuppressWarnings({ "rawtypes", "unchecked" })
167        public static BooleanExpression createInFilter(Expression left, List elements) {
168    
169            if (!(left instanceof PropertyExpression)) {
170                throw new RuntimeException("Expected a property for In expression, got: " + left);
171            }
172            return UnaryExpression.createInExpression((PropertyExpression)left, elements, false);
173    
174        }
175    
176        @SuppressWarnings({ "rawtypes", "unchecked" })
177        public static BooleanExpression createNotInFilter(Expression left, List elements) {
178    
179            if (!(left instanceof PropertyExpression)) {
180                throw new RuntimeException("Expected a property for In expression, got: " + left);
181            }
182            return UnaryExpression.createInExpression((PropertyExpression)left, elements, true);
183    
184        }
185    
186        public static BooleanExpression createIsNull(Expression left) {
187            return doCreateEqual(left, ConstantExpression.NULL);
188        }
189    
190        public static BooleanExpression createIsNotNull(Expression left) {
191            return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
192        }
193    
194        public static BooleanExpression createNotEqual(Expression left, Expression right) {
195            return UnaryExpression.createNOT(createEqual(left, right));
196        }
197    
198        public static BooleanExpression createEqual(Expression left, Expression right) {
199            checkEqualOperand(left);
200            checkEqualOperand(right);
201            checkEqualOperandCompatability(left, right);
202            return doCreateEqual(left, right);
203        }
204    
205        @SuppressWarnings({ "rawtypes" })
206        private static BooleanExpression doCreateEqual(Expression left, Expression right) {
207            return new ComparisonExpression(left, right) {
208    
209                public Object evaluate(MessageEvaluationContext message) throws JMSException {
210                    Object lv = left.evaluate(message);
211                    Object rv = right.evaluate(message);
212    
213                    // If one of the values is null
214                    if (lv == null ^ rv == null) {
215                        return Boolean.FALSE;
216                    }
217                    if (lv == rv || lv.equals(rv)) {
218                        return Boolean.TRUE;
219                    }
220                    if (lv instanceof Comparable && rv instanceof Comparable) {
221                        return compare((Comparable)lv, (Comparable)rv);
222                    }
223                    return Boolean.FALSE;
224                }
225    
226                protected boolean asBoolean(int answer) {
227                    return answer == 0;
228                }
229    
230                public String getExpressionSymbol() {
231                    return "=";
232                }
233            };
234        }
235    
236        public static BooleanExpression createGreaterThan(final Expression left, final Expression right) {
237            checkLessThanOperand(left);
238            checkLessThanOperand(right);
239            return new ComparisonExpression(left, right) {
240                protected boolean asBoolean(int answer) {
241                    return answer > 0;
242                }
243    
244                public String getExpressionSymbol() {
245                    return ">";
246                }
247            };
248        }
249    
250        public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
251            checkLessThanOperand(left);
252            checkLessThanOperand(right);
253            return new ComparisonExpression(left, right) {
254                protected boolean asBoolean(int answer) {
255                    return answer >= 0;
256                }
257    
258                public String getExpressionSymbol() {
259                    return ">=";
260                }
261            };
262        }
263    
264        public static BooleanExpression createLessThan(final Expression left, final Expression right) {
265            checkLessThanOperand(left);
266            checkLessThanOperand(right);
267            return new ComparisonExpression(left, right) {
268    
269                protected boolean asBoolean(int answer) {
270                    return answer < 0;
271                }
272    
273                public String getExpressionSymbol() {
274                    return "<";
275                }
276    
277            };
278        }
279    
280        public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
281            checkLessThanOperand(left);
282            checkLessThanOperand(right);
283            return new ComparisonExpression(left, right) {
284    
285                protected boolean asBoolean(int answer) {
286                    return answer <= 0;
287                }
288    
289                public String getExpressionSymbol() {
290                    return "<=";
291                }
292            };
293        }
294    
295        /**
296         * Only Numeric expressions can be used in >, >=, < or <= expressions.s
297         *
298         * @param expr
299         */
300        public static void checkLessThanOperand(Expression expr) {
301            if (expr instanceof ConstantExpression) {
302                Object value = ((ConstantExpression)expr).getValue();
303                if (value instanceof Number) {
304                    return;
305                }
306    
307                // Else it's boolean or a String..
308                throw new RuntimeException("Value '" + expr + "' cannot be compared.");
309            }
310            if (expr instanceof BooleanExpression) {
311                throw new RuntimeException("Value '" + expr + "' cannot be compared.");
312            }
313        }
314    
315        /**
316         * Validates that the expression can be used in == or <> expression. Cannot
317         * not be NULL TRUE or FALSE litterals.
318         *
319         * @param expr
320         */
321        public static void checkEqualOperand(Expression expr) {
322            if (expr instanceof ConstantExpression) {
323                Object value = ((ConstantExpression)expr).getValue();
324                if (value == null) {
325                    throw new RuntimeException("'" + expr + "' cannot be compared.");
326                }
327            }
328        }
329    
330        /**
331         * @param left
332         * @param right
333         */
334        private static void checkEqualOperandCompatability(Expression left, Expression right) {
335            if (left instanceof ConstantExpression && right instanceof ConstantExpression) {
336                if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) {
337                    throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
338                }
339            }
340        }
341    
342        @SuppressWarnings({ "rawtypes", "unchecked" })
343        public Object evaluate(MessageEvaluationContext message) throws JMSException {
344            Comparable<Comparable> lv = (Comparable)left.evaluate(message);
345            if (lv == null) {
346                return null;
347            }
348            Comparable rv = (Comparable)right.evaluate(message);
349            if (rv == null) {
350                return null;
351            }
352            return compare(lv, rv);
353        }
354    
355        @SuppressWarnings({ "rawtypes", "unchecked" })
356        protected Boolean compare(Comparable lv, Comparable rv) {
357            Class<? extends Comparable> lc = lv.getClass();
358            Class<? extends Comparable> rc = rv.getClass();
359            // If the the objects are not of the same type,
360            // try to convert up to allow the comparison.
361            if (lc != rc) {
362                try {
363                    if (lc == Boolean.class) {
364                        if (convertStringExpressions && rc == String.class) {
365                            lv = Boolean.valueOf((String)lv).booleanValue();
366                        } else {
367                            return Boolean.FALSE;
368                        }
369                    } else if (lc == Byte.class) {
370                        if (rc == Short.class) {
371                            lv = Short.valueOf(((Number)lv).shortValue());
372                        } else if (rc == Integer.class) {
373                            lv = Integer.valueOf(((Number)lv).intValue());
374                        } else if (rc == Long.class) {
375                            lv = Long.valueOf(((Number)lv).longValue());
376                        } else if (rc == Float.class) {
377                            lv = new Float(((Number)lv).floatValue());
378                        } else if (rc == Double.class) {
379                            lv = new Double(((Number)lv).doubleValue());
380                        } else if (convertStringExpressions && rc == String.class) {
381                            rv = Byte.valueOf((String)rv);
382                        } else {
383                            return Boolean.FALSE;
384                        }
385                    } else if (lc == Short.class) {
386                        if (rc == Integer.class) {
387                            lv = Integer.valueOf(((Number)lv).intValue());
388                        } else if (rc == Long.class) {
389                            lv = Long.valueOf(((Number)lv).longValue());
390                        } else if (rc == Float.class) {
391                            lv = new Float(((Number)lv).floatValue());
392                        } else if (rc == Double.class) {
393                            lv = new Double(((Number)lv).doubleValue());
394                        } else if (convertStringExpressions && rc == String.class) {
395                            rv = Short.valueOf((String)rv);
396                        } else {
397                            return Boolean.FALSE;
398                        }
399                    } else if (lc == Integer.class) {
400                        if (rc == Long.class) {
401                            lv = Long.valueOf(((Number)lv).longValue());
402                        } else if (rc == Float.class) {
403                            lv = new Float(((Number)lv).floatValue());
404                        } else if (rc == Double.class) {
405                            lv = new Double(((Number)lv).doubleValue());
406                        } else if (convertStringExpressions && rc == String.class) {
407                            rv = Integer.valueOf((String)rv);
408                        } else {
409                            return Boolean.FALSE;
410                        }
411                    } else if (lc == Long.class) {
412                        if (rc == Integer.class) {
413                            rv = Long.valueOf(((Number)rv).longValue());
414                        } else if (rc == Float.class) {
415                            lv = new Float(((Number)lv).floatValue());
416                        } else if (rc == Double.class) {
417                            lv = new Double(((Number)lv).doubleValue());
418                        } else if (convertStringExpressions && rc == String.class) {
419                            rv = Long.valueOf((String)rv);
420                        } else {
421                            return Boolean.FALSE;
422                        }
423                    } else if (lc == Float.class) {
424                        if (rc == Integer.class) {
425                            rv = new Float(((Number)rv).floatValue());
426                        } else if (rc == Long.class) {
427                            rv = new Float(((Number)rv).floatValue());
428                        } else if (rc == Double.class) {
429                            lv = new Double(((Number)lv).doubleValue());
430                        } else if (convertStringExpressions && rc == String.class) {
431                            rv = Float.valueOf((String)rv);
432                        } else {
433                            return Boolean.FALSE;
434                        }
435                    } else if (lc == Double.class) {
436                        if (rc == Integer.class) {
437                            rv = new Double(((Number)rv).doubleValue());
438                        } else if (rc == Long.class) {
439                            rv = new Double(((Number)rv).doubleValue());
440                        } else if (rc == Float.class) {
441                            rv = new Float(((Number)rv).doubleValue());
442                        } else if (convertStringExpressions && rc == String.class) {
443                            rv = Double.valueOf((String)rv);
444                        } else {
445                            return Boolean.FALSE;
446                        }
447                    } else if (convertStringExpressions && lc == String.class) {
448                        if (rc == Boolean.class) {
449                            lv = Boolean.valueOf((String)lv);
450                        } else if (rc == Byte.class) {
451                            lv = Byte.valueOf((String)lv);
452                        } else if (rc == Short.class) {
453                            lv = Short.valueOf((String)lv);
454                        } else if (rc == Integer.class) {
455                            lv = Integer.valueOf((String)lv);
456                        } else if (rc == Long.class) {
457                            lv = Long.valueOf((String)lv);
458                        } else if (rc == Float.class) {
459                            lv = Float.valueOf((String)lv);
460                        } else if (rc == Double.class) {
461                            lv = Double.valueOf((String)lv);
462                        } else {
463                            return Boolean.FALSE;
464                        }
465                    } else {
466                        return Boolean.FALSE;
467                    }
468                } catch(NumberFormatException e) {
469                    return Boolean.FALSE;
470                }
471            }
472            return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
473        }
474    
475        protected abstract boolean asBoolean(int answer);
476    
477        public boolean matches(MessageEvaluationContext message) throws JMSException {
478            Object object = evaluate(message);
479            return object != null && object == Boolean.TRUE;
480        }
481    
482    }