KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mckoi > database > jdbc > MConnection


1 /**
2  * com.mckoi.database.jdbc.MConnection 20 Jul 2000
3  *
4  * Mckoi SQL Database ( http://www.mckoi.com/database )
5  * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * Version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License Version 2 for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * Version 2 along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  * Change Log:
21  *
22  *
23  */

24
25 package com.mckoi.database.jdbc;
26
27 import java.io.*;
28 import java.sql.*;
29 import java.util.Properties JavaDoc;
30 import java.util.Vector JavaDoc;
31 import java.util.StringTokenizer JavaDoc;
32 import com.mckoi.database.global.ColumnDescription;
33 import com.mckoi.database.global.ObjectTransfer;
34 import com.mckoi.database.global.StreamableObject;
35 import com.mckoi.util.ByteArrayUtil;
36 import java.util.Hashtable JavaDoc;
37 //#IFDEF(JDBC2.0)
38
import java.util.Map JavaDoc;
39 //#ENDIF
40

41 /**
42  * JDBC implementation of the connection object to a Mckoi database. The
43  * implementation specifics for how the connection talks with the database
44  * is left up to the implementation of DatabaseInterface.
45  * <p>
46  * This object is thread safe. It may be accessed safely from concurrent
47  * threads.
48  *
49  * @author Tobias Downer
50  */

