Adding JMCR-Stable version
[Benchmarks_CSolver.git] / JMCR-Stable / real-world application / jigsaw / src / org / w3c / www / protocol / http / cache / CacheFilter.java
diff --git a/JMCR-Stable/real-world application/jigsaw/src/org/w3c/www/protocol/http/cache/CacheFilter.java b/JMCR-Stable/real-world application/jigsaw/src/org/w3c/www/protocol/http/cache/CacheFilter.java
new file mode 100644 (file)
index 0000000..8250592
--- /dev/null
@@ -0,0 +1,899 @@
+// CacheFilter.java\r
+// $Id: CacheFilter.java,v 1.2 2010/06/15 17:53:00 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.io.File;\r
+import java.lang.reflect.Method;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.net.URL;\r
+import java.net.MalformedURLException;\r
+import java.util.Vector;\r
+import java.util.Hashtable;\r
+\r
+import org.w3c.util.ObservableProperties;\r
+import org.w3c.util.PropertyMonitoring;\r
+import org.w3c.util.URLUtils;\r
+\r
+import org.w3c.www.protocol.http.HttpException;\r
+import org.w3c.www.protocol.http.HttpManager;\r
+import org.w3c.www.protocol.http.PropRequestFilter;\r
+import org.w3c.www.protocol.http.PropRequestFilterException;\r
+import org.w3c.www.protocol.http.Reply;\r
+import org.w3c.www.protocol.http.Request;\r
+import org.w3c.www.protocol.http.RequestFilter;\r
+\r
+import org.w3c.www.http.HTTP;\r
+import org.w3c.www.http.HttpCacheControl;\r
+import org.w3c.www.http.HttpEntityMessage;\r
+import org.w3c.www.http.HttpFactory;\r
+import org.w3c.www.http.HttpInvalidValueException;\r
+import org.w3c.www.http.HttpMessage;\r
+import org.w3c.www.http.HttpReplyMessage;\r
+import org.w3c.www.http.HttpRequestMessage;\r
+import org.w3c.www.http.HttpSetCookieList;\r
+import org.w3c.www.http.HttpWarning;\r
+\r
+public class CacheFilter implements PropRequestFilter, PropertyMonitoring {\r
+    // serializer class\r
+    public static final String SERIALIZER_P = \r
+    "org.w3c.www.protocol.http.cache.serializerclass";\r
+    // sweeper class\r
+    public static final String SWEEPER_P    = \r
+    "org.w3c.www.protocol.http.cache.sweeperclass";\r
+    // validator class\r
+    public static final String VALIDATOR_P  = \r
+    "org.w3c.www.protocol.http.cache.validatorclass";\r
+\r
+    /**\r
+     * Name of the property enabling the connected/disconnected mode\r
+     */\r
+    public static final String CACHE_CONNECTED_P\r
+    = "org.w3c.www.protocol.http.cache.connected";\r
+   /**\r
+     * Name of the property indicating if this cache is shared.\r
+     * <p>This property defaults to <strong>true</strong>.\r
+     */\r
+    public static final\r
+    String SHARED_P = "org.w3c.www.protocol.http.cache.shared";\r
+\r
+    /**\r
+     * The name of the properties indicating the size of the cache (in bytes).\r
+     * This property will give the value of the disk-based cache size. This\r
+     * value only takes into account the size of the entities saved, not\r
+     * the size of the associated headers, and not the physical size on the\r
+     * disc.\r
+     * <p>This property defaults to <strong>5000000</strong> bytes.\r
+     */\r
+    public static final \r
+    String CACHE_SIZE_P = "org.w3c.www.protocol.http.cache.size";\r
+\r
+    /**\r
+     * Name of the property indicating if the cache is in debug mode.\r
+     * <p>This property defaults to <strong>false</strong>.\r
+     */\r
+    public static final \r
+    String DEBUG_P = "org.w3c.www.protocol.http.cache.debug";\r
+\r
+    /**\r
+     * The state used to disable that filter per request. Also set by the cache\r
+     * if the request cannot be fullfilled by caches, as detected by this \r
+     * filter.\r
+     */\r
+    public final static String\r
+    STATE_NOCACHE = "org.w3c.www.protocol.http.cache.dont";\r
+    /**\r
+     * Name of the request state used to collect warnings.\r
+     */\r
+    public final static \r
+    String STATE_WARNINGS = \r
+    "org.w3c.www.protocol.http.cache.CacheFilter.warns";\r
+    /**\r
+     * Name of the request state used tokeep track of original request\r
+     */\r
+    public final static\r
+    String STATE_ORIGREQ = \r
+    "org.w3c.www.protocol.http.cache.CacheFilter.origreq";\r
+    /**\r
+     * Name of the request state that marks a request as being a revalidation.\r
+     */\r
+    public final static \r
+    String STATE_REVALIDATION = "org.w3c.www.protocol.http.cache.revalidation";\r
+\r
+    /**\r
+     * The HTTP warning used to notify of a disconnected cache.\r
+     */\r
+    protected static HttpWarning WARN_DISCONNECTED = null;\r
+    /**\r
+     * The HTTP warning used to mark invalid entries\r
+     */\r
+    protected static HttpWarning WARN_STALE = null;\r
+    /**\r
+     * The HTTP warning used to indicate a heuristic expiration time.\r
+     */\r
+    protected static HttpWarning WARN_HEURISTIC = null;\r
+\r
+    static {\r
+       // Build the std "disconnected" warning\r
+       HttpWarning w = null;\r
+       w = HttpFactory.makeWarning(HttpWarning.DISCONNECTED_OPERATION);\r
+       w.setAgent("Jigsaw");\r
+       w.setText("The required cached resource is stale.");\r
+       WARN_DISCONNECTED = w;\r
+       // Build the stale std warning\r
+       w = HttpFactory.makeWarning(HttpWarning.STALE);\r
+       w.setAgent("Jigsaw");\r
+       w.setText("The returned entry is stale.");\r
+       WARN_STALE = w;\r
+       // Build the heuristic expiration warning:\r
+       w = HttpFactory.makeWarning(HttpWarning.HEURISTIC_EXPIRATION);\r
+       w.setAgent("Jigsaw");\r
+       w.setText("Heuristic expiration time used on this entry.");\r
+       WARN_HEURISTIC = w;\r
+    }\r
+\r
+    /**\r
+     * The properties we initialized ourself from.\r
+     */\r
+    protected ObservableProperties props = null;\r
+    // our validator\r
+    protected CacheValidator validator;\r
+    // our caches tore\r
+    protected CacheStore     store;\r
+    // our cache sweeper\r
+    protected CacheSweeper   sweeper;\r
+    // our cache serializer\r
+    protected CacheSerializer serializer;\r
+    // is the cache connected?\r
+    protected boolean connected = true;\r
+    // is the cache shared?\r
+    protected boolean shared = true;\r
+    // The cache size\r
+    protected long size = 20971520; // 20Mo is the default\r
+    protected File directory = null;\r
+    // should ew debug this?\r
+    protected boolean debug = false;\r
+    // the hastable of not downloaded resources\r
+    protected Hashtable precache = new Hashtable(10); \r
+    // the hastable of the URI to be downloaded\r
+    protected Hashtable uritable = new Hashtable(10);\r
+\r
+    /**\r
+     * return the cache sweeper used by the cache\r
+     * @return an instance of CacheSweeper\r
+     */\r
+    public CacheSweeper getSweeper() {\r
+       return sweeper;\r
+    }\r
+\r
+    /**\r
+     * return the serializer used by the cache\r
+     * @return an instance of Serializer\r
+     */\r
+    public CacheSerializer getSerializer() {\r
+       return serializer;\r
+    }\r
+\r
+    /**\r
+     * return the cache validator used by the cache\r
+     * @return an instance of CacheValidator\r
+     */\r
+    public CacheValidator getValidator() {\r
+        return validator;\r
+    }\r
+\r
+    /**\r
+     * is the cache shared?\r
+     * @return a boolean, true if the cache is shared\r
+     */\r
+    public boolean isShared() {\r
+       return shared;\r
+    }\r
+    /**\r
+     * is the cache connected?\r
+     * @return a boolean, true if the cache is connected\r
+     */\r
+    public boolean isConnected() {\r
+       return connected;\r
+    }\r
+\r
+    /**\r
+     * Display some output, related to the request (used for debugging)\r
+     */\r
+    protected final void trace(Request request, String msg) {\r
+       System.out.println(request.getURL()+": "+msg);\r
+    }\r
+\r
+    /**\r
+     * Add a warning, to be emitted at reply time.\r
+     * The cache filter keeps track, through a specific piece of request state\r
+     * of the warnings to be emitted at reply time (if any).\r
+     * <p>During request processing, cached resources can add any kind\r
+     * of warnings, which will be collected and forwarded back to the reply.\r
+     * @param request The request being process, and whose reply requires\r
+     * some warnings.\r
+     * @param warning The warning to be emitted if ever we use the cache\r
+     * filter to answer the above request.\r
+     */\r
+    protected void addWarning(Request request, HttpWarning warning) {\r
+       Vector vw = (Vector) request.getState(STATE_WARNINGS);\r
+       if ( vw == null ) {\r
+           vw = new Vector(4);\r
+           request.setState(STATE_WARNINGS, vw);\r
+       }\r
+       vw.addElement(warning);\r
+    }\r
+\r
+    /**\r
+     * Copy all warnings colllected into the given reply.\r
+     * This method collects all HTTP warnings saved during request processing\r
+     * and create (if needed) the approporiate warning header in the given\r
+     * reply.\r
+     * @param request The request that has been processed by the cache filter.\r
+     * @param reply The reply that has been constructed from the cache.\r
+     * @see #addWarning\r
+     */\r
+    protected final void setWarnings(Request request, Reply reply) {\r
+       Vector vw = (Vector) request.getState(STATE_WARNINGS);\r
+       if ( vw == null )\r
+           return;\r
+       HttpWarning ws[] = new HttpWarning[vw.size()];\r
+       vw.copyInto(ws);\r
+       reply.setWarning(ws);\r
+    }\r
+\r
+    /**\r
+     * check if we can use the cache or not for this request\r
+     * It marks the request as being not cachable if false.\r
+     * @param a request, the incoming client-side request\r
+     * @return a boolean, true if we can use the cache\r
+     */\r
+    public boolean canUseCache(Request req) {\r
+       // RFC2616: 14.32 Cache-Control equivalence of Pragma: no-cache\r
+       // RFC2616: 14.9.2 no-store directive\r
+       // RFC2616: 14.9.4 End-to-end reload\r
+       if (req.hasPragma("no-cache") || (req.getNoCache() != null)) {\r
+           req.setState(CacheState.STATE_NOCACHE, Boolean.TRUE);\r
+           return false;\r
+       }\r
+       if (req.checkNoStore()) {\r
+           req.setState(CacheState.STATE_STORABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       String method = req.getMethod();\r
+       if (!method.equals("GET") && !method.equals("HEAD") ) {\r
+           req.setState(CacheState.STATE_NOCACHE, Boolean.TRUE);\r
+           return false;\r
+       }\r
+       return true;\r
+    }\r
+\r
+    /**\r
+     * Checks if, according to the headers of the reply, an entity may\r
+     * be cached or not, it decorates also the reply\r
+     * @param a request, the client side request\r
+     * @param a reply, the client side reply\r
+     * @return a boolean, true if the resource can be cached\r
+     */\r
+    public boolean canCache(Request req, Reply rep) {\r
+       String method = req.getMethod();\r
+       // only cache GET and HEAD\r
+       if (!method.equals("GET") /* FIXME && !method.equals("HEAD")*/ ) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       // don't cache HTTP/0.9 replies for now\r
+       if (req.getMajorVersion() == 0) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       // Ugly Hack for lame cookies\r
+       if (rep.getSetCookie() != null) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       // RFC2616: 13.4 Response cacheability\r
+       int status = rep.getStatus();\r
+       if ( (status != HTTP.OK) &&\r
+            (status != HTTP.NON_AUTHORITATIVE_INFORMATION) &&\r
+            (status != HTTP.PARTIAL_CONTENT) &&\r
+            (status != HTTP.MULTIPLE_CHOICE) &&\r
+            (status != HTTP.MOVED_PERMANENTLY) &&\r
+            (status != HTTP.GONE)) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       HttpCacheControl repcc = null;\r
+       try {\r
+           repcc = rep.getCacheControl();\r
+       } catch (HttpInvalidValueException ex) {\r
+           // invalid header, be safe and avoid caching\r
+           repcc = HttpFactory.parseCacheControl("no-cache");\r
+           rep.setCacheControl(repcc);\r
+       }\r
+       // first check if we are told that we can cache the resource\r
+       if (repcc != null) {\r
+           // RFC2616: 14.9.1 Cache-Control: public overrides everything\r
+           if (repcc.checkPublic()) {\r
+               rep.setState(CacheState.STATE_CACHABLE, Boolean.TRUE);\r
+               return true;\r
+           }\r
+           // RFC2616: 14.9.1 Cache-Control: private\r
+           // We are not handling for now the field names that may be\r
+           // associated\r
+           if (isShared() && (repcc.getPrivate() != null)) {\r
+               rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+               return false;\r
+           }\r
+       }\r
+       // RFC2616: 14.9.1 no-cache, note that we are not using\r
+       // the optional field-names (it is a MAY)\r
+       if (rep.getNoCache() != null) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       } \r
+       // HTTP/1.[01] Pragma no-cache RFC2616: 14.32 Cache-Control equivalence\r
+       if (rep.hasPragma("no-cache")) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       // Now check for URI and HTTP/1.0, \r
+       // RFC2616: 13.9 HTTP/1.0 with ? in the URI and no Expires should not\r
+       // be cached.\r
+       if ((req.getURL().getFile().indexOf('?') != -1) && \r
+           ((rep.getMajorVersion() == 1) && (rep.getMinorVersion() == 0))\r
+           && (rep.getExpires() == -1)) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       // Do we have an authentication?\r
+       // without a cache-control, we deny caching for now.\r
+       if (req.hasAuthorization()) {\r
+           rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       // by default, it is cacheable\r
+       rep.setState(CacheState.STATE_CACHABLE, Boolean.TRUE);\r
+       return true;\r
+    }\r
+\r
+    /**\r
+     * Checks if, according to the headers of a reply we can store\r
+     * the resource.\r
+     * Note that a resource may be cachable, but not storable (memory cache)\r
+     * although is MUST do its best to get rid of it asap, in our case we just\r
+     * don't store it!\r
+     * @param a request, the client side request\r
+     * @param a reply, the client side reply\r
+     * @return a boolean, true if the resource can be stored by the cache\r
+     */\r
+    public boolean canStore(Request req, Reply rep) {\r
+       // RFC2616: 14.9.2 What can be stored...\r
+       if (req.checkNoStore() || rep.checkNoStore()) {\r
+           rep.setState(CacheState.STATE_STORABLE, Boolean.FALSE);\r
+           return false;\r
+       }\r
+       rep.setState(CacheState.STATE_CACHABLE, Boolean.TRUE);\r
+       return true;\r
+    }\r
+\r
+    /**\r
+     * Modify a request to ask for a revalidation\r
+     * @param the resource to be revalidated\r
+     * @param request, the original request to be modified\r
+     */\r
+    protected Request setRequestRevalidation(CachedResource res, Request req) {\r
+       try {\r
+           return store.getCachedResource(res).setRequestRevalidation(req);\r
+       } catch (InvalidCacheException ex) {\r
+           // should never happen as we know it is in the cache\r
+       }\r
+       return null;\r
+    }\r
+\r
+    /**\r
+     * The request pre-processing hook.\r
+     * Before each request is launched, all filters will be called back through\r
+     * this method. They will generally set up additional request header\r
+     * fields to enhance the request.\r
+     * @param request The request that is about to be launched.\r
+     * @return An instance of Reply if the filter could handle the request,\r
+     * or <strong>null</strong> if processing should continue normally.\r
+     * @exception HttpException If the filter is supposed to fulfill the\r
+     * request, but some error happened during that processing.\r
+     */\r
+    public Reply ingoingFilter(Request request) \r
+       throws HttpException\r
+    {\r
+       // can we use the cache?\r
+       if (!canUseCache(request)) {\r
+           if (debug) {\r
+               trace(request, "*** Can't use cache");\r
+           }\r
+           // we will invalidate this resource, will do that only\r
+           // on real entity resource, not on negotiated ones\r
+           if (connected) {\r
+               CachedResource res = null;\r
+               EntityCachedResource invalidRes = null;\r
+               try {\r
+                   URL _ru = request.getURL();\r
+                   String requrl = URLUtils.normalize(_ru).toExternalForm();\r
+                   res = store.getCachedResourceReference(requrl);\r
+                   if (res != null) {\r
+                       invalidRes = (EntityCachedResource)\r
+                           res.lookupResource(request);\r
+                   }\r
+               } catch (InvalidCacheException ex) {\r
+                   invalidRes = null;\r
+               }\r
+               if (invalidRes != null) {\r
+                   invalidRes.setWillRevalidate(true);\r
+               }\r
+               request.setState(STATE_NOCACHE, Boolean.TRUE);\r
+               return null;\r
+           } else {\r
+               // disconnected, abort now!\r
+               Reply reply = request.makeReply(HTTP.GATEWAY_TIMEOUT);\r
+               reply.setContent("The cache cannot be use for "\r
+                                + "<p><code>"+request.getMethod()+"</code> "\r
+                                + "<strong>"+request.getURL()+"</strong>"\r
+                                + ". <p>It is disconnected.");\r
+               return reply;\r
+           }\r
+       }\r
+       // let's try to get the resource!\r
+       URL _ru = request.getURL();\r
+       String requrl = URLUtils.normalize(_ru).toExternalForm();\r
+       // in the pre-cache, wait for full download\r
+       // FIXME should be better than this behaviour...\r
+       // see EntityCachedResource perform's FIXME ;)\r
+       if (precache.containsKey(requrl)) {\r
+           if (debug)\r
+               System.out.println("*** Already downloading: "+ requrl);\r
+           try {\r
+               CachedResource cr = (CachedResource)precache.get(requrl);\r
+               return cr.perform(request);\r
+           } catch (Exception ex) {\r
+               // there was a problem with the previous request, \r
+               // it may be better to do it by ourself\r
+           }\r
+       }\r
+       \r
+       CachedResource res = null;\r
+       try {\r
+           res = store.getCachedResourceReference(requrl);\r
+       } catch (InvalidCacheException ex) {\r
+           res = null;\r
+       }\r
+       // are we disconnected?\r
+       if (request.checkOnlyIfCached() || !connected ) {\r
+           // and no entries...\r
+           EntityCachedResource ecr = null;\r
+           if (res != null) {\r
+               ecr = (EntityCachedResource) res.lookupResource(request);\r
+           }\r
+           if ((res == null) || (ecr == null)) {\r
+               if ( debug )\r
+                   trace(request, "unavailable (disconnected).");\r
+               Reply reply = request.makeReply(HTTP.GATEWAY_TIMEOUT);\r
+               reply.setContent("The cache doesn't have an entry for "\r
+                                + "<p><strong>"+request.getURL()+"</strong>"\r
+                                + ". <p>And it is disconnected.");\r
+               return reply;\r
+           }\r
+           // yeah!\r
+           if (debug) {\r
+               trace(request, (connected) ? " hit - only if cached" : \r
+                     " hit while disconneced" );\r
+           }\r
+           if (!validator.isValid(ecr, request)) {\r
+               addWarning(request, WARN_STALE);\r
+           }\r
+           addWarning(request, WARN_DISCONNECTED);\r
+           Reply reply = ecr.perform(request);\r
+               // Add any warnings collected during processing to the reply:\r
+           setWarnings(request, reply);\r
+//FIXME            request.setState(STATE_HOW, HOW_HIT);\r
+           return reply;\r
+       }\r
+       // in connected mode, we should now take care of revalidation and such\r
+       if (res != null) {\r
+           // if not fully loaded, ask for a revalidation FIXME\r
+           if ((res.getLoadState() == CachedResource.STATE_LOAD_PARTIAL) ||\r
+               (res.getLoadState() == CachedResource.STATE_LOAD_ERROR)) {\r
+               setRequestRevalidation(res, request);\r
+               return null;\r
+           }\r
+           if ( validator.isValid(res, request) ) {\r
+               try {\r
+                   store.updateResourceGeneration(res);\r
+               } catch (InvalidCacheException ex) {\r
+                   // should be ok so...\r
+               }\r
+//FIXME            request.setState(STATE_HOW, HOW_HIT);\r
+               Reply rep = res.perform(request);\r
+               return rep;\r
+           } else {\r
+               if (debug) {\r
+                   System.out.println("*** Revalidation asked for " + requrl);\r
+               }\r
+               // ask for a revalidation\r
+               setRequestRevalidation(res, request);\r
+               return null;\r
+           }\r
+       }\r
+       // lock here while we are waiting for the download\r
+       while (uritable.containsKey(requrl)) {\r
+           synchronized (uritable) {\r
+               try {\r
+                   uritable.wait();\r
+               } catch (InterruptedException ex) {}\r
+           }\r
+           if (precache.containsKey(requrl)) {\r
+               if (debug)\r
+                   System.out.println("*** Already downloading: "+ requrl);\r
+               CachedResource cr = (CachedResource)precache.get(requrl);\r
+               return cr.perform(request);\r
+           }\r
+           uritable.put(requrl, requrl);\r
+       }\r
+       return null;\r
+    }\r
+\r
+    /**\r
+     * This filter  handle exceptions.\r
+     * @param request The request that triggered the exception.\r
+     * @param ex The triggered exception.\r
+     * @return Always <strong>false</strong>.\r
+     */\r
+    public boolean exceptionFilter(Request request, HttpException ex) {\r
+       URL _ru;\r
+       _ru = request.getURL();\r
+       String requrl = URLUtils.normalize(_ru).toExternalForm();\r
+       synchronized (uritable) {\r
+           uritable.remove(requrl);\r
+           uritable.notifyAll();\r
+       }       \r
+       return false;\r
+    }    \r
+\r
+    /**\r
+     * The request post-processing hook.\r
+     * After each request has been replied to by the target server (be it a \r
+     * proxy or the actual origin server), each filter's outgoingFilter\r
+     * method is called.\r
+     * <p>It gets the original request, and the actual reply as a parameter,\r
+     * and should return whatever reply it wants the caller to get.\r
+     * @param request The original (handled) request.\r
+     * @param reply The reply, as emited by the target server, or constructed\r
+     * by some other filter.\r
+     * @exception HttpException If the reply emitted by the server is not\r
+     * a valid HTTP reply.\r
+     */\r
+    public Reply outgoingFilter(Request request, Reply reply) \r
+       throws HttpException\r
+    {\r
+       URL url = URLUtils.normalize(request.getURL());\r
+       // Yeah ! We win:\r
+       CachedResource c;\r
+       c = (CachedResource) request.getState(CacheState.STATE_RESOURCE);\r
+       if ( c != null ) {\r
+           if ( debug )\r
+               trace(request, "revalidated "+reply.getStatus());\r
+//             request.setState(STATE_HOW\r
+//                              , ((reply.getStatus() == HTTP.NOT_MODIFIED)\r
+//                                 ? HOW_REVALIDATION_SUCCESS\r
+//                                 : HOW_REVALIDATION_FAILURE));\r
+//             Reply valrep = c.validate(request, reply);\r
+               // Client or Server error, ask for revalidation\r
+//             if (valrep.getStatus()/100 >= 4) {\r
+//                 c.setWillRevalidate(true);\r
+//             }\r
+           if (reply.getStatus() == HTTP.NOT_MODIFIED) {\r
+               // FIXME revalidateResource(request, reply)\r
+               validator.revalidateResource(c, request, reply);\r
+               try {\r
+                   store.storeCachedResource(c, c.getCurrentLength());\r
+               } catch (InvalidCacheException ex) {\r
+                   if (debug) {\r
+                       ex.printStackTrace();\r
+                   }\r
+               }\r
+               // extract the original request\r
+               Request origreq;\r
+               origreq = (Request) request.getState(CacheState.STATE_ORIGREQ);\r
+               return c.perform(origreq);\r
+           } else {\r
+               // delete it\r
+               c.delete();\r
+               store.getState().notifyResourceDeleted(c);\r
+           }\r
+       }\r
+       // don't use cache, exit asap\r
+       if (!canCache(request, reply)) {\r
+           request.setState(STATE_NOCACHE, Boolean.TRUE);\r
+           if (debug) {\r
+               System.out.println("*** Can't cache reply");\r
+           }\r
+           String requrl = url.toExternalForm();\r
+           precache.remove(requrl);\r
+           synchronized (uritable) {\r
+               uritable.remove(requrl);\r
+               uritable.notifyAll();\r
+           }\r
+           invalidateOnReply(request, reply);\r
+           return null;\r
+       }\r
+       if (!canStore(request, reply)) {\r
+//         request.setState(STATE_NOSTORE, Boolean.TRUE);\r
+           if (debug) {\r
+               System.out.println("*** Can't store reply");\r
+           }\r
+           String requrl = url.toExternalForm();\r
+           precache.remove(requrl);\r
+           synchronized (uritable) {\r
+               uritable.remove(requrl);\r
+               uritable.notifyAll();\r
+           }\r
+           invalidateOnReply(request, reply);\r
+           return null;\r
+       }\r
+       pushDocument(request, reply);\r
+       return null;\r
+    }\r
+\r
+    private void invalidateOnReply(Request request, Reply reply) {\r
+       URL url = request.getURL();\r
+       // now invalidate the entities per rfc2616#13.11\r
+       if ((request.getMethod() == HTTP.POST) ||\r
+           (request.getMethod() == HTTP.PUT) ||\r
+           (request.getMethod() == HTTP.DELETE)) {\r
+           String rloc = reply.getLocation();\r
+           String rcloc = reply.getContentLocation();\r
+           URL urloc, urcloc;\r
+           // FIXME use a private method instead of duplicating\r
+           if (rloc != null) {\r
+               try {\r
+                   urloc = new URL(rloc);\r
+                   // only if host match\r
+                   if (URLUtils.equalsProtocolHostPort(url,urloc)) {\r
+                       CachedResource res = null;\r
+                       try {\r
+                           res = store.getCachedResourceReference(rloc);\r
+                           if (res != null) {\r
+                               res.setWillRevalidate(true);\r
+                           }\r
+                       } catch (InvalidCacheException ex) {\r
+                               // weird, but it won't stop us :)\r
+                       }\r
+                   }\r
+               } catch (MalformedURLException mule) {\r
+                   // nothing to do\r
+               }\r
+           }\r
+           if (rcloc != null) {\r
+               try {\r
+                   urcloc = new URL(url, rcloc);\r
+                   // only if host match\r
+                   if (URLUtils.equalsProtocolHostPort(url,urcloc)) {\r
+                       CachedResource res = null;\r
+                       try {\r
+                           String surcloc = urcloc.toExternalForm();\r
+                           res = store.getCachedResourceReference(surcloc);\r
+                           if (res != null) {\r
+                               res.setWillRevalidate(true);\r
+                           }\r
+                       } catch (InvalidCacheException ex) {\r
+                               // weird, but it won't stop us :)\r
+                       }\r
+                   }\r
+               } catch (MalformedURLException mule) {\r
+                   // nothing to do\r
+               }\r
+           }\r
+       }\r
+    }\r
+           \r
+\r
+    public void sync() {\r
+       if (debug) {\r
+           System.out.println("*** Synching the CacheFilter");\r
+       }\r
+       try {\r
+           store.sync();\r
+       } catch (Exception ex) {\r
+           System.err.println(getClass().getName()+": Unable to save cache.");\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Property monitoring for the CacheFilter.\r
+     * The CacheFilter allows you to dynamically (typically through the \r
+     * property setter) change the class of the sweeper, the validator, \r
+     * the size...\r
+     * @param name The name of the property that has changed.\r
+     * @return A boolean, <strong>true</strong> if the change was made, \r
+     *    <strong>false</strong> otherwise.\r
+     */\r
+    public boolean propertyChanged(String name) {\r
+       if ( name.equals(SERIALIZER_P) ) {\r
+           CacheSerializer cs = null;\r
+           try {\r
+               Class c;\r
+               c = Class.forName(props.getString(name, null));\r
+               cs = (CacheSerializer) c.newInstance();\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+           } catch (Exception ex) {\r
+               return false;\r
+           }\r
+           serializer = cs;\r
+           return true;\r
+       } else if ( name.equals(SWEEPER_P) ) {\r
+           CacheSweeper cs = null;\r
+           try {\r
+               Class c;\r
+               c = Class.forName(props.getString(name, null));\r
+               cs = (CacheSweeper) c.newInstance();\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+           } catch (Exception ex) {\r
+               return false;\r
+           }\r
+           // looks good, let's restart with this one!\r
+           sweeper.destroy();\r
+           sweeper = cs;\r
+           sweeper.start();\r
+           return true;\r
+       } else if ( name.equals(VALIDATOR_P) ) {\r
+           CacheValidator cv = null;\r
+           try {\r
+               Class c;\r
+               c = Class.forName(props.getString(name, null));\r
+               cv = (CacheValidator) c.newInstance();\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+           } catch (Exception ex) {\r
+               return false;\r
+           }\r
+           validator = cv;\r
+           return true;\r
+       } else if ( name.equals(DEBUG_P) ) {\r
+           debug = props.getBoolean(name, debug);\r
+           return true;\r
+       } else if ( name.equals(SHARED_P) ) {\r
+           shared = props.getBoolean(name, shared);\r
+           return true;\r
+       } else if ( name.equals(CACHE_CONNECTED_P)) {\r
+           connected = props.getBoolean(name, true);\r
+           return true;\r
+       }\r
+       // ask the store if it can do something\r
+       return store.propertyChanged(name);\r
+    }\r
+\r
+    /**\r
+     * Push a document in the cache.\r
+     * The caller has to forge a a request and a reply before being able\r
+     * to make something\r
+     * enter the cache. \r
+     * The request should provide at least:\r
+     * <dl>\r
+     * <dt>URL<dl>The URL (key for cache lookups)\r
+     * <dt>Method<dl>The method that was "applied" to URL to get forged\r
+     * reply.\r
+     * </dl>\r
+     * <p>It is recommended that the reply provides at least\r
+     * the following informations:\r
+     * <dl>\r
+     * <dt>Status Code</dl>A valid HTTP/1.1 status code (probably <strong>\r
+     * 200</code>)\r
+     * <dt>InputStream<dl>Containing the entity to be cached,\r
+     * <dt>EntityTag<dl>A valid entity tag for the document,\r
+     * <dt>CacheControl<dl>Appropriate HTTP/1.1 cache controls for that\r
+     *     document,\r
+     * <dt>Mime headers<dl>At least a valid content type, and probably a\r
+     *     content length (to check consistency with the reply body).\r
+     * </dt>\r
+     */\r
+    public void pushDocument(Request request, Reply reply) {\r
+       URL url = URLUtils.normalize(request.getURL());\r
+       try {\r
+           synchronized (uritable) {\r
+               CachedResource r = null;\r
+               String requrl = url.toExternalForm();\r
+               r = CachedResourceFactory.createResource(this, request,\r
+                                                        reply);\r
+               if (r.uploading)\r
+                   precache.put(requrl, r);\r
+               uritable.remove(requrl);\r
+               uritable.notifyAll();\r
+           }\r
+           if ( debug )\r
+               trace(request, "enters cache.");\r
+       } catch (Exception ex) {\r
+           ex.printStackTrace();\r
+       }\r
+    }\r
+\r
+    /**\r
+     * do what is needed when an upload is done!\r
+     * ie: remove from the precache and put in the store\r
+     * @param the CachedResource to be moved.\r
+     */\r
+    protected synchronized void cleanUpload(CachedResource cr) {\r
+       // FIXME we should check the state!\r
+       precache.remove(cr.getIdentifier());\r
+       try {\r
+           store.storeCachedResource(cr);\r
+       } catch (InvalidCacheException ex) {\r
+           if (debug) {\r
+               ex.printStackTrace();\r
+           }\r
+       }\r
+    }\r
+\r
+    /**\r
+     * @return the store used a CacheStore\r
+     */\r
+    public CacheStore getStore() {\r
+       return store;\r
+    }\r
+\r
+    public void initialize(HttpManager manager) \r
+       throws PropRequestFilterException \r
+    {\r
+       String validator_c;\r
+       String sweeper_c;\r
+       String serializer_c;\r
+       props = manager.getProperties();\r
+\r
+       shared    = props.getBoolean(SHARED_P, true);\r
+       connected = props.getBoolean(CACHE_CONNECTED_P, true);\r
+       debug     = props.getBoolean(DEBUG_P, false);\r
+       // now create the add-on classes\r
+       validator_c = props.getString(VALIDATOR_P,\r
+                      "org.w3c.www.protocol.http.cache.SimpleCacheValidator");\r
+       sweeper_c = props.getString(SWEEPER_P,\r
+                        "org.w3c.www.protocol.http.cache.SimpleCacheSweeper");\r
+       serializer_c = props.getString(SERIALIZER_P,\r
+                     "org.w3c.www.protocol.http.cache.SimpleCacheSerializer");\r
+       try {\r
+           Class c;\r
+           c = Class.forName(validator_c);\r
+           validator =  (CacheValidator) c.newInstance();\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+           validator.initialize(this);\r
+           c = Class.forName(sweeper_c);\r
+           sweeper = (CacheSweeper) c.newInstance();\r
+           sweeper.initialize(this);\r
+           c = Class.forName(serializer_c);\r
+           serializer = (CacheSerializer) c.newInstance();\r
+       } catch (Exception ex) {\r
+           // a fatal error! The cache won't be loaded...\r
+           ex.printStackTrace();\r
+           throw new PropRequestFilterException("Unable to start cache");\r
+       }\r
+       // now create the store as we have the basic things here\r
+       store = new CacheStore();\r
+       try {\r
+           store.initialize(this);\r
+       } catch (InvalidCacheException ex) {\r
+           // hum no worky, should do some action there!\r
+           if (debug) {\r
+               ex.printStackTrace();\r
+           }\r
+       }\r
+       // now start the sweeper\r
+       sweeper.start();\r
+       // Start the ActiveStream handler:\r
+       ActiveStream.initialize();\r
+       // Register for property changes:\r
+       props.registerObserver(this);\r
+       // Now, we are ready, register that filter:\r
+       manager.setFilter(this);\r
+    }\r
+}\r