KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hsqldb > TriggerDef


1 /* Copyright (c) 2001-2005, The HSQL Development Group
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of the HSQL Development Group nor the names of its
15  * contributors may be used to endorse or promote products derived from this
16  * software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */

30
31
32 package org.hsqldb;
33
34 import org.hsqldb.HsqlNameManager.HsqlName;
35 import org.hsqldb.lib.HsqlDeque;
36 import org.hsqldb.lib.StringConverter;
37 import org.hsqldb.lib.StringUtil;
38
39 // peterhudson@users 20020130 - patch 478657 by peterhudson - triggers support
40
// fredt@users 20020130 - patch 1.7.0 by fredt
41
// added new class as jdk 1.1 does not allow use of LinkedList
42
// fredt@users 20030727 - signature and other alterations
43
// fredt@users 20040430 - changes by mattshaw@users to allow termination of the
44
// trigger thread -
45

46 /**
47  * Represents an HSQLDB Trigger definition. <p>
48  *
49  * Provides services regarding HSLDB Trigger execution and metadata. <p>
50  *
51  * Development of the trigger implementation sponsored by Logicscope
52  * Realisations Ltd
53  *
54  * @author Peter Hudson - Logicscope Realisations Ltd
55  * @version 1.7.0 (1.0.0.3)
56  * Revision History: 1.0.0.1 First release in hsqldb 1.61
57  * 1.0.0.2 'nowait' support to prevent deadlock 1.0.0.3 multiple row
58  * queue for each trigger
59  */

