KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > cache > Cache


1 /******************************************************************************
2  * Cache.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.cache;
11
12 import java.net.*;
13 import java.io.*;
14 import java.util.Map JavaDoc;
15 import java.util.Properties JavaDoc;
16 import java.util.Iterator JavaDoc;
17
18 import org.openlaszlo.xml.internal.XMLUtils;
19 import org.openlaszlo.utils.FileUtils;
20 import org.openlaszlo.utils.ChainedException;
21 import org.openlaszlo.utils.LZHttpUtils;
22 import org.openlaszlo.utils.MathUtils;
23 import org.openlaszlo.server.LPS;
24
25 import org.openlaszlo.data.Data;
26 import org.openlaszlo.data.DataSource;
27 import org.openlaszlo.data.DataSourceException;
28
29 import org.apache.commons.collections.SequencedHashMap;
30
31 import org.apache.log4j.*;
32
33 import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
34 import EDU.oswego.cs.dl.util.concurrent.Sync;
35
36 /**
37  * A base class for maintaining a disk-backed cache of requests.
38  *
39  * Cache has 2 sequenced hash map of Items. Each item uniquely
40  * represents something to be cached (and transcoded). The
41  * first map contains items that are "in memory". Items that
42  * are "in memory" are also stored on disk. The second map
43  * contains items that are only on disk.
44  *
45  * The cache chucks out (via LRU) items from each map when it is beyond
46  * its maximum size for that map. Each time an item is requested,
47  * it is moved to the front of the "in-memory" map.
48  *
49  * Each item has its own lock. When an item is requested,
50  * we lock it and then go to the DataSource to see if
51  * we need to refresh any copy of it we have.
52  * When an item has it's size change or it marked dirty, it
53  * is also marked as needing its size changed reckoned with the
54  * cache sizes. Whenever the cache is locked and runs into
55  * an item that needs reckoning, it updates the cache sizes
56  * to reflect the item.
57  *
58  * The cache supports multiple encodings of individual items.
59  *
60  * @author <a HREF="mailto:bloch@laszlosystems.com">Eric Bloch</a>
61  */

