--- /dev/null
+// CacheGeneration.java\r
+// $Id: CacheGeneration.java,v 1.1 2010/06/15 12:25:09 smhuang Exp $\r
+// (c) COPYRIGHT MIT, INRIA and Keio, 1999.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.www.protocol.http.cache;\r
+\r
+import java.util.Enumeration;\r
+import java.util.Hashtable;\r
+import java.util.Vector;\r
+\r
+import java.io.File;\r
+import java.io.PrintStream;\r
+\r
+import org.w3c.util.LRUAble;\r
+import org.w3c.util.LRUList;\r
+import org.w3c.util.LookupTable;\r
+import org.w3c.util.SyncLRUList;\r
+\r
+public class CacheGeneration implements LRUAble {\r
+ // the usual debug flag\r
+ private static final boolean debug = true;\r
+\r
+ // hashtable of resources\r
+ private Hashtable lookupTable = null;\r
+ // the LRUList of resources\r
+ private SyncLRUList lruList = null;\r
+ // the occupation of this generation\r
+ private long bytecount = 0;\r
+ // the capacity of this generation\r
+ private long bytelimit = 0;\r
+ // stored size\r
+ private long bytestored = 0;\r
+ // serialization flag\r
+ private boolean saved = false;\r
+ // serialization flag\r
+ private boolean loaded = false;\r
+ // resource count\r
+ private int cr_count = 0;\r
+ // the ID of this generation\r
+ private int id = 0;\r
+\r
+ // a Vector or resource to be removed\r
+ private Vector toDel = null;\r
+\r
+ // our father\r
+ private CacheStore store = null;\r
+\r
+ protected File generationFile = null;\r
+\r
+ /**\r
+ * set the file where the generation is stored\r
+ * @param generationFile the file\r
+ */\r
+ public void setGenerationFile(File generationFile) {\r
+ this.generationFile = generationFile;\r
+ }\r
+\r
+ /**\r
+ * get the generation file\r
+ * @return a File\r
+ */\r
+ public File getGenerationFile() {\r
+ return generationFile;\r
+ }\r
+\r
+ /**\r
+ * Is the generation loaded?\r
+ * @return a boolean\r
+ */\r
+ public boolean isLoaded() {\r
+ return loaded;\r
+ }\r
+\r
+ /**\r
+ * Set the generation as loaded or unloaded\r
+ * @param loaded the new loaded flag\r
+ */\r
+ protected void setLoaded(boolean loaded) {\r
+ this.loaded = loaded;\r
+ }\r
+\r
+ /**\r
+ * Is the generation saved?\r
+ * @return a boolean\r
+ */\r
+ public boolean isSaved() {\r
+ return saved;\r
+ }\r
+\r
+ /**\r
+ * Set the generation as saved or not.\r
+ * @param saved a boolean\r
+ */\r
+ protected void setSaved(boolean saved) {\r
+ this.saved = saved;\r
+ }\r
+\r
+ /**\r
+ * LRU management - previous entry.\r
+ */\r
+ protected LRUAble prev = null;\r
+ /**\r
+ * LRU management - next entry.\r
+ */\r
+ protected LRUAble next = null;\r
+\r
+ /**\r
+ * LRU management - Get next node.\r
+ * @return A CacheGeneration instance.\r
+ */\r
+ public LRUAble getNext() {\r
+ return next;\r
+ }\r
+\r
+ /**\r
+ * LRU management - Get previous node.\r
+ * @return A CacheGeneration instance.\r
+ */\r
+ public LRUAble getPrev() {\r
+ return prev;\r
+ }\r
+\r
+ /**\r
+ * LRU management - Set next node.\r
+ */\r
+ public synchronized void setNext(LRUAble next) {\r
+ this.next = next;\r
+ }\r
+\r
+ /**\r
+ * LRU management - Set previous node.\r
+ */\r
+ public synchronized void setPrev(LRUAble prev) {\r
+ this.prev = prev;\r
+ }\r
+\r
+ /**\r
+ * Get the ID of this generation\r
+ * @return an int, the generation number\r
+ */\r
+ public int getId() {\r
+ return id;\r
+ }\r
+\r
+ /**\r
+ * Set the ID of this generation\r
+ * Useful to reuse generation\r
+ * @param an integer, the new generation number\r
+ */\r
+ public synchronized void setId(int id) {\r
+ this.id = id;\r
+ }\r
+\r
+ /**\r
+ * Give the acual occupation level of this generation\r
+ * @return a long, the number of bytes of this generation\r
+ */\r
+ public long getCachedByteCount() {\r
+ return bytecount;\r
+ }\r
+\r
+ /**\r
+ * Give the fill ratio for the cached resources\r
+ * @return a float between 0 and 1\r
+ */\r
+ public float getFillRatio() {\r
+ return ((float) bytecount / (float) bytelimit);\r
+ }\r
+\r
+ /**\r
+ * Give the acual storeage occupation level of this generation\r
+ * @return a long, the number of bytes of this generation\r
+ */\r
+ public long getStoredByteCount() {\r
+ return bytestored;\r
+ }\r
+\r
+ /**\r
+ * Get the bytecount limit for this generation\r
+ * @return a long, the maximum number of bytes\r
+ */\r
+ public long getByteLimit() {\r
+ return bytelimit;\r
+ }\r
+\r
+ /**\r
+ * Set the new bytecount limit, not that it may perform a cleanup\r
+ * if necessary.\r
+ * @param long, the new maximum number of bytes\r
+ */\r
+ public synchronized void setByteLimit(long newlimit) {\r
+ bytelimit = newlimit;\r
+ if (newlimit >= bytecount) {\r
+ return;\r
+ }\r
+ // try to get some space \r
+ long to_save = newlimit - bytecount;\r
+ // be nice\r
+ to_save -= collectSpace(newlimit - bytecount, true);\r
+ // then get the space we want ;)\r
+ to_save -= collectSpace(to_save, false);\r
+ }\r
+\r
+ /**\r
+ * Deletes a resource from the "to be deleted" vector\r
+ * it updates also the number of bye stored in this generation\r
+ * @return the number of bytes saved.\r
+ */\r
+ public long deleteStored(CachedResource cr) {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ if (debug) {\r
+ System.out.println("Deleting "+cr.getIdentifier()+\r
+ "from generation: "+id);\r
+ }\r
+ toDel.removeElement(cr);\r
+ long saved = cr.delete();\r
+ synchronized(this) {\r
+ bytestored -= saved;\r
+ }\r
+ store.getState().notifyResourceDeleted(cr);\r
+ return saved;\r
+ }\r
+\r
+ /**\r
+ * Check if a resource has been cached in this generation\r
+ * @param url the resource url\r
+ * @return a boolean\r
+ */\r
+ public synchronized boolean containsResource(String url) {\r
+ return (lookupTable.get(url) != null);\r
+ }\r
+\r
+ /**\r
+ * Get all the files handled by this generation\r
+ * @return an enumeration of File\r
+ */\r
+ public synchronized Enumeration getFiles() {\r
+ Vector files = new Vector();\r
+ if (loaded) {\r
+ Enumeration fenum = lookupTable.elements();\r
+ while (fenum.hasMoreElements()) {\r
+ CachedResource cr = (CachedResource) fenum.nextElement();\r
+ File file = cr.getFile();\r
+ if (file != null) {\r
+ files.addElement(file);\r
+ }\r
+ }\r
+ } else {\r
+ Enumeration fenum = lookupTable.elements();\r
+ while (fenum.hasMoreElements()) {\r
+ String file = (String) fenum.nextElement();\r
+ if (! file.equals("")) {\r
+ files.addElement(new File(file));\r
+ }\r
+ }\r
+ }\r
+ return files.elements();\r
+ }\r
+\r
+ /**\r
+ * Get the CachedResource relative to the given URL.\r
+ * @param url the URL of the CachedResource to find\r
+ * @return a CachedResource or null.\r
+ */\r
+ public synchronized CachedResource lookupResource(String url) {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ return (CachedResource)lookupTable.get(url);\r
+ }\r
+\r
+ /**\r
+ * can this resource be stored?\r
+ * If the resource is in the generation, only the delta will be taken\r
+ * into account\r
+ * @param CachedResource cr, the candidate.\r
+ * @param long size, the size of the candidate.\r
+ * @return a boolean, if this generation can cache it or not\r
+ */\r
+ private boolean canStore(CachedResource cr, long size) {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ CachedResource old_cr = null;\r
+ old_cr = (CachedResource) lookupTable.get(cr.getIdentifier());\r
+ if (old_cr != null) {\r
+ long delta = size - old_cr.getCurrentLength();\r
+ if ( (bytecount + delta) > bytelimit) {\r
+ return false;\r
+ }\r
+ }\r
+ if ((size + bytecount) > bytelimit) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Adds this resource, if possible\r
+ * @param cr, the candidate.\r
+ * @param size, the size of the candidate.\r
+ * @return a boolean, true if this resource has been cached\r
+ */\r
+ public synchronized boolean addResource(CachedResource cr, \r
+ long size,\r
+ long oldsize)\r
+ {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ if (canStore(cr, size)) {\r
+ CachedResource old_cr = null;\r
+ old_cr = (CachedResource) lookupTable.get(cr.getIdentifier());\r
+ // do we already have this resource?\r
+ if (old_cr != null) {\r
+ // this is the real oldsize\r
+ oldsize = old_cr.getCurrentLength();\r
+ long delta = size - oldsize;\r
+ if ((bytecount + delta) > bytelimit) {\r
+ return false;\r
+ } \r
+ lookupTable.remove(cr.getIdentifier());\r
+ lruList.remove(cr);\r
+ bytecount -= oldsize;\r
+ toDel.addElement(old_cr);\r
+ store.getState().notifyResourceReplaced(cr, oldsize);\r
+ } else {\r
+ store.getState().notifyResourceAdded(cr, oldsize);\r
+ }\r
+ lookupTable.put(cr.getIdentifier(), cr);\r
+ cr.generation = this;\r
+ lruList.toHead(cr);\r
+ bytestored += size;\r
+ bytecount += size;\r
+ saved = false;\r
+ cr_count++;\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Load a CachedResource in this generation. (to be used only at\r
+ * generation loading). This method load only valid cachedresource.\r
+ * Check if the associated file exists and has the right size.\r
+ * @param CachedResource the CachedResource to load.\r
+ */\r
+ protected void loadCachedResource(CachedResource cr) {\r
+ File file = cr.getFile();\r
+ if ((file != null) && \r
+ ((! file.exists()) || (file.length() != cr.getCurrentLength()))) {\r
+ //don't load invalid cachedresource\r
+ return;\r
+ }\r
+ lookupTable.put(cr.getIdentifier(), cr);\r
+ cr.generation = this;\r
+ lruList.toHead(cr);\r
+ long size = cr.getCurrentLength();\r
+ bytestored += size;\r
+ bytecount += size;\r
+ cr_count++;\r
+ }\r
+\r
+ /**\r
+ * Remove the resource from the generation (but don't delete it).\r
+ * @param cr the CachedResource to remove.\r
+ * @return the number of byte saved\r
+ * @exception NoSuchResourceException if this resource was not in this \r
+ * generation \r
+ */\r
+ public synchronized long removeResource(CachedResource cr) \r
+ throws NoSuchResourceException\r
+ {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ return removeResource(cr.getIdentifier());\r
+ }\r
+\r
+ /**\r
+ * Remove the resource from the generation (but don't delete it).\r
+ * @param cr the CachedResource to remove.\r
+ * @return the number of byte saved\r
+ * @exception NoSuchResourceException if this resource was not in this \r
+ * generation \r
+ */\r
+ public synchronized long removeResource(String url) \r
+ throws NoSuchResourceException\r
+ {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ CachedResource old_cr = _removeResource(url);\r
+ return old_cr.getCurrentLength();\r
+ }\r
+\r
+ /**\r
+ * Remove the resource from the generation and update the bytecount \r
+ * variable.\r
+ * @param url the CachedResource URL\r
+ * @return the CachedResource removed.\r
+ * @exception NoSuchResourceException if this resource was not in this \r
+ * generation\r
+ */\r
+ private CachedResource _removeResource(String url) \r
+ throws NoSuchResourceException\r
+ {\r
+ if (debug) {\r
+ System.err.println("*** removing from generation "+id+": " + url);\r
+ }\r
+ CachedResource old_cr = (CachedResource) lookupTable.get(url);\r
+ if (old_cr == null) {\r
+ String msg = url + " not found in generation " + id;\r
+ throw new NoSuchResourceException(msg);\r
+ }\r
+ lookupTable.remove(url);\r
+ lruList.remove(old_cr);\r
+ long b_saved = old_cr.getCurrentLength();\r
+ if (debug) {\r
+ System.err.println("*** removed... saved " + b_saved + " bytes");\r
+ }\r
+ bytecount -= b_saved;\r
+ bytestored -= b_saved;\r
+ saved = false;\r
+ cr_count--;\r
+ return old_cr;\r
+ }\r
+\r
+ /**\r
+ * will garbage collect up to "size" bytes in this generation.\r
+ * WARNING: this is not synchronized, use with caution!\r
+ * @param long the number of bytes to be collected\r
+ * @param check, a boolean, used to validate or not the resource before\r
+ * deleting them (ie: delete only invalid resources)\r
+ * @return a long, the number of bytes saved\r
+ * from disk afterward using delete.\r
+ */\r
+\r
+ public long collectSpace(long size, boolean check) {\r
+ if (! loaded) {\r
+ // load me, and unload another generation is necessary.\r
+ try {\r
+ store.loadGeneration(this);\r
+ } catch (InvalidCacheException ex) {\r
+ // oups! cache corrupted?\r
+ if (debug) {\r
+ System.err.println("*** Collecting "+\r
+ "Unable to load generation ["+\r
+ getId()+"]");\r
+ System.err.println(ex.getMessage());\r
+ }\r
+ return 0;\r
+ }\r
+ }\r
+ Vector res_vect = new Vector();\r
+ long collected = 0;\r
+ CachedResource cr, ncr;\r
+ CacheValidator validator;\r
+ \r
+ if (debug) {\r
+ System.err.println("*** Collecting " + size + " bytes " + \r
+ ((check) ? "with" : "without") + " checking");\r
+ }\r
+ // dumb check\r
+ if (size <= 0)\r
+ return 0;\r
+ if ((size > bytecount) && !check) {\r
+ return emptyGeneration();\r
+ }\r
+ validator = store.getValidator();\r
+ // start with the oldest ones\r
+ cr = (CachedResource) lruList.getTail();\r
+ while (cr != null) {\r
+ // check if we can delete the resource or not\r
+ if (check) {\r
+ if (validator.checkStaleness(cr)) {\r
+ cr = (CachedResource) lruList.getPrev(cr);\r
+ continue;\r
+ }\r
+ }\r
+ ncr = (CachedResource) lruList.getPrev(cr);\r
+ synchronized(this) {\r
+ lookupTable.remove(cr.getIdentifier());\r
+ lruList.remove(cr);\r
+ saved = false;\r
+ cr_count--;\r
+ store.getState().notifyResourceToBeDeleted(cr);\r
+ }\r
+ collected += cr.getCurrentLength();\r
+ toDel.addElement(cr);\r
+ if (collected >= size) {\r
+ break;\r
+ }\r
+ cr = ncr;\r
+ }\r
+ synchronized (this) {\r
+ bytecount -= collected;\r
+ }\r
+ if (debug) {\r
+ System.err.println("*** Collected " + collected + \r
+ " bytes from generation "+id);\r
+ }\r
+ return collected;\r
+ }\r
+\r
+ /**\r
+ * empty this generation\r
+ * @return a long, the number of bytes saved\r
+ */\r
+ protected long emptyGeneration() {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ Hashtable saved_table;\r
+ long collected;\r
+ if (debug) {\r
+ System.err.println("*** Deleting Generation " + id + " ("\r
+ + bytecount + ")");\r
+ }\r
+ synchronized (this) {\r
+ collected = bytecount;\r
+ saved_table = lookupTable;\r
+ lookupTable = new Hashtable();\r
+ lruList = new SyncLRUList();\r
+ bytecount = 0;\r
+ cr_count = 0;\r
+ }\r
+ Enumeration e = saved_table.elements();\r
+ while (e.hasMoreElements()) {\r
+ CachedResource cr = (CachedResource) e.nextElement();\r
+ toDel.addElement(cr);\r
+ store.getState().notifyResourceToBeDeleted(cr);\r
+ }\r
+ generationFile.delete();\r
+ saved = false;\r
+ return collected;\r
+ }\r
+\r
+ /**\r
+ * Get the CachedResource of this generation (except the "to be\r
+ * deleted" resources)\r
+ * @return an Enumeration of CachedResource\r
+ */\r
+ public Enumeration getCachedResources() {\r
+ if (! loaded)\r
+ throw new UnloadedGenerationException("generation "+id);\r
+ return lookupTable.elements();\r
+ }\r
+\r
+ /**\r
+ * get the deleted but still stored resource\r
+ * @returns an enumeration of CachedResources\r
+ */\r
+ public Enumeration getDeletedResources() {\r
+ return toDel.elements();\r
+ }\r
+\r
+ /**\r
+ * Set this Generation as a description (update the saved and loaded \r
+ * status)\r
+ * @param tables the LookupTables containing attribute descriptions\r
+ */\r
+ protected void setDescription(LookupTable tables[]) {\r
+ clean();\r
+ saved = true;\r
+ bytestored = 0;\r
+ bytecount = 0;\r
+ cr_count = 0;\r
+ for (int i = 0 ; i < tables.length ; i++) {\r
+ LookupTable table = tables[i];\r
+ try {\r
+ String stored = \r
+ (String)table.get(CachedResource.NAME_CURRENT_LENGTH);\r
+ String id = \r
+ (String)table.get(CachedResource.NAME_IDENTIFIER);\r
+ String file =\r
+ (String)table.get(CachedResource.NAME_FILE);\r
+ file = (file == null) ? "" : file;\r
+ bytestored += Integer.parseInt(stored);\r
+ lookupTable.put(id, file);\r
+ } catch (Exception ex) {\r
+ if (debug) {\r
+ System.err.println("Unable to load description in ["+\r
+ getId()+"] "+ex.getMessage());\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Unload the generation, transform CachedResources to descriptions.\r
+ */\r
+ public void unload() {\r
+ // bytestored unchanged\r
+ clean();\r
+ saved = true;\r
+ bytecount = 0;\r
+ cr_count = 0;\r
+ Enumeration kenum = lookupTable.keys();\r
+ while(kenum.hasMoreElements()) {\r
+ lookupTable.put(kenum.nextElement(), Boolean.TRUE);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * delete the serialized resource file from the disk\r
+ */\r
+ protected void deleteGenerationFile() {\r
+ generationFile.delete();\r
+ }\r
+\r
+ /**\r
+ * Get the current number of resource loaded.\r
+ * @return an long\r
+ */\r
+ public int getCRCount() {\r
+ return cr_count;\r
+ }\r
+\r
+ /**\r
+ * Clean this generation.\r
+ */\r
+ public void clean() {\r
+ this.lookupTable = new Hashtable();\r
+ this.lruList = new SyncLRUList();\r
+ this.loaded = false;\r
+ }\r
+\r
+ /**\r
+ * copy the content of the generation here\r
+ * @parameter a CacheGeneration, the one we want to dump here\r
+ */\r
+ protected synchronized void copyInto(CacheGeneration gen) {\r
+ // add everything at the "right" place\r
+ LRUList ngenLruList = gen.lruList;\r
+ CachedResource cr = (CachedResource) ngenLruList.getHead();\r
+ while (cr != null) {\r
+ CachedResource next_cr = (CachedResource) ngenLruList.getNext(cr);\r
+ cr.generation = this;\r
+ lruList.toTail(cr);\r
+ lookupTable.put(cr.getIdentifier(), cr);\r
+ cr = next_cr;\r
+ }\r
+ gen.setSaved(false);\r
+ // now dump the toDel vector\r
+ Enumeration e = gen.toDel.elements();\r
+ while (e.hasMoreElements()) {\r
+ toDel.add(e.nextElement());\r
+ }\r
+ // update the values\r
+ cr_count += gen.cr_count;\r
+ bytecount += gen.bytecount;\r
+ bytestored += gen.bytestored;\r
+ // finally state that we have been modified\r
+ saved = false;\r
+ }\r
+\r
+ public CacheGeneration(CacheStore store, long maxsize) {\r
+ this.store = store;\r
+ this.toDel = new Vector();\r
+ this.lookupTable = new Hashtable();\r
+ this.bytelimit = maxsize;\r
+ this.lruList = new SyncLRUList();\r
+ }\r
+}\r
+\r
+\r