60 class TriggerDef extends Thread JavaDoc {
61
62     /**
63      * member variables
64      */

65     static final int NUM_TRIGGER_OPS = 3; // {ins,del,upd}
66
static final int NUM_TRIGS = NUM_TRIGGER_OPS * 2 * 2; // {b, a},{fer, fes}
67

68     // other variables
69
HsqlName name;
70     String JavaDoc when;
71     String JavaDoc operation;
72     boolean forEachRow;
73     boolean nowait; // block or overwrite if queue full
74
int maxRowsQueued; // max size of queue of pending triggers
75

76     /**
77      * Retrieves the queue size assigned to trigger definitions when no
78      * queue size is explicitly declared. <p>
79      *
80      * @return the queue size assigned to trigger definitions when no
81      * queue size is explicitly declared
82      */

83     public static int getDefaultQueueSize() {
84         return defaultQueueSize;
85     }
86
87     protected static int defaultQueueSize = 1024;
88     Table table;
89     Trigger trigger;
90     String JavaDoc triggerClassName;
91     int vectorIndex; // index into HsqlArrayList[]
92

93     //protected boolean busy; // firing trigger in progress
94
protected HsqlDeque pendingQueue; // row triggers pending
95
protected int rowsQueued; // rows in pendingQueue
96
protected boolean valid = true; // parsing valid
97
protected volatile boolean keepGoing = true;
98
99     /**
100      * Constructs a new TriggerDef object to represent an HSQLDB trigger
101      * declared in an SQL CREATE TRIGGER statement.
102      *
103      * Changes in 1.7.2 allow the queue size to be specified as 0. A zero
104      * queue size causes the Trigger.fire() code to run in the main thread of
105      * execution (fully inside the enclosing transaction). Otherwise, the code
106      * is run in the Trigger's own thread.
107      * (fredt@users)
108      *
109      * @param name The trigger object's HsqlName
110      * @param when the String representation of whether the trigger fires
111      * before or after the triggering event
112      * @param operation the String representation of the triggering operation;
113      * currently insert, update, or delete
114      * @param forEach indicates whether the trigger is fired for each row
115      * (true) or statement (false)
116      * @param table the Table object upon which the indicated operation
117      * fires the trigger
118      * @param triggerClassName the fully qualified named of the class implementing
119      * the org.hsqldb.Trigger (trigger body) interface
120      * @param noWait do not wait for available space on the pending queue; if
121      * the pending queue does not have fewer than nQueueSize queued items,
122      * then overwrite the current tail instead
123      * @param queueSize the length to which the pending queue may grow before
124      * further additions are either blocked or overwrite the tail entry,
125      * as determined by noWait
126      * @throws HsqlException - Invalid input parameter
127      */

128     public TriggerDef(HsqlNameManager.HsqlName name, String JavaDoc when,
129                       String JavaDoc operation, boolean forEach, Table table,
130                       String JavaDoc triggerClassName, boolean noWait, int queueSize,
131                       ClassLoader JavaDoc loader) throws HsqlException {
132
133         this.name = name;
134         this.when = when;
135         this.operation = operation;
136         this.forEachRow = forEach;
137         this.nowait = noWait;
138         this.maxRowsQueued = queueSize;
139         this.table = table;
140         vectorIndex = SqlToIndex();
141         this.triggerClassName = triggerClassName;
142         rowsQueued = 0;
143         pendingQueue = new HsqlDeque();
144
145         if (vectorIndex < 0) {
146             throw Trace.error(Trace.UNEXPECTED_TOKEN,
147                               Trace.CREATE_TRIGGER_COMMAND_1);
148         }
149
150         Class JavaDoc cl;
151
152         try {
153             cl = loader == null ? Class.forName(triggerClassName)
154                                 : loader.loadClass(triggerClassName);
155         } catch (ClassNotFoundException JavaDoc e) {
156             valid = false;
157             cl = DefaultTrigger.class;
158         }
159
160         try {
161
162             // dynamically instantiate it
163
trigger = (Trigger) cl.newInstance();
164         } catch (Exception JavaDoc e) {
165             valid = false;
166             cl = DefaultTrigger.class;
167         }
168     }
169
170     /**
171      * Retrieves the SQL character sequence required to (re)create the
172      * trigger, as a StringBuffer
173      *
174      * @return the SQL character sequence required to (re)create the
175      * trigger
176      */

177     public StringBuffer JavaDoc getDDL() {
178
179         StringBuffer JavaDoc a = new StringBuffer JavaDoc(256);
180
181         a.append(Token.T_CREATE).append(' ');
182         a.append(Token.T_TRIGGER).append(' ');
183         a.append(name.statementName).append(' ');
184         a.append(when).append(' ');
185         a.append(operation).append(' ');
186         a.append(Token.T_ON).append(' ');
187         a.append(table.getName().statementName).append(' ');
188
189         if (forEachRow) {
190             a.append(Token.T_FOR).append(' ');
191             a.append(Token.T_EACH).append(' ');
192             a.append(Token.T_ROW).append(' ');
193         }
194
195         if (nowait) {
196             a.append(Token.T_NOWAIT).append(' ');
197         }
198
199         if (maxRowsQueued != getDefaultQueueSize()) {
200             a.append(Token.T_QUEUE).append(' ');
201             a.append(maxRowsQueued).append(' ');
202         }
203
204         a.append(Token.T_CALL).append(' ');
205         a.append(StringConverter.toQuotedString(triggerClassName, '"',
206                 false));
207
208         return a;
209     }
210
211     /**
212      * SqlToIndex method declaration <P>
213      *
214      * Given the SQL creating the trigger, say what the index to the
215      * HsqlArrayList[] is
216      *
217      * @return index to the HsqlArrayList[]
218      */

219     public int SqlToIndex() {
220
221         int indx;
222
223         if (operation.equals(Token.T_INSERT)) {
224             indx = Trigger.INSERT_AFTER;
225         } else if (operation.equals(Token.T_DELETE)) {
226             indx = Trigger.DELETE_AFTER;
227         } else if (operation.equals(Token.T_UPDATE)) {
228             indx = Trigger.UPDATE_AFTER;
229         } else {
230             return -1;
231         }
232
233         if (when.equals(Token.T_BEFORE)) {
234             indx += NUM_TRIGGER_OPS; // number of operations
235
} else if (!when.equals(Token.T_AFTER)) {
236             return -1;
237         }
238
239         if (forEachRow) {
240             indx += 2 * NUM_TRIGGER_OPS;
241         }
242
243         return indx;
244     }
245
246     public static int indexToRight(int idx) {
247
248         switch (idx) {
249
250             case Trigger.DELETE_AFTER :
251             case Trigger.DELETE_AFTER_ROW :
252             case Trigger.DELETE_BEFORE :
253             case Trigger.DELETE_BEFORE_ROW :
254                 return UserManager.DELETE;
255
256             case Trigger.INSERT_AFTER :
257             case Trigger.INSERT_AFTER_ROW :
258             case Trigger.INSERT_BEFORE :
259             case Trigger.INSERT_BEFORE_ROW :
260                 return UserManager.INSERT;
261
262             case Trigger.UPDATE_AFTER :
263             case Trigger.UPDATE_AFTER_ROW :
264             case Trigger.UPDATE_BEFORE :
265             case Trigger.UPDATE_BEFORE_ROW :
266                 return UserManager.UPDATE;
267
268             default :
269                 return 0;
270         }
271     }
272
273     /**
274      * run method declaration <P>
275      *
276      * the trigger JSP is run in its own thread here. Its job is simply to
277      * wait until it is told by the main thread that it should fire the
278      * trigger.
279      */

280     public void run() {
281
282         while (keepGoing) {
283             TriggerData triggerData = popPair();
284
285             if (triggerData != null) {
286                 if (triggerData.username != null) {
287                     trigger.fire(this.vectorIndex, name.name,
288                                  table.getName().name, triggerData.oldRow,
289                                  triggerData.newRow);
290                 }
291             }
292         }
293     }
294
295     /**
296      * start the thread if this is threaded
297      */

298     public synchronized void start() {
299
300         if (maxRowsQueued != 0) {
301             super.start();
302         }
303     }
304
305     /**
306      * signal the thread to stop
307      */

308     public synchronized void terminate() {
309
310         keepGoing = false;
311
312         notify();
313     }
314
315     /**
316      * pop2 method declaration <P>
317      *
318      * The consumer (trigger) thread waits for an event to be queued <P>
319      *
320      * <B>Note: </B> This push/pop pairing assumes a single producer thread
321      * and a single consumer thread _only_.
322      *
323      * @return Description of the Return Value
324      */

325     synchronized TriggerData popPair() {
326
327         if (rowsQueued == 0) {
328             try {
329                 wait(); // this releases the lock monitor
330
} catch (InterruptedException JavaDoc e) {
331
332                 /* ignore and resume */
333             }
334         }
335
336         rowsQueued--;
337
338         notify(); // notify push's wait
339

340         if (pendingQueue.size() == 0) {
341             return null;
342         } else {
343             return (TriggerData) pendingQueue.removeFirst();
344         }
345     }
346
347     /**
348      * The main thread tells the trigger thread to fire by this call.
349      * If this Trigger is not threaded then the fire method is caled
350      * immediately and executed by the main thread. Otherwise, the row
351      * data objects are added to the queue to be used by the Trigger thread.
352      *
353      * @param row1
354      * @param row2
355      */

356     synchronized void pushPair(Session session, Object JavaDoc[] row1,
357                                Object JavaDoc[] row2) {
358
359         if (maxRowsQueued == 0) {
360             trigger.fire(vectorIndex, name.name, table.getName().name, row1,
361                          row2);
362
363             return;
364         }
365
366         if (rowsQueued >= maxRowsQueued) {
367             if (nowait) {
368                 pendingQueue.removeLast(); // overwrite last
369
} else {
370                 try {
371                     wait();
372                 } catch (InterruptedException JavaDoc e) {
373
374                     /* ignore and resume */
375                 }
376
377                 rowsQueued++;
378             }
379         } else {
380             rowsQueued++;
381         }
382
383         pendingQueue.add(new TriggerData(session, row1, row2));
384         notify(); // notify pop's wait
385
}
386
387     /**
388      * Method declaration
389      *
390      * @return
391      */

392     public boolean isBusy() {
393         return rowsQueued != 0;
394     }
395
396     /**
397      * Method declaration
398      *
399      * @return
400      */

401     public boolean isValid() {
402         return valid;
403     }
404
405     /**
406      * Class to store the data used to fire a trigger. The username attribute
407      * is not used but it allows developers to change the signature of the
408      * fire method of the Trigger class and pass the user name to the Trigger.
409      */

410     class TriggerData {
411
412         public Object JavaDoc[] oldRow;
413         public Object JavaDoc[] newRow;
414         public String JavaDoc username;
415
416         public TriggerData(Session session, Object JavaDoc[] oldRow,
417                            Object JavaDoc[] newRow) {
418
419             this.oldRow = oldRow;
420             this.newRow = newRow;
421             this.username = session.getUsername();
422         }
423     }
424
425     static class DefaultTrigger implements org.hsqldb.Trigger {
426
427         public void fire(int i, String JavaDoc name, String JavaDoc table, Object JavaDoc[] row1,
428                          Object JavaDoc[] row2) {
429             throw new RuntimeException JavaDoc("Missing Trigger class!");
430         }
431     }
432 }
433
Popular Tags