51
52 public class MConnection implements Connection, DatabaseCallBack {
53
54   /**
55    * A cache of all rows retrieved from the server. This cuts down the
56    * number of requests to the server by caching rows that are accessed
57    * frequently. Note that cells are only cached within a ResultSet bounds.
58    * Two different ResultSet's will not share cells in the cache.
59    */

60   private RowCache row_cache;
61
62   /**
63    * The JDBC URL used to make this connection.
64    */

65   private String JavaDoc url;
66
67   /**
68    * SQL warnings for this connection.
69    */

70   private SQLWarning head_warning;
71
72   /**
73    * Set to true if the connection is closed.
74    */

75   private boolean is_closed;
76
77   /**
78    * Set to true if the connection is in auto-commit mode. (By default,
79    * auto_commit is enabled).
80    */

81   private boolean auto_commit;
82
83   /**
84    * The interface to the database.
85    */

86   private DatabaseInterface db_interface;
87
88   /**
89    * The list of trigger listeners registered with the connection.
90    */

91   private Vector JavaDoc trigger_list;
92
93   /**
94    * A Thread that handles all dispatching of trigger events to the JDBC
95    * client.
96    */

97   private TriggerDispatchThread trigger_thread;
98
99   /**
100    * If the ResultSet.getObject method should return the raw object type (eg.
101    * BigDecimal for Integer, String for chars, etc) then this is set to false.
102    * If this is true (the default) the 'getObject' methods return the
103    * correct object types as specified by the JDBC specification.
104    */

105   private boolean strict_get_object;
106
107   /**
108    * If the ResultSetMetaData.getColumnName method should return a succinct
109    * form of the column name as most JDBC implementations do, this should
110    * be set to false (the default). If old style verbose column names should
111    * be returned for compatibility with older Mckoi applications, this is
112    * set to true.
113    */

114   private boolean verbose_column_names;
115
116   /**
117    * This is set to true if the MResultSet column lookup methods are case
118    * insensitive. This should be set to true for any database that has
119    * case insensitive identifiers.
120    */

121   private boolean case_insensitive_identifiers;
122
123   /**
124    * A mapping from a streamable object id to InputStream used to represent
125    * the object when being uploaded to the database engine.
126    */

127   private Hashtable JavaDoc s_object_hold;
128
129   /**
130    * An unique id count given to streamable object being uploaded to the
131    * server.
132    */

133   private long s_object_id;
134   
135   
136
137   // For synchronization in this object,
138
private Object JavaDoc lock = new Object JavaDoc();
139
140
141
142   /**
143    * Constructor.
144    */

145   public MConnection(String JavaDoc url, DatabaseInterface db_interface,
146                      int cache_size, int max_size) {
147     this.url = url;
148     this.db_interface = db_interface;
149     is_closed = true;
150     auto_commit = true;
151     trigger_list = new Vector JavaDoc();
152     strict_get_object = true;
153     verbose_column_names = false;
154     case_insensitive_identifiers = false;
155     row_cache = new RowCache(cache_size, max_size);
156     s_object_hold = new Hashtable JavaDoc();
157     s_object_id = 0;
158   }
159
160   /**
161    * Toggles strict get object.
162    * <p>
163    * If the 'getObject' method should return the raw object type (eg.
164    * BigDecimal for Integer, String for chars, etc) then this is set to false.
165    * If this is true (the default) the 'getObject' methods return the
166    * correct object types as specified by the JDBC specification.
167    * <p>
168    * The default is true.
169    */

170   public void setStrictGetObject(boolean status) {
171     strict_get_object = status;
172   }
173
174   /**
175    * Returns true if strict get object is enabled (default).
176    */

177   public boolean isStrictGetObject() {
178     return strict_get_object;
179   }
180
181   /**
182    * Toggles verbose column names from ResultSetMetaData.
183    * <p>
184    * If this is set to true, getColumnName will return 'APP.Part.id' for a
185    * column name. If it is false getColumnName will return 'id'. This
186    * property is for compatibility with older Mckoi applications.
187    */

188   public void setVerboseColumnNames(boolean status) {
189     verbose_column_names = status;
190   }
191
192   /**
193    * Returns true if ResultSetMetaData should return verbose column names.
194    */

195   public boolean verboseColumnNames() {
196     return verbose_column_names;
197   }
198
199   /**
200    * Toggles whether this connection is handling identifiers as case
201    * insensitive or not. If this is true then 'getString("app.id")' will
202    * match against 'APP.id', etc.
203    */

204   public void setCaseInsensitiveIdentifiers(boolean status) {
205     case_insensitive_identifiers = status;
206   }
207
208   /**
209    * Returns true if the database has case insensitive identifiers.
210    */

211   public boolean isCaseInsensitiveIdentifiers() {
212     return case_insensitive_identifiers;
213   }
214
215
216 // private static void printByteArray(byte[] array) {
217
// System.out.println("Length: " + array.length);
218
// for (int i = 0; i < array.length; ++i) {
219
// System.out.print(array[i]);
220
// System.out.print(", ");
221
// }
222
// }
223

224   /**
225    * Returns the row Cache object for this connection.
226    */

227   protected final RowCache getRowCache() {
228     return row_cache;
229   }
230
231   /**
232    * Adds a new SQLWarning to the chain.
233    */

234   protected final void addSQLWarning(SQLWarning warning) {
235     synchronized (lock) {
236       if (head_warning == null) {
237         head_warning = warning;
238       }
239       else {
240         head_warning.setNextWarning(warning);
241       }
242     }
243   }
244
245   /**
246    * Closes this connection by calling the 'dispose' method in the database
247    * interface.
248    */

249   public final void internalClose() throws SQLException {
250     synchronized (lock) {
251       if (!isClosed()) {
252         try {
253           db_interface.dispose();
254         }
255         finally {
256           is_closed = true;
257         }
258       }
259     }
260   }
261
262   /**
263    * Returns this MConnection wrapped in a MckoiConnection object.
264    */

265   MckoiConnection getMckoiConnection() {
266     return new MckoiConnection(this);
267   }
268
269   /**
270    * Attempts to login to the database interface with the given default schema,
271    * username and password. If the authentication fails an SQL exception is
272    * generated.
273    */

274   public void login(String JavaDoc default_schema, String JavaDoc username, String JavaDoc password)
275                                                          throws SQLException {
276
277     synchronized (lock) {
278       if (!is_closed) {
279         throw new SQLException(
280                         "Unable to login to connection because it is open.");
281       }
282     }
283
284     if (username == null || username.equals("") ||
285         password == null || password.equals("")) {
286       throw new SQLException("username or password have not been set.");
287     }
288
289     // Set the default schema to username if it's null
290
if (default_schema == null) {
291       default_schema = username;
292     }
293
294     // Login with the username/password
295
boolean li = db_interface.login(default_schema, username, password, this);
296     synchronized (lock) {
297       is_closed = !li;
298     }
299     if (!li) {
300       throw new SQLException("User authentication failed for: " + username);
301     }
302
303     // Determine if this connection is case insensitive or not,
304
setCaseInsensitiveIdentifiers(false);
305     Statement stmt = createStatement();
306     ResultSet rs = stmt.executeQuery("SHOW CONNECTION_INFO");
307     while (rs.next()) {
308       String JavaDoc key = rs.getString(1);
309       if (key.equals("case_insensitive_identifiers")) {
310         String JavaDoc val = rs.getString(2);
311         setCaseInsensitiveIdentifiers(val.equals("true"));
312       }
313       else if (key.equals("auto_commit")) {
314         String JavaDoc val = rs.getString(2);
315         auto_commit = val.equals("true");
316       }
317     }
318     rs.close();
319     stmt.close();
320
321   }
322
323   // ---------- Package Protected ----------
324

325   /**
326    * Returns the url string used to make this connection.
327    */

328   String JavaDoc getURL() {
329     return url;
330   }
331
332   /**
333    * Logs into the JDBC server running on a remote machine. Throws an
334    * exception if user authentication fails.
335    */

336   void login(Properties JavaDoc info, String JavaDoc default_schema) throws SQLException {
337
338     String JavaDoc username = info.getProperty("user", "");
339     String JavaDoc password = info.getProperty("password", "");
340
341     login(default_schema, username, password);
342   }
343
344 // /**
345
// * Cancels a result set that is downloading.
346
// */
347
// void cancelResultSet(MResultSet result_set) throws SQLException {
348
// disposeResult(result_set.getResultID());
349
//
350
//// connection_thread.disposeResult(result_set.getResultID());
351
// }
352

353   /**
354    * Uploads any streamable objects found in an SQLQuery into the database.
355    */

356   private void uploadStreamableObjects(SQLQuery sql) throws SQLException {
357     
358     // Push any streamable objects that are present in the query onto the
359
// server.
360
Object JavaDoc[] vars = sql.getVars();
361     try {
362       for (int i = 0; i < vars.length; ++i) {
363         // For each streamable object.
364
if (vars[i] != null && vars[i] instanceof StreamableObject) {
365           // Buffer size is fixed to 64 KB
366
final int BUF_SIZE = 64 * 1024;
367
368           StreamableObject s_object = (StreamableObject) vars[i];
369           long offset = 0;
370           final byte type = s_object.getType();
371           final long total_len = s_object.getSize();
372           final long id = s_object.getIdentifier();
373           final byte[] buf = new byte[BUF_SIZE];
374
375           // Get the InputStream from the StreamableObject hold
376
Object JavaDoc sob_id = new Long JavaDoc(id);
377           InputStream i_stream = (InputStream) s_object_hold.get(sob_id);
378           if (i_stream == null) {
379             throw new RuntimeException JavaDoc(
380                 "Assertion failed: Streamable object InputStream is not available.");
381           }
382
383           while (offset < total_len) {
384             // Fill the buffer
385
int index = 0;
386             final int block_read =
387                        (int) Math.min((long) BUF_SIZE, (total_len - offset));
388             int to_read = block_read;
389             while (to_read > 0) {
390               int count = i_stream.read(buf, index, to_read);
391               if (count == -1) {
392                 throw new IOException("Premature end of stream.");
393               }
394               index += count;
395               to_read -= count;
396             }
397
398             // Send the part of the streamable object to the database.
399
db_interface.pushStreamableObjectPart(type, id, total_len,
400                                                   buf, offset, block_read);
401             // Increment the offset and upload the next part of the object.
402
offset += block_read;
403           }
404
405           // Remove the streamable object once it has been written
406
s_object_hold.remove(sob_id);
407
408 // [ Don't close the input stream - we may only want to put a part of
409
// the stream into the database and keep the file open. ]
410
// // Close the input stream
411
// i_stream.close();
412

413         }
414       }
415     }
416     catch (IOException e) {
417       e.printStackTrace(System.err);
418       throw new SQLException("IO Error pushing large object to server: " +
419                              e.getMessage());
420     }
421   }
422
423   /**
424    * Sends the batch of SQLQuery objects to the database to be executed. The
425    * given array of MResultSet will be the consumer objects for the query
426    * results. If a query succeeds then we are guarenteed to know that size of
427    * the result set.
428    * <p>
429    * This method blocks until all of the queries have been processed by the
430    * database.
431    */

432   void executeQueries(SQLQuery[] queries, MResultSet[] results)
433                                                           throws SQLException {
434     // For each query
435
for (int i = 0; i < queries.length; ++i) {
436       executeQuery(queries[i], results[i]);
437     }
438   }
439
440   /**
441    * Sends the SQL string to the database to be executed. The given MResultSet
442    * is the consumer for the results from the database. We are guarenteed
443    * that if the query succeeds that we know the size of the result set and
444    * at least first first row of the set.
445    * <p>
446    * This method will block until we have received the result header
447    * information.
448    */

449   void executeQuery(SQLQuery sql, MResultSet result_set) 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.getColumnCount()];
457     for (int i = 0; i < col_list.length; ++i) {
458       col_list[i] = resp.getColumnDescription(i);
459     }
460     // Set up the result set to the result format and update the time taken to
461
// execute the query on the server.
462
result_set.connSetup(resp.getResultID(), col_list, resp.getRowCount());
463     result_set.setQueryTime(resp.getQueryTimeMillis());
464
465   }
466
467   /**
468    * Called by MResultSet to query a part of a result from the server. Returns
469    * a List that represents the result from the server.
470    */

