Adding JMCR-Stable version
[Benchmarks_CSolver.git] / JMCR-Stable / real-world application / jigsaw / src / org / w3c / jigsaw / filters / CacheFilter.java
diff --git a/JMCR-Stable/real-world application/jigsaw/src/org/w3c/jigsaw/filters/CacheFilter.java b/JMCR-Stable/real-world application/jigsaw/src/org/w3c/jigsaw/filters/CacheFilter.java
new file mode 100644 (file)
index 0000000..b252d6d
--- /dev/null
@@ -0,0 +1,648 @@
+// CacheFilter.java\r
+// $Id: CacheFilter.java,v 1.2 2010/06/15 17:52:54 smhuang Exp $\r
+// (c) COPYRIGHT MIT and INRIA, 1996.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.jigsaw.filters ;\r
+\r
+import java.util.Dictionary;\r
+import java.util.Hashtable;\r
+\r
+import java.io.ByteArrayInputStream;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.PrintStream;\r
+\r
+import java.net.URL ;\r
+\r
+import org.w3c.tools.resources.Attribute;\r
+import org.w3c.tools.resources.AttributeRegistry;\r
+import org.w3c.tools.resources.FilterInterface;\r
+import org.w3c.tools.resources.IntegerAttribute;\r
+import org.w3c.tools.resources.ProtocolException;\r
+import org.w3c.tools.resources.ReplyInterface;\r
+import org.w3c.tools.resources.RequestInterface;\r
+import org.w3c.tools.resources.Resource;\r
+import org.w3c.tools.resources.ResourceFilter;\r
+\r
+import org.w3c.tools.resources.ProtocolException;\r
+\r
+import org.w3c.www.http.HTTP;\r
+import org.w3c.www.http.HttpEntityMessage;\r
+import org.w3c.www.http.HttpEntityTag;\r
+import org.w3c.www.http.HttpMessage;\r
+import org.w3c.www.http.HttpReplyMessage;\r
+import org.w3c.www.http.HttpRequestMessage;\r
+\r
+import org.w3c.util.AsyncLRUList;\r
+import org.w3c.util.LRUList;\r
+import org.w3c.util.LRUNode;\r
+\r
+import org.w3c.jigsaw.http.Reply;\r
+import org.w3c.jigsaw.http.Request;\r
+\r
+class CacheException extends Exception {\r
+    public CacheException(String msg) { super(msg) ; }\r
+}\r
+\r
+class CacheEntry extends LRUNode {\r
+    /** The normalized url. (Ready to be used as key) */\r
+    private String url ;\r
+\r
+    /** The actual cached content */\r
+    byte[] content ;\r
+\r
+    /** The model reply */\r
+    Reply reply ;\r
+\r
+    /** The maximum allowed age */\r
+    private int maxage ;\r
+\r
+    public String toString()\r
+    {\r
+       return "[\"" + url + "\" " + maxage + "]" ;\r
+    }\r
+\r
+    public final String getURL()\r
+    {\r
+       return url ;\r
+    }\r
+\r
+    public final int getSize()\r
+    {\r
+       return content.length ;\r
+    }\r
+\r
+    private void readContent(Reply reply)\r
+       throws IOException\r
+    {\r
+       ByteArrayOutputStream out = null ;\r
+       InputStream in = reply.openStream() ;\r
+\r
+       if(reply.hasContentLength()) \r
+           out = new ByteArrayOutputStream(reply.getContentLength()) ;\r
+       else\r
+           out = new ByteArrayOutputStream(8192) ;\r
+\r
+       byte[] buf = new byte[4096] ;\r
+       int len = 0 ;\r
+       while( (len = in.read(buf)) != -1)\r
+           out.write(buf,0,len) ;\r
+\r
+       in.close() ;\r
+       out.close() ;\r
+\r
+       content = out.toByteArray() ;\r
+\r
+       reply.setStream(new ByteArrayInputStream(content)) ;\r
+    }\r
+\r
+    /**\r
+     * Construct a CacheEntry from the given reply,\r
+     * maybe using the given default max age\r
+     */\r
+    CacheEntry(Request request, Reply reply, int defMaxAge)\r
+       throws CacheException \r
+    {\r
+       url = Cache.getNormalizedURL(request) ;\r
+       \r
+       try {\r
+           readContent(reply) ;\r
+       } catch(IOException ex) {\r
+           throw new CacheException("cannot read reply content") ;\r
+       }\r
+\r
+       this.reply = (Reply) reply.getClone() ;\r
+       this.reply.setStream((InputStream) null) ;\r
+\r
+       // Set the date artificially, since Jigsaw only sets the date\r
+       // header on ultimate emission of the reply.\r
+       long date = this.reply.getDate() ;\r
+       if(date == -1) {\r
+           date = System.currentTimeMillis() ;\r
+           date -= date % 1000 ;\r
+           this.reply.setDate(date) ;\r
+       }\r
+\r
+       setMaxAge(reply,defMaxAge) ;\r
+\r
+    }\r
+\r
+    /**\r
+     * Sets this entry's maxage from available data, or\r
+     * falls back to the specified default.\r
+     */\r
+    private void setMaxAge(Reply reply, int def)\r
+    {\r
+       if( ( maxage = reply.getMaxAge() ) == -1 ) {\r
+           long exp = reply.getExpires() ;\r
+           long date = reply.getDate() ;\r
+           \r
+           if(exp != -1 && date != -1) {\r
+               \r
+               maxage = (int) (reply.getExpires() - reply.getDate()) ;\r
+               if(maxage<0) maxage = 0 ; \r
+               \r
+           } else {\r
+               maxage = def ;\r
+           }\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Sets this entry's maxage from available data, or\r
+     * leaves it unchanged if reply doesn't say anything.\r
+     */\r
+    private void setMaxAge(Reply reply)\r
+    {\r
+       setMaxAge(reply,maxage) ;\r
+    }\r
+\r
+    /**\r
+     * Make a reply for this entry.\r
+     */\r
+    Reply getReply(Request request)\r
+    {\r
+       Reply newReply = (Reply) reply.getClone() ;\r
+\r
+       int age = getAge() ; \r
+       if(age!=-1) newReply.setAge(age) ;\r
+\r
+       boolean notMod = false ;\r
+\r
+       HttpEntityTag[] etags = request.getIfNoneMatch() ;\r
+       HttpEntityTag tag = this.reply.getETag() ;\r
+       if(etags != null && tag != null) {\r
+           boolean noneMatch = true ;\r
+           String sTag = tag.getTag() ;\r
+           for(int i=0;i<etags.length;i++) {\r
+               if(sTag.equals(etags[i].getTag())) {\r
+                   noneMatch = false ;\r
+                   break ;\r
+               }\r
+           }\r
+           notMod = !noneMatch ;\r
+       } else {\r
+           long ims = request.getIfModifiedSince() ;\r
+           long lmd = this.reply.getLastModified() ;\r
+           if(ims != -1 && lmd != -1) \r
+               notMod = lmd > ims ;\r
+       }\r
+\r
+       if(notMod) {\r
+           System.out.println("**** replying NOT_MODIFIED") ;\r
+           newReply.setStatus(HTTP.NOT_MODIFIED) ;\r
+       }\r
+       else if(! request.getMethod().equals("HEAD"))\r
+           newReply.setStream(new ByteArrayInputStream(content)) ;\r
+       else\r
+           newReply.setStream((InputStream) null) ;\r
+       return newReply ;       \r
+    }\r
+\r
+    /**\r
+     * Returns the age of this entry in seconds,\r
+     * or -1 if age cannot be determined.\r
+     */\r
+    int getAge()\r
+    {\r
+       int age1 = reply.getAge() ;\r
+       \r
+       long age2 = -1 ;\r
+       long date = reply.getDate() ;\r
+       if(date != -1 ) {\r
+           age2 = System.currentTimeMillis() ;\r
+           age2 -= date ;\r
+           age2 /= 1000 ;\r
+       }\r
+       \r
+       return age1>=age2 ? age1 : (int) age2 ;\r
+    }  \r
+\r
+    /**\r
+     * Make a reply for this entry, which was validated\r
+     * by the server with the given reply.\r
+     */\r
+    final Reply getReply(Request request, Reply servReply)\r
+    {\r
+       System.out.println("**** Validated entry") ;\r
+       setMaxAge(servReply) ;\r
+       long date = servReply.getDate() ;\r
+       if(date == -1) {\r
+           date = System.currentTimeMillis() ;\r
+           date -= date % 1000 ;\r
+       }\r
+       this.reply.setDate(date) ;\r
+       return getReply(request) ;\r
+    }\r
+\r
+    /** \r
+     * Turn the given request into a conditional request,\r
+     * using the appropriate validators (if any). \r
+     */\r
+    void makeConditional(Request request)\r
+    {\r
+       System.out.println("**** Making conditional request for validation") ;\r
+       HttpEntityTag[] et = { reply.getETag() } ;\r
+       if(et[0] != null) request.setIfNoneMatch(et) ;\r
+       \r
+       long lm = reply.getLastModified() ;\r
+       if(lm != -1) request.setIfModifiedSince(lm) ;\r
+    }\r
+\r
+    /**\r
+     * Is this entry fresh, according to the requirements\r
+     * of the request?\r
+     */\r
+    boolean isFresh(Request request)\r
+    {\r
+       int age = getAge() ;\r
+       System.out.println("**** age: "+age+" maxage: "+maxage) ;\r
+       return age != -1 ? (maxage > age) : (maxage > 0) ;\r
+    }\r
+}\r
+\r
+class Cache {\r
+\r
+    private static final String STATE_NORM_URL =\r
+       "org.w3c.jigsaw.filters.Cache.normURL" ;\r
+\r
+    /** Our maximum size in bytes */\r
+    private int maxSize ;\r
+    /** Our maximum size in entries */\r
+    private int maxEntries ;\r
+\r
+    /** Current size in bytes */\r
+    private int size ;\r
+\r
+    /** The default max age */\r
+    private int defaultMaxAge ;\r
+\r
+    /**\r
+     * This maps URLs (maybe processed) vs entries\r
+     */\r
+    Dictionary /*<String,CacheEntry>*/ entries ;\r
+\r
+    /**\r
+     * This keeps track of LRU entries\r
+     */\r
+    LRUList /*<CacheEntry>*/ lruList ;\r
+\r
+    public Cache(int maxSize, int maxEntries, int defaultMaxAge)\r
+    {\r
+       this.maxSize = maxSize ;\r
+       this.maxEntries = maxEntries ;\r
+       this.defaultMaxAge = defaultMaxAge ;\r
+\r
+       this.size = 0 ;\r
+\r
+       lruList = new AsyncLRUList() ;\r
+       entries = new Hashtable(20) ;\r
+    }\r
+\r
+    /**\r
+     * Stores a new reply in a CacheEntry.\r
+     * Takes care of handling the LRU list, and of possible overwriting\r
+     * @exception CacheException fixme doc\r
+     */\r
+    public void store(Request request, Reply reply)\r
+       throws CacheException\r
+    {\r
+       System.out.println("**** Storing reply in cache") ;\r
+       // Enforce maxEntries\r
+       if(maxEntries > 0 && entries.size() == maxEntries)\r
+           flushLRU() ;\r
+\r
+       // Try to enforce maxSize\r
+       if(maxSize > 0 && reply.hasContentLength()) {\r
+           int maxEntSize = maxSize - reply.getContentLength() ;\r
+           while(entries.size() > maxEntSize)\r
+               if(!flushLRU()) break ;\r
+       }\r
+\r
+       CacheEntry ce = new CacheEntry(request, reply, defaultMaxAge) ;\r
+\r
+       synchronized(this) {\r
+           size += ce.getSize() ;\r
+           CacheEntry old = (CacheEntry) entries.put(ce.getURL(),ce) ;\r
+           if(old!=null) lruList.remove(old) ;\r
+           lruList.toHead(ce) ;\r
+       }\r
+    }\r
+       \r
+\r
+    /**\r
+     * Retrieves a CacheEntry corresponding to the request.\r
+     * Should mark it as MRU\r
+     */\r
+    public CacheEntry retrieve(Request request)\r
+    {\r
+       String url = getNormalizedURL(request) ;\r
+       CacheEntry ce = (CacheEntry) entries.get(url) ;\r
+       return ce==null ? null : ce ;\r
+    }\r
+\r
+    /**\r
+     * Removes the CacheEntry corresponding to the request.\r
+     */\r
+    public synchronized void remove(Request request)\r
+    {\r
+       System.out.println("**** Removing from cache") ;\r
+       CacheEntry ce = (CacheEntry)\r
+           entries.remove(getNormalizedURL(request)) ;\r
+       if(ce == null) return ;\r
+       \r
+       lruList.remove(ce) ;\r
+    }\r
+\r
+    /**\r
+     * Gets rid of the LRU element\r
+     */\r
+    private synchronized final boolean flushLRU()\r
+    {\r
+       if(entries.size() == 0) return false ;\r
+\r
+       CacheEntry ce = (CacheEntry) lruList.removeTail() ;\r
+       entries.remove(ce.getURL()) ;\r
+       size -= ce.getSize() ;\r
+\r
+       return true ;\r
+    }\r
+\r
+    /** This might be unnecessary */\r
+    static String getNormalizedURL(Request request) {\r
+       String nurl = (String) request.getState(STATE_NORM_URL) ;\r
+       if(nurl!=null) return nurl ;\r
+\r
+       URL url = request.getURL() ;\r
+       nurl = url.getFile() ;\r
+       \r
+       request.setState(STATE_NORM_URL,nurl) ;\r
+       return nurl ;\r
+    }\r
+}\r
+\r
+public class CacheFilter extends ResourceFilter {\r
+    protected Cache cache = null ;\r
+\r
+    protected final static String STATE_TAG\r
+       = "org.w3c.jigsaw.filters.CacheFilter.tag" ;\r
+\r
+    protected static int ATTR_MAX_SIZE = -1 ;\r
+    protected static int ATTR_MAX_ENTRIES = -1 ;\r
+    protected static int ATTR_DEFAULT_MAX_AGE = -1 ;\r
+\r
+    static {\r
+       Attribute a   = null;\r
+       Class     cls = null;\r
+       \r
+       try {\r
+           cls = Class.forName("org.w3c.jigsaw.filters.CacheFilter");\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+       } catch (Exception ex) {\r
+           ex.printStackTrace();\r
+           System.exit(1);\r
+       }\r
+       // Declare the maximum cache size attribute:\r
+       a = new IntegerAttribute("maxSize"\r
+                                , new Integer(8192)\r
+                                , Attribute.EDITABLE);\r
+       ATTR_MAX_SIZE= AttributeRegistry.registerAttribute(cls, a);\r
+       // Declare the maximum number of entries attribute:\r
+       a = new IntegerAttribute("maxEntries"\r
+                                , new Integer(-1)\r
+                                , Attribute.EDITABLE);\r
+       ATTR_MAX_ENTRIES = AttributeRegistry.registerAttribute(cls, a);\r
+       // Declare the default maxage attribute\r
+       a = new IntegerAttribute("defaultMaxAge"\r
+                                , new Integer(300) // 5min\r
+                                , Attribute.EDITABLE);\r
+       ATTR_DEFAULT_MAX_AGE = AttributeRegistry.registerAttribute(cls, a);\r
+    }\r
+\r
+    public int getMaxSize()\r
+    {\r
+       return ((Integer) getValue(ATTR_MAX_SIZE, \r
+                                  new Integer(-1))).intValue() ;\r
+    }\r
+\r
+    public int getMaxEntries()\r
+    {\r
+       return ((Integer) getValue(ATTR_MAX_ENTRIES, \r
+                                  new Integer(-1))).intValue() ;\r
+    }\r
+\r
+    public int getDefaultMaxAge() {\r
+       return ((Integer) getValue(ATTR_DEFAULT_MAX_AGE, new Integer(300)))\r
+           .intValue() ;\r
+    }\r
+\r
+    private final void tag(Request request)\r
+    {\r
+       request.setState(STATE_TAG,Boolean.TRUE) ;\r
+    }\r
+\r
+    private final boolean isTagged(Request request)\r
+    {\r
+       return request.hasState(STATE_TAG) ;\r
+    }\r
+\r
+    private Reply applyIn(Request request,\r
+                         FilterInterface[] filters,\r
+                         int fidx)\r
+       throws ProtocolException\r
+    {\r
+       // Apply remaining ingoing filters\r
+       Reply fr = null ;\r
+       for(int i = fidx+1 ;\r
+           i<filters.length && filters[i] != null ;\r
+           ++i) {\r
+           fr = (Reply) (filters[i].ingoingFilter(request, filters, i)) ;\r
+           if(fr != null) \r
+               return fr ;\r
+       }\r
+       return null ;\r
+    }\r
+\r
+    private Reply applyOut(Request request,\r
+                          Reply reply,\r
+                          FilterInterface[] filters,\r
+                          int fidx)\r
+       throws ProtocolException\r
+    {\r
+       Reply fr = null ;\r
+       for(int i=fidx-1;\r
+           i>=0 && filters[i] != null;\r
+           i--) {\r
+           fr = (Reply) (filters[i].outgoingFilter(request,reply,filters,i)) ;\r
+           if(fr != null)\r
+               return fr ;\r
+       }\r
+       return null ;\r
+    }\r
+\r
+    private final Reply applyOut(Request request,\r
+                                Reply reply,\r
+                                FilterInterface[] filters )\r
+       throws ProtocolException\r
+    {\r
+       return applyOut(request,reply,filters,filters.length) ;\r
+    }\r
+\r
+    private void makeInconditional(Request request) {\r
+       request.setHeaderValue(request.H_IF_MATCH, null) ;\r
+       request.setHeaderValue(request.H_IF_MODIFIED_SINCE, null) ;\r
+       request.setHeaderValue(request.H_IF_NONE_MATCH, null) ;\r
+       request.setHeaderValue(request.H_IF_RANGE, null) ;\r
+       request.setHeaderValue(request.H_IF_UNMODIFIED_SINCE, null) ;\r
+    }\r
+       \r
+    /**\r
+     * @return A Reply instance, if the filter did know how to answer\r
+     * the request without further processing, <strong>null</strong> \r
+     * otherwise. \r
+     * @exception ProtocolException \r
+     * If processing should be interrupted,\r
+     * because an abnormal situation occured. \r
+     */ \r
+    public ReplyInterface ingoingFilter(RequestInterface req,\r
+                                       FilterInterface[] filters,\r
+                                       int fidx)\r
+       throws ProtocolException\r
+    {\r
+       Request request = (Request) req;\r
+       if(cache == null)\r
+           cache = new Cache(getMaxSize(),\r
+                             getMaxEntries(),\r
+                             getDefaultMaxAge()) ;\r
+\r
+       String method = request.getMethod() ;\r
+       if(! ( method.equals("HEAD") ||\r
+              method.equals("GET") ) )\r
+           return null ;       // Enforce write-through\r
+\r
+       tag(request) ;\r
+\r
+       if(isCachable(request)) {\r
+           CacheEntry cachEnt = cache.retrieve(request) ;\r
+           if(cachEnt != null) {\r
+               System.out.println("**** Examining entry: "+cachEnt) ;\r
+               Reply fRep = null ;\r
+               if( cachEnt.isFresh(request) ) {\r
+                   \r
+                   fRep = applyIn(request,filters,fidx) ;\r
+                   if(fRep != null) return fRep ;\r
+\r
+                   // Get the reply (adjusting age too) [?]\r
+                   Reply reply = cachEnt.getReply(request) ;\r
+\r
+                   fRep = applyOut(request,reply,filters) ;\r
+                   if(fRep != null) return fRep ;\r
+                   \r
+                   System.out.println("**** Replying from cache") ;\r
+                   return reply ;\r
+                   \r
+               } else {\r
+                   cachEnt.makeConditional(request) ;\r
+                   return null ;\r
+               }\r
+           } else {\r
+               System.out.println("**** Not in cache") ;\r
+           }\r
+       } else {\r
+           System.out.println("**** Request not cachable") ;\r
+       }\r
+\r
+       makeInconditional(request) ;\r
+                       \r
+       return null ;\r
+    }\r
+\r
+    /**\r
+     * @param request The original request.\r
+     * @param reply It's original reply. \r
+     * @return A Reply instance, or <strong>null</strong> if processing \r
+     * should continue normally. \r
+     * @exception ProtocolException If processing should be interrupted\r
+     * because an abnormal situation occured. \r
+     */\r
+    public ReplyInterface outgoingFilter(RequestInterface req,\r
+                                        ReplyInterface rep,\r
+                                        FilterInterface[] filters,\r
+                                        int fidx)\r
+       throws ProtocolException \r
+    {\r
+       Request request = (Request) req;\r
+       Reply   reply   = (Reply) rep;\r
+       // Be transparent if request is not "ours"\r
+       if(!isTagged(request))\r
+           return null ;\r
+       \r
+       if(isCachable(reply)) {\r
+           switch(reply.getStatus()) {\r
+           case HTTP.OK:\r
+           case HTTP.NO_CONTENT:\r
+           case HTTP.MULTIPLE_CHOICE:\r
+           case HTTP.MOVED_PERMANENTLY:\r
+               // Store the reply and let it through\r
+               try {\r
+                   cache.store(request,reply) ;\r
+               } catch(CacheException ex) {\r
+                   // not much to do...\r
+               } finally {\r
+                   return null ;\r
+               }\r
+           case HTTP.NOT_MODIFIED:\r
+               // This means we're validating\r
+               CacheEntry cachEnt = cache.retrieve(request) ;\r
+               if(cachEnt != null)\r
+                   reply = cachEnt.getReply(request,reply) ;\r
+               break ;\r
+           default:\r
+               cache.remove(request) ; \r
+               return null ;\r
+           }\r
+       } else {\r
+           System.out.println("**** Reply not cachable") ;\r
+           cache.remove(request) ;\r
+       }\r
+       \r
+       // Apply remaining filters and return modified reply\r
+       Reply fRep = applyOut(request,reply,filters,fidx) ;\r
+\r
+       if(fRep != null) return fRep ;\r
+       else return reply ;\r
+    }\r
+\r
+    /**\r
+     * Does this request permit caching?\r
+     * (It's still half-baked)\r
+     */\r
+    private boolean isCachable(Request request)\r
+    {\r
+       if(request.checkNoStore()) return false ;\r
+\r
+       String[] nc = request.getNoCache() ;\r
+       if(nc != null) return false ; // for now\r
+       \r
+       return true ;\r
+    }\r
+\r
+    /**\r
+     * Does this reply permit caching?\r
+     * (It's still half-baked)\r
+     */\r
+    private boolean isCachable(Reply reply)\r
+    {\r
+       if(reply.checkNoStore() ||\r
+          reply.getPrivate() != null) return false ;\r
+       \r
+       return true ;\r
+       \r
+    }\r
+\r
+}\r
+\r
+\r