001: // CacheFilter.java
002: // $Id: CacheFilter.java,v 1.92 2003/04/02 19:08:09 ylafon Exp $
003: // (c) COPYRIGHT MIT, INRIA and Keio, 1999.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.www.protocol.http.cache;
007:
008: import java.io.File;
009: import java.lang.reflect.Method;
010: import java.lang.reflect.InvocationTargetException;
011: import java.net.URL;
012: import java.net.MalformedURLException;
013: import java.util.Vector;
014: import java.util.Hashtable;
015:
016: import org.w3c.util.ObservableProperties;
017: import org.w3c.util.PropertyMonitoring;
018: import org.w3c.util.URLUtils;
019:
020: import org.w3c.www.protocol.http.HttpException;
021: import org.w3c.www.protocol.http.HttpManager;
022: import org.w3c.www.protocol.http.PropRequestFilter;
023: import org.w3c.www.protocol.http.PropRequestFilterException;
024: import org.w3c.www.protocol.http.Reply;
025: import org.w3c.www.protocol.http.Request;
026: import org.w3c.www.protocol.http.RequestFilter;
027:
028: import org.w3c.www.http.HTTP;
029: import org.w3c.www.http.HttpCacheControl;
030: import org.w3c.www.http.HttpEntityMessage;
031: import org.w3c.www.http.HttpFactory;
032: import org.w3c.www.http.HttpInvalidValueException;
033: import org.w3c.www.http.HttpMessage;
034: import org.w3c.www.http.HttpReplyMessage;
035: import org.w3c.www.http.HttpRequestMessage;
036: import org.w3c.www.http.HttpSetCookieList;
037: import org.w3c.www.http.HttpWarning;
038:
039: public class CacheFilter implements PropRequestFilter,
040: PropertyMonitoring {
041: // serializer class
042: public static final String SERIALIZER_P = "org.w3c.www.protocol.http.cache.serializerclass";
043: // sweeper class
044: public static final String SWEEPER_P = "org.w3c.www.protocol.http.cache.sweeperclass";
045: // validator class
046: public static final String VALIDATOR_P = "org.w3c.www.protocol.http.cache.validatorclass";
047:
048: /**
049: * Name of the property enabling the connected/disconnected mode
050: */
051: public static final String CACHE_CONNECTED_P = "org.w3c.www.protocol.http.cache.connected";
052: /**
053: * Name of the property indicating if this cache is shared.
054: * <p>This property defaults to <strong>true</strong>.
055: */
056: public static final String SHARED_P = "org.w3c.www.protocol.http.cache.shared";
057:
058: /**
059: * The name of the properties indicating the size of the cache (in bytes).
060: * This property will give the value of the disk-based cache size. This
061: * value only takes into account the size of the entities saved, not
062: * the size of the associated headers, and not the physical size on the
063: * disc.
064: * <p>This property defaults to <strong>5000000</strong> bytes.
065: */
066: public static final String CACHE_SIZE_P = "org.w3c.www.protocol.http.cache.size";
067:
068: /**
069: * Name of the property indicating if the cache is in debug mode.
070: * <p>This property defaults to <strong>false</strong>.
071: */
072: public static final String DEBUG_P = "org.w3c.www.protocol.http.cache.debug";
073:
074: /**
075: * The state used to disable that filter per request. Also set by the cache
076: * if the request cannot be fullfilled by caches, as detected by this
077: * filter.
078: */
079: public final static String STATE_NOCACHE = "org.w3c.www.protocol.http.cache.dont";
080: /**
081: * Name of the request state used to collect warnings.
082: */
083: public final static String STATE_WARNINGS = "org.w3c.www.protocol.http.cache.CacheFilter.warns";
084: /**
085: * Name of the request state used tokeep track of original request
086: */
087: public final static String STATE_ORIGREQ = "org.w3c.www.protocol.http.cache.CacheFilter.origreq";
088: /**
089: * Name of the request state that marks a request as being a revalidation.
090: */
091: public final static String STATE_REVALIDATION = "org.w3c.www.protocol.http.cache.revalidation";
092:
093: /**
094: * The HTTP warning used to notify of a disconnected cache.
095: */
096: protected static HttpWarning WARN_DISCONNECTED = null;
097: /**
098: * The HTTP warning used to mark invalid entries
099: */
100: protected static HttpWarning WARN_STALE = null;
101: /**
102: * The HTTP warning used to indicate a heuristic expiration time.
103: */
104: protected static HttpWarning WARN_HEURISTIC = null;
105:
106: static {
107: // Build the std "disconnected" warning
108: HttpWarning w = null;
109: w = HttpFactory.makeWarning(HttpWarning.DISCONNECTED_OPERATION);
110: w.setAgent("Jigsaw");
111: w.setText("The required cached resource is stale.");
112: WARN_DISCONNECTED = w;
113: // Build the stale std warning
114: w = HttpFactory.makeWarning(HttpWarning.STALE);
115: w.setAgent("Jigsaw");
116: w.setText("The returned entry is stale.");
117: WARN_STALE = w;
118: // Build the heuristic expiration warning:
119: w = HttpFactory.makeWarning(HttpWarning.HEURISTIC_EXPIRATION);
120: w.setAgent("Jigsaw");
121: w.setText("Heuristic expiration time used on this entry.");
122: WARN_HEURISTIC = w;
123: }
124:
125: /**
126: * The properties we initialized ourself from.
127: */
128: protected ObservableProperties props = null;
129: // our validator
130: protected CacheValidator validator;
131: // our caches tore
132: protected CacheStore store;
133: // our cache sweeper
134: protected CacheSweeper sweeper;
135: // our cache serializer
136: protected CacheSerializer serializer;
137: // is the cache connected?
138: protected boolean connected = true;
139: // is the cache shared?
140: protected boolean shared = true;
141: // The cache size
142: protected long size = 20971520; // 20Mo is the default
143: protected File directory = null;
144: // should ew debug this?
145: protected boolean debug = false;
146: // the hastable of not downloaded resources
147: protected Hashtable precache = new Hashtable(10);
148: // the hastable of the URI to be downloaded
149: protected Hashtable uritable = new Hashtable(10);
150:
151: /**
152: * return the cache sweeper used by the cache
153: * @return an instance of CacheSweeper
154: */
155: public CacheSweeper getSweeper() {
156: return sweeper;
157: }
158:
159: /**
160: * return the serializer used by the cache
161: * @return an instance of Serializer
162: */
163: public CacheSerializer getSerializer() {
164: return serializer;
165: }
166:
167: /**
168: * return the cache validator used by the cache
169: * @return an instance of CacheValidator
170: */
171: public CacheValidator getValidator() {
172: return validator;
173: }
174:
175: /**
176: * is the cache shared?
177: * @return a boolean, true if the cache is shared
178: */
179: public boolean isShared() {
180: return shared;
181: }
182:
183: /**
184: * is the cache connected?
185: * @return a boolean, true if the cache is connected
186: */
187: public boolean isConnected() {
188: return connected;
189: }
190:
191: /**
192: * Display some output, related to the request (used for debugging)
193: */
194: protected final void trace(Request request, String msg) {
195: System.out.println(request.getURL() + ": " + msg);
196: }
197:
198: /**
199: * Add a warning, to be emitted at reply time.
200: * The cache filter keeps track, through a specific piece of request state
201: * of the warnings to be emitted at reply time (if any).
202: * <p>During request processing, cached resources can add any kind
203: * of warnings, which will be collected and forwarded back to the reply.
204: * @param request The request being process, and whose reply requires
205: * some warnings.
206: * @param warning The warning to be emitted if ever we use the cache
207: * filter to answer the above request.
208: */
209: protected void addWarning(Request request, HttpWarning warning) {
210: Vector vw = (Vector) request.getState(STATE_WARNINGS);
211: if (vw == null) {
212: vw = new Vector(4);
213: request.setState(STATE_WARNINGS, vw);
214: }
215: vw.addElement(warning);
216: }
217:
218: /**
219: * Copy all warnings colllected into the given reply.
220: * This method collects all HTTP warnings saved during request processing
221: * and create (if needed) the approporiate warning header in the given
222: * reply.
223: * @param request The request that has been processed by the cache filter.
224: * @param reply The reply that has been constructed from the cache.
225: * @see #addWarning
226: */
227: protected final void setWarnings(Request request, Reply reply) {
228: Vector vw = (Vector) request.getState(STATE_WARNINGS);
229: if (vw == null)
230: return;
231: HttpWarning ws[] = new HttpWarning[vw.size()];
232: vw.copyInto(ws);
233: reply.setWarning(ws);
234: }
235:
236: /**
237: * check if we can use the cache or not for this request
238: * It marks the request as being not cachable if false.
239: * @param a request, the incoming client-side request
240: * @return a boolean, true if we can use the cache
241: */
242: public boolean canUseCache(Request req) {
243: // RFC2616: 14.32 Cache-Control equivalence of Pragma: no-cache
244: // RFC2616: 14.9.2 no-store directive
245: // RFC2616: 14.9.4 End-to-end reload
246: if (req.hasPragma("no-cache") || (req.getNoCache() != null)) {
247: req.setState(CacheState.STATE_NOCACHE, Boolean.TRUE);
248: return false;
249: }
250: if (req.checkNoStore()) {
251: req.setState(CacheState.STATE_STORABLE, Boolean.FALSE);
252: return false;
253: }
254: String method = req.getMethod();
255: if (!method.equals("GET") && !method.equals("HEAD")) {
256: req.setState(CacheState.STATE_NOCACHE, Boolean.TRUE);
257: return false;
258: }
259: return true;
260: }
261:
262: /**
263: * Checks if, according to the headers of the reply, an entity may
264: * be cached or not, it decorates also the reply
265: * @param a request, the client side request
266: * @param a reply, the client side reply
267: * @return a boolean, true if the resource can be cached
268: */
269: public boolean canCache(Request req, Reply rep) {
270: String method = req.getMethod();
271: // only cache GET and HEAD
272: if (!method.equals("GET") /* FIXME && !method.equals("HEAD")*/) {
273: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
274: return false;
275: }
276: // don't cache HTTP/0.9 replies for now
277: if (req.getMajorVersion() == 0) {
278: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
279: return false;
280: }
281: // Ugly Hack for lame cookies
282: if (rep.getSetCookie() != null) {
283: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
284: return false;
285: }
286: // RFC2616: 13.4 Response cacheability
287: int status = rep.getStatus();
288: if ((status != HTTP.OK)
289: && (status != HTTP.NON_AUTHORITATIVE_INFORMATION)
290: && (status != HTTP.PARTIAL_CONTENT)
291: && (status != HTTP.MULTIPLE_CHOICE)
292: && (status != HTTP.MOVED_PERMANENTLY)
293: && (status != HTTP.GONE)) {
294: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
295: return false;
296: }
297: HttpCacheControl repcc = null;
298: try {
299: repcc = rep.getCacheControl();
300: } catch (HttpInvalidValueException ex) {
301: // invalid header, be safe and avoid caching
302: repcc = HttpFactory.parseCacheControl("no-cache");
303: rep.setCacheControl(repcc);
304: }
305: // first check if we are told that we can cache the resource
306: if (repcc != null) {
307: // RFC2616: 14.9.1 Cache-Control: public overrides everything
308: if (repcc.checkPublic()) {
309: rep.setState(CacheState.STATE_CACHABLE, Boolean.TRUE);
310: return true;
311: }
312: // RFC2616: 14.9.1 Cache-Control: private
313: // We are not handling for now the field names that may be
314: // associated
315: if (isShared() && (repcc.getPrivate() != null)) {
316: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
317: return false;
318: }
319: }
320: // RFC2616: 14.9.1 no-cache, note that we are not using
321: // the optional field-names (it is a MAY)
322: if (rep.getNoCache() != null) {
323: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
324: return false;
325: }
326: // HTTP/1.[01] Pragma no-cache RFC2616: 14.32 Cache-Control equivalence
327: if (rep.hasPragma("no-cache")) {
328: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
329: return false;
330: }
331: // Now check for URI and HTTP/1.0,
332: // RFC2616: 13.9 HTTP/1.0 with ? in the URI and no Expires should not
333: // be cached.
334: if ((req.getURL().getFile().indexOf('?') != -1)
335: && ((rep.getMajorVersion() == 1) && (rep
336: .getMinorVersion() == 0))
337: && (rep.getExpires() == -1)) {
338: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
339: return false;
340: }
341: // Do we have an authentication?
342: // without a cache-control, we deny caching for now.
343: if (req.hasAuthorization()) {
344: rep.setState(CacheState.STATE_CACHABLE, Boolean.FALSE);
345: return false;
346: }
347: // by default, it is cacheable
348: rep.setState(CacheState.STATE_CACHABLE, Boolean.TRUE);
349: return true;
350: }
351:
352: /**
353: * Checks if, according to the headers of a reply we can store
354: * the resource.
355: * Note that a resource may be cachable, but not storable (memory cache)
356: * although is MUST do its best to get rid of it asap, in our case we just
357: * don't store it!
358: * @param a request, the client side request
359: * @param a reply, the client side reply
360: * @return a boolean, true if the resource can be stored by the cache
361: */
362: public boolean canStore(Request req, Reply rep) {
363: // RFC2616: 14.9.2 What can be stored...
364: if (req.checkNoStore() || rep.checkNoStore()) {
365: rep.setState(CacheState.STATE_STORABLE, Boolean.FALSE);
366: return false;
367: }
368: rep.setState(CacheState.STATE_CACHABLE, Boolean.TRUE);
369: return true;
370: }
371:
372: /**
373: * Modify a request to ask for a revalidation
374: * @param the resource to be revalidated
375: * @param request, the original request to be modified
376: */
377: protected Request setRequestRevalidation(CachedResource res,
378: Request req) {
379: try {
380: return store.getCachedResource(res).setRequestRevalidation(
381: req);
382: } catch (InvalidCacheException ex) {
383: // should never happen as we know it is in the cache
384: }
385: return null;
386: }
387:
388: /**
389: * The request pre-processing hook.
390: * Before each request is launched, all filters will be called back through
391: * this method. They will generally set up additional request header
392: * fields to enhance the request.
393: * @param request The request that is about to be launched.
394: * @return An instance of Reply if the filter could handle the request,
395: * or <strong>null</strong> if processing should continue normally.
396: * @exception HttpException If the filter is supposed to fulfill the
397: * request, but some error happened during that processing.
398: */
399: public Reply ingoingFilter(Request request) throws HttpException {
400: // can we use the cache?
401: if (!canUseCache(request)) {
402: if (debug) {
403: trace(request, "*** Can't use cache");
404: }
405: // we will invalidate this resource, will do that only
406: // on real entity resource, not on negotiated ones
407: if (connected) {
408: CachedResource res = null;
409: EntityCachedResource invalidRes = null;
410: try {
411: URL _ru = request.getURL();
412: String requrl = URLUtils.normalize(_ru)
413: .toExternalForm();
414: res = store.getCachedResourceReference(requrl);
415: if (res != null) {
416: invalidRes = (EntityCachedResource) res
417: .lookupResource(request);
418: }
419: } catch (InvalidCacheException ex) {
420: invalidRes = null;
421: }
422: if (invalidRes != null) {
423: invalidRes.setWillRevalidate(true);
424: }
425: request.setState(STATE_NOCACHE, Boolean.TRUE);
426: return null;
427: } else {
428: // disconnected, abort now!
429: Reply reply = request.makeReply(HTTP.GATEWAY_TIMEOUT);
430: reply.setContent("The cache cannot be use for "
431: + "<p><code>" + request.getMethod()
432: + "</code> " + "<strong>" + request.getURL()
433: + "</strong>" + ". <p>It is disconnected.");
434: return reply;
435: }
436: }
437: // let's try to get the resource!
438: URL _ru = request.getURL();
439: String requrl = URLUtils.normalize(_ru).toExternalForm();
440: // in the pre-cache, wait for full download
441: // FIXME should be better than this behaviour...
442: // see EntityCachedResource perform's FIXME ;)
443: if (precache.containsKey(requrl)) {
444: if (debug)
445: System.out
446: .println("*** Already downloading: " + requrl);
447: try {
448: CachedResource cr = (CachedResource) precache
449: .get(requrl);
450: return cr.perform(request);
451: } catch (Exception ex) {
452: // there was a problem with the previous request,
453: // it may be better to do it by ourself
454: }
455: }
456:
457: CachedResource res = null;
458: try {
459: res = store.getCachedResourceReference(requrl);
460: } catch (InvalidCacheException ex) {
461: res = null;
462: }
463: // are we disconnected?
464: if (request.checkOnlyIfCached() || !connected) {
465: // and no entries...
466: EntityCachedResource ecr = null;
467: if (res != null) {
468: ecr = (EntityCachedResource) res
469: .lookupResource(request);
470: }
471: if ((res == null) || (ecr == null)) {
472: if (debug)
473: trace(request, "unavailable (disconnected).");
474: Reply reply = request.makeReply(HTTP.GATEWAY_TIMEOUT);
475: reply.setContent("The cache doesn't have an entry for "
476: + "<p><strong>" + request.getURL()
477: + "</strong>" + ". <p>And it is disconnected.");
478: return reply;
479: }
480: // yeah!
481: if (debug) {
482: trace(request, (connected) ? " hit - only if cached"
483: : " hit while disconneced");
484: }
485: if (!validator.isValid(ecr, request)) {
486: addWarning(request, WARN_STALE);
487: }
488: addWarning(request, WARN_DISCONNECTED);
489: Reply reply = ecr.perform(request);
490: // Add any warnings collected during processing to the reply:
491: setWarnings(request, reply);
492: //FIXME request.setState(STATE_HOW, HOW_HIT);
493: return reply;
494: }
495: // in connected mode, we should now take care of revalidation and such
496: if (res != null) {
497: // if not fully loaded, ask for a revalidation FIXME
498: if ((res.getLoadState() == CachedResource.STATE_LOAD_PARTIAL)
499: || (res.getLoadState() == CachedResource.STATE_LOAD_ERROR)) {
500: setRequestRevalidation(res, request);
501: return null;
502: }
503: if (validator.isValid(res, request)) {
504: try {
505: store.updateResourceGeneration(res);
506: } catch (InvalidCacheException ex) {
507: // should be ok so...
508: }
509: //FIXME request.setState(STATE_HOW, HOW_HIT);
510: Reply rep = res.perform(request);
511: return rep;
512: } else {
513: if (debug) {
514: System.out.println("*** Revalidation asked for "
515: + requrl);
516: }
517: // ask for a revalidation
518: setRequestRevalidation(res, request);
519: return null;
520: }
521: }
522: // lock here while we are waiting for the download
523: while (uritable.containsKey(requrl)) {
524: synchronized (uritable) {
525: try {
526: uritable.wait();
527: } catch (InterruptedException ex) {
528: }
529: }
530: if (precache.containsKey(requrl)) {
531: if (debug)
532: System.out.println("*** Already downloading: "
533: + requrl);
534: CachedResource cr = (CachedResource) precache
535: .get(requrl);
536: return cr.perform(request);
537: }
538: uritable.put(requrl, requrl);
539: }
540: return null;
541: }
542:
543: /**
544: * This filter handle exceptions.
545: * @param request The request that triggered the exception.
546: * @param ex The triggered exception.
547: * @return Always <strong>false</strong>.
548: */
549: public boolean exceptionFilter(Request request, HttpException ex) {
550: URL _ru;
551: _ru = request.getURL();
552: String requrl = URLUtils.normalize(_ru).toExternalForm();
553: synchronized (uritable) {
554: uritable.remove(requrl);
555: uritable.notifyAll();
556: }
557: return false;
558: }
559:
560: /**
561: * The request post-processing hook.
562: * After each request has been replied to by the target server (be it a
563: * proxy or the actual origin server), each filter's outgoingFilter
564: * method is called.
565: * <p>It gets the original request, and the actual reply as a parameter,
566: * and should return whatever reply it wants the caller to get.
567: * @param request The original (handled) request.
568: * @param reply The reply, as emited by the target server, or constructed
569: * by some other filter.
570: * @exception HttpException If the reply emitted by the server is not
571: * a valid HTTP reply.
572: */
573: public Reply outgoingFilter(Request request, Reply reply)
574: throws HttpException {
575: URL url = URLUtils.normalize(request.getURL());
576: // Yeah ! We win:
577: CachedResource c;
578: c = (CachedResource) request
579: .getState(CacheState.STATE_RESOURCE);
580: if (c != null) {
581: if (debug)
582: trace(request, "revalidated " + reply.getStatus());
583: // request.setState(STATE_HOW
584: // , ((reply.getStatus() == HTTP.NOT_MODIFIED)
585: // ? HOW_REVALIDATION_SUCCESS
586: // : HOW_REVALIDATION_FAILURE));
587: // Reply valrep = c.validate(request, reply);
588: // Client or Server error, ask for revalidation
589: // if (valrep.getStatus()/100 >= 4) {
590: // c.setWillRevalidate(true);
591: // }
592: if (reply.getStatus() == HTTP.NOT_MODIFIED) {
593: // FIXME revalidateResource(request, reply)
594: validator.revalidateResource(c, request, reply);
595: try {
596: store.storeCachedResource(c, c.getCurrentLength());
597: } catch (InvalidCacheException ex) {
598: if (debug) {
599: ex.printStackTrace();
600: }
601: }
602: // extract the original request
603: Request origreq;
604: origreq = (Request) request
605: .getState(CacheState.STATE_ORIGREQ);
606: return c.perform(origreq);
607: } else {
608: // delete it
609: c.delete();
610: store.getState().notifyResourceDeleted(c);
611: }
612: }
613: // don't use cache, exit asap
614: if (!canCache(request, reply)) {
615: request.setState(STATE_NOCACHE, Boolean.TRUE);
616: if (debug) {
617: System.out.println("*** Can't cache reply");
618: }
619: String requrl = url.toExternalForm();
620: precache.remove(requrl);
621: synchronized (uritable) {
622: uritable.remove(requrl);
623: uritable.notifyAll();
624: }
625: invalidateOnReply(request, reply);
626: return null;
627: }
628: if (!canStore(request, reply)) {
629: // request.setState(STATE_NOSTORE, Boolean.TRUE);
630: if (debug) {
631: System.out.println("*** Can't store reply");
632: }
633: String requrl = url.toExternalForm();
634: precache.remove(requrl);
635: synchronized (uritable) {
636: uritable.remove(requrl);
637: uritable.notifyAll();
638: }
639: invalidateOnReply(request, reply);
640: return null;
641: }
642: pushDocument(request, reply);
643: return null;
644: }
645:
646: private void invalidateOnReply(Request request, Reply reply) {
647: URL url = request.getURL();
648: // now invalidate the entities per rfc2616#13.11
649: if ((request.getMethod() == HTTP.POST)
650: || (request.getMethod() == HTTP.PUT)
651: || (request.getMethod() == HTTP.DELETE)) {
652: String rloc = reply.getLocation();
653: String rcloc = reply.getContentLocation();
654: URL urloc, urcloc;
655: // FIXME use a private method instead of duplicating
656: if (rloc != null) {
657: try {
658: urloc = new URL(rloc);
659: // only if host match
660: if (URLUtils.equalsProtocolHostPort(url, urloc)) {
661: CachedResource res = null;
662: try {
663: res = store
664: .getCachedResourceReference(rloc);
665: if (res != null) {
666: res.setWillRevalidate(true);
667: }
668: } catch (InvalidCacheException ex) {
669: // weird, but it won't stop us :)
670: }
671: }
672: } catch (MalformedURLException mule) {
673: // nothing to do
674: }
675: }
676: if (rcloc != null) {
677: try {
678: urcloc = new URL(url, rcloc);
679: // only if host match
680: if (URLUtils.equalsProtocolHostPort(url, urcloc)) {
681: CachedResource res = null;
682: try {
683: String surcloc = urcloc.toExternalForm();
684: res = store
685: .getCachedResourceReference(surcloc);
686: if (res != null) {
687: res.setWillRevalidate(true);
688: }
689: } catch (InvalidCacheException ex) {
690: // weird, but it won't stop us :)
691: }
692: }
693: } catch (MalformedURLException mule) {
694: // nothing to do
695: }
696: }
697: }
698: }
699:
700: public void sync() {
701: if (debug) {
702: System.out.println("*** Synching the CacheFilter");
703: }
704: try {
705: store.sync();
706: } catch (Exception ex) {
707: System.err.println(getClass().getName()
708: + ": Unable to save cache.");
709: }
710: }
711:
712: /**
713: * Property monitoring for the CacheFilter.
714: * The CacheFilter allows you to dynamically (typically through the
715: * property setter) change the class of the sweeper, the validator,
716: * the size...
717: * @param name The name of the property that has changed.
718: * @return A boolean, <strong>true</strong> if the change was made,
719: * <strong>false</strong> otherwise.
720: */
721: public boolean propertyChanged(String name) {
722: if (name.equals(SERIALIZER_P)) {
723: CacheSerializer cs = null;
724: try {
725: Class c;
726: c = Class.forName(props.getString(name, null));
727: cs = (CacheSerializer) c.newInstance();
728: } catch (Exception ex) {
729: return false;
730: }
731: serializer = cs;
732: return true;
733: } else if (name.equals(SWEEPER_P)) {
734: CacheSweeper cs = null;
735: try {
736: Class c;
737: c = Class.forName(props.getString(name, null));
738: cs = (CacheSweeper) c.newInstance();
739: } catch (Exception ex) {
740: return false;
741: }
742: // looks good, let's restart with this one!
743: sweeper.destroy();
744: sweeper = cs;
745: sweeper.start();
746: return true;
747: } else if (name.equals(VALIDATOR_P)) {
748: CacheValidator cv = null;
749: try {
750: Class c;
751: c = Class.forName(props.getString(name, null));
752: cv = (CacheValidator) c.newInstance();
753: } catch (Exception ex) {
754: return false;
755: }
756: validator = cv;
757: return true;
758: } else if (name.equals(DEBUG_P)) {
759: debug = props.getBoolean(name, debug);
760: return true;
761: } else if (name.equals(SHARED_P)) {
762: shared = props.getBoolean(name, shared);
763: return true;
764: } else if (name.equals(CACHE_CONNECTED_P)) {
765: connected = props.getBoolean(name, true);
766: return true;
767: }
768: // ask the store if it can do something
769: return store.propertyChanged(name);
770: }
771:
772: /**
773: * Push a document in the cache.
774: * The caller has to forge a a request and a reply before being able
775: * to make something
776: * enter the cache.
777: * The request should provide at least:
778: * <dl>
779: * <dt>URL<dl>The URL (key for cache lookups)
780: * <dt>Method<dl>The method that was "applied" to URL to get forged
781: * reply.
782: * </dl>
783: * <p>It is recommended that the reply provides at least
784: * the following informations:
785: * <dl>
786: * <dt>Status Code</dl>A valid HTTP/1.1 status code (probably <strong>
787: * 200</code>)
788: * <dt>InputStream<dl>Containing the entity to be cached,
789: * <dt>EntityTag<dl>A valid entity tag for the document,
790: * <dt>CacheControl<dl>Appropriate HTTP/1.1 cache controls for that
791: * document,
792: * <dt>Mime headers<dl>At least a valid content type, and probably a
793: * content length (to check consistency with the reply body).
794: * </dt>
795: */
796: public void pushDocument(Request request, Reply reply) {
797: URL url = URLUtils.normalize(request.getURL());
798: try {
799: synchronized (uritable) {
800: CachedResource r = null;
801: String requrl = url.toExternalForm();
802: r = CachedResourceFactory.createResource(this , request,
803: reply);
804: if (r.uploading)
805: precache.put(requrl, r);
806: uritable.remove(requrl);
807: uritable.notifyAll();
808: }
809: if (debug)
810: trace(request, "enters cache.");
811: } catch (Exception ex) {
812: ex.printStackTrace();
813: }
814: }
815:
816: /**
817: * do what is needed when an upload is done!
818: * ie: remove from the precache and put in the store
819: * @param the CachedResource to be moved.
820: */
821: protected synchronized void cleanUpload(CachedResource cr) {
822: // FIXME we should check the state!
823: precache.remove(cr.getIdentifier());
824: try {
825: store.storeCachedResource(cr);
826: } catch (InvalidCacheException ex) {
827: if (debug) {
828: ex.printStackTrace();
829: }
830: }
831: }
832:
833: /**
834: * @return the store used a CacheStore
835: */
836: public CacheStore getStore() {
837: return store;
838: }
839:
840: public void initialize(HttpManager manager)
841: throws PropRequestFilterException {
842: String validator_c;
843: String sweeper_c;
844: String serializer_c;
845: props = manager.getProperties();
846:
847: shared = props.getBoolean(SHARED_P, true);
848: connected = props.getBoolean(CACHE_CONNECTED_P, true);
849: debug = props.getBoolean(DEBUG_P, false);
850: // now create the add-on classes
851: validator_c = props.getString(VALIDATOR_P,
852: "org.w3c.www.protocol.http.cache.SimpleCacheValidator");
853: sweeper_c = props.getString(SWEEPER_P,
854: "org.w3c.www.protocol.http.cache.SimpleCacheSweeper");
855: serializer_c = props
856: .getString(SERIALIZER_P,
857: "org.w3c.www.protocol.http.cache.SimpleCacheSerializer");
858: try {
859: Class c;
860: c = Class.forName(validator_c);
861: validator = (CacheValidator) c.newInstance();
862: validator.initialize(this );
863: c = Class.forName(sweeper_c);
864: sweeper = (CacheSweeper) c.newInstance();
865: sweeper.initialize(this );
866: c = Class.forName(serializer_c);
867: serializer = (CacheSerializer) c.newInstance();
868: } catch (Exception ex) {
869: // a fatal error! The cache won't be loaded...
870: ex.printStackTrace();
871: throw new PropRequestFilterException(
872: "Unable to start cache");
873: }
874: // now create the store as we have the basic things here
875: store = new CacheStore();
876: try {
877: store.initialize(this );
878: } catch (InvalidCacheException ex) {
879: // hum no worky, should do some action there!
880: if (debug) {
881: ex.printStackTrace();
882: }
883: }
884: // now start the sweeper
885: sweeper.start();
886: // Start the ActiveStream handler:
887: ActiveStream.initialize();
888: // Register for property changes:
889: props.registerObserver(this );
890: // Now, we are ready, register that filter:
891: manager.setFilter(this);
892: }
893: }
|