471   ResultPart requestResultPart(int result_id, int start_row, int count_rows)
472                                                           throws SQLException {
473     return db_interface.getResultPart(result_id, start_row, count_rows);
474   }
475
476   /**
477    * Requests a part of a streamable object from the server.
478    */

479   StreamableObjectPart requestStreamableObjectPart(int result_id,
480          long streamable_object_id, long offset, int len) throws SQLException {
481     return db_interface.getStreamableObjectPart(result_id,
482                                           streamable_object_id, offset, len);
483   }
484   
485   /**
486    * Disposes of the server-side resources associated with the result set with
487    * result_id. This should be called either before we start the download of
488    * a new result set, or when we have finished with the resources of a result
489    * set.
490    */

491   void disposeResult(int result_id) throws SQLException {
492     // Clear the row cache.
493
// It would be better if we only cleared row entries with this
494
// table_id. We currently clear the entire cache which means there will
495
// be traffic created for other open result sets.
496
// System.out.println(result_id);
497
// row_cache.clear();
498
// Only dispose if the connection is open
499
if (!is_closed) {
500       db_interface.disposeResult(result_id);
501     }
502   }
503
504   /**
505    * Adds a TriggerListener that listens for all triggers events with the name
506    * given. Triggers are created with the 'CREATE TRIGGER' syntax.
507    */

