KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > controller > cache > result > ResultCache


1 /**
2  * C-JDBC: Clustered JDBC.
3  * Copyright (C) 2002-2005 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Contact: c-jdbc@objectweb.org
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by the
9  * Free Software Foundation; either version 2.1 of the License, or any later
10  * version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20  *
21  * Initial developer(s): Emmanuel Cecchet.
22  * Contributor(s): Julie Marguerite, Sara Bouchenak, Nicolas Modrzyk.
23  */

24
25 package org.objectweb.cjdbc.controller.cache.result;
26
27 import java.util.ArrayList JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.util.Iterator JavaDoc;
31
32 import org.objectweb.cjdbc.common.i18n.Translate;
33 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest;
34 import org.objectweb.cjdbc.common.sql.CreateRequest;
35 import org.objectweb.cjdbc.common.sql.ParsingGranularities;
36 import org.objectweb.cjdbc.common.sql.RequestType;
37 import org.objectweb.cjdbc.common.sql.SelectRequest;
38 import org.objectweb.cjdbc.common.sql.UpdateRequest;
39 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
40 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
41 import org.objectweb.cjdbc.controller.cache.CacheException;
42 import org.objectweb.cjdbc.controller.cache.CacheStatistics;
43 import org.objectweb.cjdbc.controller.cache.result.entries.AbstractResultCacheEntry;
44 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryEager;
45 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryNoCache;
46 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryRelaxed;
47 import org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema;
48 import org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseTable;
49 import org.objectweb.cjdbc.controller.cache.result.threads.EagerCacheThread;
50 import org.objectweb.cjdbc.controller.cache.result.threads.RelaxedCacheThread;
51 import org.objectweb.cjdbc.controller.virtualdatabase.ControllerResultSet;
52 import org.objectweb.cjdbc.driver.Field;
53
54 /**
55  * This is a query cache implementation with tunable granularity. <br>
56  * Cache invalidation granularity can take on of the following values:
57  * <ul>
58  * <li><code>NO_INVALIDATE</code>: no invalidation, the cache is
59  * inconsistent and this should just be used to determine hit ratio upper bound.
60  * </li>
61  * <li><code>DATABASE</code>: the cache is flushed each time the database is
62  * updated (every INSERT, UPDATE, DELETE, ... statement).</li>
63  * <li><code>TABLE</code>: table granularity, entries in the cache are
64  * invalidated based on table dependencies.</li>
65  * <li><code>COLUMN</code>: column granularity, entries in the cache are
66  * invalidated based on column dependencies</li>
67  * <li><code>COLUMN_UNIQUE</code>: same as <code>COLUMN</code> except that
68  * <code>UNIQUE</code> queries that selects a single row based on a key are
69  * invalidated only when needed.
70  * </ul>
71  *
72  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
73  * @author <a HREF="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
74  * @author <a HREF="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
75  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
76  * @version 1.0
77  */

