001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import org.hsqldb.HsqlNameManager.HsqlName;
034: import org.hsqldb.lib.HsqlDeque;
035: import org.hsqldb.lib.StringConverter;
036: import org.hsqldb.lib.StringUtil;
037:
038: // peterhudson@users 20020130 - patch 478657 by peterhudson - triggers support
039: // fredt@users 20020130 - patch 1.7.0 by fredt
040: // added new class as jdk 1.1 does not allow use of LinkedList
041: // fredt@users 20030727 - signature and other alterations
042: // fredt@users 20040430 - changes by mattshaw@users to allow termination of the
043: // trigger thread -
044:
045: /**
046: * Represents an HSQLDB Trigger definition. <p>
047: *
048: * Provides services regarding HSLDB Trigger execution and metadata. <p>
049: *
050: * Development of the trigger implementation sponsored by Logicscope
051: * Realisations Ltd
052: *
053: * @author Peter Hudson - Logicscope Realisations Ltd
054: * @version 1.7.0 (1.0.0.3)
055: * Revision History: 1.0.0.1 First release in hsqldb 1.61
056: * 1.0.0.2 'nowait' support to prevent deadlock 1.0.0.3 multiple row
057: * queue for each trigger
058: */
059: class TriggerDef extends Thread {
060:
061: /**
062: * member variables
063: */
064: static final int NUM_TRIGGER_OPS = 3; // {ins,del,upd}
065: static final int NUM_TRIGS = NUM_TRIGGER_OPS * 2 * 2; // {b, a},{fer, fes}
066:
067: // other variables
068: HsqlName name;
069: String when;
070: String operation;
071: boolean forEachRow;
072: boolean nowait; // block or overwrite if queue full
073: int maxRowsQueued; // max size of queue of pending triggers
074:
075: /**
076: * Retrieves the queue size assigned to trigger definitions when no
077: * queue size is explicitly declared. <p>
078: *
079: * @return the queue size assigned to trigger definitions when no
080: * queue size is explicitly declared
081: */
082: public static int getDefaultQueueSize() {
083: return defaultQueueSize;
084: }
085:
086: protected static int defaultQueueSize = 1024;
087: Table table;
088: Trigger trigger;
089: String triggerClassName;
090: int vectorIndex; // index into HsqlArrayList[]
091:
092: //protected boolean busy; // firing trigger in progress
093: protected HsqlDeque pendingQueue; // row triggers pending
094: protected int rowsQueued; // rows in pendingQueue
095: protected boolean valid = true; // parsing valid
096: protected volatile boolean keepGoing = true;
097:
098: /**
099: * Constructs a new TriggerDef object to represent an HSQLDB trigger
100: * declared in an SQL CREATE TRIGGER statement.
101: *
102: * Changes in 1.7.2 allow the queue size to be specified as 0. A zero
103: * queue size causes the Trigger.fire() code to run in the main thread of
104: * execution (fully inside the enclosing transaction). Otherwise, the code
105: * is run in the Trigger's own thread.
106: * (fredt@users)
107: *
108: * @param name The trigger object's HsqlName
109: * @param when the String representation of whether the trigger fires
110: * before or after the triggering event
111: * @param operation the String representation of the triggering operation;
112: * currently insert, update, or delete
113: * @param forEach indicates whether the trigger is fired for each row
114: * (true) or statement (false)
115: * @param table the Table object upon which the indicated operation
116: * fires the trigger
117: * @param triggerClassName the fully qualified named of the class implementing
118: * the org.hsqldb.Trigger (trigger body) interface
119: * @param noWait do not wait for available space on the pending queue; if
120: * the pending queue does not have fewer than nQueueSize queued items,
121: * then overwrite the current tail instead
122: * @param queueSize the length to which the pending queue may grow before
123: * further additions are either blocked or overwrite the tail entry,
124: * as determined by noWait
125: * @throws HsqlException - Invalid input parameter
126: */
127: public TriggerDef(HsqlNameManager.HsqlName name, String when,
128: String operation, boolean forEach, Table table,
129: String triggerClassName, boolean noWait, int queueSize,
130: ClassLoader loader) throws HsqlException {
131:
132: this .name = name;
133: this .when = when;
134: this .operation = operation;
135: this .forEachRow = forEach;
136: this .nowait = noWait;
137: this .maxRowsQueued = queueSize;
138: this .table = table;
139: vectorIndex = SqlToIndex();
140: this .triggerClassName = triggerClassName;
141: rowsQueued = 0;
142: pendingQueue = new HsqlDeque();
143:
144: if (vectorIndex < 0) {
145: throw Trace.error(Trace.UNEXPECTED_TOKEN,
146: Trace.CREATE_TRIGGER_COMMAND_1);
147: }
148:
149: Class cl;
150:
151: try {
152: cl = loader == null ? Class.forName(triggerClassName)
153: : loader.loadClass(triggerClassName);
154: } catch (ClassNotFoundException e) {
155: valid = false;
156: cl = DefaultTrigger.class;
157: }
158:
159: try {
160:
161: // dynamically instantiate it
162: trigger = (Trigger) cl.newInstance();
163: } catch (Exception e) {
164: valid = false;
165: cl = DefaultTrigger.class;
166: }
167: }
168:
169: /**
170: * Retrieves the SQL character sequence required to (re)create the
171: * trigger, as a StringBuffer
172: *
173: * @return the SQL character sequence required to (re)create the
174: * trigger
175: */
176: public StringBuffer getDDL() {
177:
178: StringBuffer a = new StringBuffer(256);
179:
180: a.append(Token.T_CREATE).append(' ');
181: a.append(Token.T_TRIGGER).append(' ');
182: a.append(name.statementName).append(' ');
183: a.append(when).append(' ');
184: a.append(operation).append(' ');
185: a.append(Token.T_ON).append(' ');
186: a.append(table.getName().statementName).append(' ');
187:
188: if (forEachRow) {
189: a.append(Token.T_FOR).append(' ');
190: a.append(Token.T_EACH).append(' ');
191: a.append(Token.T_ROW).append(' ');
192: }
193:
194: if (nowait) {
195: a.append(Token.T_NOWAIT).append(' ');
196: }
197:
198: if (maxRowsQueued != getDefaultQueueSize()) {
199: a.append(Token.T_QUEUE).append(' ');
200: a.append(maxRowsQueued).append(' ');
201: }
202:
203: a.append(Token.T_CALL).append(' ');
204: a.append(StringConverter.toQuotedString(triggerClassName, '"',
205: false));
206:
207: return a;
208: }
209:
210: /**
211: * SqlToIndex method declaration <P>
212: *
213: * Given the SQL creating the trigger, say what the index to the
214: * HsqlArrayList[] is
215: *
216: * @return index to the HsqlArrayList[]
217: */
218: public int SqlToIndex() {
219:
220: int indx;
221:
222: if (operation.equals(Token.T_INSERT)) {
223: indx = Trigger.INSERT_AFTER;
224: } else if (operation.equals(Token.T_DELETE)) {
225: indx = Trigger.DELETE_AFTER;
226: } else if (operation.equals(Token.T_UPDATE)) {
227: indx = Trigger.UPDATE_AFTER;
228: } else {
229: return -1;
230: }
231:
232: if (when.equals(Token.T_BEFORE)) {
233: indx += NUM_TRIGGER_OPS; // number of operations
234: } else if (!when.equals(Token.T_AFTER)) {
235: return -1;
236: }
237:
238: if (forEachRow) {
239: indx += 2 * NUM_TRIGGER_OPS;
240: }
241:
242: return indx;
243: }
244:
245: public static int indexToRight(int idx) {
246:
247: switch (idx) {
248:
249: case Trigger.DELETE_AFTER:
250: case Trigger.DELETE_AFTER_ROW:
251: case Trigger.DELETE_BEFORE:
252: case Trigger.DELETE_BEFORE_ROW:
253: return UserManager.DELETE;
254:
255: case Trigger.INSERT_AFTER:
256: case Trigger.INSERT_AFTER_ROW:
257: case Trigger.INSERT_BEFORE:
258: case Trigger.INSERT_BEFORE_ROW:
259: return UserManager.INSERT;
260:
261: case Trigger.UPDATE_AFTER:
262: case Trigger.UPDATE_AFTER_ROW:
263: case Trigger.UPDATE_BEFORE:
264: case Trigger.UPDATE_BEFORE_ROW:
265: return UserManager.UPDATE;
266:
267: default:
268: return 0;
269: }
270: }
271:
272: /**
273: * run method declaration <P>
274: *
275: * the trigger JSP is run in its own thread here. Its job is simply to
276: * wait until it is told by the main thread that it should fire the
277: * trigger.
278: */
279: public void run() {
280:
281: while (keepGoing) {
282: TriggerData triggerData = popPair();
283:
284: if (triggerData != null) {
285: if (triggerData.username != null) {
286: trigger.fire(this .vectorIndex, name.name, table
287: .getName().name, triggerData.oldRow,
288: triggerData.newRow);
289: }
290: }
291: }
292: }
293:
294: /**
295: * start the thread if this is threaded
296: */
297: public synchronized void start() {
298:
299: if (maxRowsQueued != 0) {
300: super .start();
301: }
302: }
303:
304: /**
305: * signal the thread to stop
306: */
307: public synchronized void terminate() {
308:
309: keepGoing = false;
310:
311: notify();
312: }
313:
314: /**
315: * pop2 method declaration <P>
316: *
317: * The consumer (trigger) thread waits for an event to be queued <P>
318: *
319: * <B>Note: </B> This push/pop pairing assumes a single producer thread
320: * and a single consumer thread _only_.
321: *
322: * @return Description of the Return Value
323: */
324: synchronized TriggerData popPair() {
325:
326: if (rowsQueued == 0) {
327: try {
328: wait(); // this releases the lock monitor
329: } catch (InterruptedException e) {
330:
331: /* ignore and resume */
332: }
333: }
334:
335: rowsQueued--;
336:
337: notify(); // notify push's wait
338:
339: if (pendingQueue.size() == 0) {
340: return null;
341: } else {
342: return (TriggerData) pendingQueue.removeFirst();
343: }
344: }
345:
346: /**
347: * The main thread tells the trigger thread to fire by this call.
348: * If this Trigger is not threaded then the fire method is caled
349: * immediately and executed by the main thread. Otherwise, the row
350: * data objects are added to the queue to be used by the Trigger thread.
351: *
352: * @param row1
353: * @param row2
354: */
355: synchronized void pushPair(Session session, Object[] row1,
356: Object[] row2) {
357:
358: if (maxRowsQueued == 0) {
359: trigger.fire(vectorIndex, name.name, table.getName().name,
360: row1, row2);
361:
362: return;
363: }
364:
365: if (rowsQueued >= maxRowsQueued) {
366: if (nowait) {
367: pendingQueue.removeLast(); // overwrite last
368: } else {
369: try {
370: wait();
371: } catch (InterruptedException e) {
372:
373: /* ignore and resume */
374: }
375:
376: rowsQueued++;
377: }
378: } else {
379: rowsQueued++;
380: }
381:
382: pendingQueue.add(new TriggerData(session, row1, row2));
383: notify(); // notify pop's wait
384: }
385:
386: /**
387: * Method declaration
388: *
389: * @return
390: */
391: public boolean isBusy() {
392: return rowsQueued != 0;
393: }
394:
395: /**
396: * Method declaration
397: *
398: * @return
399: */
400: public boolean isValid() {
401: return valid;
402: }
403:
404: /**
405: * Class to store the data used to fire a trigger. The username attribute
406: * is not used but it allows developers to change the signature of the
407: * fire method of the Trigger class and pass the user name to the Trigger.
408: */
409: class TriggerData {
410:
411: public Object[] oldRow;
412: public Object[] newRow;
413: public String username;
414:
415: public TriggerData(Session session, Object[] oldRow,
416: Object[] newRow) {
417:
418: this .oldRow = oldRow;
419: this .newRow = newRow;
420: this .username = session.getUsername();
421: }
422: }
423:
424: static class DefaultTrigger implements org.hsqldb.Trigger {
425:
426: public void fire(int i, String name, String table,
427: Object[] row1, Object[] row2) {
428: throw new RuntimeException("Missing Trigger class!");
429: }
430: }
431: }
|