508   void addTriggerListener(String JavaDoc trigger_name, TriggerListener listener) {
509     synchronized (trigger_list) {
510       trigger_list.addElement(trigger_name);
511       trigger_list.addElement(listener);
512     }
513   }
514
515   /**
516    * Removes the TriggerListener for the given trigger name.
517    */

518   void removeTriggerListener(String JavaDoc trigger_name, TriggerListener listener) {
519     synchronized (trigger_list) {
520       for (int i = trigger_list.size() - 2; i >= 0; i -= 2) {
521         if (trigger_list.elementAt(i).equals(trigger_name) &&
522             trigger_list.elementAt(i + 1).equals(listener)) {
523           trigger_list.removeElementAt(i);
524           trigger_list.removeElementAt(i);
525         }
526       }
527     }
528   }
529
530   
531   /**
532    * Creates a StreamableObject on the JDBC client side given an InputStream,
533    * and length and a type. When this method returns, a StreamableObject
534    * entry will be added to the hold.
535    */

536   StreamableObject createStreamableObject(InputStream x,
537                                           int length, byte type) {
538     long ob_id;
539     synchronized (s_object_hold) {
540       ob_id = s_object_id;
541       ++s_object_id;
542       // Add the stream to the hold and get the unique id
543
s_object_hold.put(new Long JavaDoc(ob_id), x);
544     }
545     // Create and return the StreamableObject
546
return new StreamableObject(type, length, ob_id);
547   }
548
549   /**
550    * Removes the StreamableObject from the hold on the JDBC client. This should
551    * be called when the MPreparedStatement closes.
552    */

