1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2000-2001,2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42 */
43
44 package org.exolab.jms.selector;
45
46 import java.io.StringReader;
47
48 import javax.jms.InvalidSelectorException;
49 import javax.jms.Message;
50
51 import org.exolab.jms.selector.parser.SelectorLexer;
52 import org.exolab.jms.selector.parser.SelectorParser;
53 import org.exolab.jms.selector.parser.SelectorTreeParser;
54
55
56 /***
57 * This class enables messages to be filtered using a message selector.
58 * This is a String whose syntax is based on a subset of the SQL92
59 * conditional expression syntax.
60 *
61 * A selector can contain:
62 * <ul>
63 * <li>Literals:</li>
64 * <ul>
65 * <li>A string literal is enclosed in single quotes with an included
66 * single quote represented by doubled single quote such as 'literal'
67 * and 'literal''s'; like Java <i>String</i> literals these use the
68 * unicode character encoding.
69 * </li>
70 * <li>An exact numeric literal is a numeric value without a decimal point
71 * such as 57, -957, +62; numbers in the range of Java <i>long</i> are
72 * supported. Exact numeric literals use the Java integer literal syntax.
73 * </li>
74 * <li>An approximate numeric literal is a numeric value in scientific
75 * notation such as 7E3, -57.9E2 or a numeric value with a decimal such
76 * as 7., -95.7, +6.2; numbers in the range of Java <i>double</i>
77 * are supported. Approximate literals use the Java floating point
78 * literal syntax.
79 * </li>
80 * <li>The boolean literals <i>TRUE </i>and <i>FALSE</i>.</li>
81 * </ul>
82 * <li>Identifiers:</li>
83 * <ul>
84 * <li>Identifiers use the Java identifier syntax. They are case sensitive.
85 * </li>
86 * <li>Identifiers cannot be the names <i>NULL</i>, <i>TRUE</i>, or
87 * <i>FALSE</i>.
88 * </li>
89 * <li>Identifiers cannot be <i>NOT, AND, OR, BETWEEN, LIKE, IN</i>, and
90 * <i>IS</i>.
91 * </li>
92 * <li>Identifiers are either header field references or property references.
93 * </li>
94 * <br>Message header field references are restricted to
95 * <i>JMSDeliveryMode</i>, <i>JMSPriority</i>, <i>JMSMessageID</i>,
96 * <i>JMSTimestamp</i>, <i>JMSCorrelationID</i>, and <i>JMSType</i>.
97 * <i>JMSMessageID</i>, <i>JMSCorrelationID</i>, and <i>JMSType</i>
98 * values may be <i>null</i> and if so are treated as a NULL value.
99 * <li>Any name beginning with 'JMSX' is a JMS defined property name.</li>
100 * <li>Any name beginning with 'JMS_' is a provider-specific property name.
101 * </li>
102 * <li>Any name that does not begin with 'JMS' is an application-specific
103 * property name. If a property is referenced that does not exist in a
104 * message its value is NULL. If it does exist, its value is the
105 * corresponding property value.
106 * </li>
107 * </ul>
108 * <li>Expressions:</li>
109 * <ul>
110 * <li>A selector is a conditional expression; a selector that evaluates to
111 * true matches; a selector that evaluates to false or unknown does not
112 * match.
113 * </li>
114 * <li>Arithmetic expressions are composed of themselves, arithmetic
115 * operations, identifiers with numeric values and numeric literals.
116 * </li>
117 * <li>Conditional expressions are composed of themselves, comparison
118 * operations, logical operations, identifiers with boolean values and
119 * boolean literals.
120 * </li>
121 * <li>Standard bracketing () for ordering expression evaluation is
122 * supported.
123 * </li>
124 * <li>Logical operators in precedence order: NOT, AND, OR.</li>
125 * <li>Comparison operators: =, >, >=, <, <=, <> (not equal).
126 * </li>
127 * <li>Only <i>like </i>type values can be compared. One exception is that it
128 * is valid to compare exact numeric values and approximate numeric
129 * values (the type conversion required is defined by the rules of Java
130 * numeric promotion). If the comparison of non-like type values is
131 * attempted, the selector is always false.
132 * </li>
133 * <li><i>String</i> and <i>Boolean</i> comparison is restricted to = and
134 * <>. Two strings are equal if and only if they contain the same
135 * sequence of characters.
136 * </li>
137 * </ul>
138 * <li>Arithmetic operators in precedence order:</li>
139 * <ul>
140 * <li>+, - unary</li>
141 * <li>*, / multiplication and division</li>
142 * <li>+, - addition and subtraction</li>
143 * <li>Arithmetic operations use Java numeric promotion.</li>
144 * </ul>
145 *
146 * <li><i>arithmetic-expr1 </i>[NOT] BETWEEN <i>arithmetic-expr2 </i>AND<i>
147 * arithmetic-expr3</i> comparison operator
148 * </li>
149 * <ul>
150 * <li>age BETWEEN 15 and 19 is equivalent to age >= 15 AND age <= 19
151 * </li>
152 * <li>age NOT BETWEEN 15 and 19 is equivalent to age < 15 OR age > 19
153 * </li>
154 * </ul>
155 * <li><i>identifier </i>[NOT] IN (<i>string-literal1, string-literal2,...
156 * </i>)
157 * </li>
158 * <br>comparison operator where identifier has a <i>String</i> or NULL
159 * value.
160 * <ul>
161 * <li>Country IN ('UK', 'US', 'France') is true for 'UK' and false for
162 * 'Peru'. It is equivalent to the expression (Country = ' UK') OR
163 * (Country = ' US') OR (Country = ' France')
164 * </li>
165 * <li>Country NOT IN (' UK', 'US', 'France') is false for 'UK' and true
166 * for 'Peru'. It is equivalent to the expression NOT ((Country = 'UK')
167 * OR (Country = 'US') OR (Country = 'France'))
168 * </li>
169 * <li>If <i>identifier </i>of an IN or NOT IN operation is NULL the value
170 * of the operation is unknown.
171 * </li>
172 * </ul>
173 * <li><i>identifier </i>[NOT] LIKE <i>pattern-value</i> [ESCAPE
174 * <i>escape-character</i>]
175 * </li>
176 * <br>comparison operator, where <i>identifier</i> has a <i>String</i>
177 * value; <i>pattern-value</i> is a string literal where '_' stands for
178 * any single character; '%' stands for any sequence of characters
179 * (including the empty sequence); and all other characters stand for
180 * themselves. The optional <i>escape-character</i> is a single character
181 * string literal whose character is used to escape the special meaning
182 * of the '_' and '%' in <i>pattern-value</i>.
183 * <ul>
184 * <li><i>phone LIKE '12%3'</i> is true for '123', '12993' and false for
185 * '1234'
186 * </li>
187 * <li><i>word LIKE 'l_se'</i> is true for 'lose' and false for 'loose'
188 * </li>
189 * <li><i>underscored LIKE '\_%' ESCAPE '\'</i> is true for '_foo' and
190 * false for 'bar'
191 * </li>
192 * <li><i>phone NOT LIKE '12%3'</i> is false for '123' and '12993' and
193 * true for '1234'
194 * </li>
195 * <li>If <i>identifier</i> of a LIKE or NOT LIKE operation is NULL the
196 * value of the operation is unknown.
197 * </li>
198 * </ul>
199 * <li><i>identifier</i> IS NULL</li>
200 * <br>comparison operator tests for a null header field value, or a
201 * missing property value.
202 * <ul>
203 * <li><i>prop_name</i> IS NULL</li>
204 * <li><i>identifier</i> IS NOT NULL comparison operator tests for the
205 * existence of a non null header field value or property value.
206 * </li>
207 * <li><i>prop_name</i> IS NOT NULL</li>
208 * </ul>
209 * </ul></ul>
210 *
211 * @version $Revision: 1.1 $ $Date: 2004/11/26 01:50:44 $
212 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
213 */
214 public class Selector {
215
216 /***
217 * The 'compiled' expression
218 */
219 private final Expression _evaluator;
220
221
222 /***
223 * Construct a message selector that selects messages based on the
224 * supplied expression.
225 *
226 * @param expression the conditional expression
227 * @throws InvalidSelectorException if expression is invalid
228 */
229 public Selector(final String expression) throws InvalidSelectorException {
230 try {
231 if (expression == null || expression.length() == 0) {
232
233 _evaluator = Literal.booleanLiteral(true);
234 } else {
235 SelectorLexer lexer = new SelectorLexer(
236 new StringReader(expression));
237 lexer.initialise();
238
239 SelectorParser parser = new SelectorParser(lexer);
240 parser.initialise();
241 parser.selector();
242
243 SelectorTreeParser builder = new SelectorTreeParser();
244 builder.initialise(new DefaultExpressionFactory());
245 _evaluator = builder.selector(parser.getAST());
246 }
247 } catch (Exception exception) {
248 throw new InvalidSelectorException(exception.toString());
249 }
250 }
251
252 /***
253 * Return if message is selected by the expression
254 *
255 * @param message the message
256 * @return <code>true</code> if the message is selected, otherwise
257 * <code>false</code>
258 */
259 public boolean selects(final Message message) {
260 boolean result = false;
261 try {
262 SObject value = _evaluator.evaluate(message);
263 if (value instanceof SBool) {
264 result = ((SBool) value).value();
265 }
266 } catch (TypeMismatchException ignore) {
267 }
268 return result;
269 }
270
271 }