001: // EntityCachedResource.java
002: // $Id: EntityCachedResource.java,v 1.37 2003/02/24 14:13:08 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.net.URL;
009:
010: import java.util.Enumeration;
011:
012: import java.io.BufferedInputStream;
013: import java.io.FileInputStream;
014: import java.io.FileOutputStream;
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.OutputStream;
018: import java.io.PrintStream;
019:
020: import org.w3c.util.ArrayDictionary;
021: import org.w3c.tools.resources.Attribute;
022: import org.w3c.tools.resources.AttributeRegistry;
023: import org.w3c.tools.resources.BooleanAttribute;
024: import org.w3c.tools.resources.IntegerAttribute;
025: import org.w3c.tools.resources.LongAttribute;
026: import org.w3c.tools.resources.StringAttribute;
027: import org.w3c.www.http.HeaderDescription;
028: import org.w3c.www.http.HTTP;
029: import org.w3c.www.http.HttpCacheControl;
030: import org.w3c.www.http.HttpContentRange;
031: import org.w3c.www.http.HttpRange;
032: import org.w3c.www.http.HttpEntityTag;
033: import org.w3c.www.http.HttpFactory;
034: import org.w3c.www.http.ByteRangeOutputStream;
035: import org.w3c.www.protocol.http.HttpException;
036: import org.w3c.www.protocol.http.Reply;
037: import org.w3c.www.protocol.http.Request;
038: import org.w3c.www.mime.MimeType;
039: import org.w3c.jigsaw.frames.MimeTypeAttribute;
040:
041: /**
042: * A cached resource with an entity
043: */
044: public class EntityCachedResource extends CachedResource {
045:
046: /**
047: * Condition check return code - Condition existed but failed.
048: */
049: public static final int COND_FAILED = 1;
050: /**
051: * Condition check return code - Condition existed and succeeded.
052: */
053: public static final int COND_OK = 2;
054: /**
055: * Condition check return code - Condition existed and succeeded
056: * but is a weak validation.
057: */
058: public static final int COND_WEAK = 3;
059:
060: /**
061: * Attribute index - The Content-Type of the resource
062: */
063: protected static int ATTR_CONTENT_TYPE = -1;
064: /**
065: * Attribute index - The resource's max age.
066: */
067: protected static int ATTR_FRESHNESS_LIFETIME = -1;
068: /**
069: * Attribute index - The initial age of this resource.
070: */
071: protected static int ATTR_INITIAL_AGE = -1;
072: /**
073: * Attribute index - The response time
074: */
075: protected static int ATTR_RESPONSE_TIME = -1;
076: /**
077: * Attribute index - Revalidate flag
078: */
079: protected static int ATTR_REVALIDATE = -1;
080: /**
081: * Attribute index - The download state
082: */
083: protected static int ATTR_LOAD_STATE = -1;
084:
085: static {
086: Attribute a = null;
087: Class c = null;
088: try {
089: c = Class
090: .forName("org.w3c.www.protocol.http.cache.EntityCachedResource");
091: } catch (Exception ex) {
092: ex.printStackTrace();
093: System.exit(1);
094: }
095: // Declare the contenht type attribute:
096: a = new MimeTypeAttribute("content-type", null,
097: Attribute.COMPUTED);
098: ATTR_CONTENT_TYPE = AttributeRegistry.registerAttribute(c, a);
099: // Declare the max-age (freshness lifetime) value.
100: a = new IntegerAttribute("freshness-lifetime", null,
101: Attribute.COMPUTED);
102: ATTR_FRESHNESS_LIFETIME = AttributeRegistry.registerAttribute(
103: c, a);
104: // Declare the initial age value.
105: a = new IntegerAttribute("initial-age", null,
106: Attribute.COMPUTED);
107: ATTR_INITIAL_AGE = AttributeRegistry.registerAttribute(c, a);
108: // Declare the response time value.
109: a = new LongAttribute("response-time", null, Attribute.COMPUTED);
110: ATTR_RESPONSE_TIME = AttributeRegistry.registerAttribute(c, a);
111: // Declare the response time value.
112: a = new BooleanAttribute("revalidate", Boolean.FALSE,
113: Attribute.COMPUTED);
114: ATTR_REVALIDATE = AttributeRegistry.registerAttribute(c, a);
115: }
116:
117: // some download specific variables
118: protected boolean revalidating = false;
119: protected boolean regetting = false;
120: protected boolean hasEntity = false;
121: protected int oldsize = -1;
122: protected int wantedsize = -1;
123: // our cache filter, if we need to notify it
124: protected CacheFilter filter;
125:
126: /**
127: * Get the Content-Type of the cached resource of <code>null</code> if
128: * there is no mime type (it should NEVER happen!)
129: * @return a MimeType
130: */
131: public MimeType getContentType() {
132: return (MimeType) getValue(ATTR_CONTENT_TYPE, null);
133: }
134:
135: /**
136: * Set the Content-Type of this cached resource
137: * @param a MimeType, the mime type of this resource
138: */
139: public void setContentType(MimeType type) {
140: setValue(ATTR_CONTENT_TYPE, type);
141: }
142:
143: /**
144: * Get this resource's freshness lifetime (RFC2616: 13.2.4).
145: * @return A long number of seconds for which that entry will remain
146: * valid, or <strong>-1</strong> if undefined.
147: */
148: public int getFreshnessLifetime() {
149: return getInt(ATTR_FRESHNESS_LIFETIME, -1);
150: }
151:
152: /**
153: * Set this cached entry . freshness lifetime (RFC2616: 13.2.4).
154: * @param maxage A number of seconds during which the entry will
155: * remain valid, or <strong>-1</strong> to undefine previous setting.
156: */
157: public void setFreshnessLifetime(int freshnessLifetime) {
158: setInt(ATTR_FRESHNESS_LIFETIME, freshnessLifetime);
159: }
160:
161: /**
162: * Get this cached entry initial age.
163: * @return A long number of seconds giving the initial age
164: * or <strong>-1</strong> if undefined.
165: */
166: public int getInitialAge() {
167: return getInt(ATTR_INITIAL_AGE, -1);
168: }
169:
170: /**
171: * Set this resource's initial age.
172: * @param initage The initial age as a number of seconds
173: * or <strong>-1</strong> to undefine previous setting.
174: */
175: public void setInitialAge(int initage) {
176: setInt(ATTR_INITIAL_AGE, initage);
177: }
178:
179: /**
180: * Get the time of the response used to cached that entry.
181: * @return A long number of milliseconds since Java epoch, or <strong>
182: * -1</strong> if undefined.
183: */
184: public long getResponseTime() {
185: return getLong(ATTR_RESPONSE_TIME, -1);
186: }
187:
188: /**
189: * Set this cached entry response time.
190: * @param responsetime A long number of milliseconds indicating the
191: * response time relative to Java epoch, or <strong>-1</strong> to
192: * undefined previous setting.
193: */
194: public void setResponseTime(long responsetime) {
195: setLong(ATTR_RESPONSE_TIME, responsetime);
196: }
197:
198: /**
199: * Get the revalidate flag
200: * @return a boolean, <code>true</code> if the proxy must revalidate
201: * stale entries
202: * -1</strong> if undefined.
203: */
204: public boolean getRevalidate() {
205: return getBoolean(ATTR_REVALIDATE, false);
206: }
207:
208: /**
209: * Set this cached entry revalidate flag.
210: * @param validate, a boolean, <code>true</code> if this entry needs
211: * to be revalidated while stale.
212: */
213: public void setRevalidate(boolean validate) {
214: setBoolean(ATTR_REVALIDATE, validate);
215: }
216:
217: /**
218: * Get the entity tag associated with that cached entry
219: * @return the entity tag or <strong>null</strong> if undefined
220: */
221: public HttpEntityTag getHETag() {
222: if (definesAttribute(ATTR_ETAG)) {
223: if (etags == null) {
224: etags = new HttpEntityTag[1];
225: etags[0] = HttpFactory.parseETag(getETag());
226: }
227: return etags[0];
228: }
229: return null;
230: }
231:
232: // FIXME add entity tag here
233:
234: // end of the basic accessors
235:
236: /**
237: * Get the cached data for that cached entry.
238: * @return A <em>non-buffered</em> output stream.
239: */
240: public synchronized InputStream getInputStream() throws IOException {
241: return new BufferedInputStream(new FileInputStream(getFile()));
242: }
243:
244: /**
245: * Get the current age of this resource
246: * @return a long the current age of this resource
247: */
248: public int getCurrentAge() {
249: long now = System.currentTimeMillis();
250: // RFC2616: 13.2.3 Age Calculation
251: return (int) (getInitialAge() + ((now - getResponseTime()) / 1000));
252: }
253:
254: /**
255: * Try to validate an <code>If-Modified-Since</code> request.
256: * @param request The request to validate.
257: * @return An integer, <code>COND_FAILED</code>, if the condition was
258: * checked, but failed; <code>COND_OK</code> of condition was checked
259: * and succeeded, <strong>0</strong> otherwise.
260: */
261:
262: public int checkIfModifiedSince(Request request) {
263: // Check for an If-Modified-Since conditional:
264: long ims = request.getIfModifiedSince();
265: long cmt = getLastModified();
266: if (ims >= 0) {
267: if (cmt > 0) {
268: long s_cmt = cmt / 1000;
269: long s_ims = ims / 1000;
270: if (s_cmt < s_ims) {
271: return COND_FAILED;
272: } else if (s_cmt == s_ims) {
273: return COND_WEAK;
274: }
275: return COND_OK;
276: }
277: }
278: return 0;
279: }
280:
281: /**
282: * Try to validate an <code>If-Unmodified-Since</code> request.
283: * @param request The request to validate.
284: * @return An integer, <code>COND_FAILED</code>, if the condition was
285: * checked, but failed; <code>COND_OK</code> of condition was checked
286: * and succeeded, <strong>0</strong> otherwise.
287: */
288:
289: public int checkIfUnmodifiedSince(Request request) {
290: // Check for an If-Unmodified-Since conditional:
291: long iums = request.getIfUnmodifiedSince();
292: long cmt = getLastModified();
293: if (iums >= 0)
294: return ((cmt > 0) && (cmt - 1000) >= iums) ? COND_FAILED
295: : COND_OK;
296: return 0;
297: }
298:
299: /**
300: * Try to validate an <code>If-Match</code> request.
301: * @param request The request to validate.
302: * @return An integer, <code>COND_FAILED</code>, if the condition was
303: * checked, but failed; <code>COND_OK</code> of condition was checked
304: * and succeeded, <strong>0</strong> otherwise.
305: */
306:
307: public int checkIfMatch(Request request) {
308: HttpEntityTag tags[] = request.getIfMatch();
309: if (tags != null) {
310: HttpEntityTag etag = getHETag();
311: // Good, real validators in use:
312: if (etag != null) {
313: for (int i = 0; i < tags.length; i++) {
314: HttpEntityTag t = tags[i];
315: if (t.getTag().equals(etag.getTag())) {
316: if (t.isWeak() || etag.isWeak()) {
317: return COND_WEAK;
318: } else {
319: return COND_OK;
320: }
321: }
322: }
323: }
324: return COND_FAILED;
325: }
326: return 0;
327: }
328:
329: /**
330: * Try to validate an <code>If-None-Match</code> request.
331: * @param request The request to validate.
332: * @return An integer, <code>COND_FAILED</code>, if the condition was
333: * checked, but failed; <code>COND_OK</code> of condition was checked
334: * and succeeded, <strong>0</strong> otherwise.
335: */
336:
337: public int checkIfNoneMatch(Request request) {
338: String setag = getETag();
339: HttpEntityTag etag = null;
340: // Check for an If-None-Match conditional:
341: HttpEntityTag tags[] = request.getIfNoneMatch();
342: if (setag != null) {
343: etag = HttpFactory.parseETag(getETag());
344: }
345: if (tags != null) {
346: if (etag == null) {
347: return COND_OK;
348: }
349: int status = COND_OK;
350: for (int i = 0; i < tags.length; i++) {
351: HttpEntityTag t = tags[i];
352: if (t.getTag().equals(etag.getTag())) {
353: // if (t.isWeak() && !etag.isWeak()) {
354: if (t.isWeak() || etag.isWeak()) {
355: status = COND_WEAK;
356: } else {
357: return COND_FAILED;
358: }
359: }
360: if (t.getTag().equals("*")) {
361: return COND_FAILED;
362: }
363: }
364: return status;
365: }
366: return 0;
367: }
368:
369: /**
370: * Called when the tee succeed, it allows you to notify a listener of the
371: * Tee that the download completed succesfully with a specific size
372: * @parameter the size received, an integer
373: */
374: public synchronized void notifyTeeSuccess(int size) {
375: int state = getLoadState();
376: try {
377: if (wantedsize > 0) {
378: if (!regetting) {
379: // sanity check
380: if (state == STATE_NOT_LOADED) {
381: if (size == wantedsize) {
382: // cool! the right size and it was the first
383: // download!
384: setCurrentLength(size);
385: setLoadState(STATE_LOAD_COMPLETE);
386: } else {
387: // argh! wrong size and a success hum...
388: setCurrentLength(size);
389: setLoadState(STATE_LOAD_ERROR);
390: System.out.println(getIdentifier()
391: + ": tee stream mismatch, "
392: + "bytes(adv/got)=" + wantedsize
393: + "/" + size);
394: }
395: } else {
396: // how can we end up here, I frankly don't know
397: setCurrentLength(size);
398: setLoadState(STATE_LOAD_ERROR);
399: System.out.println(getIdentifier()
400: + ": UNKNOWN STATE for "
401: + "tee stream!, bytes(adv/got)="
402: + wantedsize + "/" + size);
403: }
404: } else {
405: // we asked for the diff, and we have it!!!
406: if (size == wantedsize) {
407: setCurrentLength(oldsize + wantedsize);
408: setLoadState(STATE_LOAD_COMPLETE);
409: } else {
410: // argh! wrong size and a success hum...
411: setCurrentLength(size);
412: setLoadState(STATE_LOAD_ERROR);
413: System.out.println(getIdentifier()
414: + ": tee stream mismatch in reget, "
415: + "bytes(adv/got)=" + wantedsize + "/"
416: + size);
417: }
418: }
419: } else {
420: // we didn't knew the size, we should trust what we got
421: // (unless it is HTTP/1.0)
422: setCurrentLength(size);
423: // FIXME (a trust flag to select btw COMPLETE and UNKNOWN
424: setLoadState(STATE_LOAD_COMPLETE);
425: }
426: // Update cache filter space usage:
427: // filter.markUsed(this, oldsize, wantedsize);
428:
429: } finally {
430: cleanUpload();
431: }
432: }
433:
434: public void notifyTeeFailure(int size) {
435: System.out
436: .println(getIdentifier() + ": tee streaming failed !");
437: int state = getLoadState();
438:
439: setCurrentLength(size);
440: setLoadState(STATE_LOAD_ERROR);
441: System.out.println(getIdentifier() + ": tee stream mismatch, "
442: + "bytes(adv/got)=" + wantedsize + "/" + size);
443: // and finish the thing!
444: cleanUpload();
445: }
446:
447: // FIXME should be called after every upload
448: protected synchronized void cleanUpload() {
449: // FIXME reset a whole bunch of stuff
450: uploading = false;
451: filter.cleanUpload(this );
452: notifyAll();
453: }
454:
455: /**
456: * FIXME Will be replaced soon, so that multiple people may share
457: * the same temporary resource.
458: * Wait for the upload to finish, if needed.
459: */
460: protected final synchronized void waitUpload() {
461: while (uploading) {
462: try {
463: wait();
464: } catch (InterruptedException ex) {
465: }
466: }
467: }
468:
469: /**
470: * handle a range request, according to the first range or the
471: * request FIXME we should handle all the ranges at some point...
472: */
473: protected Reply handleRangeRequest(Request request, HttpRange r) {
474: // Should we check against a IfRange header ?
475: HttpEntityTag t = request.getIfRange();
476: if (t != null) {
477: if (t.isWeak() || !t.getTag().equals(getHETag().getTag()))
478: return null;
479: }
480: // Check the range:
481: int cl = getContentLength();
482: int fb = r.getFirstPosition();
483: int lb = r.getLastPosition();
484: int sz;
485: if (fb > cl - 1) { // first byte already out of range
486: HttpContentRange cr = HttpFactory.makeContentRange("bytes",
487: 0, cl - 1, cl);
488: Reply rr;
489: rr = request
490: .makeReply(HTTP.REQUESTED_RANGE_NOT_SATISFIABLE);
491: rr.setContentLength(-1);
492: rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);
493: rr.setContentMD5(null);
494: return rr;
495: }
496: if ((fb < 0) && (lb >= 0)) { // ex: bytes=-20 final 20 bytes
497: if (lb >= cl) // cut the end
498: lb = cl;
499: sz = lb;
500: fb = cl - lb;
501: lb = cl - 1;
502: } else if (lb < 0) { // ex: bytes=10- the last size - 10
503: lb = cl - 1;
504: sz = lb - fb + 1;
505: } else { // ex: bytes=10-20
506: if (lb >= cl) // cut the end
507: lb = cl - 1;
508: sz = lb - fb + 1;
509: }
510: if ((fb < 0) || (lb < 0) || (fb <= lb)) {
511: HttpContentRange cr = null;
512: fb = (fb < 0) ? 0 : fb;
513: lb = ((lb > cl) || (lb < 0)) ? cl : lb;
514: cr = HttpFactory.makeContentRange("bytes", fb, lb, cl);
515: // Emit reply:
516: Reply rr = request.makeReply(HTTP.PARTIAL_CONTENT);
517: try {
518: rr.setContentMD5(null); // just in case :)
519: rr.setContentLength(sz);
520: rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);
521: rr.setStream(new ByteRangeOutputStream(getFile(), fb,
522: lb + 1));
523: return rr;
524: } catch (IOException ex) {
525: }
526: }
527: return null;
528: }
529:
530: /**
531: * decorate the reply header with some meta information taken
532: * from the cached resource
533: * @return a reply, the one we just updated
534: */
535: protected Reply setReplyHeaders(Reply reply) {
536: int status = reply.getStatus();
537: if (status != HTTP.NOT_MODIFIED) {
538: // FIXME check for byte range replies
539: reply.setContentLength(getContentLength());
540: // dump the headers we know.
541: reply.setContentMD5(getContentMD5());
542: reply.setContentLanguage(getContentLanguage());
543: reply.setContentEncoding(getContentEncoding());
544: reply.setContentType(getContentType());
545: reply.setLastModified(getLastModified());
546: reply.setVary(getVary());
547: }
548: reply.setETag(getHETag());
549: long date = getDate();
550: if (date > 0)
551: reply.setDate(getDate());
552: reply.setAge(getCurrentAge());
553: ArrayDictionary a = getExtraHeaders();
554: if (a != null) {
555: // This is the slowest operation of the whole cache :-(
556: Enumeration e = a.keys();
557: while (e.hasMoreElements()) {
558: String hname = (String) e.nextElement();
559: String hvalue = (String) a.get(hname);
560: reply.setValue(hname, hvalue);
561: }
562: }
563: if ((filter != null) && filter.isShared()) {
564: HttpCacheControl hcc = reply.getCacheControl();
565: if (hcc != null) {
566: String priv[] = hcc.getPrivate();
567: if (priv != null) {
568: for (int i = 0; i < priv.length; i++) {
569: // remove headers that are private if we are
570: // a shared cache (rfc2616#14.9, rfc2616#14.9.1)
571: reply.setHeaderValue(priv[i], null);
572: }
573: }
574: }
575: }
576: return reply;
577: }
578:
579: /**
580: * check the validators namely LMT/Etags according to rfc2616 rules
581: * @return An integer, either <code>COND_FAILED</cond> if condition
582: * was checked, but failed, <code>COND_OK</code> if condition was checked
583: * and succeeded, or <strong>0</strong> if the condition was not checked
584: * at all (eg because the resource or the request didn't support it).
585: */
586: public int checkValidators(Request request) {
587: int v_inm = checkIfNoneMatch(request);
588: int v_ims = checkIfModifiedSince(request);
589:
590: if ((v_inm == COND_OK) || (v_ims == COND_OK)) {
591: return COND_OK;
592: }
593: if ((v_inm == COND_FAILED) || (v_ims == COND_FAILED)) {
594: return COND_FAILED;
595: }
596: if ((v_inm == COND_WEAK) || (v_ims == COND_WEAK)) {
597: return COND_FAILED;
598: }
599: return 0;
600: }
601:
602: /**
603: * This cached entry has been checked valid, perform given request.
604: * @param request The request to perform.
605: * @return An Reply instance.
606: * @exception HttpException If something went wrong.
607: */
608: public Reply perform(Request request) throws HttpException {
609: // If the resource is currently being uploaded, wait:
610: waitUpload();
611: // Now perform the request:
612: try {
613: Reply reply = null;
614: boolean needsEntity = true;
615: // Handle range requests:
616: HttpRange ranges[] = request.getRange();
617: if ((ranges != null) && (ranges.length == 1))
618: reply = handleRangeRequest(request, ranges[0]);
619: // Handle full retreivals:
620: if (reply == null) {
621: int status = getStatus();
622: // Try validating first
623: // NOTE: We know we are only dealing with GETs and HEADs here
624: // otherwise the cache wouldn't be used...
625: int cim = checkIfMatch(request);
626: if ((cim == COND_FAILED) || (cim == COND_WEAK)) {
627: status = HTTP.PRECONDITION_FAILED;
628: needsEntity = false;
629: reply = request.makeReply(status);
630: reply.setContent("Pre-conditions failed.");
631: throw new HttpException(request, reply,
632: "pre-condition");
633: } else if (checkIfUnmodifiedSince(request) == COND_FAILED) {
634: status = HTTP.PRECONDITION_FAILED;
635: reply = request.makeReply(status);
636: reply.setContent("Pre-conditions failed.");
637: throw new HttpException(request, reply,
638: "pre-condition");
639: } else if (checkValidators(request) == COND_FAILED) {
640: status = HTTP.NOT_MODIFIED;
641: needsEntity = false;
642: }
643: // Emit reply:
644: reply = request.makeReply(status);
645: if (needsEntity) {
646: reply.setStream(getInputStream());
647: }
648: }
649: setReplyHeaders(reply);
650: // Check if entity is needed:
651: String mth = request.getMethod();
652: if (mth.equals("HEAD") || mth.equals("OPTIONS"))
653: reply.setStream(null);
654: // filter.markUsed(this);
655: return reply;
656: } catch (IOException ex) {
657: // if (debug)
658: // ex.printStackTrace();
659: // Some exception occured, delete that resource (no longer usefull)
660: // delete();
661: }
662: return null;
663: }
664:
665: /**
666: * Try using an active stream to cache the content.
667: * Byte size usage is taken care of only at the end of the download
668: * to make sure we get the right sizes (might different from the
669: * advertized ones).
670: * @return An InputStream instance if active caching was possible,
671: * <strong>null</strong> otherwise.
672: */
673: public synchronized InputStream tryActiveCacheContent(InputStream in)
674: throws IOException {
675: // If we don't return null, we *are* responsible for cleaning up
676: // the upload *whatever* happens ...
677: InputStream tee = null;
678: OutputStream out = null;
679: uploading = true;
680: // Open the output stream:
681: try {
682: out = new FileOutputStream(getFile());
683: } catch (IOException ex) {
684: // if (debug)
685: ex.printStackTrace();
686: // We'll let cacheContent take care of that situation:
687: return null;
688: }
689: // We might be able to use active streams:
690: // if (upnewsize > ACTIVE_STREAM_THRESOLD ) {
691: tee = ActiveStream.createTee(this , in, out);
692: if (tee != null)
693: return tee;
694: // }
695: // We were not able to active stream:
696: try {
697: out.close();
698: } catch (IOException ex) {
699: }
700: return null;
701: }
702:
703: /**
704: * The basic initialization
705: */
706: public void initialize(Object values[]) {
707: super .initialize(values);
708: }
709:
710: /**
711: * sets some useful information about the entity
712: * @param the request that requested this entity
713: * @param the reply triggered by this request
714: */
715: protected void updateInfo(Request request, Reply rep) {
716: String mth = request.getMethod();
717: Reply reply = (Reply) rep.getClone();
718: boolean hasEntity = !(mth.equals("HEAD") || mth
719: .equals("OPTIONS"));
720: // is it a revalidation?
721: if (!request.hasState(CacheState.STATE_REVALIDATION)) {
722: // no, go for it!
723: HttpCacheControl hcc = reply.getCacheControl();
724: // first we should NOT cache headers protected by a no-cache
725: // per rfc2616@14.9
726: if (hcc != null) {
727: String nocache[] = hcc.getNoCache();
728: if (nocache != null) {
729: for (int i = 0; i < nocache.length; i++) {
730: reply.setHeaderValue(nocache[i], null);
731: }
732: }
733: }
734: setStatus(reply.getStatus());
735: setContentType(reply.getContentType());
736: setContentLength(reply.getContentLength());
737: setLastModified(reply.getLastModified());
738: setContentMD5(reply.getContentMD5());
739: String vary[] = reply.getVary();
740: setVary(vary);
741: if (vary != null) {
742: // update the conneg headers
743: ArrayDictionary a = null;
744: for (int i = 0; i < vary.length; i++) {
745: if (vary[i].equals("*")) {
746: continue;
747: }
748: if (a == null) {
749: a = new ArrayDictionary(vary.length);
750: }
751: a.put(vary[i].toLowerCase(), request
752: .getValue(vary[i]));
753: }
754: // FIXME we should be able to update to save multiple
755: // matches, but with a limitation of course
756: if (a != null) {
757: setConnegHeaders(a);
758: }
759: }
760: if (reply.hasHeader(reply.H_ETAG)) {
761: setETag(reply.getETag().toString());
762: } else {
763: // be safe here!
764: setETag(null);
765: }
766: ArrayDictionary a = new ArrayDictionary(5, 5);
767: Enumeration e = reply.enumerateHeaderDescriptions();
768: while (e.hasMoreElements()) {
769: HeaderDescription d = (HeaderDescription) e
770: .nextElement();
771: // Skip all well-known headers:
772: if (d.isHeader(Reply.H_CONTENT_TYPE)
773: || d.isHeader(Reply.H_CONTENT_LENGTH)
774: || d.isHeader(Reply.H_LAST_MODIFIED)
775: || d.isHeader(Reply.H_ETAG)
776: || d.isHeader(Reply.H_AGE)
777: || d.isHeader(Reply.H_DATE)
778: || d.isHeader(Reply.H_VARY)
779: || d.isHeader(Reply.H_CONNECTION)
780: || d.isHeader(Reply.H_PROXY_CONNECTION)
781: || d.isHeader(Reply.H_TRANSFER_ENCODING)
782: || d.isHeader(Reply.H_CONTENT_MD5)
783: || d.getName().equalsIgnoreCase("keep-alive"))
784: continue;
785: // This is an extra header:
786: a.put(d.getName(), reply.getValue(d));
787: }
788: setExtraHeaders(a);
789: // FIXME add the headers ;)
790:
791: }
792: }
793:
794: /**
795: * This cached entry needs revalidation, it will modify the
796: * request to do that.
797: */
798: public Request setRequestRevalidation(Request request) {
799: Request origreq = (Request) request.getClone();
800: request.setState(CacheState.STATE_RESOURCE, this );
801: request.setState(CacheState.STATE_ORIGREQ, origreq);
802: // At this point, we use the suggested way of using date as etag:
803: request.setIfModifiedSince(getLastModified());
804: // But if we do have an etag, we also uses it, as recommended:
805: if ((etags == null) && (getETag() != null)) {
806: etags = new HttpEntityTag[1];
807: etags[0] = HttpFactory.parseETag(getETag());
808: }
809: request.setIfNoneMatch(etags);
810: // We have to remove all other conditionals here:
811: request.setIfRange(null);
812: request.setRange(null);
813: request.setIfUnmodifiedSince(-1);
814: request.setIfMatch(null);
815: return request;
816: }
817:
818: /**
819: * A constructor for new resources that will get some data
820: * directly
821: * FIXME params
822: */
823: public EntityCachedResource(CacheFilter filter, Request req,
824: Reply rep) {
825: invalidated = false;
826: setValue(ATTR_IDENTIFIER, req.getURL().toExternalForm());
827: // Keep fast track of the filter:
828: this .filter = filter;
829: // update the headers
830: updateInfo(req, rep);
831: // and do some calculation according to the validator
832: filter.getValidator().updateExpirationInfo(this , req, rep);
833: // Save the content of resource into the content cache:
834: setFile(filter.getStore().getNewEntryFile());
835: wantedsize = rep.getContentLength();
836: InputStream in;
837: try {
838: in = tryActiveCacheContent(rep.getInputStream());
839: if (in == null) {
840: // something bad happened
841: // in = cacheContent(reply.getInputStream());
842: }
843: rep.setStream(in);
844: } catch (IOException ex) {
845: // FIXME
846: }
847: }
848:
849: public EntityCachedResource() {
850: super();
851: }
852: }
|