553   void removeStreamableObject(StreamableObject s_object) {
554     s_object_hold.remove(new Long JavaDoc(s_object.getIdentifier()));
555   }
556   
557   
558   // ---------- Implemented from DatabaseCallBack ----------
559

560   // NOTE: For JDBC standalone apps, the thread that calls this will be a
561
// WorkerThread.
562
// For JDBC client/server apps, the thread that calls this will by the
563
// connection thread that listens for data from the server.
564
public void databaseEvent(int event_type, String JavaDoc event_message) {
565     if (event_type == 99) {
566       if (trigger_thread == null) {
567         trigger_thread = new TriggerDispatchThread();
568         trigger_thread.start();
569       }
570       trigger_thread.dispatchTrigger(event_message);
571     }
572     else {
573       throw new Error JavaDoc("Unrecognised database event: " + event_type);
574     }
575
576 // System.out.println("[com.mckoi.jdbc.MConnection] Event received:");
577
// System.out.println(event_type);
578
// System.out.println(event_message);
579
}
580
581
582   // ---------- Implemented from Connection ----------
583

584   public Statement createStatement() throws SQLException {
585     return new MStatement(this);
586   }
587
588   public PreparedStatement prepareStatement(String JavaDoc sql) throws SQLException {
589     return new MPreparedStatement(this, sql);
590   }
591
592   public CallableStatement prepareCall(String JavaDoc sql) throws SQLException {
593     throw MSQLException.unsupported();
594   }
595                                               
596   public String JavaDoc nativeSQL(String JavaDoc sql) throws SQLException {
597     // We don't do any client side parsing of the sql statement.
598
return sql;
599   }
600
601   public void setAutoCommit(boolean autoCommit) throws SQLException {
602     // The SQL to put into auto-commit mode.
603
ResultSet result;
604     if (autoCommit) {
605       result = createStatement().executeQuery("SET AUTO COMMIT ON");
606       auto_commit = true;
607       result.close();
608     }
609     else {
610       result = createStatement().executeQuery("SET AUTO COMMIT OFF");
611       auto_commit = false;
612       result.close();
613     }
614   }
615
616   public boolean getAutoCommit() throws SQLException {
617     return auto_commit;
618 // // Query the database for this info.
619
// ResultSet result;
620
// result = createStatement().executeQuery(
621
// "SHOW CONNECTION_INFO WHERE var = 'auto_commit'");
622
// boolean auto_commit_mode = false;
623
// if (result.next()) {
624
// auto_commit_mode = result.getString(2).equals("true");
625
// }
626
// result.close();
627
// return auto_commit_mode;
628
}
629
630   public void commit() throws SQLException {
631     ResultSet result;
632     result = createStatement().executeQuery("COMMIT");
633     result.close();
634   }
635
636   public void rollback() throws SQLException {
637     ResultSet result;
638     result = createStatement().executeQuery("ROLLBACK");
639     result.close();
640   }
641
642   public void close() throws SQLException {
643
644     if (!isClosed()) {
645       internalClose();
646     }
647
648 // if (!isClosed()) {
649
// try {
650
// internalClose();
651
// }
652
// finally {
653
// MDriver.connectionClosed(this);
654
// }
655
// }
656

657 // synchronized (lock) {
658
// if (!isClosed()) {
659
// try {
660
// db_interface.dispose();
661
// MDriver.connectionClosed(this);
662
// }
663
// finally {
664
// is_closed = true;
665
// }
666
// }
667
// }
668
}
669
670   public boolean isClosed() throws SQLException {
671     synchronized (lock) {
672       return is_closed;
673     }
674   }
675
676   //======================================================================
677
// Advanced features:
678

