001: /**
002: * com.mckoi.database.TableBackedCache 12 Mar 2003
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;
024:
025: import com.mckoi.util.Cache;
026: import com.mckoi.util.IntegerVector;
027:
028: /**
029: * A TableBackedCache is a special type of a cache in a DataTableConglomerate
030: * that is backed by a table in the database. The purpose of this object is to
031: * provide efficient access to some specific information in a table via a
032: * cache.
033: * <p>
034: * For example, we use this object to provide cached access to the system
035: * privilege tables. The engine often performs identical types of priv
036: * queries on the database and it's desirable to cache the access to this
037: * table.
038: * <p>
039: * This class provides the following services;
040: * 1) Allows for an instance of this object to be attached to a single
041: * DatabaseConnection
042: * 2) Listens for any changes that are committed to the table(s) and flushes the
043: * cache as neccessary.
044: * <p>
045: * Note that this object is designed to fit into the pure serializable
046: * transaction isolation system that Mckoi employs. This object will provide
047: * a view of the table as it was when the transaction started. When the
048: * transaction commits (or rollsback) the view is updated to the most current
049: * version. If a change is committed to the tables this cache is backed by,
050: * the cache is only flushed when there are no open transactions on the
051: * connection.
052: *
053: * @author Tobias Downer
054: */
055:
056: abstract class TableBackedCache {
057:
058: /**
059: * The table that this cache is backed by.
060: */
061: private TableName backed_by_table;
062:
063: /**
064: * The list of added rows to the table above when a change is
065: * committed.
066: */
067: private IntegerVector added_list;
068:
069: /**
070: * The list of removed rows from the table above when a change is
071: * committed.
072: */
073: private IntegerVector removed_list;
074:
075: /**
076: * Set to true when the backing DatabaseConnection has a transaction open.
077: */
078: private boolean transaction_active;
079:
080: /**
081: * The listener object.
082: */
083: private TransactionModificationListener listener;
084:
085: /**
086: * Constructs this object.
087: */
088: protected TableBackedCache(TableName table) {
089: this .backed_by_table = table;
090:
091: added_list = new IntegerVector();
092: removed_list = new IntegerVector();
093: }
094:
095: /**
096: * Adds new row ids to the given list.
097: */
098: private void addRowsToList(int[] from, IntegerVector list) {
099: if (from != null) {
100: for (int i = 0; i < from.length; ++i) {
101: list.addInt(from[i]);
102: }
103: }
104: }
105:
106: /**
107: * Attaches this object to a conglomerate. This applies the appropriate
108: * listeners to the tables.
109: */
110: final void attachTo(TableDataConglomerate conglomerate) {
111: // TableDataConglomerate conglomerate = connection.getConglomerate();
112: TableName table_name = backed_by_table;
113: listener = new TransactionModificationListener() {
114: public void tableChange(TableModificationEvent evt) {
115: // Ignore.
116: }
117:
118: public void tableCommitChange(
119: TableCommitModificationEvent evt) {
120: TableName table_name = evt.getTableName();
121: if (table_name.equals(backed_by_table)) {
122: synchronized (removed_list) {
123: addRowsToList(evt.getAddedRows(), added_list);
124: addRowsToList(evt.getRemovedRows(),
125: removed_list);
126: }
127: }
128: }
129: };
130: conglomerate.addTransactionModificationListener(table_name,
131: listener);
132: }
133:
134: /**
135: * Call to detach this object from a TableDataConglomerate.
136: */
137: final void detatchFrom(TableDataConglomerate conglomerate) {
138: // TableDataConglomerate conglomerate = connection.getConglomerate();
139: TableName table_name = backed_by_table;
140: conglomerate.removeTransactionModificationListener(table_name,
141: listener);
142: }
143:
144: /**
145: * Called from DatabaseConnection to notify this object that a new transaction
146: * has been started. When a transaction has started, any committed changes
147: * to the table must NOT be immediately reflected in this cache. Only
148: * when the transaction commits is there a possibility of the cache
149: * information being incorrect.
150: */
151: final void transactionStarted() {
152: transaction_active = true;
153: internalPurgeCache();
154: }
155:
156: /**
157: * Called from DatabaseConnection to notify that object that a transaction
158: * has closed. When a transaction is closed, information in the cache may
159: * be invalidated. For example, if rows 10 - 50 were delete then any
160: * information in the cache that touches this data must be flushed from the
161: * cache.
162: */
163: final void transactionFinished() {
164: transaction_active = false;
165: internalPurgeCache();
166: }
167:
168: /**
169: * Internal method which copies the 'added' and 'removed' row lists and
170: * calls the 'purgeCacheOfInvalidatedEntries' method.
171: */
172: private void internalPurgeCache() {
173: // Make copies of the added_list and removed_list
174: IntegerVector add, remove;
175: synchronized (removed_list) {
176: add = new IntegerVector(added_list);
177: remove = new IntegerVector(removed_list);
178: // Clear the added and removed list
179: added_list.clear();
180: removed_list.clear();
181: }
182: // Make changes to the cache
183: purgeCacheOfInvalidatedEntries(add, remove);
184: }
185:
186: /**
187: * This method is called when the transaction starts and finishes and must
188: * purge the cache of all invalidated entries.
189: * <p>
190: * Note that this method must NOT make any queries on the database. It must
191: * only, at the most, purge the cache of invalid entries. A trivial
192: * implementation of this might completely clear the cache of all data if
193: * removed_row.size() > 0.
194: */
195: abstract void purgeCacheOfInvalidatedEntries(
196: IntegerVector added_rows, IntegerVector removed_rows);
197:
198: }
|