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