679   public DatabaseMetaData getMetaData() throws SQLException {
680     return new MDatabaseMetaData(this);
681   }
682
683   public void setReadOnly(boolean readOnly) throws SQLException {
684     // Hint ignored
685
}
686
687   public boolean isReadOnly() throws SQLException {
688     // Currently we don't support read locked transactions.
689
return false;
690   }
691
692   public void setCatalog(String JavaDoc catalog) throws SQLException {
693     // Silently ignored ;-)
694
}
695
696   public String JavaDoc getCatalog() throws SQLException {
697     // Catalog's not supported
698
return null;
699   }
700
701   public void setTransactionIsolation(int level) throws SQLException {
702     if (level != TRANSACTION_SERIALIZABLE) {
703       throw new SQLException("Only 'TRANSACTION_SERIALIZABLE' supported.");
704     }
705   }
706
707   public int getTransactionIsolation() throws SQLException {
708     return TRANSACTION_SERIALIZABLE;
709   }
710
711   public SQLWarning getWarnings() throws SQLException {
712     synchronized (lock) {
713       return head_warning;
714     }
715   }
716
717   public void clearWarnings() throws SQLException {
718     synchronized (lock) {
719       head_warning = null;
720     }
721   }
722
723 //#IFDEF(JDBC2.0)
724

725   //--------------------------JDBC 2.0-----------------------------
726

727   public Statement createStatement(int resultSetType,
728                                int resultSetConcurrency) throws SQLException {
729     Statement statement = createStatement();
730     // PENDING - set default result set type and result set concurrency for
731
// statement
732
return statement;
733   }
734
735   public PreparedStatement prepareStatement(String JavaDoc sql, int resultSetType,
736                                int resultSetConcurrency) throws SQLException {
737     PreparedStatement statement = prepareStatement(sql);
738     // PENDING - set default result set type and result set concurrency for
739
// statement
740
return statement;
741   }
742
743   public CallableStatement prepareCall(String JavaDoc sql, int resultSetType,
744                                int resultSetConcurrency) throws SQLException {
745     throw MSQLException.unsupported();
746   }
747
748   // ISSUE: I can see using 'Map' here is going to break compatibility with
749
// Java 1.1. Even though testing with 1.1.8 on Linux and NT turned out
750
// fine, I have a feeling some verifiers on web browsers aren't going to
751
// like this.
752
public Map JavaDoc getTypeMap() throws SQLException {
753     throw MSQLException.unsupported();
754   }
755
756   public void setTypeMap(Map JavaDoc map) throws SQLException {
757     throw MSQLException.unsupported();
758   }
759
760 //#ENDIF
761

762 //#IFDEF(JDBC3.0)
763

764   //--------------------------JDBC 3.0-----------------------------
765

766   public void setHoldability(int holdability) throws SQLException {
767     // Currently holdability can not be set to CLOSE_CURSORS_AT_COMMIT though
768
// it could be implemented.
769
if (holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
770       throw new SQLException(
771                      "CLOSE_CURSORS_AT_COMMIT holdability is not supported.");
772     }
773   }
774
775   public int getHoldability() throws SQLException {
776     return ResultSet.HOLD_CURSORS_OVER_COMMIT;
777   }
778
779   public Savepoint setSavepoint() throws SQLException {
780     throw MSQLException.unsupported();
781   }
782
783   public Savepoint setSavepoint(String JavaDoc name) throws SQLException {
784     throw MSQLException.unsupported();
785   }
786
787   public void rollback(Savepoint savepoint) throws SQLException {
788     throw MSQLException.unsupported();
789   }
790
791   public void releaseSavepoint(Savepoint savepoint) throws SQLException {
792     throw MSQLException.unsupported();
793   }
794
795   public Statement createStatement(int resultSetType, int resultSetConcurrency,
796                         int resultSetHoldability) throws SQLException {
797     // Currently holdability can not be set to CLOSE_CURSORS_AT_COMMIT though
798
// it could be implemented.
799
if (resultSetHoldability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
800       throw new SQLException(
801                      "CLOSE_CURSORS_AT_COMMIT holdability is not supported.");
802     }
803     return createStatement(resultSetType, resultSetConcurrency);
804   }
805
806   public PreparedStatement prepareStatement(
807                  String JavaDoc sql, int resultSetType, int resultSetConcurrency,
808                  int resultSetHoldability) throws SQLException {
809     // Currently holdability can not be set to CLOSE_CURSORS_AT_COMMIT though
810
// it could be implemented.
811
if (resultSetHoldability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
812       throw new SQLException(
813                      "CLOSE_CURSORS_AT_COMMIT holdability is not supported.");
814     }
815     return prepareStatement(sql, resultSetType, resultSetConcurrency);
816   }
817
818   public CallableStatement prepareCall(String JavaDoc sql, int resultSetType,
819      int resultSetConcurrency, int resultSetHoldability) throws SQLException {
820     throw MSQLException.unsupported();
821   }
822
823   public PreparedStatement prepareStatement(String JavaDoc sql, int autoGeneratedKeys)
824                                                          throws SQLException {
825     throw MSQLException.unsupported();
826   }
827
828   public PreparedStatement prepareStatement(String JavaDoc sql, int columnIndexes[])
829                                                          throws SQLException {
830     throw MSQLException.unsupported();
831   }
832
833   public PreparedStatement prepareStatement(String JavaDoc sql, String JavaDoc columnNames[])
834                                                          throws SQLException {
835     throw MSQLException.unsupported();
836   }
837
838 //#ENDIF
839

