001: /**
002: * com.mckoi.database.jdbc.MConnection 20 Jul 2000
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database.jdbc;
024:
025: import java.io.*;
026: import java.sql.*;
027: import java.util.Properties;
028: import java.util.Vector;
029: import java.util.StringTokenizer;
030: import com.mckoi.database.global.ColumnDescription;
031: import com.mckoi.database.global.ObjectTransfer;
032: import com.mckoi.database.global.StreamableObject;
033: import com.mckoi.util.ByteArrayUtil;
034: import java.util.Hashtable; //#IFDEF(JDBC2.0)
035: import java.util.Map;
036:
037: //#ENDIF
038:
039: /**
040: * JDBC implementation of the connection object to a Mckoi database. The
041: * implementation specifics for how the connection talks with the database
042: * is left up to the implementation of DatabaseInterface.
043: * <p>
044: * This object is thread safe. It may be accessed safely from concurrent
045: * threads.
046: *
047: * @author Tobias Downer
048: */
049:
050: public class MConnection implements Connection, DatabaseCallBack {
051:
052: /**
053: * A cache of all rows retrieved from the server. This cuts down the
054: * number of requests to the server by caching rows that are accessed
055: * frequently. Note that cells are only cached within a ResultSet bounds.
056: * Two different ResultSet's will not share cells in the cache.
057: */
058: private RowCache row_cache;
059:
060: /**
061: * The JDBC URL used to make this connection.
062: */
063: private String url;
064:
065: /**
066: * SQL warnings for this connection.
067: */
068: private SQLWarning head_warning;
069:
070: /**
071: * Set to true if the connection is closed.
072: */
073: private boolean is_closed;
074:
075: /**
076: * Set to true if the connection is in auto-commit mode. (By default,
077: * auto_commit is enabled).
078: */
079: private boolean auto_commit;
080:
081: /**
082: * The interface to the database.
083: */
084: private DatabaseInterface db_interface;
085:
086: /**
087: * The list of trigger listeners registered with the connection.
088: */
089: private Vector trigger_list;
090:
091: /**
092: * A Thread that handles all dispatching of trigger events to the JDBC
093: * client.
094: */
095: private TriggerDispatchThread trigger_thread;
096:
097: /**
098: * If the ResultSet.getObject method should return the raw object type (eg.
099: * BigDecimal for Integer, String for chars, etc) then this is set to false.
100: * If this is true (the default) the 'getObject' methods return the
101: * correct object types as specified by the JDBC specification.
102: */
103: private boolean strict_get_object;
104:
105: /**
106: * If the ResultSetMetaData.getColumnName method should return a succinct
107: * form of the column name as most JDBC implementations do, this should
108: * be set to false (the default). If old style verbose column names should
109: * be returned for compatibility with older Mckoi applications, this is
110: * set to true.
111: */
112: private boolean verbose_column_names;
113:
114: /**
115: * This is set to true if the MResultSet column lookup methods are case
116: * insensitive. This should be set to true for any database that has
117: * case insensitive identifiers.
118: */
119: private boolean case_insensitive_identifiers;
120:
121: /**
122: * A mapping from a streamable object id to InputStream used to represent
123: * the object when being uploaded to the database engine.
124: */
125: private Hashtable s_object_hold;
126:
127: /**
128: * An unique id count given to streamable object being uploaded to the
129: * server.
130: */
131: private long s_object_id;
132:
133: // For synchronization in this object,
134: private Object lock = new Object();
135:
136: /**
137: * Constructor.
138: */
139: public MConnection(String url, DatabaseInterface db_interface,
140: int cache_size, int max_size) {
141: this .url = url;
142: this .db_interface = db_interface;
143: is_closed = true;
144: auto_commit = true;
145: trigger_list = new Vector();
146: strict_get_object = true;
147: verbose_column_names = false;
148: case_insensitive_identifiers = false;
149: row_cache = new RowCache(cache_size, max_size);
150: s_object_hold = new Hashtable();
151: s_object_id = 0;
152: }
153:
154: /**
155: * Toggles strict get object.
156: * <p>
157: * If the 'getObject' method should return the raw object type (eg.
158: * BigDecimal for Integer, String for chars, etc) then this is set to false.
159: * If this is true (the default) the 'getObject' methods return the
160: * correct object types as specified by the JDBC specification.
161: * <p>
162: * The default is true.
163: */
164: public void setStrictGetObject(boolean status) {
165: strict_get_object = status;
166: }
167:
168: /**
169: * Returns true if strict get object is enabled (default).
170: */
171: public boolean isStrictGetObject() {
172: return strict_get_object;
173: }
174:
175: /**
176: * Toggles verbose column names from ResultSetMetaData.
177: * <p>
178: * If this is set to true, getColumnName will return 'APP.Part.id' for a
179: * column name. If it is false getColumnName will return 'id'. This
180: * property is for compatibility with older Mckoi applications.
181: */
182: public void setVerboseColumnNames(boolean status) {
183: verbose_column_names = status;
184: }
185:
186: /**
187: * Returns true if ResultSetMetaData should return verbose column names.
188: */
189: public boolean verboseColumnNames() {
190: return verbose_column_names;
191: }
192:
193: /**
194: * Toggles whether this connection is handling identifiers as case
195: * insensitive or not. If this is true then 'getString("app.id")' will
196: * match against 'APP.id', etc.
197: */
198: public void setCaseInsensitiveIdentifiers(boolean status) {
199: case_insensitive_identifiers = status;
200: }
201:
202: /**
203: * Returns true if the database has case insensitive identifiers.
204: */
205: public boolean isCaseInsensitiveIdentifiers() {
206: return case_insensitive_identifiers;
207: }
208:
209: // private static void printByteArray(byte[] array) {
210: // System.out.println("Length: " + array.length);
211: // for (int i = 0; i < array.length; ++i) {
212: // System.out.print(array[i]);
213: // System.out.print(", ");
214: // }
215: // }
216:
217: /**
218: * Returns the row Cache object for this connection.
219: */
220: protected final RowCache getRowCache() {
221: return row_cache;
222: }
223:
224: /**
225: * Adds a new SQLWarning to the chain.
226: */
227: protected final void addSQLWarning(SQLWarning warning) {
228: synchronized (lock) {
229: if (head_warning == null) {
230: head_warning = warning;
231: } else {
232: head_warning.setNextWarning(warning);
233: }
234: }
235: }
236:
237: /**
238: * Closes this connection by calling the 'dispose' method in the database
239: * interface.
240: */
241: public final void internalClose() throws SQLException {
242: synchronized (lock) {
243: if (!isClosed()) {
244: try {
245: db_interface.dispose();
246: } finally {
247: is_closed = true;
248: }
249: }
250: }
251: }
252:
253: /**
254: * Returns this MConnection wrapped in a MckoiConnection object.
255: */
256: MckoiConnection getMckoiConnection() {
257: return new MckoiConnection(this );
258: }
259:
260: /**
261: * Attempts to login to the database interface with the given default schema,
262: * username and password. If the authentication fails an SQL exception is
263: * generated.
264: */
265: public void login(String default_schema, String username,
266: String password) throws SQLException {
267:
268: synchronized (lock) {
269: if (!is_closed) {
270: throw new SQLException(
271: "Unable to login to connection because it is open.");
272: }
273: }
274:
275: if (username == null || username.equals("") || password == null
276: || password.equals("")) {
277: throw new SQLException(
278: "username or password have not been set.");
279: }
280:
281: // Set the default schema to username if it's null
282: if (default_schema == null) {
283: default_schema = username;
284: }
285:
286: // Login with the username/password
287: boolean li = db_interface.login(default_schema, username,
288: password, this );
289: synchronized (lock) {
290: is_closed = !li;
291: }
292: if (!li) {
293: throw new SQLException("User authentication failed for: "
294: + username);
295: }
296:
297: // Determine if this connection is case insensitive or not,
298: setCaseInsensitiveIdentifiers(false);
299: Statement stmt = createStatement();
300: ResultSet rs = stmt.executeQuery("SHOW CONNECTION_INFO");
301: while (rs.next()) {
302: String key = rs.getString(1);
303: if (key.equals("case_insensitive_identifiers")) {
304: String val = rs.getString(2);
305: setCaseInsensitiveIdentifiers(val.equals("true"));
306: } else if (key.equals("auto_commit")) {
307: String val = rs.getString(2);
308: auto_commit = val.equals("true");
309: }
310: }
311: rs.close();
312: stmt.close();
313:
314: }
315:
316: // ---------- Package Protected ----------
317:
318: /**
319: * Returns the url string used to make this connection.
320: */
321: String getURL() {
322: return url;
323: }
324:
325: /**
326: * Logs into the JDBC server running on a remote machine. Throws an
327: * exception if user authentication fails.
328: */
329: void login(Properties info, String default_schema)
330: throws SQLException {
331:
332: String username = info.getProperty("user", "");
333: String password = info.getProperty("password", "");
334:
335: login(default_schema, username, password);
336: }
337:
338: // /**
339: // * Cancels a result set that is downloading.
340: // */
341: // void cancelResultSet(MResultSet result_set) throws SQLException {
342: // disposeResult(result_set.getResultID());
343: //
344: //// connection_thread.disposeResult(result_set.getResultID());
345: // }
346:
347: /**
348: * Uploads any streamable objects found in an SQLQuery into the database.
349: */
350: private void uploadStreamableObjects(SQLQuery sql)
351: throws SQLException {
352:
353: // Push any streamable objects that are present in the query onto the
354: // server.
355: Object[] vars = sql.getVars();
356: try {
357: for (int i = 0; i < vars.length; ++i) {
358: // For each streamable object.
359: if (vars[i] != null
360: && vars[i] instanceof StreamableObject) {
361: // Buffer size is fixed to 64 KB
362: final int BUF_SIZE = 64 * 1024;
363:
364: StreamableObject s_object = (StreamableObject) vars[i];
365: long offset = 0;
366: final byte type = s_object.getType();
367: final long total_len = s_object.getSize();
368: final long id = s_object.getIdentifier();
369: final byte[] buf = new byte[BUF_SIZE];
370:
371: // Get the InputStream from the StreamableObject hold
372: Object sob_id = new Long(id);
373: InputStream i_stream = (InputStream) s_object_hold
374: .get(sob_id);
375: if (i_stream == null) {
376: throw new RuntimeException(
377: "Assertion failed: Streamable object InputStream is not available.");
378: }
379:
380: while (offset < total_len) {
381: // Fill the buffer
382: int index = 0;
383: final int block_read = (int) Math.min(
384: (long) BUF_SIZE, (total_len - offset));
385: int to_read = block_read;
386: while (to_read > 0) {
387: int count = i_stream.read(buf, index,
388: to_read);
389: if (count == -1) {
390: throw new IOException(
391: "Premature end of stream.");
392: }
393: index += count;
394: to_read -= count;
395: }
396:
397: // Send the part of the streamable object to the database.
398: db_interface.pushStreamableObjectPart(type, id,
399: total_len, buf, offset, block_read);
400: // Increment the offset and upload the next part of the object.
401: offset += block_read;
402: }
403:
404: // Remove the streamable object once it has been written
405: s_object_hold.remove(sob_id);
406:
407: // [ Don't close the input stream - we may only want to put a part of
408: // the stream into the database and keep the file open. ]
409: // // Close the input stream
410: // i_stream.close();
411:
412: }
413: }
414: } catch (IOException e) {
415: e.printStackTrace(System.err);
416: throw new SQLException(
417: "IO Error pushing large object to server: "
418: + e.getMessage());
419: }
420: }
421:
422: /**
423: * Sends the batch of SQLQuery objects to the database to be executed. The
424: * given array of MResultSet will be the consumer objects for the query
425: * results. If a query succeeds then we are guarenteed to know that size of
426: * the result set.
427: * <p>
428: * This method blocks until all of the queries have been processed by the
429: * database.
430: */
431: void executeQueries(SQLQuery[] queries, MResultSet[] results)
432: throws SQLException {
433: // For each query
434: for (int i = 0; i < queries.length; ++i) {
435: executeQuery(queries[i], results[i]);
436: }
437: }
438:
439: /**
440: * Sends the SQL string to the database to be executed. The given MResultSet
441: * is the consumer for the results from the database. We are guarenteed
442: * that if the query succeeds that we know the size of the result set and
443: * at least first first row of the set.
444: * <p>
445: * This method will block until we have received the result header
446: * information.
447: */
448: void executeQuery(SQLQuery sql, MResultSet result_set)
449: throws SQLException {
450:
451: uploadStreamableObjects(sql);
452: // Execute the query,
453: QueryResponse resp = db_interface.execQuery(sql);
454:
455: // The format of the result
456: ColumnDescription[] col_list = new ColumnDescription[resp
457: .getColumnCount()];
458: for (int i = 0; i < col_list.length; ++i) {
459: col_list[i] = resp.getColumnDescription(i);
460: }
461: // Set up the result set to the result format and update the time taken to
462: // execute the query on the server.
463: result_set.connSetup(resp.getResultID(), col_list, resp
464: .getRowCount());
465: result_set.setQueryTime(resp.getQueryTimeMillis());
466:
467: }
468:
469: /**
470: * Called by MResultSet to query a part of a result from the server. Returns
471: * a List that represents the result from the server.
472: */
473: ResultPart requestResultPart(int result_id, int start_row,
474: int count_rows) throws SQLException {
475: return db_interface.getResultPart(result_id, start_row,
476: count_rows);
477: }
478:
479: /**
480: * Requests a part of a streamable object from the server.
481: */
482: StreamableObjectPart requestStreamableObjectPart(int result_id,
483: long streamable_object_id, long offset, int len)
484: throws SQLException {
485: return db_interface.getStreamableObjectPart(result_id,
486: streamable_object_id, offset, len);
487: }
488:
489: /**
490: * Disposes of the server-side resources associated with the result set with
491: * result_id. This should be called either before we start the download of
492: * a new result set, or when we have finished with the resources of a result
493: * set.
494: */
495: void disposeResult(int result_id) throws SQLException {
496: // Clear the row cache.
497: // It would be better if we only cleared row entries with this
498: // table_id. We currently clear the entire cache which means there will
499: // be traffic created for other open result sets.
500: // System.out.println(result_id);
501: // row_cache.clear();
502: // Only dispose if the connection is open
503: if (!is_closed) {
504: db_interface.disposeResult(result_id);
505: }
506: }
507:
508: /**
509: * Adds a TriggerListener that listens for all triggers events with the name
510: * given. Triggers are created with the 'CREATE TRIGGER' syntax.
511: */
512: void addTriggerListener(String trigger_name,
513: TriggerListener listener) {
514: synchronized (trigger_list) {
515: trigger_list.addElement(trigger_name);
516: trigger_list.addElement(listener);
517: }
518: }
519:
520: /**
521: * Removes the TriggerListener for the given trigger name.
522: */
523: void removeTriggerListener(String trigger_name,
524: TriggerListener listener) {
525: synchronized (trigger_list) {
526: for (int i = trigger_list.size() - 2; i >= 0; i -= 2) {
527: if (trigger_list.elementAt(i).equals(trigger_name)
528: && trigger_list.elementAt(i + 1).equals(
529: listener)) {
530: trigger_list.removeElementAt(i);
531: trigger_list.removeElementAt(i);
532: }
533: }
534: }
535: }
536:
537: /**
538: * Creates a StreamableObject on the JDBC client side given an InputStream,
539: * and length and a type. When this method returns, a StreamableObject
540: * entry will be added to the hold.
541: */
542: StreamableObject createStreamableObject(InputStream x, int length,
543: byte type) {
544: long ob_id;
545: synchronized (s_object_hold) {
546: ob_id = s_object_id;
547: ++s_object_id;
548: // Add the stream to the hold and get the unique id
549: s_object_hold.put(new Long(ob_id), x);
550: }
551: // Create and return the StreamableObject
552: return new StreamableObject(type, length, ob_id);
553: }
554:
555: /**
556: * Removes the StreamableObject from the hold on the JDBC client. This should
557: * be called when the MPreparedStatement closes.
558: */
559: void removeStreamableObject(StreamableObject s_object) {
560: s_object_hold.remove(new Long(s_object.getIdentifier()));
561: }
562:
563: // ---------- Implemented from DatabaseCallBack ----------
564:
565: // NOTE: For JDBC standalone apps, the thread that calls this will be a
566: // WorkerThread.
567: // For JDBC client/server apps, the thread that calls this will by the
568: // connection thread that listens for data from the server.
569: public void databaseEvent(int event_type, String event_message) {
570: if (event_type == 99) {
571: if (trigger_thread == null) {
572: trigger_thread = new TriggerDispatchThread();
573: trigger_thread.start();
574: }
575: trigger_thread.dispatchTrigger(event_message);
576: } else {
577: throw new Error("Unrecognised database event: "
578: + event_type);
579: }
580:
581: // System.out.println("[com.mckoi.jdbc.MConnection] Event received:");
582: // System.out.println(event_type);
583: // System.out.println(event_message);
584: }
585:
586: // ---------- Implemented from Connection ----------
587:
588: public Statement createStatement() throws SQLException {
589: return new MStatement(this );
590: }
591:
592: public PreparedStatement prepareStatement(String sql)
593: throws SQLException {
594: return new MPreparedStatement(this , sql);
595: }
596:
597: public CallableStatement prepareCall(String sql)
598: throws SQLException {
599: throw MSQLException.unsupported();
600: }
601:
602: public String nativeSQL(String sql) throws SQLException {
603: // We don't do any client side parsing of the sql statement.
604: return sql;
605: }
606:
607: public void setAutoCommit(boolean autoCommit) throws SQLException {
608: // The SQL to put into auto-commit mode.
609: ResultSet result;
610: if (autoCommit) {
611: result = createStatement().executeQuery(
612: "SET AUTO COMMIT ON");
613: auto_commit = true;
614: result.close();
615: } else {
616: result = createStatement().executeQuery(
617: "SET AUTO COMMIT OFF");
618: auto_commit = false;
619: result.close();
620: }
621: }
622:
623: public boolean getAutoCommit() throws SQLException {
624: return auto_commit;
625: // // Query the database for this info.
626: // ResultSet result;
627: // result = createStatement().executeQuery(
628: // "SHOW CONNECTION_INFO WHERE var = 'auto_commit'");
629: // boolean auto_commit_mode = false;
630: // if (result.next()) {
631: // auto_commit_mode = result.getString(2).equals("true");
632: // }
633: // result.close();
634: // return auto_commit_mode;
635: }
636:
637: public void commit() throws SQLException {
638: ResultSet result;
639: result = createStatement().executeQuery("COMMIT");
640: result.close();
641: }
642:
643: public void rollback() throws SQLException {
644: ResultSet result;
645: result = createStatement().executeQuery("ROLLBACK");
646: result.close();
647: }
648:
649: public void close() throws SQLException {
650:
651: if (!isClosed()) {
652: internalClose();
653: }
654:
655: // if (!isClosed()) {
656: // try {
657: // internalClose();
658: // }
659: // finally {
660: // MDriver.connectionClosed(this);
661: // }
662: // }
663:
664: // synchronized (lock) {
665: // if (!isClosed()) {
666: // try {
667: // db_interface.dispose();
668: // MDriver.connectionClosed(this);
669: // }
670: // finally {
671: // is_closed = true;
672: // }
673: // }
674: // }
675: }
676:
677: public boolean isClosed() throws SQLException {
678: synchronized (lock) {
679: return is_closed;
680: }
681: }
682:
683: //======================================================================
684: // Advanced features:
685:
686: public DatabaseMetaData getMetaData() throws SQLException {
687: return new MDatabaseMetaData(this );
688: }
689:
690: public void setReadOnly(boolean readOnly) throws SQLException {
691: // Hint ignored
692: }
693:
694: public boolean isReadOnly() throws SQLException {
695: // Currently we don't support read locked transactions.
696: return false;
697: }
698:
699: public void setCatalog(String catalog) throws SQLException {
700: // Silently ignored ;-)
701: }
702:
703: public String getCatalog() throws SQLException {
704: // Catalog's not supported
705: return null;
706: }
707:
708: public void setTransactionIsolation(int level) throws SQLException {
709: if (level != TRANSACTION_SERIALIZABLE) {
710: throw new SQLException(
711: "Only 'TRANSACTION_SERIALIZABLE' supported.");
712: }
713: }
714:
715: public int getTransactionIsolation() throws SQLException {
716: return TRANSACTION_SERIALIZABLE;
717: }
718:
719: public SQLWarning getWarnings() throws SQLException {
720: synchronized (lock) {
721: return head_warning;
722: }
723: }
724:
725: public void clearWarnings() throws SQLException {
726: synchronized (lock) {
727: head_warning = null;
728: }
729: }
730:
731: //#IFDEF(JDBC2.0)
732:
733: //--------------------------JDBC 2.0-----------------------------
734:
735: public Statement createStatement(int resultSetType,
736: int resultSetConcurrency) throws SQLException {
737: Statement statement = createStatement();
738: // PENDING - set default result set type and result set concurrency for
739: // statement
740: return statement;
741: }
742:
743: public PreparedStatement prepareStatement(String sql,
744: int resultSetType, int resultSetConcurrency)
745: throws SQLException {
746: PreparedStatement statement = prepareStatement(sql);
747: // PENDING - set default result set type and result set concurrency for
748: // statement
749: return statement;
750: }
751:
752: public CallableStatement prepareCall(String sql, int resultSetType,
753: int resultSetConcurrency) throws SQLException {
754: throw MSQLException.unsupported();
755: }
756:
757: // ISSUE: I can see using 'Map' here is going to break compatibility with
758: // Java 1.1. Even though testing with 1.1.8 on Linux and NT turned out
759: // fine, I have a feeling some verifiers on web browsers aren't going to
760: // like this.
761: public Map getTypeMap() throws SQLException {
762: throw MSQLException.unsupported();
763: }
764:
765: public void setTypeMap(Map map) throws SQLException {
766: throw MSQLException.unsupported();
767: }
768:
769: //#ENDIF
770:
771: //#IFDEF(JDBC3.0)
772:
773: //--------------------------JDBC 3.0-----------------------------
774:
775: public void setHoldability(int holdability) throws SQLException {
776: // Currently holdability can not be set to CLOSE_CURSORS_AT_COMMIT though
777: // it could be implemented.
778: if (holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
779: throw new SQLException(
780: "CLOSE_CURSORS_AT_COMMIT holdability is not supported.");
781: }
782: }
783:
784: public int getHoldability() throws SQLException {
785: return ResultSet.HOLD_CURSORS_OVER_COMMIT;
786: }
787:
788: public Savepoint setSavepoint() throws SQLException {
789: throw MSQLException.unsupported();
790: }
791:
792: public Savepoint setSavepoint(String name) throws SQLException {
793: throw MSQLException.unsupported();
794: }
795:
796: public void rollback(Savepoint savepoint) throws SQLException {
797: throw MSQLException.unsupported();
798: }
799:
800: public void releaseSavepoint(Savepoint savepoint)
801: throws SQLException {
802: throw MSQLException.unsupported();
803: }
804:
805: public Statement createStatement(int resultSetType,
806: int resultSetConcurrency, int resultSetHoldability)
807: throws SQLException {
808: // Currently holdability can not be set to CLOSE_CURSORS_AT_COMMIT though
809: // it could be implemented.
810: if (resultSetHoldability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
811: throw new SQLException(
812: "CLOSE_CURSORS_AT_COMMIT holdability is not supported.");
813: }
814: return createStatement(resultSetType, resultSetConcurrency);
815: }
816:
817: public PreparedStatement prepareStatement(String sql,
818: int resultSetType, int resultSetConcurrency,
819: int resultSetHoldability) throws SQLException {
820: // Currently holdability can not be set to CLOSE_CURSORS_AT_COMMIT though
821: // it could be implemented.
822: if (resultSetHoldability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
823: throw new SQLException(
824: "CLOSE_CURSORS_AT_COMMIT holdability is not supported.");
825: }
826: return prepareStatement(sql, resultSetType,
827: resultSetConcurrency);
828: }
829:
830: public CallableStatement prepareCall(String sql, int resultSetType,
831: int resultSetConcurrency, int resultSetHoldability)
832: throws SQLException {
833: throw MSQLException.unsupported();
834: }
835:
836: public PreparedStatement prepareStatement(String sql,
837: int autoGeneratedKeys) throws SQLException {
838: throw MSQLException.unsupported();
839: }
840:
841: public PreparedStatement prepareStatement(String sql,
842: int columnIndexes[]) throws SQLException {
843: throw MSQLException.unsupported();
844: }
845:
846: public PreparedStatement prepareStatement(String sql,
847: String columnNames[]) throws SQLException {
848: throw MSQLException.unsupported();
849: }
850:
851: //#ENDIF
852:
853: // ---------- Inner classes ----------
854:
855: /**
856: * The thread that handles all dispatching of trigger events.
857: */
858: private class TriggerDispatchThread extends Thread {
859:
860: private Vector trigger_messages_queue = new Vector();
861:
862: TriggerDispatchThread() {
863: setDaemon(true);
864: setName("Mckoi - Trigger Dispatcher");
865: }
866:
867: /**
868: * Dispatches a trigger message to the listeners.
869: */
870: private void dispatchTrigger(String event_message) {
871: synchronized (trigger_messages_queue) {
872: trigger_messages_queue.addElement(event_message);
873: trigger_messages_queue.notifyAll();
874: }
875: }
876:
877: // Thread run method
878: public void run() {
879:
880: while (true) {
881: try {
882: String message;
883: synchronized (trigger_messages_queue) {
884: while (trigger_messages_queue.size() == 0) {
885: try {
886: trigger_messages_queue.wait();
887: } catch (InterruptedException e) { /* ignore */
888: }
889: }
890: message = (String) trigger_messages_queue
891: .elementAt(0);
892: trigger_messages_queue.removeElementAt(0);
893: }
894:
895: // 'message' is a message to process...
896: // The format of a trigger message is:
897: // "[trigger_name] [trigger_source] [trigger_fire_count]"
898: // System.out.println("TRIGGER EVENT: " + message);
899:
900: StringTokenizer tok = new StringTokenizer(message,
901: " ");
902: String trigger_name = (String) tok.nextElement();
903: String trigger_source = (String) tok.nextElement();
904: String trigger_fire_count = (String) tok
905: .nextElement();
906:
907: Vector fired_triggers = new Vector();
908: // Create a list of Listener's that are listening for this trigger.
909: synchronized (trigger_list) {
910: for (int i = 0; i < trigger_list.size(); i += 2) {
911: String to_listen_for = (String) trigger_list
912: .elementAt(i);
913: if (to_listen_for.equals(trigger_name)) {
914: TriggerListener listener = (TriggerListener) trigger_list
915: .elementAt(i + 1);
916: // NOTE, we can't call 'listener.triggerFired' here because
917: // it's not a good idea to call user code when we are
918: // synchronized over 'trigger_list' (deadlock concerns).
919: fired_triggers.addElement(listener);
920: }
921: }
922: }
923:
924: // Fire them triggers.
925: for (int i = 0; i < fired_triggers.size(); ++i) {
926: TriggerListener listener = (TriggerListener) fired_triggers
927: .elementAt(i);
928: listener.triggerFired(trigger_name);
929: }
930:
931: } catch (Throwable t) {
932: t.printStackTrace(System.err);
933: }
934:
935: }
936:
937: }
938:
939: }
940:
941: }
|