Source code: com/presumo/jms/selector/Parser.java
1 /**
2 * This file is part of Presumo.
3 *
4 * Presumo is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * Presumo is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Presumo; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 *
19 * Copyright 2001 Dan Greff
20 */
21 package com.presumo.jms.selector;
22
23 import com.presumo.util.log.Logger;
24 import com.presumo.util.log.LoggerFactory;
25 import com.presumo.jms.resources.Resources;
26
27 import java.util.LinkedList;
28 import javax.jms.InvalidSelectorException;
29 import javax.jms.Message;
30
31
32 /**
33 * Access point to the filter functionality for the rest of the JMS
34 * implementation. This class (along with every other class in this package)
35 * is on the critical path for message throughput performance. Thus it is,
36 * and must remain optimized.
37 *
38 * Parser is a singleton instead of a series of static methods because I read
39 * somewhere that instance methods are faster than static methods (never
40 * personally verified this). The class is thread safe, but to prevent the
41 * routing thread from having to obtain a lock for every evaluation, a thread
42 * can obtain the lock once and call several evaluations before releasing
43 * the lock.
44 *
45 * @author Dan Greff
46 */
47 public final class Parser {
48
49 private static Parser onlyInstance;
50 private Thread lockOwner = null;
51
52 /////////////////////////////////////////////////////////////////////////
53 // Static Methods //
54 /////////////////////////////////////////////////////////////////////////
55 public static synchronized Parser getInstance()
56 {
57 if (onlyInstance == null)
58 onlyInstance = new Parser();
59 return onlyInstance;
60 }
61
62 /////////////////////////////////////////////////////////////////////////
63 // Constructors //
64 /////////////////////////////////////////////////////////////////////////
65 protected Parser()
66 {
67 super();
68 }
69
70 /**
71 * Called to obtain the mutex lock for the current thread. ReleaseLock()
72 * must be called by the same thread.
73 */
74 public synchronized void obtainLock()
75 {
76 while (lockOwner != null) {
77 try {
78 this.wait();
79 } catch (InterruptedException ie) {}
80 }
81
82 lockOwner = Thread.currentThread();
83 }
84
85 /**
86 * Called to release the mutex lock held for this parser instance
87 */
88 public synchronized void releaseLock()
89 {
90 if (! lockOwner.equals(Thread.currentThread())) {
91 throw new IllegalStateException("Thread " + Thread.currentThread() +
92 "attempted to releaseLock held by "+ lockOwner);
93 }
94 lockOwner = null;
95 this.notifyAll();
96 }
97
98 /**
99 * The evaluate() method stores information about its last evaluation. This
100 * is combined with the fact that all logically equivelant JMS filters in
101 * a JVM are represented by the same instance of JmsOperand represents a
102 * signficant optimization. The filtering is used by ONLY the routing thread
103 * for evaluation, and for every message the thread has to deliver this method
104 * is called. Then for every filter the routing thread needs to check the
105 * message against the evaluate() method is called. If any sql expressions
106 * (or subexpressions!!) are duplicated in the filters, they will only be
107 * evaluated once.
108 */
109 public void resetEvaluateOnce()
110 {
111 JmsOperand.resetStoredEvals();
112 }
113
114
115 /**
116 * Must be used in conjunction with resetEvaluateOnce().
117 */
118 public boolean evaluate(JmsOperand root, Message msg)
119 {
120 logger.entry("evaluate", root.unParse(), msg);
121 if (root == null || msg == null)
122 return false;
123
124 boolean retval = false;
125 try {
126 JmsOperand result = root.evaluateOnce(msg);
127 if (result == JmsBooleanLiteral.TRUE)
128 retval = true;
129 } catch (SelectorFalseException e) {
130 e.printStackTrace();
131 }
132
133 logger.exit("evaluate", new Boolean(retval));
134 return retval;
135 }
136
137 /**
138 * Parse the filter and return a JmsOperand representing the root
139 * node of the filter's expression tree.
140 *
141 * @exception InvalidSelectorException
142 * If a syntax error was detected in the filter.
143 */
144 public JmsOperand parseFilter(String filter)
145 throws InvalidSelectorException
146 {
147
148 JmsOperand retval = null;
149 obtainLock();
150 try {
151 retval = SqlJmsParser.getFilter(filter);
152 } catch (ParseException e) {
153 throw new InvalidSelectorException(e.toString());
154 } finally {
155 releaseLock();
156 }
157
158 return retval;
159 }
160
161
162 /**
163 * @return A jms-sql query representative of the expression
164 * tree <code>root</code>
165 */
166 public String unparse(JmsOperand root)
167 {
168 obtainLock();
169
170 String retval = null;
171 try {
172
173 if (root == null)
174 retval = "false";
175 else
176 retval = root.unParse();
177
178 } finally {
179 releaseLock();
180 }
181
182 return retval;
183 }
184
185 /**
186 * Logically and's together two trees.
187 * The returned expression tree must be deleted.
188 */
189 public JmsOperand andTogether(JmsOperand lv, JmsOperand rv)
190 {
191
192 if (lv == null || rv == null)
193 throw new IllegalArgumentException();
194
195 JmsOperand retval = null;
196 obtainLock();
197 try {
198
199 lv.incrementAllRefCounts();
200 rv.incrementAllRefCounts();
201 retval = JmsBinaryAnd.getInstance(lv, rv);
202
203 } finally {
204 releaseLock();
205 }
206
207 return retval;
208 }
209
210 /**
211 * Logically or's together the trees
212 * The returned expression tree must be deleted
213 */
214 public JmsOperand orTogether(JmsOperand [] roots)
215 {
216
217 if (roots == null || roots.length == 0)
218 throw new IllegalArgumentException();
219
220 LinkedList noRepeats = new LinkedList();
221
222 // Construct a list that has no repeats.
223 // Same running time as bubble sort :(
224 int i, j;
225 for (i=0; i < roots.length; i++) {
226 JmsOperand currentSearch = roots[i];
227 if (currentSearch == null) continue;
228
229 // null out any duplicates
230 for (j= i+1; j < roots.length; j++) {
231 if (roots[j] == currentSearch)
232 roots[j] = null;
233 }
234 noRepeats.add(currentSearch);
235 }
236
237 // Now construct a new binary expression tree.
238 //
239 JmsOperand retval = null;
240 obtainLock();
241 try {
242
243 if (noRepeats.size() == 0)
244 retval = null;
245 else if (noRepeats.size() == 1) {
246 JmsOperand single = (JmsOperand) noRepeats.removeFirst();
247 single.incrementAllRefCounts();
248 retval = single;
249 } else {
250 JmsOperand loperand = (JmsOperand) noRepeats.removeFirst();
251 JmsOperand roperand = (JmsOperand) noRepeats.removeFirst();
252 loperand.incrementAllRefCounts();
253 roperand.incrementAllRefCounts();
254 loperand = JmsBinaryOr.getInstance(loperand, roperand);
255
256 while (noRepeats.size() > 0) {
257 roperand = (JmsOperand) noRepeats.removeFirst();
258 roperand.incrementAllRefCounts();
259 loperand = JmsBinaryOr.getInstance(loperand, roperand);
260 }
261 retval = loperand;
262 }
263
264 } finally {
265 releaseLock();
266 }
267
268 return retval;
269 }
270
271 /**
272 * @return Because of optimizations made during parsing
273 * the several references will be left around unless
274 * this method is called. The internal logic should
275 * eventually be switched to use WeakReferences so
276 * that the parser does not have to rely on this method
277 * being called to prevent a memory leak.
278 */
279 public void delete(JmsOperand root)
280 {
281 if (root != null) {
282 obtainLock();
283 try {
284
285 root.delete();
286
287 } finally {
288 releaseLock();
289 }
290 }
291
292 }
293
294 ////////////////////////////// Misc stuff ////////////////////////////////
295
296 private static Logger logger =
297 LoggerFactory.getLogger(Parser.class, Resources.getBundle());
298
299 ///////////////////////////////////////////////////////////////////////////
300
301 }