840   // ---------- Inner classes ----------
841

842   /**
843    * The thread that handles all dispatching of trigger events.
844    */

845   private class TriggerDispatchThread extends Thread JavaDoc {
846
847     private Vector JavaDoc trigger_messages_queue = new Vector JavaDoc();
848
849     TriggerDispatchThread() {
850       setDaemon(true);
851       setName("Mckoi - Trigger Dispatcher");
852     }
853
854     /**
855      * Dispatches a trigger message to the listeners.
856      */

857     private void dispatchTrigger(String JavaDoc event_message) {
858       synchronized (trigger_messages_queue) {
859         trigger_messages_queue.addElement(event_message);
860         trigger_messages_queue.notifyAll();
861       }
862     }
863
864     // Thread run method
865
public void run() {
866
867       while (true) {
868         try {
869           String JavaDoc message;
870           synchronized (trigger_messages_queue) {
871             while (trigger_messages_queue.size() == 0) {
872               try {
873                 trigger_messages_queue.wait();
874               }
875               catch (InterruptedException JavaDoc e) { /* ignore */ }
876             }
877             message = (String JavaDoc) trigger_messages_queue.elementAt(0);
878             trigger_messages_queue.removeElementAt(0);
879           }
880
881           // 'message' is a message to process...
882
// The format of a trigger message is:
883
// "[trigger_name] [trigger_source] [trigger_fire_count]"
884
// System.out.println("TRIGGER EVENT: " + message);
885

886           StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(message, " ");
887           String JavaDoc trigger_name = (String JavaDoc) tok.nextElement();
888           String JavaDoc trigger_source = (String JavaDoc) tok.nextElement();
889           String JavaDoc trigger_fire_count = (String JavaDoc) tok.nextElement();
890
891           Vector JavaDoc fired_triggers = new Vector JavaDoc();
892           // Create a list of Listener's that are listening for this trigger.
893
synchronized (trigger_list) {
894             for (int i = 0; i < trigger_list.size(); i += 2) {
895               String JavaDoc to_listen_for = (String JavaDoc) trigger_list.elementAt(i);
896               if (to_listen_for.equals(trigger_name)) {
897                 TriggerListener listener =
898                              (TriggerListener) trigger_list.elementAt(i + 1);
899                 // NOTE, we can't call 'listener.triggerFired' here because
900
// it's not a good idea to call user code when we are
901
// synchronized over 'trigger_list' (deadlock concerns).
902
fired_triggers.addElement(listener);
903               }
904             }
905           }
906
907           // Fire them triggers.
908
for (int i = 0; i < fired_triggers.size(); ++i) {
909             TriggerListener listener =
910                                 (TriggerListener) fired_triggers.elementAt(i);
911             listener.triggerFired(trigger_name);
912           }
913
914         }
915         catch (Throwable JavaDoc t) {
916           t.printStackTrace(System.err);
917         }
918
919       }
920
921     }
922
923   }
924
925 }
926
Popular Tags