001: // CacheGeneration.java
002: // $Id: CacheGeneration.java,v 1.37 2007/02/09 22:16:38 ylafon Exp $
003: // (c) COPYRIGHT MIT, INRIA and Keio, 1999.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.www.protocol.http.cache;
007:
008: import java.util.Enumeration;
009: import java.util.Hashtable;
010: import java.util.Vector;
011:
012: import java.io.File;
013: import java.io.PrintStream;
014:
015: import org.w3c.util.LRUAble;
016: import org.w3c.util.LRUList;
017: import org.w3c.util.LookupTable;
018: import org.w3c.util.SyncLRUList;
019:
020: public class CacheGeneration implements LRUAble {
021: // the usual debug flag
022: private static final boolean debug = true;
023:
024: // hashtable of resources
025: private Hashtable lookupTable = null;
026: // the LRUList of resources
027: private SyncLRUList lruList = null;
028: // the occupation of this generation
029: private long bytecount = 0;
030: // the capacity of this generation
031: private long bytelimit = 0;
032: // stored size
033: private long bytestored = 0;
034: // serialization flag
035: private boolean saved = false;
036: // serialization flag
037: private boolean loaded = false;
038: // resource count
039: private int cr_count = 0;
040: // the ID of this generation
041: private int id = 0;
042:
043: // a Vector or resource to be removed
044: private Vector toDel = null;
045:
046: // our father
047: private CacheStore store = null;
048:
049: protected File generationFile = null;
050:
051: /**
052: * set the file where the generation is stored
053: * @param generationFile the file
054: */
055: public void setGenerationFile(File generationFile) {
056: this .generationFile = generationFile;
057: }
058:
059: /**
060: * get the generation file
061: * @return a File
062: */
063: public File getGenerationFile() {
064: return generationFile;
065: }
066:
067: /**
068: * Is the generation loaded?
069: * @return a boolean
070: */
071: public boolean isLoaded() {
072: return loaded;
073: }
074:
075: /**
076: * Set the generation as loaded or unloaded
077: * @param loaded the new loaded flag
078: */
079: protected void setLoaded(boolean loaded) {
080: this .loaded = loaded;
081: }
082:
083: /**
084: * Is the generation saved?
085: * @return a boolean
086: */
087: public boolean isSaved() {
088: return saved;
089: }
090:
091: /**
092: * Set the generation as saved or not.
093: * @param saved a boolean
094: */
095: protected void setSaved(boolean saved) {
096: this .saved = saved;
097: }
098:
099: /**
100: * LRU management - previous entry.
101: */
102: protected LRUAble prev = null;
103: /**
104: * LRU management - next entry.
105: */
106: protected LRUAble next = null;
107:
108: /**
109: * LRU management - Get next node.
110: * @return A CacheGeneration instance.
111: */
112: public LRUAble getNext() {
113: return next;
114: }
115:
116: /**
117: * LRU management - Get previous node.
118: * @return A CacheGeneration instance.
119: */
120: public LRUAble getPrev() {
121: return prev;
122: }
123:
124: /**
125: * LRU management - Set next node.
126: */
127: public synchronized void setNext(LRUAble next) {
128: this .next = next;
129: }
130:
131: /**
132: * LRU management - Set previous node.
133: */
134: public synchronized void setPrev(LRUAble prev) {
135: this .prev = prev;
136: }
137:
138: /**
139: * Get the ID of this generation
140: * @return an int, the generation number
141: */
142: public int getId() {
143: return id;
144: }
145:
146: /**
147: * Set the ID of this generation
148: * Useful to reuse generation
149: * @param an integer, the new generation number
150: */
151: public synchronized void setId(int id) {
152: this .id = id;
153: }
154:
155: /**
156: * Give the acual occupation level of this generation
157: * @return a long, the number of bytes of this generation
158: */
159: public long getCachedByteCount() {
160: return bytecount;
161: }
162:
163: /**
164: * Give the fill ratio for the cached resources
165: * @return a float between 0 and 1
166: */
167: public float getFillRatio() {
168: return ((float) bytecount / (float) bytelimit);
169: }
170:
171: /**
172: * Give the acual storeage occupation level of this generation
173: * @return a long, the number of bytes of this generation
174: */
175: public long getStoredByteCount() {
176: return bytestored;
177: }
178:
179: /**
180: * Get the bytecount limit for this generation
181: * @return a long, the maximum number of bytes
182: */
183: public long getByteLimit() {
184: return bytelimit;
185: }
186:
187: /**
188: * Set the new bytecount limit, not that it may perform a cleanup
189: * if necessary.
190: * @param long, the new maximum number of bytes
191: */
192: public synchronized void setByteLimit(long newlimit) {
193: bytelimit = newlimit;
194: if (newlimit >= bytecount) {
195: return;
196: }
197: // try to get some space
198: long to_save = newlimit - bytecount;
199: // be nice
200: to_save -= collectSpace(newlimit - bytecount, true);
201: // then get the space we want ;)
202: to_save -= collectSpace(to_save, false);
203: }
204:
205: /**
206: * Deletes a resource from the "to be deleted" vector
207: * it updates also the number of bye stored in this generation
208: * @return the number of bytes saved.
209: */
210: public long deleteStored(CachedResource cr) {
211: if (!loaded)
212: throw new UnloadedGenerationException("generation " + id);
213: if (debug) {
214: System.out.println("Deleting " + cr.getIdentifier()
215: + "from generation: " + id);
216: }
217: toDel.removeElement(cr);
218: long saved = cr.delete();
219: synchronized (this ) {
220: bytestored -= saved;
221: }
222: store.getState().notifyResourceDeleted(cr);
223: return saved;
224: }
225:
226: /**
227: * Check if a resource has been cached in this generation
228: * @param url the resource url
229: * @return a boolean
230: */
231: public synchronized boolean containsResource(String url) {
232: return (lookupTable.get(url) != null);
233: }
234:
235: /**
236: * Get all the files handled by this generation
237: * @return an enumeration of File
238: */
239: public synchronized Enumeration getFiles() {
240: Vector files = new Vector();
241: if (loaded) {
242: Enumeration fenum = lookupTable.elements();
243: while (fenum.hasMoreElements()) {
244: CachedResource cr = (CachedResource) fenum
245: .nextElement();
246: File file = cr.getFile();
247: if (file != null) {
248: files.addElement(file);
249: }
250: }
251: } else {
252: Enumeration fenum = lookupTable.elements();
253: while (fenum.hasMoreElements()) {
254: String file = (String) fenum.nextElement();
255: if (!file.equals("")) {
256: files.addElement(new File(file));
257: }
258: }
259: }
260: return files.elements();
261: }
262:
263: /**
264: * Get the CachedResource relative to the given URL.
265: * @param url the URL of the CachedResource to find
266: * @return a CachedResource or null.
267: */
268: public synchronized CachedResource lookupResource(String url) {
269: if (!loaded)
270: throw new UnloadedGenerationException("generation " + id);
271: return (CachedResource) lookupTable.get(url);
272: }
273:
274: /**
275: * can this resource be stored?
276: * If the resource is in the generation, only the delta will be taken
277: * into account
278: * @param CachedResource cr, the candidate.
279: * @param long size, the size of the candidate.
280: * @return a boolean, if this generation can cache it or not
281: */
282: private boolean canStore(CachedResource cr, long size) {
283: if (!loaded)
284: throw new UnloadedGenerationException("generation " + id);
285: CachedResource old_cr = null;
286: old_cr = (CachedResource) lookupTable.get(cr.getIdentifier());
287: if (old_cr != null) {
288: long delta = size - old_cr.getCurrentLength();
289: if ((bytecount + delta) > bytelimit) {
290: return false;
291: }
292: }
293: if ((size + bytecount) > bytelimit) {
294: return false;
295: }
296: return true;
297: }
298:
299: /**
300: * Adds this resource, if possible
301: * @param cr, the candidate.
302: * @param size, the size of the candidate.
303: * @return a boolean, true if this resource has been cached
304: */
305: public synchronized boolean addResource(CachedResource cr,
306: long size, long oldsize) {
307: if (!loaded)
308: throw new UnloadedGenerationException("generation " + id);
309: if (canStore(cr, size)) {
310: CachedResource old_cr = null;
311: old_cr = (CachedResource) lookupTable.get(cr
312: .getIdentifier());
313: // do we already have this resource?
314: if (old_cr != null) {
315: // this is the real oldsize
316: oldsize = old_cr.getCurrentLength();
317: long delta = size - oldsize;
318: if ((bytecount + delta) > bytelimit) {
319: return false;
320: }
321: lookupTable.remove(cr.getIdentifier());
322: lruList.remove(cr);
323: bytecount -= oldsize;
324: toDel.addElement(old_cr);
325: store.getState().notifyResourceReplaced(cr, oldsize);
326: } else {
327: store.getState().notifyResourceAdded(cr, oldsize);
328: }
329: lookupTable.put(cr.getIdentifier(), cr);
330: cr.generation = this ;
331: lruList.toHead(cr);
332: bytestored += size;
333: bytecount += size;
334: saved = false;
335: cr_count++;
336: return true;
337: }
338: return false;
339: }
340:
341: /**
342: * Load a CachedResource in this generation. (to be used only at
343: * generation loading). This method load only valid cachedresource.
344: * Check if the associated file exists and has the right size.
345: * @param CachedResource the CachedResource to load.
346: */
347: protected void loadCachedResource(CachedResource cr) {
348: File file = cr.getFile();
349: if ((file != null)
350: && ((!file.exists()) || (file.length() != cr
351: .getCurrentLength()))) {
352: //don't load invalid cachedresource
353: return;
354: }
355: lookupTable.put(cr.getIdentifier(), cr);
356: cr.generation = this ;
357: lruList.toHead(cr);
358: long size = cr.getCurrentLength();
359: bytestored += size;
360: bytecount += size;
361: cr_count++;
362: }
363:
364: /**
365: * Remove the resource from the generation (but don't delete it).
366: * @param cr the CachedResource to remove.
367: * @return the number of byte saved
368: * @exception NoSuchResourceException if this resource was not in this
369: * generation
370: */
371: public synchronized long removeResource(CachedResource cr)
372: throws NoSuchResourceException {
373: if (!loaded)
374: throw new UnloadedGenerationException("generation " + id);
375: return removeResource(cr.getIdentifier());
376: }
377:
378: /**
379: * Remove the resource from the generation (but don't delete it).
380: * @param cr the CachedResource to remove.
381: * @return the number of byte saved
382: * @exception NoSuchResourceException if this resource was not in this
383: * generation
384: */
385: public synchronized long removeResource(String url)
386: throws NoSuchResourceException {
387: if (!loaded)
388: throw new UnloadedGenerationException("generation " + id);
389: CachedResource old_cr = _removeResource(url);
390: return old_cr.getCurrentLength();
391: }
392:
393: /**
394: * Remove the resource from the generation and update the bytecount
395: * variable.
396: * @param url the CachedResource URL
397: * @return the CachedResource removed.
398: * @exception NoSuchResourceException if this resource was not in this
399: * generation
400: */
401: private CachedResource _removeResource(String url)
402: throws NoSuchResourceException {
403: if (debug) {
404: System.err.println("*** removing from generation " + id
405: + ": " + url);
406: }
407: CachedResource old_cr = (CachedResource) lookupTable.get(url);
408: if (old_cr == null) {
409: String msg = url + " not found in generation " + id;
410: throw new NoSuchResourceException(msg);
411: }
412: lookupTable.remove(url);
413: lruList.remove(old_cr);
414: long b_saved = old_cr.getCurrentLength();
415: if (debug) {
416: System.err.println("*** removed... saved " + b_saved
417: + " bytes");
418: }
419: bytecount -= b_saved;
420: bytestored -= b_saved;
421: saved = false;
422: cr_count--;
423: return old_cr;
424: }
425:
426: /**
427: * will garbage collect up to "size" bytes in this generation.
428: * WARNING: this is not synchronized, use with caution!
429: * @param long the number of bytes to be collected
430: * @param check, a boolean, used to validate or not the resource before
431: * deleting them (ie: delete only invalid resources)
432: * @return a long, the number of bytes saved
433: * from disk afterward using delete.
434: */
435:
436: public long collectSpace(long size, boolean check) {
437: if (!loaded) {
438: // load me, and unload another generation is necessary.
439: try {
440: store.loadGeneration(this );
441: } catch (InvalidCacheException ex) {
442: // oups! cache corrupted?
443: if (debug) {
444: System.err.println("*** Collecting "
445: + "Unable to load generation [" + getId()
446: + "]");
447: System.err.println(ex.getMessage());
448: }
449: return 0;
450: }
451: }
452: Vector res_vect = new Vector();
453: long collected = 0;
454: CachedResource cr, ncr;
455: CacheValidator validator;
456:
457: if (debug) {
458: System.err.println("*** Collecting " + size + " bytes "
459: + ((check) ? "with" : "without") + " checking");
460: }
461: // dumb check
462: if (size <= 0)
463: return 0;
464: if ((size > bytecount) && !check) {
465: return emptyGeneration();
466: }
467: validator = store.getValidator();
468: // start with the oldest ones
469: cr = (CachedResource) lruList.getTail();
470: while (cr != null) {
471: // check if we can delete the resource or not
472: if (check) {
473: if (validator.checkStaleness(cr)) {
474: cr = (CachedResource) lruList.getPrev(cr);
475: continue;
476: }
477: }
478: ncr = (CachedResource) lruList.getPrev(cr);
479: synchronized (this ) {
480: lookupTable.remove(cr.getIdentifier());
481: lruList.remove(cr);
482: saved = false;
483: cr_count--;
484: store.getState().notifyResourceToBeDeleted(cr);
485: }
486: collected += cr.getCurrentLength();
487: toDel.addElement(cr);
488: if (collected >= size) {
489: break;
490: }
491: cr = ncr;
492: }
493: synchronized (this ) {
494: bytecount -= collected;
495: }
496: if (debug) {
497: System.err.println("*** Collected " + collected
498: + " bytes from generation " + id);
499: }
500: return collected;
501: }
502:
503: /**
504: * empty this generation
505: * @return a long, the number of bytes saved
506: */
507: protected long emptyGeneration() {
508: if (!loaded)
509: throw new UnloadedGenerationException("generation " + id);
510: Hashtable saved_table;
511: long collected;
512: if (debug) {
513: System.err.println("*** Deleting Generation " + id + " ("
514: + bytecount + ")");
515: }
516: synchronized (this ) {
517: collected = bytecount;
518: saved_table = lookupTable;
519: lookupTable = new Hashtable();
520: lruList = new SyncLRUList();
521: bytecount = 0;
522: cr_count = 0;
523: }
524: Enumeration e = saved_table.elements();
525: while (e.hasMoreElements()) {
526: CachedResource cr = (CachedResource) e.nextElement();
527: toDel.addElement(cr);
528: store.getState().notifyResourceToBeDeleted(cr);
529: }
530: generationFile.delete();
531: saved = false;
532: return collected;
533: }
534:
535: /**
536: * Get the CachedResource of this generation (except the "to be
537: * deleted" resources)
538: * @return an Enumeration of CachedResource
539: */
540: public Enumeration getCachedResources() {
541: if (!loaded)
542: throw new UnloadedGenerationException("generation " + id);
543: return lookupTable.elements();
544: }
545:
546: /**
547: * get the deleted but still stored resource
548: * @returns an enumeration of CachedResources
549: */
550: public Enumeration getDeletedResources() {
551: return toDel.elements();
552: }
553:
554: /**
555: * Set this Generation as a description (update the saved and loaded
556: * status)
557: * @param tables the LookupTables containing attribute descriptions
558: */
559: protected void setDescription(LookupTable tables[]) {
560: clean();
561: saved = true;
562: bytestored = 0;
563: bytecount = 0;
564: cr_count = 0;
565: for (int i = 0; i < tables.length; i++) {
566: LookupTable table = tables[i];
567: try {
568: String stored = (String) table
569: .get(CachedResource.NAME_CURRENT_LENGTH);
570: String id = (String) table
571: .get(CachedResource.NAME_IDENTIFIER);
572: String file = (String) table
573: .get(CachedResource.NAME_FILE);
574: file = (file == null) ? "" : file;
575: bytestored += Integer.parseInt(stored);
576: lookupTable.put(id, file);
577: } catch (Exception ex) {
578: if (debug) {
579: System.err
580: .println("Unable to load description in ["
581: + getId() + "] " + ex.getMessage());
582: }
583: }
584: }
585: }
586:
587: /**
588: * Unload the generation, transform CachedResources to descriptions.
589: */
590: public void unload() {
591: // bytestored unchanged
592: clean();
593: saved = true;
594: bytecount = 0;
595: cr_count = 0;
596: Enumeration kenum = lookupTable.keys();
597: while (kenum.hasMoreElements()) {
598: lookupTable.put(kenum.nextElement(), Boolean.TRUE);
599: }
600: }
601:
602: /**
603: * delete the serialized resource file from the disk
604: */
605: protected void deleteGenerationFile() {
606: generationFile.delete();
607: }
608:
609: /**
610: * Get the current number of resource loaded.
611: * @return an long
612: */
613: public int getCRCount() {
614: return cr_count;
615: }
616:
617: /**
618: * Clean this generation.
619: */
620: public void clean() {
621: this .lookupTable = new Hashtable();
622: this .lruList = new SyncLRUList();
623: this .loaded = false;
624: }
625:
626: /**
627: * copy the content of the generation here
628: * @parameter a CacheGeneration, the one we want to dump here
629: */
630: protected synchronized void copyInto(CacheGeneration gen) {
631: // add everything at the "right" place
632: LRUList ngenLruList = gen.lruList;
633: CachedResource cr = (CachedResource) ngenLruList.getHead();
634: while (cr != null) {
635: CachedResource next_cr = (CachedResource) ngenLruList
636: .getNext(cr);
637: cr.generation = this ;
638: lruList.toTail(cr);
639: lookupTable.put(cr.getIdentifier(), cr);
640: cr = next_cr;
641: }
642: gen.setSaved(false);
643: // now dump the toDel vector
644: Enumeration e = gen.toDel.elements();
645: while (e.hasMoreElements()) {
646: toDel.add(e.nextElement());
647: }
648: // update the values
649: cr_count += gen.cr_count;
650: bytecount += gen.bytecount;
651: bytestored += gen.bytestored;
652: // finally state that we have been modified
653: saved = false;
654: }
655:
656: public CacheGeneration(CacheStore store, long maxsize) {
657: this .store = store;
658: this .toDel = new Vector();
659: this .lookupTable = new Hashtable();
660: this .bytelimit = maxsize;
661: this .lruList = new SyncLRUList();
662: }
663: }
|