Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

ResultCache.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2004 French National Institute For Research In Computer
00004  * Science And Control (INRIA).
00005  * Contact: c-jdbc@objectweb.org
00006  * 
00007  * This library is free software; you can redistribute it and/or modify it
00008  * under the terms of the GNU Lesser General Public License as published by the
00009  * Free Software Foundation; either version 2.1 of the License, or any later
00010  * version.
00011  * 
00012  * This library is distributed in the hope that it will be useful, but WITHOUT
00013  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
00015  * for more details.
00016  * 
00017  * You should have received a copy of the GNU Lesser General Public License
00018  * along with this library; if not, write to the Free Software Foundation,
00019  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00020  *
00021  * Initial developer(s): Emmanuel Cecchet.
00022  * Contributor(s): Julie Marguerite, Sara Bouchenak, Nicolas Modrzyk.
00023  */
00024 
00025 package org.objectweb.cjdbc.controller.cache.result;
00026 
00027 import java.util.ArrayList;
00028 import java.util.HashMap;
00029 import java.util.HashSet;
00030 import java.util.Iterator;
00031 
00032 import org.objectweb.cjdbc.common.i18n.Translate;
00033 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest;
00034 import org.objectweb.cjdbc.common.sql.CreateRequest;
00035 import org.objectweb.cjdbc.common.sql.ParsingGranularities;
00036 import org.objectweb.cjdbc.common.sql.RequestType;
00037 import org.objectweb.cjdbc.common.sql.SelectRequest;
00038 import org.objectweb.cjdbc.common.sql.UpdateRequest;
00039 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
00040 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
00041 import org.objectweb.cjdbc.controller.cache.CacheException;
00042 import org.objectweb.cjdbc.controller.cache.CacheStatistics;
00043 import org.objectweb.cjdbc.controller.cache.result.entries.CacheEntry;
00044 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntry;
00045 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryEager;
00046 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryNoCache;
00047 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryRelaxed;
00048 import org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema;
00049 import org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseTable;
00050 import org.objectweb.cjdbc.controller.cache.result.threads.EagerCacheThread;
00051 import org.objectweb.cjdbc.controller.cache.result.threads.RelaxedCacheThread;
00052 import org.objectweb.cjdbc.controller.virtualdatabase.ControllerResultSet;
00053 import org.objectweb.cjdbc.driver.Field;
00054 
00055 /**
00056  * This is a query cache implementation with tunable granularity. <br>
00057  * Cache invalidation granularity can take on of the following values:
00058  * <ul>
00059  * <li><code>NO_INVALIDATE</code>: no invalidation, the cache is
00060  * inconsistent and this should just be used to determine hit ratio upper bound.
00061  * </li>
00062  * <li><code>DATABASE</code>: the cache is flushed each time the database is
00063  * updated (every INSERT, UPDATE, DELETE, ... statement).</li>
00064  * <li><code>TABLE</code>: table granularity, entries in the cache are
00065  * invalidated based on table dependencies.</li>
00066  * <li><code>COLUMN</code>: column granularity, entries in the cache are
00067  * invalidated based on column dependencies</li>
00068  * <li><code>COLUMN_UNIQUE</code>: same as <code>COLUMN</code> except that
00069  * <code>UNIQUE</code> queries that selects a single row based on a key are
00070  * invalidated only when needed.
00071  * </ul>
00072  * 
00073  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00074  * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
00075  * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
00076  * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
00077  * @version 1.0
00078  */
00079 public abstract class ResultCache extends AbstractResultCache
00080 {
00081   //
00082   // How the code is organized?
00083   //
00084   // 1. Member variables
00085   // 2. Constructor
00086   // 3. Cache management
00087   // 4. Transaction management
00088   // 5. Debug/Monitoring
00089   //
00090 
00091   // Max number of cache entries
00092   private int                    maxEntries;
00093   /** Pending query timeout in ms. Default is: 0 (wait forever). */
00094   private long                   pendingQueryTimeout = 0;
00095   // queries: SQL -> ResultCacheEntry
00096   private HashMap                queries;
00097   // Pending SQL requests (String)
00098   private HashSet                pendingQueries;
00099   // The rules to apply for this cache
00100   private HashSet                cachingRules;
00101   private ResultCacheRule        defaultRule;
00102   private ArrayList              relaxedCache;
00103 
00104   // LRU (head) of cache entries for replacement
00105   private CacheEntry             lruHead;
00106   // LRU (tail) of cache entries for replacement
00107   private CacheEntry             lruTail;
00108 
00109   // Database schema
00110   protected CacheDatabaseSchema  cdbs;
00111 
00112   private CacheStatistics        stats;
00113 
00114   private RelaxedCacheThread     relaxedThread;
00115   private static final boolean[] TRUE_TRUE           = new boolean[]{true, true};
00116   private boolean                flushingCache;
00117   private EagerCacheThread       eagerThread;
00118   private ArrayList              eagerCache;
00119 
00120   /*
00121    * Constructor
00122    */
00123 
00124   /**
00125    * Creates a new <code>Cache</code> instance.
00126    * 
00127    * @param maxEntries maximum number of cache entries
00128    * @param pendingTimeout pending queries timeout
00129    */
00130   public ResultCache(int maxEntries, int pendingTimeout)
00131   {
00132     this.maxEntries = maxEntries;
00133     this.pendingQueryTimeout = pendingTimeout;
00134     cdbs = null;
00135     stats = new CacheStatistics();
00136     queries = new HashMap(1000, (float) 0.75);
00137     pendingQueries = new HashSet();
00138     cachingRules = new HashSet();
00139     relaxedCache = new ArrayList();
00140     eagerCache = new ArrayList();
00141     lruHead = null;
00142     lruTail = null;
00143     defaultRule = null;
00144     relaxedThread = new RelaxedCacheThread(this);
00145     relaxedThread.setPriority(9);
00146     relaxedThread.start();
00147     eagerThread = new EagerCacheThread(this);
00148     eagerThread.setPriority(9);
00149     eagerThread.start();
00150   }
00151 
00152   /**
00153    * Returns the pending query timeout in seconds.
00154    * 
00155    * @return the pending query timeout.
00156    * @see #setPendingQueryTimeout
00157    */
00158   public int getPendingQueryTimeout()
00159   {
00160     return (int) (pendingQueryTimeout / 1000);
00161   }
00162 
00163   /**
00164    * Sets the pending query timeout in seconds.
00165    * 
00166    * @param pendingQueryTimeout the pending query timeout to set.
00167    * @see #getPendingQueryTimeout
00168    */
00169   public void setPendingQueryTimeout(int pendingQueryTimeout)
00170   {
00171     this.pendingQueryTimeout = pendingQueryTimeout * 1000L;
00172   }
00173 
00174   /**
00175    * Possibly we want to access the queries in the cache for timing purposes
00176    * 
00177    * @return the <code>HashMap</code> of queries (not synchronized)
00178    */
00179   public HashMap getQueries()
00180   {
00181     return this.queries;
00182   }
00183 
00184   /**
00185    * Sets the <code>DatabaseSchema</code> of the current virtual database.
00186    * 
00187    * @param dbs a <code>DatabaseSchema</code> value
00188    * @see org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema
00189    */
00190   public void setDatabaseSchema(DatabaseSchema dbs)
00191   {
00192     if (cdbs == null)
00193     {
00194       logger.info(Translate.get("resultcache.setting.database.schema"));
00195       cdbs = new CacheDatabaseSchema(dbs);
00196     }
00197     else
00198     { // Schema is updated, compute the diff !
00199       CacheDatabaseSchema newSchema = new CacheDatabaseSchema(dbs);
00200       ArrayList tables = cdbs.getTables();
00201       ArrayList newTables = newSchema.getTables();
00202       if (newTables == null)
00203       { // New schema is empty (no backend is active anymore)
00204         logger.info(Translate.get("resultcache.flusing.whole.cache"));
00205         flushCache();
00206         cdbs = null;
00207         return;
00208       }
00209 
00210       // Remove extra-tables
00211       for (int i = 0; i < tables.size(); i++)
00212       {
00213         CacheDatabaseTable t = (CacheDatabaseTable) tables.get(i);
00214         if (!newSchema.hasTable(t.getName()))
00215         {
00216           t.invalidateAll();
00217           cdbs.removeTable(t);
00218           if (logger.isInfoEnabled())
00219             logger.info(Translate
00220                 .get("resultcache.removing.table", t.getName()));
00221         }
00222       }
00223 
00224       // Add missing tables
00225       int size = newTables.size();
00226       for (int i = 0; i < size; i++)
00227       {
00228         CacheDatabaseTable t = (CacheDatabaseTable) newTables.get(i);
00229         if (!cdbs.hasTable(t.getName()))
00230         {
00231           cdbs.addTable(t);
00232           if (logger.isInfoEnabled())
00233             logger.info(Translate.get("resultcache.adding.table", t.getName()));
00234         }
00235       }
00236     }
00237   }
00238 
00239   /**
00240    * Merge the given <code>DatabaseSchema</code> with the current one.
00241    * 
00242    * @param dbs a <code>DatabaseSchema</code> value
00243    * @see org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema
00244    */
00245   public void mergeDatabaseSchema(DatabaseSchema dbs)
00246   {
00247     try
00248     {
00249       logger.info(Translate.get("resultcache.merging.new.database.schema"));
00250       cdbs.mergeSchema(new CacheDatabaseSchema(dbs));
00251     }
00252     catch (Exception e)
00253     {
00254       logger.error(Translate.get("resultcache.error.while.merging", e));
00255     }
00256   }
00257 
00258   /**
00259    * Add a rule for this <code>ResultCache</code>
00260    * 
00261    * @param rule that contains information on the action to perform for a
00262    *                 specific query
00263    */
00264   public void addCachingRule(ResultCacheRule rule)
00265   {
00266     cachingRules.add(rule);
00267   }
00268 
00269   /**
00270    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#getDefaultRule()
00271    */
00272   public ResultCacheRule getDefaultRule()
00273   {
00274     return defaultRule;
00275   }
00276 
00277   /**
00278    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#setDefaultRule(ResultCacheRule)
00279    */
00280   public void setDefaultRule(ResultCacheRule defaultRule)
00281   {
00282     this.defaultRule = defaultRule;
00283   }
00284 
00285   /**
00286    * Finds the behavior of the cache with the given query skeleton. If the query
00287    * match a pattern of a rule then we get the associated action for this,
00288    * otherwise we look for the default behavior.
00289    * 
00290    * @param request to get action for
00291    * @return the <code>CacheBehavior</code> associated for this query.
00292    */
00293   private CacheBehavior getCacheBehavior(SelectRequest request)
00294   {
00295     CacheBehavior behavior = null;
00296     for (Iterator iter = cachingRules.iterator(); iter.hasNext();)
00297     {
00298       behavior = ((ResultCacheRule) iter.next()).matches(request);
00299       if (behavior != null)
00300       {
00301         break;
00302       }
00303     }
00304     if (behavior == null)
00305       behavior = defaultRule.getCacheBehavior();
00306     if (logger.isDebugEnabled())
00307       logger.debug(Translate.get("resultcache.behavior.for.request",
00308           new String[]{request.getSQL(), behavior.getType()}));
00309     return behavior;
00310   }
00311 
00312   /*
00313    * Cache Management
00314    */
00315 
00316   /**
00317    * Do we need invalidation after an update request, given a
00318    * ControllerResultSet. Note that this method is meant to be used with unique
00319    * queries where the ControllerResultSet is the result of a pk selection (like
00320    * an Entity Bean).
00321    * 
00322    * @param result that could be in the cache
00323    * @param request the update we want to get updated values from
00324    * @return boolean[] {needInvalidate,needToSendQuery}
00325    */
00326   public boolean[] needInvalidate(ControllerResultSet result,
00327       UpdateRequest request)
00328   {
00329     HashMap updatedValues = request.getUpdatedValues();
00330     boolean needInvalidate = false;
00331     boolean needToSendQuery = false;
00332     String value;
00333     String columnName;
00334     try
00335     {
00336       // If we don't have exactly one row, we don't handle the optimization
00337       if ((result == null) || (result.getData() == null)
00338           || (result.getData().size() != 1))
00339         return TRUE_TRUE;
00340     }
00341     catch (Exception e)
00342     {
00343       return TRUE_TRUE;
00344     }
00345     Field[] fields = result.getFields();
00346     ArrayList data = result.getData();
00347     int size = fields.length;
00348     for (Iterator iter = updatedValues.keySet().iterator(); iter.hasNext();)
00349     {
00350       columnName = (String) iter.next();
00351       value = (String) updatedValues.get(columnName);
00352       for (int i = 0; i < size; i++)
00353       { // Find the corresponding column in the ResultSet by comparing column
00354         // names
00355 
00356         // We can have something like:
00357         // FIRSTNAME and ADDRESS.FIRSTNAME
00358         if (columnName.equals(fields[i].getFieldName()))
00359         {
00360           Object o = ((Object[]) data.get(0))[i];
00361           if (!value.equals(o))
00362           {
00363             // The value from the cache entry is different we need to update
00364             // the
00365             // cache and the database
00366             return TRUE_TRUE;
00367           }
00368           else
00369             break;
00370         }
00371       }
00372       // We don't need to invalidate the cache because the columns affected are
00373       // different but we need to send the query to the database.
00374       needToSendQuery = true;
00375       // We don't stop here though because other columns could be updated and
00376       // we
00377       // could need invalidation
00378     }
00379     return new boolean[]{needInvalidate, needToSendQuery};
00380   }
00381 
00382   /**
00383    * Adds an entry request/reply to the cache. Note that if the request was
00384    * already in the cache, only the result is updated.
00385    * 
00386    * @param request the request
00387    * @param result the result corresponding to the request
00388    * @exception CacheException if an error occurs
00389    */
00390   public void addToCache(SelectRequest request, ControllerResultSet result)
00391       throws CacheException
00392   {
00393     String sqlQuery = request.getSQL();
00394 
00395     // Sanity checks
00396     if (request.getCacheAbility() == RequestType.UNCACHEABLE)
00397       throw new CacheException(Translate.get("resultcache.uncacheable.request",
00398           sqlQuery));
00399 
00400     if (result == null)
00401       throw new CacheException(Translate.get("resultcache.null.result",
00402           sqlQuery));
00403 
00404     boolean notifyThread = false;
00405 
00406     try
00407     {
00408       synchronized (pendingQueries)
00409       {
00410         // Remove the pending query from the list and wake up
00411         // all waiting queries
00412         if (pendingQueries.remove(sqlQuery))
00413         {
00414           if (logger.isDebugEnabled())
00415             logger.debug(Translate.get("resultcache.removing.pending.query",
00416                 sqlQuery));
00417           pendingQueries.notifyAll();
00418         }
00419         else
00420           logger.warn(Translate.get(
00421               "resultcache.removing.pending.query.failed", sqlQuery));
00422 
00423         // Check against streamable ResultSets
00424         if (result.hasMoreData())
00425         {
00426           logger.info(Translate.get("resultcache.streamed.resultset", request
00427               .getSQLShortForm(20)));
00428           return;
00429         }
00430 
00431         if (logger.isDebugEnabled())
00432           logger.debug(Translate.get("resultcache.adding.query", sqlQuery));
00433 
00434         CacheEntry ce;
00435         synchronized (queries)
00436         {
00437           // Check first that the query is not already in the cache
00438           ce = (ResultCacheEntry) queries.get(sqlQuery);
00439           if (ce == null)
00440           {
00441             // Not in cache, add this entry
00442             // check the rule
00443             CacheBehavior behavior = getCacheBehavior(request);
00444             ce = behavior.getCacheEntry(request, result, this);
00445             if (ce instanceof ResultCacheEntryNoCache)
00446               return;
00447 
00448             // Test size of cache
00449             if (maxEntries > 0)
00450             {
00451               int size = queries.size();
00452               if (size >= maxEntries)
00453                 // LRU replacement policy: Remove the oldest cache entry
00454                 removeOldest();
00455             }
00456             // Add to the cache
00457             queries.put(sqlQuery, ce);
00458 
00459             notifyThread = true;
00460 
00461           }
00462           else
00463           { // Oh, oh, already in cache ...
00464             if (ce.isValid())
00465               logger.warn(Translate.get(
00466                   "resultcache.modifying.result.valid.entry", sqlQuery));
00467             ce.setResult(result);
00468           }
00469 
00470           // Update LRU
00471           if (lruHead != null)
00472           {
00473             lruHead.setPrev(ce);
00474             ce.setNext(lruHead);
00475             ce.setPrev(null);
00476           }
00477           if (lruTail == null)
00478             lruTail = ce;
00479           lruHead = ce; // This is also fine if LRUHead == null
00480         }
00481         processAddToCache(ce);
00482 
00483         // process thread notification out of the synchronized block on
00484         // pending queries to avoid deadlock, while adding/removing
00485         // on cache
00486         if (notifyThread)
00487         {
00488           //      relaxed entry
00489           if (ce instanceof ResultCacheEntryRelaxed)
00490           {
00491             ResultCacheEntryRelaxed qcer = (ResultCacheEntryRelaxed) ce;
00492             synchronized (relaxedThread)
00493             {
00494               relaxedCache.add(qcer);
00495               if (qcer.getDeadline() < relaxedThread.getThreadWakeUpTime()
00496                   || relaxedThread.getThreadWakeUpTime() == 0)
00497               {
00498                 relaxedThread.notify();
00499               }
00500             }
00501           }
00502           else if (ce instanceof ResultCacheEntryEager)
00503           {
00504             // eager entry
00505             ResultCacheEntryEager qcee = (ResultCacheEntryEager) ce;
00506             if (qcee.getDeadline() != ResultCacheEntry.NO_DEADLINE)
00507             { // Only deal with entries that specify a timeout
00508               synchronized (eagerThread)
00509               {
00510                 eagerCache.add(qcee);
00511                 if (qcee.getDeadline() < eagerThread.getThreadWakeUpTime()
00512                     || eagerThread.getThreadWakeUpTime() == 0)
00513                 {
00514                   eagerThread.notify();
00515                 }
00516               }
00517             }
00518           }
00519         }
00520 
00521       }
00522     }
00523     catch (OutOfMemoryError oome)
00524     {
00525       flushCache();
00526       System.gc();
00527       logger.warn(Translate.get("cache.memory.error.cache.flushed", this
00528           .getClass()));
00529     }
00530   }
00531 
00532   /**
00533    * Process the add to cache to update implementation specific data structures.
00534    * 
00535    * @param qe to add to the cache.
00536    */
00537   protected abstract void processAddToCache(CacheEntry qe);
00538 
00539   /**
00540    * Gets the result to the given request from the cache. The returned
00541    * <code>ResultCacheEntry</code> is <code>null</code> if the request is
00542    * not present in the cache.
00543    * <p>
00544    * An invalid <code>ResultCacheEntry</code> may be returned (it means that
00545    * the result is <code>null</code>) but the already parsed query can be
00546    * retrieved from the cache entry.
00547    * 
00548    * @param request an SQL select request
00549    * @param addToPendingQueries <code>true</code> if the request must be added
00550    *                 to the pending query list on a cache miss
00551    * @return the <code>ResultCacheEntry</code> if found, else
00552    *              <code>null</code>
00553    */
00554   public CacheEntry getFromCache(SelectRequest request,
00555       boolean addToPendingQueries)
00556   {
00557     stats.addSelect();
00558 
00559     if (request.getCacheAbility() == RequestType.UNCACHEABLE)
00560     {
00561       stats.addUncacheable();
00562       return null;
00563     }
00564 
00565     String sqlQuery = request.getSQL();
00566 
00567     // Check if we have the same query pending
00568     synchronized (pendingQueries)
00569     {
00570       if (addToPendingQueries)
00571       {
00572         long timeout = pendingQueryTimeout;
00573         // Yes, wait for the result
00574         // As we use a single lock for all pending queries, we use a
00575         // while to re-check that this wake-up was for us!
00576         while (pendingQueries.contains(sqlQuery))
00577         {
00578           try
00579           {
00580             if (logger.isDebugEnabled())
00581               logger.debug(Translate.get("resultcache.waiting.pending.query",
00582                   sqlQuery));
00583 
00584             if (timeout > 0)
00585             {
00586               long start = System.currentTimeMillis();
00587               pendingQueries.wait(pendingQueryTimeout);
00588               long end = System.currentTimeMillis();
00589               timeout = timeout - (end - start);
00590               if (timeout <= 0)
00591               {
00592                 logger.warn(Translate.get("resultcache.pending.query.timeout"));
00593                 break;
00594               }
00595             }
00596             else
00597               pendingQueries.wait();
00598           }
00599           catch (InterruptedException e)
00600           {
00601             logger.warn(Translate.get("resultcache.pending.query.timeout"));
00602             break;
00603           }
00604         }
00605       }
00606 
00607       // Check the cache
00608       ResultCacheEntry ce;
00609       synchronized (queries)
00610       {
00611         ce = (ResultCacheEntry) queries.get(sqlQuery);
00612         if (ce == null)
00613         // if ((ce == null) || !ce.isValid())
00614         { // Cache miss or dirty entry
00615           if (addToPendingQueries)
00616           {
00617             pendingQueries.add(sqlQuery);
00618             // Add this query to the pending queries
00619             if (logger.isDebugEnabled())
00620             {
00621               logger.debug(Translate.get("resultcache.cache.miss"));
00622               logger.debug(Translate.get(
00623                   "resultcache.adding.to.pending.queries", sqlQuery));
00624             }
00625           }
00626           return null;
00627         }
00628         else
00629         { // Cache hit (must update LRU)
00630           // Move cache entry to head of LRU
00631           CacheEntry before = ce.getPrev();
00632           if (before != null)
00633           {
00634             CacheEntry after = ce.getNext();
00635             before.setNext(after);
00636             if (after != null)
00637               after.setPrev(before);
00638             else
00639               // We were the tail, update the tail
00640               lruTail = before;
00641             ce.setNext(lruHead);
00642             ce.setPrev(null);
00643             if (lruHead != ce)
00644               lruHead.setPrev(ce);
00645             lruHead = ce;
00646           }
00647           // else it was already the LRU head
00648         }
00649       }
00650 
00651       if (ce.getResult() == null)
00652       {
00653         if (addToPendingQueries)
00654         {
00655           pendingQueries.add(sqlQuery);
00656           // Add this query to the pending queries
00657           if (logger.isDebugEnabled())
00658           {
00659             logger.debug(Translate.get("resultcache.cache.miss"));
00660             logger.debug(Translate.get("resultcache.adding.to.pending.queries",
00661                 sqlQuery));
00662           }
00663         }
00664         if (ce.isValid() && logger.isInfoEnabled())
00665           logger.info(Translate.get("resultcache.valid.entry.without.result",
00666               ce.getRequest().getSQL()));
00667       }
00668       else
00669       {
00670         if (logger.isDebugEnabled())
00671           logger.debug(Translate.get("resultcache.cache.hit", sqlQuery));
00672         stats.addHits();
00673       }
00674 
00675       return ce;
00676     }
00677   }
00678 
00679   /**
00680    * Removes an entry from the cache (both request and reply are dropped). The
00681    * request is NOT removed from the pending query list, but it shouldn't be in
00682    * this list.
00683    * 
00684    * @param request a <code>SelectRequest</code>
00685    */
00686   public void removeFromCache(SelectRequest request)
00687   {
00688     String sqlQuery = request.getSQL();
00689 
00690     if (logger.isDebugEnabled())
00691       logger.debug("Removing from cache: " + sqlQuery);
00692 
00693     synchronized (queries)
00694     {
00695       // Remove from the cache
00696       ResultCacheEntry ce = (ResultCacheEntry) queries.remove(sqlQuery);
00697       if (ce == null)
00698         return; // Was not in the cache!
00699       else
00700       {
00701         // Update result set
00702         ce.setResult(null);
00703         // Update LRU
00704         CacheEntry before = ce.getPrev();
00705         CacheEntry after = ce.getNext();
00706         if (before != null)
00707         {
00708           before.setNext(after);
00709           if (after != null)
00710             after.setPrev(before);
00711           else
00712             // We were the tail, update the tail
00713             lruTail = before;
00714         }
00715         else
00716         { // We are the LRUHead
00717           lruHead = ce.getNext();
00718           if (after != null)
00719             after.setPrev(null);
00720           else
00721             // We were the tail, update the tail
00722             lruTail = before;
00723         }
00724         // Remove links to other cache entries for GC
00725         ce.setNext(null);
00726         ce.setPrev(null);
00727       }
00728     }
00729   }
00730 
00731   /**
00732    * Removes an entry from the pending query list.
00733    * 
00734    * @param request a <code>SelectRequest</code>
00735    */
00736   public void removeFromPendingQueries(SelectRequest request)
00737   {
00738     String sqlQuery = request.getSQL();
00739 
00740     synchronized (pendingQueries)
00741     {
00742       // Remove the pending query from the list and wake up
00743       // all waiting queries
00744       if (pendingQueries.remove(sqlQuery))
00745         pendingQueries.notifyAll();
00746     }
00747   }
00748 
00749   /**
00750    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#isUpdateNecessary(org.objectweb.cjdbc.common.sql.UpdateRequest)
00751    */
00752   public abstract boolean isUpdateNecessary(UpdateRequest request)
00753       throws CacheException;
00754 
00755   /**
00756    * Notifies the cache that this write request has been issued, so that cache
00757    * coherency can be maintained. If the cache is distributed, this method is
00758    * reponsible for broadcasting this information to other caches.
00759    * 
00760    * @param request an <code>AbstractRequest</code> value
00761    * @exception CacheException if an error occurs
00762    */
00763   public void writeNotify(AbstractWriteRequest request) throws CacheException
00764   {
00765     // Update the stats
00766     if (request.isInsert())
00767       stats.addInsert();
00768     else if (request.isUpdate())
00769       stats.addUpdate();
00770     else if (request.isDelete())
00771       stats.addDelete();
00772     else if (request.isCreate())
00773     {
00774       stats.addCreate();
00775       // Create: we only need to update the schema
00776       if (parsingGranularity != ParsingGranularities.NO_PARSING)
00777         cdbs.addTable(new CacheDatabaseTable(((CreateRequest) request)
00778             .getDatabaseTable()));
00779       return;
00780     }
00781     else if (request.isDrop())
00782     {
00783       stats.addDrop();
00784       // Drop: we need to update the schema
00785       if (parsingGranularity != ParsingGranularities.NO_PARSING)
00786       {
00787         // Invalidate the cache entries associated with this table
00788         CacheDatabaseTable cdt = cdbs.getTable(request.getTableName());
00789         if (cdt != null)
00790         {
00791           cdt.invalidateAll();
00792           cdbs.removeTable(cdt);
00793           return;
00794         }
00795         // else: the table was not previously cached
00796         // (no previous 'select' requests on the table).
00797       }
00798     }
00799     else
00800     {
00801       stats.addUnknown();
00802     }
00803     if (logger.isDebugEnabled())
00804       logger.debug("Notifying write " + request.getSQL());
00805 
00806     processWriteNotify(request);
00807   }
00808 
00809   /**
00810    * Implementation specific invalidation of the cache.
00811    * 
00812    * @param request Write request that invalidates the cache.
00813    */
00814   protected abstract void processWriteNotify(AbstractWriteRequest request);
00815 
00816   /**
00817    * Removes all entries from the cache.
00818    */
00819   public void flushCache()
00820   {
00821     // Check if we are already flushing the cache
00822     synchronized (this)
00823     {
00824       if (flushingCache)
00825         return;
00826       flushingCache = true;
00827     }
00828 
00829     try
00830     {
00831       synchronized (queries)
00832       { // Invalidate the whole cache until it is empty
00833         while (!queries.isEmpty())
00834         {
00835           Iterator iter = queries.values().iterator();
00836           ((ResultCacheEntry) iter.next()).invalidate();
00837         }
00838       }
00839 
00840       synchronized (pendingQueries)
00841       { // Clean pending queries to unblock everyone if some queries/backends
00842         // remained in an unstable state.
00843         pendingQueries.clear();
00844         pendingQueries.notifyAll();
00845       }
00846     }
00847     finally
00848     {
00849       synchronized (this)
00850       {
00851         flushingCache = false;
00852       }
00853       if (logger.isDebugEnabled())
00854         logger.debug(Translate.get("resultcache.cache.flushed"));
00855     }
00856   }
00857 
00858   /**
00859    * Get Cache size
00860    * 
00861    * @return the approximate size of the cache in bytes
00862    */
00863   public long getCacheSize()
00864   {
00865     // No need to synchronize, the implementation returns an int
00866     return queries.size();
00867   }
00868 
00869   /**
00870    * Removes the oldest entry from the cache.
00871    * <p>
00872    * <b>!Warning! </b> This method is not synchronized and should be called in
00873    * the scope of a synchronized(queries)
00874    */
00875   private void removeOldest()
00876   {
00877     if (lruTail == null)
00878       return;
00879     // Update the LRU
00880     ResultCacheEntry oldce = (ResultCacheEntry) lruTail;
00881     lruTail = lruTail.getPrev();
00882     if (lruTail != null)
00883       lruTail.setNext(null);
00884 
00885     if (logger.isDebugEnabled())
00886       logger.debug(Translate.get("resultcache.removing.oldest.cache.entry",
00887           oldce.getRequest().getSQL()));
00888 
00889     /*
00890      * We remove the query from the hashtable so that the garbage collector can
00891      * do its job. We need to remove the query from the queries HashTable first
00892      * in case we invalidate an eager cache entry that will call removeFromCache
00893      * (and will try to update the LRU is the entry is still in the queries
00894      * HashTable). So, to be compatible with all type of cache entries: 1.
00895      * queries.remove(ce) 2. ce.invalidate
00896      */
00897     queries.remove(oldce.getRequest().getSQL());
00898 
00899     if (oldce.isValid())
00900     {
00901       oldce.setResult(null);
00902       oldce.invalidate();
00903     }
00904 
00905     stats.addRemove();
00906   }
00907 
00908   /**
00909    * Gets the needed query parsing granularity.
00910    * 
00911    * @return needed query parsing granularity
00912    */
00913   public int getParsingGranularity()
00914   {
00915     return this.parsingGranularity;
00916   }
00917 
00918   /**
00919    * Retrieve the name of this cache
00920    * 
00921    * @return name
00922    */
00923   public abstract String getName();
00924 
00925   //
00926   // Transaction management
00927   //
00928 
00929   /**
00930    * Commit a transaction given its id.
00931    * 
00932    * @param transactionId the transaction id
00933    * @throws CacheException if an error occurs
00934    */
00935   public void commit(long transactionId) throws CacheException
00936   {
00937     // Ok, the transaction has commited, nothing to do
00938   }
00939 
00940   /**
00941    * Rollback a transaction given its id.
00942    * 
00943    * @param transactionId the transaction id
00944    * @throws CacheException if an error occurs
00945    */
00946   public void rollback(long transactionId) throws CacheException
00947   {
00948     logger.info(Translate.get("resultcache.flushing.cache.cause.rollback",
00949         transactionId));
00950     flushCache();
00951   }
00952 
00953   /*
00954    * Debug/Monitoring
00955    */
00956 
00957   /**
00958    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#getCacheData
00959    */
00960   public String[][] getCacheData() throws CacheException
00961   {
00962     try
00963     {
00964       synchronized (queries)
00965       {
00966         String[][] data = new String[queries.size()][];
00967         int count = 0;
00968         for (Iterator iter = queries.values().iterator(); iter.hasNext(); count++)
00969         {
00970           ResultCacheEntry qe = (ResultCacheEntry) iter.next();
00971           if (qe != null)
00972           {
00973             data[count] = qe.toStringTable();
00974           }
00975         }
00976         return data;
00977       }
00978     }
00979     catch (Exception e)
00980     {
00981       logger.error(Translate.get("resultcache.error.retrieving.cache.data", e));
00982       throw new CacheException(e.getMessage());
00983     }
00984   }
00985 
00986   /**
00987    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#getCacheStatsData()
00988    */
00989   public String[][] getCacheStatsData() throws CacheException
00990   {
00991     String[][] data = new String[1][];
00992     String[] stat = stats.getCacheStatsData();
00993     data[0] = new String[stat.length + 1];
00994     for (int i = 0; i < stat.length; i++)
00995       data[0][i] = stat[i];
00996     data[0][data[0].length - 1] = "" + queries.size();
00997     return data;
00998   }
00999 
01000   /**
01001    * @return Returns the stats.
01002    */
01003   public CacheStatistics getCacheStatistics()
01004   {
01005     return stats;
01006   }
01007 
01008   /**
01009    * Returns the eagerCache value.
01010    * 
01011    * @return Returns the eagerCache.
01012    */
01013   public ArrayList getEagerCache()
01014   {
01015     return eagerCache;
01016   }
01017 
01018   /**
01019    * Returns the relaxedCache value.
01020    * 
01021    * @return Returns the relaxedCache.
01022    */
01023   public ArrayList getRelaxedCache()
01024   {
01025     return relaxedCache;
01026   }
01027 
01028   /**
01029    * Gets information about the request cache
01030    * 
01031    * @return <code>String</code> containing information
01032    */
01033   protected String getXmlImpl()
01034   {
01035     StringBuffer info = new StringBuffer();
01036     info.append("<" + DatabasesXmlTags.ELT_ResultCache + " "
01037         + DatabasesXmlTags.ATT_pendingTimeout + "=\"" + pendingQueryTimeout
01038         + "\" " + DatabasesXmlTags.ATT_maxNbOfEntries + "=\"" + maxEntries
01039         + "\" " + DatabasesXmlTags.ATT_granularity + "=\"" + getName() + "\">");
01040     info.append("<" + DatabasesXmlTags.ELT_DefaultResultCacheRule + " "
01041         + DatabasesXmlTags.ATT_timestampResolution + "=\""
01042         + defaultRule.getTimestampResolution() / 1000 + "\">");
01043     info.append(defaultRule.getCacheBehavior().getXml());
01044     info.append("</" + DatabasesXmlTags.ELT_DefaultResultCacheRule + ">");
01045     for (Iterator iter = cachingRules.iterator(); iter.hasNext();)
01046       info.append(((ResultCacheRule) iter.next()).getXml());
01047     info.append("</" + DatabasesXmlTags.ELT_ResultCache + ">");
01048     return info.toString();
01049   }
01050 
01051 }

Generated on Mon Apr 11 22:01:34 2005 for C-JDBC by  doxygen 1.3.9.1