78 public abstract class ResultCache extends AbstractResultCache
79 {
80   //
81
// How the code is organized?
82
//
83
// 1. Member variables
84
// 2. Constructor
85
// 3. Cache management
86
// 4. Transaction management
87
// 5. Debug/Monitoring
88
//
89

90   // Max number of cache entries
91
private int maxEntries;
92   /** Pending query timeout in ms. Default is: 0 (wait forever). */
93   private long pendingQueryTimeout = 0;
94   // queries: SQL -> AbstractResultCacheEntry
95
private HashMap JavaDoc queries;
96   // Pending SQL requests (String)
97
private HashSet JavaDoc pendingQueries;
98   // The rules to apply for this cache
99
private HashSet JavaDoc cachingRules;
100   private ResultCacheRule defaultRule;
101   private ArrayList JavaDoc relaxedCache;
102
103   // LRU (head) of cache entries for replacement
104
private AbstractResultCacheEntry lruHead;
105   // LRU (tail) of cache entries for replacement
106
private AbstractResultCacheEntry lruTail;
107
108   // Database schema
109
protected CacheDatabaseSchema cdbs;
110
111   private CacheStatistics stats;
112
113   private RelaxedCacheThread relaxedThread;
114   private static final boolean[] TRUE_TRUE = new boolean[]{true,
115       true };
116   private boolean flushingCache;
117   private EagerCacheThread eagerThread;
118   private ArrayList JavaDoc eagerCache;
119
120   /*
121    * Constructor
122    */

123
124   /**
125    * Creates a new <code>Cache</code> instance.
126    *
127    * @param maxEntries maximum number of cache entries
128    * @param pendingTimeout pending queries timeout
129    */

130   public ResultCache(int maxEntries, int pendingTimeout)
131   {
132     this.maxEntries = maxEntries;
133     this.pendingQueryTimeout = pendingTimeout;
134     cdbs = null;
135     stats = new CacheStatistics();
136     queries = new HashMap JavaDoc(1000, (float) 0.75);
137     pendingQueries = new HashSet JavaDoc();
138     cachingRules = new HashSet JavaDoc();
139     relaxedCache = new ArrayList JavaDoc();
140     eagerCache = new ArrayList JavaDoc();
141     lruHead = null;
142     lruTail = null;
143     defaultRule = null;
144     relaxedThread = new RelaxedCacheThread(this);
145     relaxedThread.setPriority(9);
146     relaxedThread.start();
147     eagerThread = new EagerCacheThread(this);
148     eagerThread.setPriority(9);
149     eagerThread.start();
150   }
151
152   /**
153    * Shutdown the result cache and all its threads.
154    */

155   public synchronized void shutdown()
156   {
157     relaxedThread.shutdown();
158     eagerThread.shutdown();
159   }
160
161   /**
162    * Returns the pending query timeout in seconds.
163    *
164    * @return the pending query timeout.
165    * @see #setPendingQueryTimeout
166    */

167   public int getPendingQueryTimeout()
168   {
169     return (int) (pendingQueryTimeout / 1000);
170   }
171
172   /**
173    * Sets the pending query timeout in seconds.
174    *
175    * @param pendingQueryTimeout the pending query timeout to set.
176    * @see #getPendingQueryTimeout
177    */

178   public void setPendingQueryTimeout(int pendingQueryTimeout)
179   {
180     this.pendingQueryTimeout = pendingQueryTimeout * 1000L;
181   }
182
183   /**
184    * Possibly we want to access the queries in the cache for timing purposes
185    *
186    * @return the <code>HashMap</code> of queries (not synchronized)
187    */

188   public HashMap JavaDoc getQueries()
189   {
190     return this.queries;
191   }
192
193   /**
194    * Sets the <code>DatabaseSchema</code> of the current virtual database.
195    *
196    * @param dbs a <code>DatabaseSchema</code> value
197    * @see org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema
198    */

199   public void setDatabaseSchema(DatabaseSchema dbs)
200   {
201     if (cdbs == null)
202     {
203       logger.info(Translate.get("resultcache.setting.database.schema"));
204       cdbs = new CacheDatabaseSchema(dbs);
205     }
206     else
207     { // Schema is updated, compute the diff !
208
CacheDatabaseSchema newSchema = new CacheDatabaseSchema(dbs);
209       ArrayList JavaDoc tables = cdbs.getTables();
210       ArrayList JavaDoc newTables = newSchema.getTables();
211       if (newTables == null)
212       { // New schema is empty (no backend is active anymore)
213
logger.info(Translate.get("resultcache.flusing.whole.cache"));
214         flushCache();
215         cdbs = null;
216         return;
217       }
218
219       // Remove extra-tables
220
for (int i = 0; i < tables.size(); i++)
221       {
222         CacheDatabaseTable t = (CacheDatabaseTable) tables.get(i);
223         if (!newSchema.hasTable(t.getName()))
224         {
225           t.invalidateAll();
226           cdbs.removeTable(t);
227           if (logger.isInfoEnabled())
228             logger.info(Translate
229                 .get("resultcache.removing.table", t.getName()));
230         }
231       }
232
233       // Add missing tables
234
int size = newTables.size();
235       for (int i = 0; i < size; i++)
236       {
237         CacheDatabaseTable t = (CacheDatabaseTable) newTables.get(i);
238         if (!cdbs.hasTable(t.getName()))
239         {
240           cdbs.addTable(t);
241           if (logger.isInfoEnabled())
242             logger.info(Translate.get("resultcache.adding.table", t.getName()));
243         }
244       }
245     }
246   }
247
248   /**
249    * Merge the given <code>DatabaseSchema</code> with the current one.
250    *
251    * @param dbs a <code>DatabaseSchema</code> value
252    * @see org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema
253    */

254   public void mergeDatabaseSchema(DatabaseSchema dbs)
255   {
256     try
257     {
258       logger.info(Translate.get("resultcache.merging.new.database.schema"));
259       cdbs.mergeSchema(new CacheDatabaseSchema(dbs));
260     }
261     catch (Exception JavaDoc e)
262     {
263       logger.error(Translate.get("resultcache.error.while.merging", e));
264     }
265   }
266
267   /**
268    * Add a rule for this <code>ResultCache</code>
269    *
270    * @param rule that contains information on the action to perform for a
271    * specific query
272    */

273   public void addCachingRule(ResultCacheRule rule)
274   {
275     cachingRules.add(rule);
276   }
277
278   /**
279    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#getDefaultRule()
280    */

281   public ResultCacheRule getDefaultRule()
282   {
283     return defaultRule;
284   }
285
286   /**
287    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#setDefaultRule(ResultCacheRule)
288    */

289   public void setDefaultRule(ResultCacheRule defaultRule)
290   {
291     this.defaultRule = defaultRule;
292   }
293
294   /**
295    * Finds the behavior of the cache with the given query skeleton. If the query
296    * match a pattern of a rule then we get the associated action for this,
297    * otherwise we look for the default behavior.
298    *
299    * @param request to get action for
300    * @return the <code>CacheBehavior</code> associated for this query.
301    */

302   private CacheBehavior getCacheBehavior(SelectRequest request)
303   {
304     CacheBehavior behavior = null;
305     for (Iterator JavaDoc iter = cachingRules.iterator(); iter.hasNext();)
306     {
307       behavior = ((ResultCacheRule) iter.next()).matches(request);
308       if (behavior != null)
309       {
310         break;
311       }
312     }
313     if (behavior == null)
314       behavior = defaultRule.getCacheBehavior();
315     if (logger.isDebugEnabled())
316       logger.debug(Translate.get("resultcache.behavior.for.request",
317           new String JavaDoc[]{request.getSQL(), behavior.getType()}));
318     return behavior;
319   }
320
321   /*
322    * Cache Management
323    */

324
325   /**
326    * Creates a unique cache entry key from the given request. The key is
327    * currently composed of the login name and the request SQL statement.
328    *
329    * @param request the request to generate the key from
330    * @return a unique cache key for this request
331    */

332   private String JavaDoc getCacheKeyFromRequest(SelectRequest request)
333   {
334     return request.getLogin() + "," + request.getSQL();
335   }
336
337   /**
338    * Do we need invalidation after an update request, given a
339    * ControllerResultSet. Note that this method is meant to be used with unique
340    * queries where the ControllerResultSet is the result of a pk selection (like
341    * an Entity Bean).
342    *
343    * @param result that could be in the cache
344    * @param request the update we want to get updated values from
345    * @return boolean[] {needInvalidate,needToSendQuery}
346    */

347   public boolean[] needInvalidate(ControllerResultSet result,
348       UpdateRequest request)
349   {
350     HashMap JavaDoc updatedValues = request.getUpdatedValues();
351     boolean needInvalidate = false;
352     boolean needToSendQuery = false;
353     String JavaDoc value;
354     String JavaDoc columnName;
355     try
356     {
357       // If we don't have exactly one row, we don't handle the optimization
358
if ((result == null) || (result.getData() == null)
359           || (result.getData().size() != 1))
360         return TRUE_TRUE;
361     }
362     catch (Exception JavaDoc e)
363     {
364       return TRUE_TRUE;
365     }
366     Field[] fields = result.getFields();
367     ArrayList JavaDoc data = result.getData();
368     int size = fields.length;
369     for (Iterator JavaDoc iter = updatedValues.keySet().iterator(); iter.hasNext();)
370     {
371       columnName = (String JavaDoc) iter.next();
372       value = (String JavaDoc) updatedValues.get(columnName);
373       for (int i = 0; i < size; i++)
374       { // Find the corresponding column in the ResultSet by comparing column
375
// names
376

377         // We can have something like:
378
// FIRSTNAME and ADDRESS.FIRSTNAME
379
if (columnName.equals(fields[i].getFieldName()))
380         {
381           Object JavaDoc o = ((Object JavaDoc[]) data.get(0))[i];
382           if (!value.equals(o))
383           {
384             // The value from the cache entry is different we need to update
385
// the
386
// cache and the database
387
return TRUE_TRUE;
388           }
389           else
390             break;
391         }
392       }
393       // We don't need to invalidate the cache because the columns affected are
394
// different but we need to send the query to the database.
395
needToSendQuery = true;
396       // We don't stop here though because other columns could be updated and
397
// we
398
// could need invalidation
399
}
400     return new boolean[]{needInvalidate, needToSendQuery};
401   }
402
403   /**
404    * Adds an entry request/reply to the cache. Note that if the request was
405    * already in the cache, only the result is updated.
406    *
407    * @param request the request
408    * @param result the result corresponding to the request
409    * @exception CacheException if an error occurs
410    */

411   public void addToCache(SelectRequest request, ControllerResultSet result)
412       throws CacheException
413   {
414     boolean notifyThread = false;
415
416     try
417     {
418       synchronized (pendingQueries)
419       {
420         // Remove the pending query from the list and wake up
421
// all waiting queries
422
removeFromPendingQueries(request);
423
424         String JavaDoc sqlQuery = getCacheKeyFromRequest(request);
425
426         // Sanity checks
427
if (request.getCacheAbility() == RequestType.UNCACHEABLE)
428           throw new CacheException(Translate.get(
429               "resultcache.uncacheable.request", sqlQuery));
430
431         if (result == null)
432           throw new CacheException(Translate.get("resultcache.null.result",
433               sqlQuery));
434
435         // Check against streamable ResultSets
436
if (result.hasMoreData())
437         {
438           logger.info(Translate.get("resultcache.streamed.resultset", request
439               .getSQLShortForm(20)));
440           return;
441         }
442
443         if (logger.isDebugEnabled())
444           logger.debug(Translate.get("resultcache.adding.query", sqlQuery));
445
446         AbstractResultCacheEntry ce;
447         synchronized (queries)
448         {
449           // Check first that the query is not already in the cache
450
ce = (AbstractResultCacheEntry) queries.get(sqlQuery);
451           if (ce == null)
452           {
453             // Not in cache, add this entry
454
// check the rule
455
CacheBehavior behavior = getCacheBehavior(request);
456             ce = behavior.getCacheEntry(request, result, this);
457             if (ce instanceof ResultCacheEntryNoCache)
458               return;
459
460             // Test size of cache
461
if (maxEntries > 0)
462             {
463               int size = queries.size();
464               if (size >= maxEntries)
465                 // LRU replacement policy: Remove the oldest cache entry
466
removeOldest();
467             }
468             // Add to the cache
469
queries.put(sqlQuery, ce);
470
471             notifyThread = true;
472           }
473           else
474           { // Oh, oh, already in cache ...
475
if (ce.isValid())
476               logger.warn(Translate.get(
477                   "resultcache.modifying.result.valid.entry", sqlQuery));
478             ce.setResult(result);
479           }
480
481           // Update LRU
482
if (lruHead != null)
483           {
484             lruHead.setPrev(ce);
485             ce.setNext(lruHead);
486             ce.setPrev(null);
487           }
488           if (lruTail == null)
489             lruTail = ce;
490           lruHead = ce; // This is also fine if LRUHead == null
491
}
492         processAddToCache(ce);
493
494         // process thread notification out of the synchronized block on
495
// pending queries to avoid deadlock, while adding/removing
496
// on cache
497
if (notifyThread)
498         {
499           // relaxed entry
500
if (ce instanceof ResultCacheEntryRelaxed)
501           {
502             ResultCacheEntryRelaxed qcer = (ResultCacheEntryRelaxed) ce;
503             synchronized (relaxedThread)
504             {
505               relaxedCache.add(qcer);
506               if (qcer.getDeadline() < relaxedThread.getThreadWakeUpTime()
507                   || relaxedThread.getThreadWakeUpTime() == 0)
508               {
509                 relaxedThread.notify();
510               }
511             }
512           }
513           else if (ce instanceof ResultCacheEntryEager)
514           {
515             // eager entry
516
ResultCacheEntryEager qcee = (ResultCacheEntryEager) ce;
517             if (qcee.getDeadline() != AbstractResultCacheEntry.NO_DEADLINE)
518             { // Only deal with entries that specify a timeout
519
synchronized (eagerThread)
520               {
521                 eagerCache.add(qcee);
522                 if (qcee.getDeadline() < eagerThread.getThreadWakeUpTime()
523                     || eagerThread.getThreadWakeUpTime() == 0)
524                 {
525                   eagerThread.notify();
526                 }
527               }
528             }
529           }
530         }
531       }
532     }
533     catch (OutOfMemoryError JavaDoc oome)
534     {
535       flushCache();
536       System.gc();
537       logger.warn(Translate.get("cache.memory.error.cache.flushed", this
538           .getClass()));
539     }
540   }
541
542   /**
543    * Process the add to cache to update implementation specific data structures.
544    *
545    * @param qe to add to the cache.
546    */

547   protected abstract void processAddToCache(AbstractResultCacheEntry qe);
548
549   /**
550    * Gets the result to the given request from the cache. The returned
551    * <code>AbstractResultCacheEntry</code> is <code>null</code> if the
552    * request is not present in the cache.
553    * <p>
554    * An invalid <code>AbstractResultCacheEntry</code> may be returned (it
555    * means that the result is <code>null</code>) but the already parsed query
556    * can be retrieved from the cache entry.
557    *
558    * @param request an SQL select request
559    * @param addToPendingQueries <code>true</code> if the request must be added
560    * to the pending query list on a cache miss
561    * @return the <code>AbstractResultCacheEntry</code> if found, else
562    * <code>null</code>
563    */

564   public AbstractResultCacheEntry getFromCache(SelectRequest request,
565       boolean addToPendingQueries)
566   {
567     stats.addSelect();
568
569     if (request.getCacheAbility() == RequestType.UNCACHEABLE)
570     {
571       stats.addUncacheable();
572       return null;
573     }
574
575     String JavaDoc sqlQuery = getCacheKeyFromRequest(request);
576
577     // Check if we have the same query pending
578
synchronized (pendingQueries)
579     {
580       if (addToPendingQueries)
581       {
582         long timeout = pendingQueryTimeout;
583         // Yes, wait for the result
584
// As we use a single lock for all pending queries, we use a
585
// while to re-check that this wake-up was for us!
586
while (pendingQueries.contains(sqlQuery))
587         {
588           try
589           {
590             if (logger.isDebugEnabled())
591               logger.debug(Translate.get("resultcache.waiting.pending.query",
592                   sqlQuery));
593
594             if (timeout > 0)
595             {
596               long start = System.currentTimeMillis();
597               pendingQueries.wait(pendingQueryTimeout);
598               long end = System.currentTimeMillis();
599               timeout = timeout - (end - start);
600               if (timeout <= 0)
601               {
602                 logger.warn(Translate.get("resultcache.pending.query.timeout"));
603                 break;
604               }
605             }
606             else
607               pendingQueries.wait();
608           }
609           catch (InterruptedException JavaDoc e)
610           {
611             logger.warn(Translate.get("resultcache.pending.query.timeout"));
612             break;
613           }
614         }
615       }
616
617       // Check the cache
618
AbstractResultCacheEntry ce;
619       synchronized (queries)
620       {
621         ce = (AbstractResultCacheEntry) queries.get(sqlQuery);
622         if (ce == null)
623         // if ((ce == null) || !ce.isValid())
624
{ // Cache miss or dirty entry
625
if (addToPendingQueries)
626           {
627             pendingQueries.add(sqlQuery);
628             // Add this query to the pending queries
629
if (logger.isDebugEnabled())
630             {
631               logger.debug(Translate.get("resultcache.cache.miss"));
632               logger.debug(Translate.get(
633                   "resultcache.adding.to.pending.queries", sqlQuery));
634             }
635           }
636           return null;
637         }
638         else
639         { // Cache hit (must update LRU)
640
// Move cache entry to head of LRU
641
AbstractResultCacheEntry before = ce.getPrev();
642           if (before != null)
643           {
644             AbstractResultCacheEntry after = ce.getNext();
645             before.setNext(after);
646             if (after != null)
647               after.setPrev(before);
648             else
649               // We were the tail, update the tail
650
lruTail = before;
651             ce.setNext(lruHead);
652             ce.setPrev(null);
653             if (lruHead != ce)
654               lruHead.setPrev(ce);
655             lruHead = ce;
656           }
657           // else it was already the LRU head
658
}
659       }
660
661       if (ce.getResult() == null)
662       {
663         if (addToPendingQueries)
664         {
665           pendingQueries.add(sqlQuery);
666           // Add this query to the pending queries
667
if (logger.isDebugEnabled())
668           {
669             logger.debug(Translate.get("resultcache.cache.miss"));
670             logger.debug(Translate.get("resultcache.adding.to.pending.queries",
671                 sqlQuery));
672           }
673         }
674         if (ce.isValid() && logger.isInfoEnabled())
675           logger.info(Translate.get("resultcache.valid.entry.without.result",
676               ce.getRequest().getSQL()));
677       }
678       else
679       {
680         if (logger.isDebugEnabled())
681           logger.debug(Translate.get("resultcache.cache.hit", sqlQuery));
682         stats.addHits();
683       }
684
685       return ce;
686     }
687   }
688
689   /**
690    * Removes an entry from the cache (both request and reply are dropped). The
691    * request is NOT removed from the pending query list, but it shouldn't be in
692    * this list.
693    *
694    * @param request a <code>SelectRequest</code>
695    */

696   public void removeFromCache(SelectRequest request)
697   {
698     String JavaDoc sqlQuery = request.getSQL();
699
700     if (logger.isDebugEnabled())
701       logger.debug("Removing from cache: " + sqlQuery);
702
703     synchronized (queries)
704     {
705       // Remove from the cache
706
AbstractResultCacheEntry ce = (AbstractResultCacheEntry) queries
707           .remove(sqlQuery);
708       if (ce == null)
709         return; // Was not in the cache!
710
else
711       {
712         // Update result set
713
ce.setResult(null);
714         // Update LRU
715
AbstractResultCacheEntry before = ce.getPrev();
716         AbstractResultCacheEntry after = ce.getNext();
717         if (before != null)
718         {
719           before.setNext(after);
720           if (after != null)
721             after.setPrev(before);
722           else
723             // We were the tail, update the tail
724
lruTail = before;
725         }
726         else
727         { // We are the LRUHead
728
lruHead = ce.getNext();
729           if (after != null)
730             after.setPrev(null);
731           else
732             // We were the tail, update the tail
733
lruTail = before;
734         }
735         // Remove links to other cache entries for GC
736
ce.setNext(null);
737         ce.setPrev(null);
738       }
739     }
740   }
741
742   /**
743    * Removes an entry from the pending query list.
744    *
745    * @param request a <code>SelectRequest</code>
746    */

747   public void removeFromPendingQueries(SelectRequest request)
748   {
749     String JavaDoc sqlQuery = getCacheKeyFromRequest(request);
750
751     synchronized (pendingQueries)
752     {
753       // Remove the pending query from the list and wake up
754
// all waiting queries
755
if (pendingQueries.remove(sqlQuery))
756       {
757         if (logger.isDebugEnabled())
758           logger.debug(Translate.get("resultcache.removing.pending.query",
759               sqlQuery));
760         pendingQueries.notifyAll();
761       }
762       else
763         logger.warn(Translate.get("resultcache.removing.pending.query.failed",
764             sqlQuery));
765     }
766   }
767
768   /**
769    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#isUpdateNecessary(org.objectweb.cjdbc.common.sql.UpdateRequest)
770    */

771   public abstract boolean isUpdateNecessary(UpdateRequest request)
772       throws CacheException;
773
774   /**
775    * Notifies the cache that this write request has been issued, so that cache
776    * coherency can be maintained. If the cache is distributed, this method is
777    * reponsible for broadcasting this information to other caches.
778    *
779    * @param request an <code>AbstractRequest</code> value
780    * @exception CacheException if an error occurs
781    */

782   public void writeNotify(AbstractWriteRequest request) throws CacheException
783   {
784     // Update the stats
785
if (request.isInsert())
786       stats.addInsert();
787     else if (request.isUpdate())
788       stats.addUpdate();
789     else if (request.isDelete())
790       stats.addDelete();
791     else if (request.isCreate())
792     {
793       stats.addCreate();
794       // Create: we only need to update the schema
795
if (parsingGranularity != ParsingGranularities.NO_PARSING)
796       {
797         CreateRequest createRequest = (CreateRequest) request;
798         if (createRequest.altersDatabaseSchema()
799             && (createRequest.getDatabaseTable() != null))
800           cdbs
801               .addTable(new CacheDatabaseTable(createRequest.getDatabaseTable()));
802       }
803       return;
804     }
805     else if (request.isDrop())
806     {
807       stats.addDrop();
808       // Drop: we need to update the schema
809
if (parsingGranularity != ParsingGranularities.NO_PARSING)
810       {
811         // Invalidate the cache entries associated with this table
812
CacheDatabaseTable cdt = cdbs.getTable(request.getTableName());
813         if (cdt != null)
814         {
815           cdt.invalidateAll();
816           cdbs.removeTable(cdt);
817           return;
818         }
819         // else: the table was not previously cached
820
// (no previous 'select' requests on the table).
821
}
822     }
823     else
824     {
825       stats.addUnknown();
826     }
827     if (logger.isDebugEnabled())
828       logger.debug("Notifying write " + request.getSQL());
829
830     processWriteNotify(request);
831   }
832
833   /**
834    * Implementation specific invalidation of the cache.
835    *
836    * @param request Write request that invalidates the cache.
837    */

838   protected abstract void processWriteNotify(AbstractWriteRequest request);
839
840   /**
841    * Removes all entries from the cache.
842    */

843   public void flushCache()
844   {
845     // Check if we are already flushing the cache
846
synchronized (this)
847     {
848       if (flushingCache)
849         return;
850       flushingCache = true;
851     }
852
853     try
854     {
855       synchronized (queries)
856       { // Invalidate the whole cache until it is empty
857
while (!queries.isEmpty())
858         {
859           Iterator JavaDoc iter = queries.values().iterator();
860           ((AbstractResultCacheEntry) iter.next()).invalidate();
861         }
862       }
863
864       synchronized (pendingQueries)
865       { // Clean pending queries to unblock everyone if some queries/backends
866
// remained in an unstable state.
867
pendingQueries.clear();
868         pendingQueries.notifyAll();
869       }
870     }
871     finally
872     {
873       synchronized (this)
874       {
875         flushingCache = false;
876       }
877       if (logger.isDebugEnabled())
878         logger.debug(Translate.get("resultcache.cache.flushed"));
879     }
880   }
881
882   /**
883    * Get Cache size
884    *
885    * @return the approximate size of the cache in bytes
886    */

887   public long getCacheSize()
888   {
889     // No need to synchronize, the implementation returns an int
890
return queries.size();
891   }
892
893   /**
894    * Removes the oldest entry from the cache.
895    * <p>
896    * <b>!Warning! </b> This method is not synchronized and should be called in
897    * the scope of a synchronized(queries)
898    */

899   private void removeOldest()
900   {
901     if (lruTail == null)
902       return;
903     // Update the LRU
904
AbstractResultCacheEntry oldce = lruTail;
905     lruTail = lruTail.getPrev();
906     if (lruTail != null)
907       lruTail.setNext(null);
908
909     if (logger.isDebugEnabled())
910       logger.debug(Translate.get("resultcache.removing.oldest.cache.entry",
911           oldce.getRequest().getSQL()));
912
913     /*
914      * We remove the query from the hashtable so that the garbage collector can
915      * do its job. We need to remove the query from the queries HashTable first
916      * in case we invalidate an eager cache entry that will call removeFromCache
917      * (and will try to update the LRU is the entry is still in the queries
918      * HashTable). So, to be compatible with all type of cache entries: 1.
919      * queries.remove(ce) 2. ce.invalidate
920      */

921     queries.remove(oldce.getRequest().getSQL());
922
923     if (oldce.isValid())
924     {
925       oldce.setResult(null);
926       oldce.invalidate();
927     }
928
929     stats.addRemove();
930   }
931
932   /**
933    * Gets the needed query parsing granularity.
934    *
935    * @return needed query parsing granularity
936    */

937   public int getParsingGranularity()
938   {
939     return this.parsingGranularity;
940   }
941
942   /**
943    * Retrieve the name of this cache
944    *
945    * @return name
946    */

947   public abstract String JavaDoc getName();
948
949   //
950
// Transaction management
951
//
952

953   /**
954    * Commit a transaction given its id.
955    *
956    * @param transactionId the transaction id
957    * @throws CacheException if an error occurs
958    */

959   public void commit(long transactionId) throws CacheException
960   {
961     // Ok, the transaction has commited, nothing to do
962
}
963
964   /**
965    * Rollback a transaction given its id.
966    *
967    * @param transactionId the transaction id
968    * @throws CacheException if an error occurs
969    */

970   public void rollback(long transactionId) throws CacheException
971   {
972     logger.info(Translate.get("resultcache.flushing.cache.cause.rollback",
973         transactionId));
974     flushCache();
975   }
976
977   /*
978    * Debug/Monitoring
979    */

980
981   /**
982    * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#getCacheData
983    */

984   public String JavaDoc[][] getCacheData() throws CacheException
985   {
986     try
987     {
988       synchronized (queries)
989       {
990         String JavaDoc[][] data = new String JavaDoc[queries.size()][];
991         int count = 0;
992         for (Iterator JavaDoc iter = queries.values().iterator(); iter.hasNext(); count++)
993         {
994           AbstractResultCacheEntry qe = (AbstractResultCacheEntry) iter.next();
995           if (qe != null)
996           {
997             data[count] = qe.toStringTable();
998           }
999         }
1000        return data;
1001      }
1002    }
1003    catch (Exception JavaDoc e)
1004    {
1005      logger.error(Translate.get("resultcache.error.retrieving.cache.data", e));
1006      throw new CacheException(e.getMessage());
1007    }
1008  }
1009
1010  /**
1011   * @see org.objectweb.cjdbc.controller.cache.result.AbstractResultCache#getCacheStatsData()
1012   */

1013  public String JavaDoc[][] getCacheStatsData() throws CacheException
1014  {
1015    String JavaDoc[][] data = new String JavaDoc[1][];
1016    String JavaDoc[] stat = stats.getCacheStatsData();
1017    data[0] = new String JavaDoc[stat.length + 1];
1018    for (int i = 0; i < stat.length; i++)
1019      data[0][i] = stat[i];
1020    data[0][data[0].length - 1] = "" + queries.size();
1021    return data;
1022  }
1023
1024  /**
1025   * @return Returns the stats.
1026   */

1027  public CacheStatistics getCacheStatistics()
1028  {
1029    return stats;
1030  }
1031
1032  /**
1033   * Returns the eagerCache value.
1034   *
1035   * @return Returns the eagerCache.
1036   */

1037  public ArrayList JavaDoc getEagerCache()
1038  {
1039    return eagerCache;
1040  }
1041
1042  /**
1043   * Returns the relaxedCache value.
1044   *
1045   * @return Returns the relaxedCache.
1046   */

1047  public ArrayList JavaDoc getRelaxedCache()
1048  {
1049    return relaxedCache;
1050  }
1051
1052  /**
1053   * Gets information about the request cache
1054   *
1055   * @return <code>String</code> containing information
1056   */

1057  protected String JavaDoc getXmlImpl()
1058  {
1059    StringBuffer JavaDoc info = new StringBuffer JavaDoc();
1060    info.append("<" + DatabasesXmlTags.ELT_ResultCache + " "
1061        + DatabasesXmlTags.ATT_pendingTimeout + "=\"" + pendingQueryTimeout
1062        + "\" " + DatabasesXmlTags.ATT_maxNbOfEntries + "=\"" + maxEntries
1063        + "\" " + DatabasesXmlTags.ATT_granularity + "=\"" + getName() + "\">");
1064    info.append("<" + DatabasesXmlTags.ELT_DefaultResultCacheRule + " "
1065        + DatabasesXmlTags.ATT_timestampResolution + "=\""
1066        + defaultRule.getTimestampResolution() / 1000 + "\">");
1067    info.append(defaultRule.getCacheBehavior().getXml());
1068    info.append("</" + DatabasesXmlTags.ELT_DefaultResultCacheRule + ">");
1069    for (Iterator JavaDoc iter = cachingRules.iterator(); iter.hasNext();)
1070      info.append(((ResultCacheRule) iter.next()).getXml());
1071    info.append("</" + DatabasesXmlTags.ELT_ResultCache + ">");
1072    return info.toString();
1073  }
1074
1075}
Popular Tags