--- /dev/null
+// 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