62 public abstract class Cache {
63
64     public final String JavaDoc DEFAULT_DISK_MAX = "500000000";
65     public final String JavaDoc DEFAULT_MEM_MAX = "1000000";
66     public final String JavaDoc DEFAULT_MEM_ITEM_MAX = "0";
67
68     /** Logger. */
69     private static Logger mLogger = Logger.getLogger(Cache.class);
70     private static Logger mLockLogger = Logger.getLogger("trace.cache.lock");
71
72     /** Name */
73     private final String JavaDoc mName;
74
75     /** See the constructor. */
76     private File mCacheDirectory;
77
78     /** Sequenced Maps of Cached Items keyed by request.
79      * All access to these maps must must be synchronized
80      */

81     private SequencedHashMap mDiskMap = null;
82     private SequencedHashMap mMemMap = null;
83     private SequencedHashMap mActiveMap = null;
84
85     /** Maxmimum size for the on-disk cache in bytes */
86     private long mMaxDiskSize = 0;
87
88     /** Maxmimum size for the in-memory cache in bytes */
89     private long mMaxMemSize = 0;
90
91     /** Maxmimum size for the in-memory cache item in bytes */
92     private long mMaxMemItemSize = 0;
93
94     /** Current size of the in-disk cache in bytes */
95     private long mDiskSize = 0;
96
97     /** Current size of the in-memory cache in bytes */
98     private long mMemSize = 0;
99
100     /** Current cache id */
101     private long mCurName = 0;
102     private Object JavaDoc mNameLock = new Object JavaDoc();
103
104     /**
105      * Creates a new <code>Cache</code> instance.
106      *
107      * @param cacheDirectory a <code>File</code> naming a directory
108      * where cache files should be kept
109      * @param dataSource back end data source for the cache
110      */

111     public Cache(String JavaDoc name, File cacheDirectory, Properties JavaDoc props)
112         throws IOException {
113
114         mCacheDirectory = cacheDirectory;
115         mName = name;
116         cacheDirectory.mkdirs();
117
118         String JavaDoc maxSize;
119         String JavaDoc load;
120         String JavaDoc mapsize;
121
122         // TODO: 2004-09-07 bloch catch NumberParseException's here
123
maxSize = props.getProperty(mName + ".disk.size", DEFAULT_DISK_MAX);
124         if (maxSize != null) {
125             mMaxDiskSize = Long.parseLong(maxSize);
126         }
127         maxSize = props.getProperty(mName + ".mem.size", DEFAULT_MEM_MAX);
128         if (maxSize != null) {
129             mMaxMemSize = Long.parseLong(maxSize);
130         }
131         maxSize = props.getProperty(mName + ".mem.item.max", DEFAULT_MEM_ITEM_MAX);
132         if (maxSize != null) {
133             mMaxMemItemSize = Long.parseLong(maxSize);
134         }
135
136         load = props.getProperty(mName + ".disk.load");
137         if (load != null) {
138             int l = Integer.parseInt(load);
139             mapsize = props.getProperty(mName + ".disk.mapsize");
140             if (mapsize != null) {
141                 float m = Float.parseFloat(mapsize);
142                 mDiskMap = new SequencedHashMap(l, m);
143             } else {
144                 mDiskMap = new SequencedHashMap(l);
145             }
146         } else {
147             mDiskMap = new SequencedHashMap();
148         }
149         load = props.getProperty(mName + ".mem.load");
150         if (load != null) {
151             int l = Integer.parseInt(load);
152             mapsize = props.getProperty(mName + ".mem.mapsize");
153             if (mapsize != null) {
154                 float m = Float.parseFloat(mapsize);
155                 mMemMap = new SequencedHashMap(l, m);
156             } else {
157                 mMemMap = new SequencedHashMap(l);
158             }
159         } else {
160             mMemMap = new SequencedHashMap();
161         }
162         load = props.getProperty(mName + ".active.load");
163         if (load != null) {
164             int l = Integer.parseInt(load);
165             mapsize = props.getProperty(mName + ".active.mapsize");
166             if (mapsize != null) {
167                 float m = Float.parseFloat(mapsize);
168                 mActiveMap = new SequencedHashMap(l, m);
169             } else {
170                 mActiveMap = new SequencedHashMap(l);
171             }
172         } else {
173             mActiveMap = new SequencedHashMap();
174         }
175
176         long t = System.currentTimeMillis();
177         loadFromDirectory();
178         mLogger.debug("loading " + mName + " took " +
179                 MathUtils.formatDouble((System.currentTimeMillis() - t)/1000.0, 2) + " seconds");
180     }
181
182     /**
183      * this routine is a place holder which can be overridden to hook into
184      * the cache loading. It is used by the cm/CompilationManager to get
185      * at the contents of the dependencyTracker as it is being loaded from
186      * disk and modify various paths that might have changed since
187      * prefetching.
188      */

189     protected void afterCacheRead(Object JavaDoc metaData) {} // override me
190

191     /**
192      * @return an item if it exists or null otherwise
193      */

194     protected synchronized Item getItem(Serializable key) {
195         mLogger.debug("getItem");
196
197         Item item = (Item)mMemMap.get(key);
198         if (item != null) {
199             return item;
200         }
201         return (Item)mDiskMap.get(key);
202     }
203
204     /**
205      * Find and optionally lock item in the cache that matches this key
206      * If the item doesn't exist, create it. If the item
207      * does exist, move it to the top of the LRU list by removing
208      * and re-adding.
209      * @return the locked item
210      */

211     protected synchronized Item findItem(Serializable key, String JavaDoc enc, boolean doLockAndLeaveActive)
212         throws IOException {
213
214         mLogger.debug("findItem");
215
216         SequencedHashMap curMap = mMemMap;
217         SequencedHashMap newMap = mMemMap;
218
219         boolean hasMemCache = (mMaxMemSize != 0);
220
221         if (!hasMemCache) {
222             newMap = mDiskMap;
223         }
224
225         Item item = (Item) curMap.get(key);
226         if (item == null) {
227             curMap = mDiskMap;
228             item = (Item) curMap.get(key);
229             if (item == null && doLockAndLeaveActive) {
230                 curMap = mActiveMap;
231                 item = (Item) curMap.get(key);
232                 // TODO: [2003-08-27 bloch] add assert in java 1.4 item.active()
233
}
234         }
235
236         // New items default to the mem cache if it exists.
237
// Note that some items that are too big may
238
// be cached on disk (and not in mem) but may
239
// temporarily be in the "mem cache" map.
240
// This is confusing, but simple and safe to implement
241
// w/out complicated locking. This is because
242
// we don't want to lock the maps while we're
243
// fetching the items and we won't know the size
244
// until we fetch the item.
245
//
246
// TODO: [2003-09-04 bloch]
247
// Note: this now only happens when doLockAndLeaveActive is
248
// false. At some point we could rearchitect to remove
249
// this wart
250

251         try {
252             if (item == null) {
253                 item = new Item(key, enc, hasMemCache);
254                 if (doLockAndLeaveActive) {
255                     item.lock();
256                     item.setActive(true);
257                 }
258                 mLogger.debug("Made new item for " + key.toString());
259             } else {
260                 if (doLockAndLeaveActive) {
261                     item.lock();
262                     item.setActive(true);
263                 }
264                 if (item.needsReckoning()) {
265                     mLogger.debug("Reckoning an old item for " + key.toString());
266                     item.reckon();
267                 }
268                 if (item.dirty()) {
269                     mLogger.debug("Found a dirty item for " + key.toString());
270                     if (doLockAndLeaveActive) {
271                         item.removeAndUnlock();
272                     } else {
273                         item.remove();
274                     }
275                     curMap.remove(key);
276                     item = new Item(key, enc, hasMemCache);
277                     if (doLockAndLeaveActive) {
278                         item.lock();
279                         item.setActive(true);
280                     }
281                     mLogger.debug("Removed and made new item for " + key.toString());
282                 } else {
283                     // Remove the item and re-add it below
284
mLogger.debug("Found old item for " + key.toString());
285                     curMap.remove(key);
286                     if (curMap == mDiskMap) {
287                         if (newMap == mMemMap) {
288                             // Update sizes when we're moving from disk to mem
289
long size = item.getSize();
290                             if (size <= mMaxMemItemSize || mMaxMemItemSize <= 0) {
291                                 item.readIntoMemory();
292                                 // Update sizes after we read into memory in case
293
// the above read fails for some bizarro reason.
294
mDiskSize -= size;
295                                 mMemSize += size;
296                             } else {
297                                 newMap = mDiskMap;
298                             }
299                         }
300                     }
301                 }
302             }
303     
304             if (!doLockAndLeaveActive) {
305                 newMap.put(key, item);
306             } else {
307                 mActiveMap.put(key, item);
308             }
309
310         } catch (IOException e) {
311             // If we get any kind of exception, we better unlock the item
312
// since no one will be able to unlock it.
313
if (doLockAndLeaveActive && item != null) {
314                 item.unlock();
315             }
316             throw e;
317         } catch (RuntimeException JavaDoc re) {
318             // If we get any kind of exception, we better unlock the item
319
// since no one will be able to unlock it.
320
if (doLockAndLeaveActive && item != null) {
321                 item.unlock();
322             }
323             throw re;
324         }
325
326         return item;
327     }
328
329     /**
330      * Reckon this item and update the cache.
331      */

332     protected synchronized void updateCache(Item item) {
333
334         item.lock();
335         item.reckon();
336         item.unlock();
337
338         // While we have the cache locked, update the maps
339
updateMapsAndReckonItems(false /* leave at least one item in each map */);
340     }
341
342     /**
343      * Reckon and deactivate this item and update the cache.
344      */

345     protected synchronized void updateCacheAndDeactivateItem(Item item) {
346
347         item.lock();
348         item.reckon();
349
350         if (item.active()) {
351             Object JavaDoc key = item.getKey();
352             mActiveMap.remove(key);
353             if (item.isInMemory()) {
354                 mMemMap.put(key, item);
355             } else {
356                 mDiskMap.put(key, item);
357             }
358             item.setActive(false);
359         }
360
361         item.unlock();
362
363         // While we have the cache locked, update the maps
364
updateMapsAndReckonItems(true);
365     }
366
367     /**
368      * Set the maximum size of the on-disk cache.
369      * Can cause the cache to remove things.
370      */

371     public synchronized void setMaxDiskSize(long size) {
372         if (size >= mMaxDiskSize || size == -1) {
373             mMaxDiskSize = size;
374             return;
375         }
376         mMaxDiskSize = size;
377         updateMapsAndReckonItems(true);
378     }
379
380     /**
381      * Get the maximum size of the on-disk cache cache
382      */

383     public synchronized long getMaxDiskSize() {
384         return mMaxDiskSize;
385     }
386
387     /**
388      * Get the current size of the on-disk cache
389      */

390     public synchronized long getDiskSize() {
391         return mDiskSize;
392     }
393
394     /**
395      * Set the maxmimum size of the in-memory cache.
396      * Can cause the cache to remove things.
397      */

398     public synchronized void setMaxMemSize(long size) {
399         if (size >= mMaxMemSize || size == -1) {
400             mMaxMemSize = size;
401             return;
402         }
403         mMaxMemSize = size;
404         updateMapsAndReckonItems(true);
405     }
406
407     /**
408      * Get the maxmimum size of the in-memory cache
409      */

410     public synchronized long getMaxMemSize() {
411         return mMaxMemSize;
412     }
413
414     /**
415      * Get the current size of the in-memory cache
416      */

417     public synchronized long getMemSize() {
418         return mMemSize;
419     }
420
421     /**
422      * Update the maps and remove items that
423      * push us beyond the maximum size of the
424      * cache. Never empty out the maps.
425      * Always leave at least one item if clearAll is false.
426      *
427      * Also ignore items that are in progress.
428      * @param clearAll if false, must leave one elt in each map
429      */

430     public synchronized void updateMapsAndReckonItems(boolean clearAll) {
431
432         long removedBytes = 0;
433
434         mLogger.debug(mName + " cache at " + mDiskSize + " bytes on-disk and " +
435                       mMemSize + " bytes in-memory.");
436
437         // Infinite mem size... (nothing will get reckoned, hope that's ok)
438
if (mMaxMemSize == -1) {
439             return;
440         }
441
442         int done = 0;
443         if (!clearAll) {
444             done = 1;
445         }
446
447         // If we have a mem cache, push items from memory to disk
448
while (mMemSize > mMaxMemSize && mMemMap.size() > done) {
449             Map.Entry JavaDoc first = mMemMap.getFirst();
450             Object JavaDoc key = first.getKey();
451             Item item = (Item)first.getValue();
452             item.lock();
453
454             if (item.needsReckoning()){
455                 item.reckon();
456             }
457
458             if (!item.dirty()) {
459                 // Some items that were too large for memory
460
// might be in this map, but are actually
461
// accounted for in the disksize already.
462
// We can skip these.
463
if (item.mInfo.isInMemory()) {
464                     removedBytes = item.removeFromMemory();
465                     mMemSize -= removedBytes;
466                     mDiskSize += removedBytes;
467                 } else {
468                     mLogger.debug("mem item was already on disk");
469                 }
470                 mLogger.debug("Pushing item " + key + " from mem to disk ("
471                         + removedBytes + " bytes)" );
472                 mMemMap.remove(key);
473                 mDiskMap.put(key, item);
474             } else {
475                 if (item.mInfo.isInMemory()) {
476                     mMemMap.remove(key);
477                     mLogger.debug("Removing dirty item " + key + " from mem ");
478                 }
479             }
480             item.unlock();
481         }
482
483         // Infinite disk size... (no disk items will get reckoned, hope that's ok)
484
if (mMaxDiskSize == -1) {
485             return;
486         }
487
488         while (mDiskSize > mMaxDiskSize && mDiskMap.size() > done) {
489             Map.Entry JavaDoc first = mDiskMap.getFirst();
490             Object JavaDoc key = first.getKey();
491             Item item = (Item)first.getValue();
492             item.lock();
493
494             if (item.needsReckoning()){
495                 item.reckon();
496             }
497
498             if (!item.dirty()) {
499                 removedBytes = item.getSize();
500                 item.removeAndUnlock();
501                 mLogger.debug("Removing item " + key + " from disk (" + removedBytes + " bytes)" );
502                 mDiskSize -= removedBytes;
503                 mDiskMap.remove(key);
504             } else {
505                 mDiskMap.remove(key);
506                 mLogger.debug("Removing dirty item " + key + " from disk ");
507             }
508         }
509
510         mLogger.debug(mName + " now at disk: " + mDiskSize + " bytes.");
511         mLogger.debug(mName + " now at mem: " + mMemSize + " bytes.");
512     }
513
514     /**
515      * Dump textual XML representation of cache to buf
516      *
517      * @param name name for XML element
518      * @param boolean if true, dump details
519      * @param buf buffer to append
520      */

521     public synchronized void dumpXML(StringBuffer JavaDoc buf, String JavaDoc name, boolean details) {
522
523         buf.append("<" + name + " \n");
524             buf.append("disk-total=\"");
525             buf.append(getMaxDiskSize());
526             buf.append("\" ");
527             buf.append("disk-in-use=\"");
528             buf.append(getDiskSize());
529             buf.append("\" ");
530             buf.append("mem-total=\"");
531             buf.append(getMaxMemSize());
532             buf.append("\" ");
533             buf.append("mem-in-use=\"");
534             buf.append(getMemSize());
535             buf.append("\" ");
536         buf.append(" >\n");
537         if (details) {
538             buf.append("<active>\n");
539                 dumpMap(buf, mActiveMap, false);
540             buf.append("</active>\n");
541             buf.append("<in-memory>\n");
542                 dumpMap(buf, mMemMap, true);
543             buf.append("</in-memory>\n");
544             buf.append("<disk>\n");
545                 dumpMap(buf, mDiskMap, true);
546             buf.append("</disk>\n");
547         }
548         buf.append("</" + name + ">\n");
549     }
550
551     /**
552      * @param buf buffer to append
553      * @param map map to dump
554      */

555     private synchronized void dumpMap(StringBuffer JavaDoc buf, SequencedHashMap map,
556             boolean lockItems) {
557
558         Iterator JavaDoc iter = map.iterator();
559         while (iter.hasNext()) {
560             buf.append("<item ");
561             Object JavaDoc key = iter.next();
562             Item item = (Item) map.get(key);
563             if (lockItems) {
564                 item.lock();
565             }
566             String JavaDoc keystring = key.toString();
567             if (keystring.length() > 128) {
568                 keystring = keystring.substring(0,127) + "...";
569             }
570             buf.append("key=\"" + XMLUtils.escapeXml(keystring) + "\" ");
571             buf.append("in-memory=\"" + (new Boolean JavaDoc(item.isInMemory())).toString() + "\" " );
572             buf.append("dirty=\"" + (new Boolean JavaDoc(item.dirty())).toString() + "\" " );
573             buf.append("active=\"" + (new Boolean JavaDoc(item.active())).toString() + "\" " );
574             buf.append("needs-reckon=\"" + (new Boolean JavaDoc(item.needsReckoning())).toString() + "\" " );
575             buf.append("mem-to-reckon=\"" + item.memToReckon() + "\" " );
576             buf.append("disk-to-reckon=\"" + item.diskToReckon() + "\" " );
577             buf.append("size=\"" + item.getSize() + "\" ");
578             buf.append("key-size=\"" + item.getKeySize() + "\" ");
579             buf.append("path-name=\"" + item.getPathName() + "\" ");
580             buf.append("info-name=\"" + item.getInfoName() + "\" ");
581             long lm = item.getInfo().getLastModified();
582             buf.append("last-modified=\"" + lm + "\" ");
583             buf.append("last-modified-gmt=\"" + LZHttpUtils.getDateString(lm) + "\" ");
584             buf.append("/>\n");
585             if (lockItems) {
586                 item.unlock();
587             }
588         }
589     }
590
591     /**
592      * Load the state of the cache from what is in the cache
593      * Then update the cache log itself to represent
594      * what we now know.
595      *
596      * FIXME: [2003-03-11] bloch the last-used time is lost when
597      * we shutdown; this means the LRU ordering isn't preserved on
598      * restart. We should add lastUsed times to the cachedInfo.
599      */

600     private synchronized void loadFromDirectory() throws IOException {
601
602         String JavaDoc[] fileNames = mCacheDirectory.list();
603
604         for(int i = 0; i < fileNames.length; i++) {
605             if (!fileNames[i].endsWith(".dat"))
606                 continue;
607
608             int x = fileNames[i].indexOf(".dat");
609             String JavaDoc name = fileNames[i].substring(0, x);
610             long n;
611             try {
612                 n = Long.parseLong(name);
613                 if (n > mCurName) {
614                     mCurName = n;
615                 }
616             } catch (NumberFormatException JavaDoc ne) {
617                 mLogger.warn("ignoring a strange .dat file in the cache named: " + fileNames[i]);
618                 continue;
619             }
620
621             Item item = null;
622             File file = new File(mCacheDirectory + File.separator + fileNames[i]);
623             try {
624                 item = new Item(file);
625             } catch (Throwable JavaDoc e) {
626                 mLogger.error("can't load cache file: " + fileNames[i]);
627                 mLogger.error("exception: ", e);
628                 mLogger.error("deleting cache files for : " + fileNames[i]);
629                 // Deletes name* files.
630
if (!FileUtils.deleteFilesStartingWith(mCacheDirectory, name)) {
631                     mLogger.error("couldn't delete some files with name: " + fileNames[i]);
632                 }
633                 continue;
634             }
635
636             Object JavaDoc key = item.getKey();
637             if (item.isInMemory()) {
638                 mMemMap.put(key, item);
639                 mMemSize += item.getSize();
640             } else {
641                 mDiskMap.put(key, item);
642                 mDiskSize += item.getSize();
643             }
644
645             String JavaDoc keystring = key.toString();
646             if (keystring.length() > 128) {
647                 keystring = keystring.substring(0,127) + "...";
648             }
649             mLogger.debug("loaded cached item for " + keystring);
650         }
651
652         updateMapsAndReckonItems(true);
653
654     }
655
656     /**
657      * Clear the cache
658      */

659     public synchronized boolean clearCache() {
660         mLogger.info("Clearing " + mName);
661
662         boolean ok1 = clearMap(mActiveMap);
663         boolean ok2 = clearMap(mDiskMap);
664         boolean ok3 = clearMap(mMemMap);
665         boolean ok4 = true;
666
667         mMemSize = 0;
668         mDiskSize = 0;
669
670         ok4 = FileUtils.deleteFiles(mCacheDirectory);
671
672         return ok1 && ok2 && ok3 && ok4;
673     }
674
675     /**
676      * Clear entries from given map
677      * @param map to clear
678      */

679     private boolean clearMap(SequencedHashMap map) {
680
681         boolean ok = true;
682         while (map.size() > 0) {
683             Map.Entry JavaDoc first = map.getFirst();
684             Object JavaDoc key = first.getKey();
685             Item item = (Item)first.getValue();
686             item.lock();
687             if (!item.removeAndUnlock()) {
688                 ok = false;
689             }
690             map.remove(key);
691         }
692         
693         return ok;
694     }
695
696     /**
697      * Return a unique name
698      *
699      * Since time always moves fwd, we shouldn't
700      * have to worry about using names we've
701      * previously "allocated".
702      *
703      * FIXME: [2003-02-25 bloch] if someone sets the clock
704      * back, we need to clear the cache; we could easily
705      * detect this using timestamps on files in the cache.
706      */

707      private long nextUniqueName() {
708
709          long name;
710
711          synchronized (mNameLock) {
712              name = System.currentTimeMillis();
713              if (name <= mCurName) {
714                  name = mCurName + 1;
715              }
716              mCurName = name;
717          }
718
719          return name;
720      }
721
722      /**
723       * A class that represents an item in the Cache.
724       *
725       * @author <a HREF="mailto:bloch@laszlosystems.com">Eric Bloch</a>
726       */

727      protected class Item implements Serializable {
728
729         transient private Sync mLock;
730         transient private boolean mDirty = false;
731         transient private boolean mNeedsReckoning = false;
732         /** true if the item is actively being updated */
733         transient private boolean mActive = false;
734
735         transient private byte mBuffer[];
736
737         transient private long mMemSizeToReckon = 0;
738         transient private long mDiskSizeToReckon = 0;
739
740
741         /**
742          * Information about this cached item.
743          */

744         CachedInfo mInfo = null;
745
746         /**
747          * Construct an Item based on this request
748          * and lock it for write.
749          */

750         public Item(Serializable key, String JavaDoc enc, boolean inMemory) {
751
752             mLock = new ReentrantLock();
753             mInfo = new CachedInfo(key, enc, inMemory, nextUniqueName());
754             mBuffer = null;
755         }
756
757         /**
758          * @return true if item is dirty
759          */

760         public boolean dirty() {
761             return mDirty;
762         }
763
764         /**
765          * @return true if item is active
766          */

767         public boolean active() {
768             return mActive;
769         }
770
771         /** set if item is active */
772         public void setActive(boolean a) {
773             mActive = a;
774         }
775
776         /**
777          * @return true if item is dirty
778          */

779         public boolean needsReckoning () {
780             return mNeedsReckoning;
781         }
782
783         /**
784          * @return mem to reckon
785          */

786         public long memToReckon() {
787             return mMemSizeToReckon;
788         }
789
790         /**
791          * @return disk to reckon
792          */

793         public long diskToReckon() {
794             return mDiskSizeToReckon;
795         }
796
797         /**
798          * Update cache sizes with size from item
799          * that haven't been reckonned yet.
800          * Must have item and entire cache locked to call this
801          */

802         public void reckon() {
803             mMemSize += mMemSizeToReckon;
804             mDiskSize += mDiskSizeToReckon;
805
806             mDiskSizeToReckon = 0;
807             mMemSizeToReckon = 0;
808
809             mNeedsReckoning = false;
810             mLogger.debug("reckoned an item");
811         }
812
813
814         /**
815          * Item should be locked during this call
816          */

817         public void readIntoMemory() throws IOException {
818             FileInputStream fis = null;
819             try {
820                 String JavaDoc p = getPathName();
821                 fis = new FileInputStream(p);
822                 mBuffer = new byte[(int)mInfo.getSize()];
823                 fis.read(mBuffer, 0, (int)mInfo.getSize());
824                 mInfo.setInMemory(true);
825             } catch (IOException e) {
826                 markDirty();
827                 throw e;
828             } finally {
829                 FileUtils.close(fis);
830             }
831         }
832
833         /**
834          * Item should be locked during this call
835          * @return size of item
836          * caller is responsible for updating cache sizes
837          */

838         public long removeFromMemory() {
839             mBuffer = null;
840             mInfo.setInMemory(false);
841             return mInfo.getSize();
842         }
843
844         /**
845          * Construct an Item based on the contents of this file
846          */

847         public Item(File f) throws IOException, OptionalDataException,
848             StreamCorruptedException, ClassNotFoundException JavaDoc
849         {
850             mLock = new ReentrantLock();
851
852             FileInputStream in = null;
853             try {
854                 in = new FileInputStream(f);
855                 ObjectInputStream istr = new ObjectInputStream(in);
856                 mInfo = (CachedInfo)istr.readObject();
857         // after reading the object, call our override routine
858
afterCacheRead(mInfo.getMetaData());
859             } finally {
860                 FileUtils.close(in);
861             }
862
863             // FIXME: [2003-08-15 pkang] isInMemory() will always evaluate to
864
// true for items that are read from disk. This is because those
865
// items are written out only when they're fetched and, by default,
866
// they are created to exist in memory. Since it's better that we
867
// don't read every item into memory during start-up, isInMemory is
868
// set to false here.
869

870             mInfo.setInMemory(false);
871
872 // if (mInfo.isInMemory()) {
873
// readIntoMemory();
874
// }
875
}
876
877         /**
878          * Lock the item
879          */

880         public void lock() {
881
882             mLockLogger.debug("acquiring lock: " + getKey());
883             try {
884                 mLock.acquire();
885             } catch (InterruptedException JavaDoc e) {
886                 throw new ChainedException(e);
887             }
888             mLockLogger.debug("acquired lock: " + getKey());
889         }
890
891         /*
892          * Done with item
893          */

894         public void unlock() {
895             mLockLogger.debug("releasing read lock: " + getKey());
896             mLock.release();
897             mLockLogger.debug("released read lock: " + getKey());
898         }
899
900         /**
901          * @return the key for this item.
902          */

903         public Serializable getKey() {
904             if (mInfo != null)
905                 return mInfo.getKey();
906             else
907                 return "uninitialized item";
908         }
909
910         /**
911          * @return whether this item is in memory or not
912          */

913         public boolean isInMemory() {
914             if (mInfo != null)
915                 return mInfo.isInMemory();
916             else
917                 return false;
918         }
919
920         /**
921          * Return the cached size of this item in bytes
922          * Must have the item read locked for this.
923          */

924         public long getSize() {
925             return mInfo.getSize();
926         }
927
928         /**
929          * Return the size of this items key in bytes.
930          * May return -1 if the size is unknown/expensive to compute.
931          * Must have the item read locked for this.
932          */

933         public long getKeySize() {
934             return mInfo.getKeySize();
935         }
936
937         /**
938          * @return item's info
939          */

940         public CachedInfo getInfo() {
941             return mInfo;
942         }
943     
944         /**
945          * Return the path name of the cached version of this item
946          */

947         public String JavaDoc getPathName() {
948             // FIXME: [2003-03-03 bloch] unhardcode gz extension someday
949
return mCacheDirectory.getAbsolutePath()
950                 + File.separator + mInfo.getName() + ".swf" +
951                 (mInfo.getEncoding() == null ? "" : ".gz") ;
952         }
953
954         /**
955          * Return the path name of the cached info for this item
956          */

957         public String JavaDoc getInfoName() {
958             return mCacheDirectory.getAbsolutePath()
959                 + File.separator + mInfo.getName() + ".dat";
960         }
961
962         /**
963          * Encoding the given item using the requested encoding.
964          * Encode to a temporary file and then rename.
965          *
966          * @return the size in bytes of the encoded file
967          * caller is responsible for updating item and cache sizes
968          */

969         public long encode(String JavaDoc enc) throws IOException {
970
971             File f = new File(getPathName());
972             File tempDir = f.getParentFile();
973             File tempFile = File.createTempFile("lzuc-", null, tempDir);
974             mLogger.debug("Temporary file is " + tempFile.getAbsolutePath());
975             FileUtils.encode(f, tempFile, enc);
976             long size = tempFile.length();
977             if (!f.delete()) {
978                 mLogger.warn("Can't delete " + f.getAbsolutePath());
979             }
980             if (!tempFile.renameTo(f)) {
981                 mLogger.error("Can't rename temporary file to " +
982                         f.getAbsolutePath());
983             }
984             return size;
985         }
986
987         /**
988          * Mark the item as dirty
989          */

990         public void markDirty() {
991             if (mDirty) {
992                 return;
993             }
994             // Removed the cache file and any buffer and mark the item
995
// as dirty
996
File f = new File(getPathName());
997             if (f.exists() && !f.delete()) {
998                 mLogger.warn("can't delete " + getPathName());
999             }
1000            f = new File(getInfoName());
1001            if (f.exists() && !f.delete()) {
1002                mLogger.warn("can't delete " + getInfoName());
1003            }
1004            mDirty = true;
1005            mBuffer = null;
1006            mNeedsReckoning = true;
1007            mLogger.debug("Marking item dirty: " + mInfo.getKey());
1008            if (mInfo.isInMemory()) {
1009                mMemSizeToReckon -= mInfo.getSize();
1010            } else {
1011                mDiskSizeToReckon -= mInfo.getSize();
1012            }
1013            mLogger.debug("disk to reckon : " + mDiskSizeToReckon +
1014                          ", mem to reckon : " + mMemSizeToReckon);
1015            mInfo.setSize(0);
1016        }
1017    
1018        /**
1019         * @return true if this item is still valid
1020         * given the current data
1021         */

1022        public boolean validForData(Data d) {
1023            // TODO: [2003-01-14 bloch] Make sure the cached
1024
// ETag, Content-Length, and Content-MD5 are ok.
1025
// Need to add these to the CachedInfo class and put
1026
// logic here to deal with missing/present values.
1027
// Will need to get the right interface between
1028
// Item/CachedInfo/Data to make this work.
1029
//
1030
//if (hdr != null) {
1031
//}
1032

1033            return true;
1034        }
1035
1036        /**
1037         * Remove the cached version of this item and then unlock it.
1038         * Must have item locked for this. Caller is responsible for updatng cache sizes
1039         * @return true if there were no problems
1040         */

1041        public boolean removeAndUnlock() {
1042            boolean ok = remove();
1043            unlock();
1044            return ok;
1045        }
1046
1047        /**
1048         * Remove the cached version of this item
1049         * @return true if there were no problems
1050         * Caller is responsible for updatng cache sizes.
1051         */

1052        private boolean remove() {
1053
1054            boolean ok = true;
1055            mLogger.debug("removing item for " + mInfo.getKey() + " (" +
1056                         getPathName() + " - " + getSize() + " bytes) ." );
1057            File f = new File(getPathName());
1058            if (f.exists() && !f.delete()) {
1059                mLogger.warn("can't delete " + getPathName());
1060                ok = false;
1061            }
1062            f = new File(getInfoName());
1063            if (f.exists() && !f.delete()) {
1064                mLogger.warn("can't delete " + getInfoName());
1065                ok = false;
1066            }
1067
1068            return ok;
1069        }
1070
1071        /**
1072         * @return stream depending on whether we're in mem
1073         * or on disk
1074         */

1075        public InputStream getStream() {
1076            if (mInfo.isInMemory()) {
1077                return new ByteArrayInputStream(mBuffer);
1078            } else {
1079                try {
1080                    return new FileInputStream(getPathName());
1081                } catch (FileNotFoundException e) {
1082                    throw new ChainedException(e.getMessage());
1083                }
1084            }
1085        }
1086
1087        /**
1088         * @return returns raw byte-array data.
1089         * This should only be used to retrieve immutable data! If the caller modifies this byte array,
1090         * it could cause trouble.
1091         */

1092        public byte[] getRawByteArray() {
1093            try {
1094                if (!mInfo.isInMemory()) {
1095                    readIntoMemory();
1096                }
1097                if (mInfo.isInMemory()) {
1098                    return mBuffer;
1099                } else {
1100                    ByteArrayOutputStream bos = null;
1101                    FileInputStream instream = null;
1102                    try {
1103                        // If it's still not in memory, maybe it was too big, so let's copy it into
1104
// a new byte array and return that.
1105
bos = new ByteArrayOutputStream();
1106                        instream = new FileInputStream(getPathName());
1107                        FileUtils.send(instream, bos);
1108                        return bos.toByteArray();
1109                    } catch (FileNotFoundException e) {
1110                        throw new ChainedException(e.getMessage());
1111                    } finally {
1112                        if (bos != null) {
1113                            bos.close();
1114                        }
1115                        if (instream != null) {
1116                            instream.close();
1117                        }
1118                    }
1119                    
1120                }
1121            } catch (IOException e) {
1122                throw new ChainedException(e.getMessage());
1123            }
1124        }
1125
1126
1127
1128
1129        /**
1130         * @return the file object
1131         */

1132        public File getFile() {
1133            return new File(getPathName());
1134        }
1135
1136        /**
1137         * Get the meta data for the current item
1138         */

1139        public Serializable getMetaData() {
1140            return mInfo.getMetaData();
1141        }
1142
1143        /**
1144         * Update the item from the given input stream
1145         * and meta data and mark the item as clean
1146         * (not dirty).
1147         *
1148         * @param in input stream; ignored if null
1149         * @param metaData
1150         */

1151        public void update(InputStream in, Serializable metaData)
1152            throws IOException {
1153
1154            mLogger.debug("updating item stream and metadata");
1155
1156            update(metaData);
1157
1158            if (in != null) {
1159                update(in);
1160            }
1161
1162            markClean();
1163        }
1164
1165        /**
1166         * Update the item from the given input stream
1167         *
1168         * @param in input stream
1169         */

1170        public void update(InputStream in)
1171            throws IOException {
1172
1173            FileOutputStream out = null;
1174
1175            try {
1176
1177                mLogger.debug("updating item from stream");
1178
1179                out = new FileOutputStream(getPathName());
1180                long size = FileUtils.send(in, out);
1181                FileUtils.close(out);
1182
1183                String JavaDoc enc = mInfo.getEncoding();
1184                if (enc != null && !enc.equals("")) {
1185                    size = encode(enc);
1186                }
1187    
1188                long oldSize = mInfo.getSize();
1189                mInfo.setSize(size);
1190                mNeedsReckoning = true;
1191
1192                // Remove size from any old item
1193
if (mInfo.isInMemory()) {
1194                    mMemSizeToReckon -= oldSize;
1195                } else {
1196                    mDiskSizeToReckon -= oldSize;
1197                }
1198    
1199                // Check to see if we're too big for memory
1200
if (mInfo.isInMemory() && mMaxMemItemSize > 0 &&
1201                        size > mMaxMemItemSize) {
1202                    mLogger.debug("this item too big for memory ");
1203                    mInfo.setInMemory(false);
1204                }
1205    
1206                if (mInfo.isInMemory()) {
1207                    mLogger.debug("reading item into memory ");
1208                    readIntoMemory();
1209                    mMemSizeToReckon += size;
1210                } else {
1211                    mDiskSizeToReckon += size;
1212                }
1213    
1214            } finally {
1215                FileUtils.close(out);
1216            }
1217        }
1218
1219        /**
1220         * Mark the item as clean
1221         */

1222        public void markClean() {
1223            mDirty = false;
1224        }
1225
1226        /**
1227         * Update the item's meta data.
1228         *
1229         * @param in input stream; ignored if null
1230         * @param metaData
1231         */

1232        public void update(Serializable metaData)
1233            throws IOException {
1234
1235            mInfo.setMetaData(metaData);
1236            updateInfo();
1237        }
1238
1239        /**
1240         * Update the item's info
1241         */

1242        public void updateInfo() throws IOException {
1243
1244            FileOutputStream out = null;
1245
1246            try {
1247
1248                // Write out the info (.dat) file
1249
out = new FileOutputStream(getInfoName());
1250                ObjectOutputStream ostr = new ObjectOutputStream(out);
1251                ostr.writeObject(mInfo);
1252                ostr.flush();
1253                ostr.close();
1254
1255            } finally {
1256                FileUtils.close(out);
1257            }
1258        }
1259    }
1260}
1261
Popular Tags