KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mckoi > database > SimpleTransaction


1 /**
2  * com.mckoi.database.SimpleTransaction 09 Mar 2003
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;
26
27 import com.mckoi.debug.DebugLogger;
28 import java.util.ArrayList JavaDoc;
29 import java.util.HashMap JavaDoc;
30
31 /**
32  * An simple implementation of Transaction that provides various facilities for
33  * implementing a Transaction object on a number of MasterTableDataSource
34  * tables. The Transaction object is designed such that concurrent
35  * modification can happen to the database via other transactions without this
36  * view of the database being changed.
37  * <p>
38  * This object does not implement any transaction control mechanisms such as
39  * 'commit' or 'rollback'. This object is most useful for setting up a
40  * short-term minimal transaction for modifying or querying some data in the
41  * database given on some view.
42  *
43  * @author Tobias Downer
44  */

45
46 public abstract class SimpleTransaction {
47
48   /**
49    * The TransactionSystem context.
50    */

51   private TransactionSystem system;
52
53   /**
54    * The list of tables that represent this transaction's view of the database.
55    * (MasterTableDataSource).
56    */

57   private ArrayList JavaDoc visible_tables;
58
59   /**
60    * An IndexSet for each visible table from the above list. These objects
61    * are used to represent index information for all tables.
62    * (IndexSet)
63    */

64   private ArrayList JavaDoc table_indices;
65
66   /**
67    * A queue of MasterTableDataSource and IndexSet objects that are pending to
68    * be cleaned up when this transaction is disposed.
69    */

70   private ArrayList JavaDoc cleanup_queue;
71   
72   /**
73    * A cache of tables that have been accessed via this transaction. This is
74    * a map of table_name -> MutableTableDataSource.
75    */

76   private HashMap JavaDoc table_cache;
77
78   /**
79    * A local cache for sequence values.
80    */

81   private HashMap JavaDoc sequence_value_cache;
82
83   /**
84    * The SequenceManager for this abstract transaction.
85    */

86   private SequenceManager sequence_manager;
87   
88   /**
89    * If true, this is a read-only transaction and does not permit any type of
90    * modification to this vew of the database.
91    */

92   private boolean read_only;
93   
94   
95   /**
96    * Constructs the AbstractTransaction. SequenceManager may be null in which
97    * case sequence generator operations are not permitted.
98    */

99   SimpleTransaction(TransactionSystem system,
100                     SequenceManager sequence_manager) {
101     this.system = system;
102     
103     this.visible_tables = new ArrayList JavaDoc();
104     this.table_indices = new ArrayList JavaDoc();
105     this.table_cache = new HashMap JavaDoc();
106     this.sequence_value_cache = new HashMap JavaDoc();
107
108     this.sequence_manager = sequence_manager;
109
110     this.read_only = false;
111   }
112
113   /**
114    * Sets this transaction as read only. A read only transaction does not
115    * allow for the view to be modified in any way.
116    */

117   public void setReadOnly() {
118     read_only = true;
119   }
120
121   /**
122    * Returns true if the transaction is read-only, otherwise returns false.
123    */

124   public boolean isReadOnly() {
125     return read_only;
126   }
127
128   /**
129    * Returns the TransactionSystem that this Transaction is part of.
130    */

131   public final TransactionSystem getSystem() {
132     return system;
133   }
134
135   /**
136    * Returns a list of all visible tables.
137    */

138   protected final ArrayList JavaDoc getVisibleTables() {
139     return visible_tables;
140   }
141   
142   /**
143    * Returns a DebugLogger object that we use to log debug messages to.
144    */

145   public final DebugLogger Debug() {
146     return getSystem().Debug();
147   }
148
149   /**
150    * Returns the number of visible tables being managed by this transaction.
151    */

152   protected int getVisibleTableCount() {
153     return visible_tables.size();
154   }
155
156   /**
157    * Returns a MasterTableDataSource object representing table 'n' in the set
158    * of tables visible in this transaction.
159    */

160   protected MasterTableDataSource getVisibleTable(int n) {
161     return (MasterTableDataSource) visible_tables.get(n);
162   }
163
164   /**
165    * Searches through the list of tables visible within this transaction and
166    * returns the MasterTableDataSource object with the given name. Returns
167    * null if no visible table with the given name could be found.
168    */

169   protected MasterTableDataSource findVisibleTable(TableName table_name,
170                                                    boolean ignore_case) {
171
172     int size = visible_tables.size();
173     for (int i = 0; i < size; ++i) {
174       MasterTableDataSource master =
175                                 (MasterTableDataSource) visible_tables.get(i);
176       DataTableDef table_def = master.getDataTableDef();
177       if (ignore_case) {
178         if (table_def.getTableName().equalsIgnoreCase(table_name)) {
179           return master;
180         }
181       }
182       else {
183         // Not ignore case
184
if (table_def.getTableName().equals(table_name)) {
185           return master;
186         }
187       }
188     }
189     return null;
190   }
191
192   /**
193    * Returns the IndexSet for the given MasterTableDataSource object that
194    * is visible in this transaction.
195    */

196   final IndexSet getIndexSetForTable(MasterTableDataSource table) {
197     int sz = table_indices.size();
198     for (int i = 0; i < sz; ++i) {
199       if (visible_tables.get(i) == table) {
200         return (IndexSet) table_indices.get(i);
201       }
202     }
203     throw new RuntimeException JavaDoc(
204                     "MasterTableDataSource not found in this transaction.");
205   }
206
207   /**
208    * Sets the IndexSet for the given MasterTableDataSource object in this
209    * transaction.
210    */

211   protected final void setIndexSetForTable(MasterTableDataSource table,
212                                            IndexSet index_set) {
213     int sz = table_indices.size();
214     for (int i = 0; i < sz; ++i) {
215       if (visible_tables.get(i) == table) {
216         table_indices.set(i, index_set);
217         return;
218       }
219     }
220     throw new RuntimeException JavaDoc(
221                     "MasterTableDataSource not found in this transaction.");
222   }
223
224   /**
225    * Returns true if the given table name is a dynamically generated table and
226    * is not a table that is found in the table list defined in this transaction
227    * object.
228    * <p>
229    * It is intended this is implemented by derived classes to handle dynamically
230    * generated tables (tables based on some function or from an external data
231    * source)
232    */

233   protected boolean isDynamicTable(TableName table_name) {
234     // By default, dynamic tables are not implemented.
235
return false;
236   }
237   
238   /**
239    * If this transaction implementation defines dynamic tables (tables whose
240    * content is determined by some function), this should return the
241    * table here as a MutableTableDataSource object. If the table is not
242    * defined an exception is generated.
243    * <p>
244    * It is intended this is implemented by derived classes to handle dynamically
245    * generated tables (tables based on some function or from an external data
246    * source)
247    */

248   protected MutableTableDataSource getDynamicTable(TableName table_name) {
249     // By default, dynamic tables are not implemented.
250
throw new StatementException("Table '" + table_name + "' not found.");
251   }
252
253   /**
254    * Returns the DataTableDef for a dynamic table defined in this transaction.
255    * <p>
256    * It is intended this is implemented by derived classes to handle dynamically
257    * generated tables (tables based on some function or from an external data
258    * source)
259    */

260   protected DataTableDef getDynamicDataTableDef(TableName table_name) {
261     // By default, dynamic tables are not implemented.
262
throw new StatementException("Table '" + table_name + "' not found.");
263   }
264   
265   /**
266    * Returns a string type describing the type of the dynamic table.
267    * <p>
268    * It is intended this is implemented by derived classes to handle dynamically
269    * generated tables (tables based on some function or from an external data
270    * source)
271    */

272   protected String JavaDoc getDynamicTableType(TableName table_name) {
273     // By default, dynamic tables are not implemented.
274
throw new StatementException("Table '" + table_name + "' not found.");
275   }
276
277   /**
278    * Returns a list of all dynamic table names. We can assume that the object
279    * returned here is static so the content of this list should not be changed.
280    * <p>
281    * It is intended this is implemented by derived classes to handle dynamically
282    * generated tables (tables based on some function or from an external data
283    * source)
284    */

285   protected TableName[] getDynamicTableList() {
286     return new TableName[0];
287   }
288
289   // -----
290

291   /**
292    * Returns a new MutableTableDataSource for the view of the
293    * MasterTableDataSource at the start of this transaction. Note that this is
294    * called only once per table accessed in this transaction.
295    */

296   abstract MutableTableDataSource createMutableTableDataSourceAtCommit(
297                                                MasterTableDataSource master);
298
299   // -----
300

301   /**
302    * Flushes the table cache and purges the cache of the entry for the given
303    * table name.
304    */

305   protected void flushTableCache(TableName table_name) {
306     table_cache.remove(table_name);
307   }
308
309   /**
310    * Adds a MasterTableDataSource and IndexSet to this transaction view.
311    */

312   void addVisibleTable(MasterTableDataSource table,
313                        IndexSet index_set) {
314     if (isReadOnly()) {
315       throw new RuntimeException JavaDoc("Transaction is read-only.");
316     }
317
318     visible_tables.add(table);
319     table_indices.add(index_set);
320   }
321
322   /**
323    * Removes a MasterTableDataSource (and its IndexSet) from this view and
324    * puts the information on the cleanup queue.
325    */

326   void removeVisibleTable(MasterTableDataSource table) {
327     if (isReadOnly()) {
328       throw new RuntimeException JavaDoc("Transaction is read-only.");
329     }
330
331     int i = visible_tables.indexOf(table);
332     if (i != -1) {
333       visible_tables.remove(i);
334       IndexSet index_set = (IndexSet) table_indices.remove(i);
335       if (cleanup_queue == null) {
336         cleanup_queue = new ArrayList JavaDoc();
337       }
338       cleanup_queue.add(table);
339       cleanup_queue.add(index_set);
340       // Remove from the table cache
341
TableName table_name = table.getTableName();
342       table_cache.remove(table_name);
343     }
344   }
345
346   /**
347    * Updates a MastertableDataSource (and its IndexSet) for this view. The
348    * existing IndexSet/MasterTableDataSource for this is put on the clean up
349    * queue.
350    */

351   void updateVisibleTable(MasterTableDataSource table,
352                           IndexSet index_set) {
353     if (isReadOnly()) {
354       throw new RuntimeException JavaDoc("Transaction is read-only.");
355     }
356
357     removeVisibleTable(table);
358     addVisibleTable(table, index_set);
359   }
360   
361   /**
362    * Disposes of all IndexSet objects currently accessed by this Transaction.
363    * This includes IndexSet objects on tables that have been dropped by
364    * operations on this transaction and are in the 'cleanup_queue' object.
365    * Disposing of the IndexSet is a common cleanup practice and would typically
366    * be used at the end of a transaction.
367    */

368   protected void disposeAllIndices() {
369     // Dispose all the IndexSet for each table
370
try {
371       for (int i = 0; i < table_indices.size(); ++i) {
372         ((IndexSet) table_indices.get(i)).dispose();
373       }
374     }
375     catch (Throwable JavaDoc e) {
376       Debug().writeException(e);
377     }
378
379     // Dispose all tables we dropped (they will be in the cleanup_queue.
380
try {
381       if (cleanup_queue != null) {
382         for (int i = 0; i < cleanup_queue.size(); i += 2) {
383           MasterTableDataSource master =
384                                 (MasterTableDataSource) cleanup_queue.get(i);
385           IndexSet index_set = (IndexSet) cleanup_queue.get(i + 1);
386           index_set.dispose();
387         }
388         cleanup_queue = null;
389       }
390     }
391     catch (Throwable JavaDoc e) {
392       Debug().writeException(e);
393     }
394
395   }
396
397   
398   // -----
399

400   /**
401    * Returns a TableDataSource object that represents the table with the
402    * given name within this transaction. This table is represented by an
403    * immutable interface.
404    */

405   public TableDataSource getTableDataSource(TableName table_name) {
406     return getTable(table_name);
407   }
408
409   /**
410    * Returns a MutableTableDataSource object that represents the table with
411    * the given name within this transaction. Any changes made to this table
412    * are only made within the context of this transaction. This means if a
413    * row is added or removed, it is not made perminant until the transaction
414    * is committed.
415    * <p>
416    * If the table does not exist then an exception is thrown.
417    */

418   public MutableTableDataSource getTable(TableName table_name) {
419
420     // If table is in the cache, return it
421
MutableTableDataSource table =
422                          (MutableTableDataSource) table_cache.get(table_name);
423     if (table != null) {
424       return table;
425     }
426
427     // Is it represented as a master table?
428
MasterTableDataSource master = findVisibleTable(table_name, false);
429
430     // Not a master table, so see if it's a dynamic table instead,
431
if (master == null) {
432       // Is this a dynamic table?
433
if (isDynamicTable(table_name)) {
434         return getDynamicTable(table_name);
435       }
436     }
437     else {
438       // Otherwise make a view of tha master table data source and put it in
439
// the cache.
440
table = createMutableTableDataSourceAtCommit(master);
441
442       // Put table name in the cache
443
table_cache.put(table_name, table);
444     }
445
446     return table;
447
448   }
449
450   /**
451    * Returns the DataTableDef for the table with the given name that is
452    * visible within this transaction.
453    * <p>
454    * Returns null if table name doesn't refer to a table that exists.
455    */

456   public DataTableDef getDataTableDef(TableName table_name) {
457     // If this is a dynamic table then handle specially
458
if (isDynamicTable(table_name)) {
459       return getDynamicDataTableDef(table_name);
460     }
461     else {
462       // Otherwise return from the pool of visible tables
463
int sz = visible_tables.size();
464       for (int i = 0; i < sz; ++i) {
465         MasterTableDataSource master =
466                                  (MasterTableDataSource) visible_tables.get(i);
467         DataTableDef table_def = master.getDataTableDef();
468         if (table_def.getTableName().equals(table_name)) {
469           return table_def;
470         }
471       }
472       return null;
473     }
474   }
475
476   /**
477    * Returns a list of table names that are visible within this transaction.
478    */

479   public TableName[] getTableList() {
480     TableName[] internal_tables = getDynamicTableList();
481
482     int sz = visible_tables.size();
483     // The result list
484
TableName[] tables = new TableName[sz + internal_tables.length];
485     // Add the master tables
486
for (int i = 0; i < sz; ++i) {
487       MasterTableDataSource master =
488                                (MasterTableDataSource) visible_tables.get(i);
489       DataTableDef table_def = master.getDataTableDef();
490       tables[i] = new TableName(table_def.getSchema(), table_def.getName());
491     }
492
493     // Add any internal system tables to the list
494
for (int i = 0; i < internal_tables.length; ++i) {
495       tables[sz + i] = internal_tables[i];
496     }
497
498     return tables;
499   }
500
501   /**
502    * Returns true if the database table object with the given name exists
503    * within this transaction.
504    */

505   public boolean tableExists(TableName table_name) {
506 // // NASTY HACK: This hack is to get around an annoying recursive problem
507
// // when resolving views. We know this table can't possibly be an
508
// // internal table.
509
// boolean is_view_table = (table_name.getName().equals("sUSRView") &&
510
// table_name.getSchema().equals("SYS_INFO"));
511
// if (is_view_table) {
512
// return findVisibleTable(table_name, false) != null;
513
// }
514
//
515
return isDynamicTable(table_name) ||
516            realTableExists(table_name);
517   }
518
519   /**
520    * Returns true if the table with the given name exists within this
521    * transaction. This is different from 'tableExists' because it does not try
522    * to resolve against dynamic tables, and is therefore useful for quickly
523    * checking if a system table exists or not.
524    */

525   final boolean realTableExists(TableName table_name) {
526     return findVisibleTable(table_name, false) != null;
527   }
528   
529   /**
530    * Attempts to resolve the given table name to its correct case assuming
531    * the table name represents a case insensitive version of the name. For
532    * example, "aPP.CuSTOMer" may resolve to "APP.Customer". If the table
533    * name can not resolve to a valid identifier it returns the input table
534    * name, therefore the actual presence of the table should always be
535    * checked by calling 'tableExists' after this method returns.
536    */

537   public TableName tryResolveCase(TableName table_name) {
538     // Is it a visable table (match case insensitive)
539
MasterTableDataSource table = findVisibleTable(table_name, true);
540     if (table != null) {
541       return table.getTableName();
542     }
543     // Is it an internal table?
544
String JavaDoc tschema = table_name.getSchema();
545     String JavaDoc tname = table_name.getName();
546     TableName[] list = getDynamicTableList();
547     for (int i = 0; i < list.length; ++i) {
548       TableName ctable = list[i];
549       if (ctable.getSchema().equalsIgnoreCase(tschema) &&
550           ctable.getName().equalsIgnoreCase(tname)) {
551         return ctable;
552       }
553     }
554
555     // No matches so return the original object.
556
return table_name;
557   }
558   
559   /**
560    * Returns the type of the table object with the given name. If the table
561    * is a base table, this method returns "TABLE". If it is a virtual table,
562    * it returns the type assigned to by the InternalTableInfo interface.
563    */

564   public String JavaDoc getTableType(TableName table_name) {
565     if (isDynamicTable(table_name)) {
566       return getDynamicTableType(table_name);
567     }
568     else if (findVisibleTable(table_name, false) != null) {
569       return "TABLE";
570     }
571     // No table found so report the error.
572
throw new RuntimeException JavaDoc("No table '" + table_name +
573                                "' to report type for.");
574   }
575
576   /**
577    * Resolves the given string to a table name, throwing an exception if
578    * the reference is ambiguous. This also generates an exception if the
579    * table object is not found.
580    */

581   public TableName resolveToTableName(String JavaDoc current_schema,
582                                       String JavaDoc name, boolean case_insensitive) {
583     TableName table_name = TableName.resolve(current_schema, name);
584     TableName[] tables = getTableList();
585     TableName found = null;
586
587     for (int i = 0; i < tables.length; ++i) {
588       boolean match;
589       if (case_insensitive) {
590         match = tables[i].equalsIgnoreCase(table_name);
591       }
592       else {
593         match = tables[i].equals(table_name);
594       }
595       if (match) {
596         if (found != null) {
597           throw new StatementException("Ambiguous reference: " + name);
598         }
599         else {
600           found = tables[i];
601         }
602       }
603     }
604
605     if (found == null) {
606       throw new StatementException("Object not found: " + name);
607     }
608
609     return found;
610   }
611
612   // ---------- Sequence management ----------
613

614   /**
615    * Flushes the sequence cache. This should be used whenever a sequence
616    * is changed.
617    */

618   void flushSequenceManager(TableName name) {
619     sequence_manager.flushGenerator(name);
620   }
621
622   /**
623    * Requests of the sequence generator the next value from the sequence.
624    * <p>
625    * NOTE: This does NOT check that the user owning this connection has the
626    * correct privs to perform this operation.
627    */

628   public long nextSequenceValue(TableName name) {
629     if (isReadOnly()) {
630       throw new RuntimeException JavaDoc(
631                 "Sequence operation not permitted for read only transaction.");
632     }
633     // Check: if null sequence manager then sequence ops not allowed.
634
if (sequence_manager == null) {
635       throw new RuntimeException JavaDoc("Sequence operations are not permitted.");
636     }
637
638     SequenceManager seq = sequence_manager;
639     long val = seq.nextValue(this, name);
640     // No synchronized because a DatabaseConnection should be single threaded
641
// only.
642
sequence_value_cache.put(name, new Long JavaDoc(val));
643     return val;
644   }
645
646   /**
647    * Returns the sequence value for the given sequence generator that
648    * was last returned by a call to 'nextSequenceValue'. If a value was not
649    * last returned by a call to 'nextSequenceValue' then a statement exception
650    * is generated.
651    * <p>
652    * NOTE: This does NOT check that the user owning this connection has the
653    * correct privs to perform this operation.
654    */

655   public long lastSequenceValue(TableName name) {
656     // No synchronized because a DatabaseConnection should be single threaded
657
// only.
658
Long JavaDoc v = (Long JavaDoc) sequence_value_cache.get(name);
659     if (v != null) {
660       return v.longValue();
661     }
662     else {
663       throw new StatementException(
664         "Current value for sequence generator " + name + " is not available.");
665     }
666   }
667
668   /**
669    * Sets the sequence value for the given sequence generator. If the generator
670    * does not exist or it is not possible to set the value for the generator
671    * then an exception is generated.
672    * <p>
673    * NOTE: This does NOT check that the user owning this connection has the
674    * correct privs to perform this operation.
675    */

676   public void setSequenceValue(TableName name, long value) {
677     if (isReadOnly()) {
678       throw new RuntimeException JavaDoc(
679                 "Sequence operation not permitted for read only transaction.");
680     }
681     // Check: if null sequence manager then sequence ops not allowed.
682
if (sequence_manager == null) {
683       throw new RuntimeException JavaDoc("Sequence operations are not permitted.");
684     }
685
686     SequenceManager seq = sequence_manager;
687     seq.setValue(this, name, value);
688
689     sequence_value_cache.put(name, new Long JavaDoc(value));
690   }
691
692   /**
693    * Returns the current unique id for the given table name. Note that this
694    * is NOT a view of the ID, it is the actual ID value at this time regardless
695    * of transaction.
696    */

697   public long currentUniqueID(TableName table_name) {
698     MasterTableDataSource master = findVisibleTable(table_name, false);
699     if (master == null) {
700       throw new StatementException(
701                      "Table with name '" + table_name + "' could not be " +
702                       "found to retrieve unique id.");
703     }
704     return master.currentUniqueID();
705   }
706   
707   /**
708    * Atomically returns a unique id that can be used as a seed for a set of
709    * unique identifiers for a table. Values returned by this method are
710    * guarenteed unique within this table. This is true even across
711    * transactions.
712    * <p>
713    * NOTE: This change can not be rolled back.
714    */

715   public long nextUniqueID(TableName table_name) {
716     if (isReadOnly()) {
717       throw new RuntimeException JavaDoc(
718                 "Sequence operation not permitted for read only transaction.");
719     }
720
721     MasterTableDataSource master = findVisibleTable(table_name, false);
722     if (master == null) {
723       throw new StatementException(
724                      "Table with name '" + table_name + "' could not be " +
725                       "found to retrieve unique id.");
726     }
727     return master.nextUniqueID();
728   }
729
730   /**
731    * Sets the unique id for the given table name. This must only be called
732    * under very controlled situations, such as when altering a table or when
733    * we need to fix sequence corruption.
734    */

735   public void setUniqueID(TableName table_name, long unique_id) {
736     if (isReadOnly()) {
737       throw new RuntimeException JavaDoc(
738                 "Sequence operation not permitted for read only transaction.");
739     }
740
741     MasterTableDataSource master = findVisibleTable(table_name, false);
742     if (master == null) {
743       throw new StatementException(
744                      "Table with name '" + table_name + "' could not be " +
745                       "found to set unique id.");
746     }
747     master.setUniqueID(unique_id);
748   }
749   
750   
751 }
752
753
Popular Tags