--- /dev/null
+// EntityCachedResource.java\r
+// $Id: EntityCachedResource.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.net.URL;\r
+\r
+import java.util.Enumeration;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.io.PrintStream;\r
+\r
+import org.w3c.util.ArrayDictionary;\r
+import org.w3c.tools.resources.Attribute;\r
+import org.w3c.tools.resources.AttributeRegistry;\r
+import org.w3c.tools.resources.BooleanAttribute;\r
+import org.w3c.tools.resources.IntegerAttribute;\r
+import org.w3c.tools.resources.LongAttribute;\r
+import org.w3c.tools.resources.StringAttribute;\r
+import org.w3c.www.http.HeaderDescription;\r
+import org.w3c.www.http.HTTP;\r
+import org.w3c.www.http.HttpCacheControl;\r
+import org.w3c.www.http.HttpContentRange;\r
+import org.w3c.www.http.HttpRange;\r
+import org.w3c.www.http.HttpEntityTag;\r
+import org.w3c.www.http.HttpFactory;\r
+import org.w3c.www.http.ByteRangeOutputStream;\r
+import org.w3c.www.protocol.http.HttpException;\r
+import org.w3c.www.protocol.http.Reply;\r
+import org.w3c.www.protocol.http.Request;\r
+import org.w3c.www.mime.MimeType;\r
+import org.w3c.jigsaw.frames.MimeTypeAttribute;\r
+\r
+/**\r
+ * A cached resource with an entity\r
+ */\r
+public class EntityCachedResource extends CachedResource {\r
+\r
+ /**\r
+ * Condition check return code - Condition existed but failed.\r
+ */\r
+ public static final int COND_FAILED = 1;\r
+ /**\r
+ * Condition check return code - Condition existed and succeeded.\r
+ */\r
+ public static final int COND_OK = 2; \r
+ /**\r
+ * Condition check return code - Condition existed and succeeded \r
+ * but is a weak validation.\r
+ */\r
+ public static final int COND_WEAK = 3; \r
+\r
+ /**\r
+ * Attribute index - The Content-Type of the resource\r
+ */\r
+ protected static int ATTR_CONTENT_TYPE = -1;\r
+ /**\r
+ * Attribute index - The resource's max age.\r
+ */\r
+ protected static int ATTR_FRESHNESS_LIFETIME = -1;\r
+ /**\r
+ * Attribute index - The initial age of this resource.\r
+ */\r
+ protected static int ATTR_INITIAL_AGE = -1;\r
+ /**\r
+ * Attribute index - The response time\r
+ */\r
+ protected static int ATTR_RESPONSE_TIME = -1; \r
+ /**\r
+ * Attribute index - Revalidate flag\r
+ */\r
+ protected static int ATTR_REVALIDATE = -1; \r
+ /**\r
+ * Attribute index - The download state\r
+ */\r
+ protected static int ATTR_LOAD_STATE = -1; \r
+\r
+ static {\r
+ Attribute a = null;\r
+ Class c = null;\r
+ try {\r
+ c = Class.forName(\r
+ "org.w3c.www.protocol.http.cache.EntityCachedResource");\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ } catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ System.exit(1);\r
+ }\r
+ // Declare the contenht type attribute:\r
+ a = new MimeTypeAttribute("content-type"\r
+ , null\r
+ , Attribute.COMPUTED);\r
+ ATTR_CONTENT_TYPE = AttributeRegistry.registerAttribute(c, a);\r
+ // Declare the max-age (freshness lifetime) value.\r
+ a = new IntegerAttribute("freshness-lifetime"\r
+ , null\r
+ , Attribute.COMPUTED);\r
+ ATTR_FRESHNESS_LIFETIME = AttributeRegistry.registerAttribute(c, a);\r
+ // Declare the initial age value.\r
+ a = new IntegerAttribute("initial-age"\r
+ , null\r
+ , Attribute.COMPUTED);\r
+ ATTR_INITIAL_AGE = AttributeRegistry.registerAttribute(c, a);\r
+ // Declare the response time value.\r
+ a = new LongAttribute("response-time"\r
+ , null\r
+ , Attribute.COMPUTED);\r
+ ATTR_RESPONSE_TIME = AttributeRegistry.registerAttribute(c, a);\r
+ // Declare the response time value.\r
+ a = new BooleanAttribute("revalidate"\r
+ , Boolean.FALSE\r
+ , Attribute.COMPUTED);\r
+ ATTR_REVALIDATE = AttributeRegistry.registerAttribute(c, a); \r
+ }\r
+\r
+ // some download specific variables\r
+ protected boolean revalidating = false;\r
+ protected boolean regetting = false;\r
+ protected boolean hasEntity = false;\r
+ protected int oldsize = -1;\r
+ protected int wantedsize = -1;\r
+ // our cache filter, if we need to notify it\r
+ protected CacheFilter filter;\r
+\r
+ /**\r
+ * Get the Content-Type of the cached resource of <code>null</code> if\r
+ * there is no mime type (it should NEVER happen!)\r
+ * @return a MimeType\r
+ */\r
+ public MimeType getContentType() { \r
+ return (MimeType) getValue(ATTR_CONTENT_TYPE, null);\r
+ }\r
+\r
+ /**\r
+ * Set the Content-Type of this cached resource\r
+ * @param a MimeType, the mime type of this resource\r
+ */\r
+ public void setContentType(MimeType type) {\r
+ setValue(ATTR_CONTENT_TYPE, type);\r
+ }\r
+\r
+ /**\r
+ * Get this resource's freshness lifetime (RFC2616: 13.2.4).\r
+ * @return A long number of seconds for which that entry will remain\r
+ * valid, or <strong>-1</strong> if undefined.\r
+ */\r
+ public int getFreshnessLifetime() {\r
+ return getInt(ATTR_FRESHNESS_LIFETIME, -1);\r
+ }\r
+\r
+ /**\r
+ * Set this cached entry . freshness lifetime (RFC2616: 13.2.4).\r
+ * @param maxage A number of seconds during which the entry will \r
+ * remain valid, or <strong>-1</strong> to undefine previous setting.\r
+ */\r
+ public void setFreshnessLifetime(int freshnessLifetime) {\r
+ setInt(ATTR_FRESHNESS_LIFETIME, freshnessLifetime);\r
+ }\r
+\r
+ /**\r
+ * Get this cached entry initial age.\r
+ * @return A long number of seconds giving the initial age\r
+ * or <strong>-1</strong> if undefined.\r
+ */\r
+ public int getInitialAge() {\r
+ return getInt(ATTR_INITIAL_AGE, -1);\r
+ }\r
+\r
+ /**\r
+ * Set this resource's initial age.\r
+ * @param initage The initial age as a number of seconds\r
+ * or <strong>-1</strong> to undefine previous setting.\r
+ */\r
+ public void setInitialAge(int initage) {\r
+ setInt(ATTR_INITIAL_AGE, initage);\r
+ }\r
+\r
+ /**\r
+ * Get the time of the response used to cached that entry.\r
+ * @return A long number of milliseconds since Java epoch, or <strong>\r
+ * -1</strong> if undefined.\r
+ */\r
+ public long getResponseTime() {\r
+ return getLong(ATTR_RESPONSE_TIME, -1);\r
+ }\r
+\r
+ /**\r
+ * Set this cached entry response time.\r
+ * @param responsetime A long number of milliseconds indicating the \r
+ * response time relative to Java epoch, or <strong>-1</strong> to \r
+ * undefined previous setting.\r
+ */\r
+ public void setResponseTime(long responsetime) {\r
+ setLong(ATTR_RESPONSE_TIME, responsetime);\r
+ }\r
+\r
+ /**\r
+ * Get the revalidate flag\r
+ * @return a boolean, <code>true</code> if the proxy must revalidate\r
+ * stale entries\r
+ * -1</strong> if undefined.\r
+ */\r
+ public boolean getRevalidate() {\r
+ return getBoolean(ATTR_REVALIDATE, false);\r
+ }\r
+\r
+ /**\r
+ * Set this cached entry revalidate flag.\r
+ * @param validate, a boolean, <code>true</code> if this entry needs\r
+ * to be revalidated while stale.\r
+ */\r
+ public void setRevalidate(boolean validate) {\r
+ setBoolean(ATTR_REVALIDATE, validate);\r
+ }\r
+\r
+ /**\r
+ * Get the entity tag associated with that cached entry\r
+ * @return the entity tag or <strong>null</strong> if undefined\r
+ */\r
+ public HttpEntityTag getHETag() {\r
+ if (definesAttribute(ATTR_ETAG)) {\r
+ if (etags == null) {\r
+ etags = new HttpEntityTag[1];\r
+ etags[0] = HttpFactory.parseETag(getETag());\r
+ }\r
+ return etags[0];\r
+ }\r
+ return null;\r
+ }\r
+ // FIXME add entity tag here\r
+\r
+ // end of the basic accessors\r
+\r
+ /**\r
+ * Get the cached data for that cached entry.\r
+ * @return A <em>non-buffered</em> output stream.\r
+ */\r
+ public synchronized InputStream getInputStream() \r
+ throws IOException\r
+ {\r
+ return new BufferedInputStream(new FileInputStream(getFile()));\r
+ }\r
+\r
+ /**\r
+ * Get the current age of this resource\r
+ * @return a long the current age of this resource\r
+ */\r
+ public int getCurrentAge() {\r
+ long now = System.currentTimeMillis();\r
+ // RFC2616: 13.2.3 Age Calculation\r
+ return (int) (getInitialAge() + ((now - getResponseTime()) / 1000));\r
+ }\r
+\r
+ /**\r
+ * Try to validate an <code>If-Modified-Since</code> request.\r
+ * @param request The request to validate.\r
+ * @return An integer, <code>COND_FAILED</code>, if the condition was\r
+ * checked, but failed; <code>COND_OK</code> of condition was checked\r
+ * and succeeded, <strong>0</strong> otherwise.\r
+ */\r
+\r
+ public int checkIfModifiedSince(Request request) {\r
+ // Check for an If-Modified-Since conditional:\r
+ long ims = request.getIfModifiedSince();\r
+ long cmt = getLastModified();\r
+ if (ims >= 0) {\r
+ if (cmt > 0) {\r
+ long s_cmt = cmt / 1000;\r
+ long s_ims = ims / 1000;\r
+ if (s_cmt < s_ims) {\r
+ return COND_FAILED;\r
+ } else if (s_cmt == s_ims) {\r
+ return COND_WEAK;\r
+ }\r
+ return COND_OK;\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /**\r
+ * Try to validate an <code>If-Unmodified-Since</code> request.\r
+ * @param request The request to validate.\r
+ * @return An integer, <code>COND_FAILED</code>, if the condition was\r
+ * checked, but failed; <code>COND_OK</code> of condition was checked\r
+ * and succeeded, <strong>0</strong> otherwise.\r
+ */\r
+\r
+ public int checkIfUnmodifiedSince(Request request) {\r
+ // Check for an If-Unmodified-Since conditional:\r
+ long iums = request.getIfUnmodifiedSince();\r
+ long cmt = getLastModified();\r
+ if ( iums >= 0 ) \r
+ return ((cmt > 0) && (cmt - 1000) >= iums) ? COND_FAILED : COND_OK;\r
+ return 0;\r
+ }\r
+\r
+ /**\r
+ * Try to validate an <code>If-Match</code> request.\r
+ * @param request The request to validate.\r
+ * @return An integer, <code>COND_FAILED</code>, if the condition was\r
+ * checked, but failed; <code>COND_OK</code> of condition was checked\r
+ * and succeeded, <strong>0</strong> otherwise.\r
+ */\r
+\r
+ public int checkIfMatch(Request request) {\r
+ HttpEntityTag tags[] = request.getIfMatch();\r
+ if ( tags != null ) {\r
+ HttpEntityTag etag = getHETag();\r
+ // Good, real validators in use:\r
+ if ( etag != null ) {\r
+ for (int i = 0 ; i < tags.length ; i++) {\r
+ HttpEntityTag t = tags[i];\r
+ if (t.getTag().equals(etag.getTag())) {\r
+ if (t.isWeak() || etag.isWeak()) {\r
+ return COND_WEAK;\r
+ } else {\r
+ return COND_OK;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return COND_FAILED;\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /**\r
+ * Try to validate an <code>If-None-Match</code> request.\r
+ * @param request The request to validate.\r
+ * @return An integer, <code>COND_FAILED</code>, if the condition was\r
+ * checked, but failed; <code>COND_OK</code> of condition was checked\r
+ * and succeeded, <strong>0</strong> otherwise.\r
+ */\r
+\r
+ public int checkIfNoneMatch(Request request) {\r
+ String setag = getETag();\r
+ HttpEntityTag etag = null;\r
+ // Check for an If-None-Match conditional:\r
+ HttpEntityTag tags[] = request.getIfNoneMatch();\r
+ if (setag != null) {\r
+ etag = HttpFactory.parseETag(getETag());\r
+ }\r
+ if ( tags != null ) {\r
+ if ( etag == null ) {\r
+ return COND_OK;\r
+ }\r
+ int status = COND_OK;\r
+ for (int i = 0 ; i < tags.length ; i++) {\r
+ HttpEntityTag t = tags[i];\r
+ if (t.getTag().equals(etag.getTag())) {\r
+// if (t.isWeak() && !etag.isWeak()) {\r
+ if (t.isWeak() || etag.isWeak()) {\r
+ status = COND_WEAK;\r
+ } else {\r
+ return COND_FAILED;\r
+ }\r
+ }\r
+ if (t.getTag().equals("*")) {\r
+ return COND_FAILED;\r
+ }\r
+ }\r
+ return status;\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /**\r
+ * Called when the tee succeed, it allows you to notify a listener of the \r
+ * Tee that the download completed succesfully with a specific size\r
+ * @parameter the size received, an integer\r
+ */\r
+ public synchronized void notifyTeeSuccess(int size) {\r
+ int state = getLoadState();\r
+ try {\r
+ if ( wantedsize > 0 ) {\r
+ if (!regetting) {\r
+ // sanity check\r
+ if (state == STATE_NOT_LOADED) {\r
+ if (size == wantedsize) {\r
+ // cool! the right size and it was the first \r
+ // download!\r
+ setCurrentLength(size);\r
+ setLoadState(STATE_LOAD_COMPLETE);\r
+ } else {\r
+ // argh! wrong size and a success hum...\r
+ setCurrentLength(size);\r
+ setLoadState(STATE_LOAD_ERROR);\r
+ System.out.println(getIdentifier()\r
+ +": tee stream mismatch, "\r
+ + "bytes(adv/got)="+\r
+ wantedsize+"/"+size);\r
+ }\r
+ } else {\r
+ // how can we end up here, I frankly don't know\r
+ setCurrentLength(size);\r
+ setLoadState(STATE_LOAD_ERROR);\r
+ System.out.println(getIdentifier()\r
+ +": UNKNOWN STATE for "\r
+ +"tee stream!, bytes(adv/got)="+\r
+ wantedsize+"/"+size);\r
+ }\r
+ } else {\r
+ // we asked for the diff, and we have it!!!\r
+ if (size == wantedsize) {\r
+ setCurrentLength(oldsize+wantedsize);\r
+ setLoadState(STATE_LOAD_COMPLETE);\r
+ } else {\r
+ // argh! wrong size and a success hum...\r
+ setCurrentLength(size);\r
+ setLoadState(STATE_LOAD_ERROR);\r
+ System.out.println(getIdentifier()\r
+ +": tee stream mismatch in reget, "\r
+ + "bytes(adv/got)="+\r
+ wantedsize+"/"+size);\r
+ }\r
+ }\r
+ } else {\r
+ // we didn't knew the size, we should trust what we got\r
+ // (unless it is HTTP/1.0)\r
+ setCurrentLength(size);\r
+ // FIXME (a trust flag to select btw COMPLETE and UNKNOWN\r
+ setLoadState(STATE_LOAD_COMPLETE);\r
+ }\r
+ // Update cache filter space usage:\r
+// filter.markUsed(this, oldsize, wantedsize);\r
+ \r
+ } finally {\r
+ cleanUpload();\r
+ }\r
+ }\r
+\r
+ public void notifyTeeFailure(int size) {\r
+ System.out.println(getIdentifier()+": tee streaming failed !");\r
+ int state = getLoadState();\r
+ \r
+ setCurrentLength(size);\r
+ setLoadState(STATE_LOAD_ERROR);\r
+ System.out.println(getIdentifier()\r
+ +": tee stream mismatch, "\r
+ + "bytes(adv/got)="+\r
+ wantedsize+"/"+size);\r
+ // and finish the thing!\r
+ cleanUpload();\r
+ }\r
+\r
+ // FIXME should be called after every upload\r
+ protected synchronized void cleanUpload() {\r
+ // FIXME reset a whole bunch of stuff\r
+ uploading = false;\r
+ filter.cleanUpload(this);\r
+ notifyAll();\r
+ }\r
+\r
+ /**\r
+ * FIXME Will be replaced soon, so that multiple people may share \r
+ * the same temporary resource.\r
+ * Wait for the upload to finish, if needed.\r
+ */\r
+ protected final synchronized void waitUpload() {\r
+ while ( uploading ) {\r
+ try {\r
+ wait();\r
+ } catch (InterruptedException ex) {\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * handle a range request, according to the first range or the\r
+ * request FIXME we should handle all the ranges at some point...\r
+ */\r
+ protected Reply handleRangeRequest(Request request, HttpRange r) {\r
+ // Should we check against a IfRange header ?\r
+ HttpEntityTag t = request.getIfRange();\r
+ if ( t != null ) {\r
+ if (t.isWeak() || ! t.getTag().equals(getHETag().getTag()))\r
+ return null;\r
+ }\r
+ // Check the range:\r
+ int cl = getContentLength();\r
+ int fb = r.getFirstPosition();\r
+ int lb = r.getLastPosition();\r
+ int sz;\r
+ if (fb > cl-1) { // first byte already out of range\r
+ HttpContentRange cr = HttpFactory.makeContentRange("bytes", 0,\r
+ cl - 1, cl);\r
+ Reply rr;\r
+ rr = request.makeReply(HTTP.REQUESTED_RANGE_NOT_SATISFIABLE);\r
+ rr.setContentLength(-1);\r
+ rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);\r
+ rr.setContentMD5(null);\r
+ return rr;\r
+ }\r
+ if ((fb < 0) && (lb >= 0)) { // ex: bytes=-20 final 20 bytes\r
+ if (lb >= cl) // cut the end\r
+ lb = cl;\r
+ sz = lb;\r
+ fb = cl - lb;\r
+ lb = cl - 1;\r
+ } else if (lb < 0) { // ex: bytes=10- the last size - 10\r
+ lb = cl-1;\r
+ sz = lb-fb+1;\r
+ } else { // ex: bytes=10-20\r
+ if (lb >= cl) // cut the end\r
+ lb = cl-1;\r
+ sz = lb-fb+1;\r
+ }\r
+ if ((fb < 0) || (lb < 0) || (fb <= lb)) {\r
+ HttpContentRange cr = null;\r
+ fb = (fb < 0) ? 0 : fb;\r
+ lb = ((lb > cl) || (lb < 0)) ? cl : lb;\r
+ cr = HttpFactory.makeContentRange("bytes", fb, lb, cl);\r
+ // Emit reply:\r
+ Reply rr = request.makeReply(HTTP.PARTIAL_CONTENT);\r
+ try {\r
+ rr.setContentMD5(null); // just in case :)\r
+ rr.setContentLength(sz);\r
+ rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);\r
+ rr.setStream(new ByteRangeOutputStream(getFile(), fb, lb+1));\r
+ return rr;\r
+ } catch (IOException ex) {\r
+ }\r
+ } \r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * decorate the reply header with some meta information taken\r
+ * from the cached resource\r
+ * @return a reply, the one we just updated\r
+ */\r
+ protected Reply setReplyHeaders(Reply reply) {\r
+ int status = reply.getStatus();\r
+ if (status != HTTP.NOT_MODIFIED) {\r
+ // FIXME check for byte range replies\r
+ reply.setContentLength(getContentLength());\r
+ // dump the headers we know.\r
+ reply.setContentMD5(getContentMD5());\r
+ reply.setContentLanguage(getContentLanguage());\r
+ reply.setContentEncoding(getContentEncoding());\r
+ reply.setContentType(getContentType());\r
+ reply.setLastModified(getLastModified());\r
+ reply.setVary(getVary());\r
+ }\r
+ reply.setETag(getHETag());\r
+ long date = getDate();\r
+ if ( date > 0 )\r
+ reply.setDate(getDate());\r
+ reply.setAge(getCurrentAge());\r
+ ArrayDictionary a = getExtraHeaders();\r
+ if ( a != null ) {\r
+ // This is the slowest operation of the whole cache :-(\r
+ Enumeration e = a.keys();\r
+ while (e.hasMoreElements() ) {\r
+ String hname = (String) e.nextElement();\r
+ String hvalue = (String) a.get(hname);\r
+ reply.setValue(hname, hvalue);\r
+ }\r
+ }\r
+ if ((filter != null) && filter.isShared()) {\r
+ HttpCacheControl hcc = reply.getCacheControl();\r
+ if (hcc != null) {\r
+ String priv[] = hcc.getPrivate();\r
+ if (priv != null) {\r
+ for (int i=0; i<priv.length; i++) {\r
+ // remove headers that are private if we are\r
+ // a shared cache (rfc2616#14.9, rfc2616#14.9.1)\r
+ reply.setHeaderValue(priv[i], null);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return reply;\r
+ }\r
+\r
+ /**\r
+ * check the validators namely LMT/Etags according to rfc2616 rules\r
+ * @return An integer, either <code>COND_FAILED</cond> if condition\r
+ * was checked, but failed, <code>COND_OK</code> if condition was checked\r
+ * and succeeded, or <strong>0</strong> if the condition was not checked\r
+ * at all (eg because the resource or the request didn't support it).\r
+ */\r
+ public int checkValidators(Request request) {\r
+ int v_inm = checkIfNoneMatch(request);\r
+ int v_ims = checkIfModifiedSince(request);\r
+\r
+ if ((v_inm == COND_OK) || (v_ims == COND_OK)) {\r
+ return COND_OK;\r
+ }\r
+ if ((v_inm == COND_FAILED) || (v_ims == COND_FAILED)) {\r
+ return COND_FAILED;\r
+ }\r
+ if ((v_inm == COND_WEAK) || (v_ims == COND_WEAK)) {\r
+ return COND_FAILED;\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /**\r
+ * This cached entry has been checked valid, perform given request.\r
+ * @param request The request to perform.\r
+ * @return An Reply instance.\r
+ * @exception HttpException If something went wrong.\r
+ */\r
+ public Reply perform(Request request)\r
+ throws HttpException\r
+ {\r
+ // If the resource is currently being uploaded, wait:\r
+ waitUpload();\r
+ // Now perform the request:\r
+ try {\r
+ Reply reply = null;\r
+ boolean needsEntity = true;\r
+ // Handle range requests:\r
+ HttpRange ranges[] = request.getRange();\r
+ if ((ranges != null) && (ranges.length == 1)) \r
+ reply = handleRangeRequest(request, ranges[0]);\r
+ // Handle full retreivals:\r
+ if ( reply == null ) {\r
+ int status = getStatus();\r
+ // Try validating first \r
+ // NOTE: We know we are only dealing with GETs and HEADs here\r
+ // otherwise the cache wouldn't be used...\r
+ int cim = checkIfMatch(request);\r
+ if ( (cim == COND_FAILED) || (cim == COND_WEAK) ) {\r
+ status = HTTP.PRECONDITION_FAILED;\r
+ needsEntity = false;\r
+ reply = request.makeReply(status);\r
+ reply.setContent("Pre-conditions failed.");\r
+ throw new HttpException(request, reply, "pre-condition");\r
+ } else if ( checkIfUnmodifiedSince(request) == COND_FAILED ) {\r
+ status = HTTP.PRECONDITION_FAILED;\r
+ reply = request.makeReply(status);\r
+ reply.setContent("Pre-conditions failed.");\r
+ throw new HttpException(request, reply, "pre-condition");\r
+ } else if ( checkValidators(request) == COND_FAILED ) {\r
+ status = HTTP.NOT_MODIFIED;\r
+ needsEntity = false;\r
+ }\r
+ // Emit reply:\r
+ reply = request.makeReply(status);\r
+ if ( needsEntity ) {\r
+ reply.setStream(getInputStream());\r
+ }\r
+ }\r
+ setReplyHeaders(reply);\r
+ // Check if entity is needed:\r
+ String mth = request.getMethod();\r
+ if ( mth.equals("HEAD") || mth.equals("OPTIONS") )\r
+ reply.setStream(null);\r
+// filter.markUsed(this);\r
+ return reply;\r
+ } catch (IOException ex) {\r
+// if (debug)\r
+// ex.printStackTrace();\r
+ // Some exception occured, delete that resource (no longer usefull)\r
+// delete();\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Try using an active stream to cache the content.\r
+ * Byte size usage is taken care of only at the end of the download\r
+ * to make sure we get the right sizes (might different from the\r
+ * advertized ones).\r
+ * @return An InputStream instance if active caching was possible,\r
+ * <strong>null</strong> otherwise.\r
+ */\r
+ public synchronized InputStream tryActiveCacheContent(InputStream in)\r
+ throws IOException\r
+ {\r
+ // If we don't return null, we *are* responsible for cleaning up\r
+ // the upload *whatever* happens ...\r
+ InputStream tee = null;\r
+ OutputStream out = null;\r
+ uploading = true;\r
+ // Open the output stream:\r
+ try {\r
+ out = new FileOutputStream(getFile());\r
+ } catch (IOException ex) {\r
+// if (debug)\r
+ ex.printStackTrace();\r
+ // We'll let cacheContent take care of that situation:\r
+ return null;\r
+ }\r
+ // We might be able to use active streams:\r
+// if (upnewsize > ACTIVE_STREAM_THRESOLD ) {\r
+ tee = ActiveStream.createTee(this, in, out);\r
+ if ( tee != null )\r
+ return tee;\r
+// } \r
+ // We were not able to active stream:\r
+ try {\r
+ out.close();\r
+ } catch (IOException ex) {\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * The basic initialization\r
+ */\r
+ public void initialize(Object values[]) {\r
+ super.initialize(values);\r
+ } \r
+\r
+ /**\r
+ * sets some useful information about the entity\r
+ * @param the request that requested this entity\r
+ * @param the reply triggered by this request\r
+ */\r
+ protected void updateInfo(Request request, Reply rep) {\r
+ String mth = request.getMethod();\r
+ Reply reply = (Reply) rep.getClone();\r
+ boolean hasEntity = !(mth.equals("HEAD") || mth.equals("OPTIONS"));\r
+ // is it a revalidation?\r
+ if (!request.hasState(CacheState.STATE_REVALIDATION)) {\r
+ // no, go for it!\r
+ HttpCacheControl hcc = reply.getCacheControl();\r
+ // first we should NOT cache headers protected by a no-cache\r
+ // per rfc2616@14.9\r
+ if (hcc != null) {\r
+ String nocache[] = hcc.getNoCache();\r
+ if (nocache != null) {\r
+ for (int i=0; i< nocache.length; i++) {\r
+ reply.setHeaderValue(nocache[i], null);\r
+ }\r
+ }\r
+ }\r
+ setStatus(reply.getStatus());\r
+ setContentType(reply.getContentType());\r
+ setContentLength(reply.getContentLength());\r
+ setLastModified(reply.getLastModified());\r
+ setContentMD5(reply.getContentMD5());\r
+ String vary[] = reply.getVary();\r
+ setVary(vary);\r
+ if (vary != null) {\r
+ // update the conneg headers\r
+ ArrayDictionary a = null;\r
+ for (int i=0; i< vary.length; i++) {\r
+ if (vary[i].equals("*")) {\r
+ continue;\r
+ }\r
+ if (a == null) {\r
+ a = new ArrayDictionary(vary.length);\r
+ }\r
+ a.put (vary[i].toLowerCase(), request.getValue(vary[i]));\r
+ }\r
+ // FIXME we should be able to update to save multiple\r
+ // matches, but with a limitation of course\r
+ if (a != null) {\r
+ setConnegHeaders(a);\r
+ }\r
+ }\r
+ if (reply.hasHeader(reply.H_ETAG)) {\r
+ setETag(reply.getETag().toString());\r
+ } else {\r
+ // be safe here!\r
+ setETag(null);\r
+ }\r
+ ArrayDictionary a = new ArrayDictionary(5, 5);\r
+ Enumeration e = reply.enumerateHeaderDescriptions();\r
+ while ( e.hasMoreElements() ) {\r
+ HeaderDescription d = (HeaderDescription) e.nextElement();\r
+ // Skip all well-known headers:\r
+ if ( d.isHeader(Reply.H_CONTENT_TYPE)\r
+ || d.isHeader(Reply.H_CONTENT_LENGTH)\r
+ || d.isHeader(Reply.H_LAST_MODIFIED)\r
+ || d.isHeader(Reply.H_ETAG)\r
+ || d.isHeader(Reply.H_AGE)\r
+ || d.isHeader(Reply.H_DATE)\r
+ || d.isHeader(Reply.H_VARY)\r
+ || d.isHeader(Reply.H_CONNECTION)\r
+ || d.isHeader(Reply.H_PROXY_CONNECTION)\r
+ || d.isHeader(Reply.H_TRANSFER_ENCODING)\r
+ || d.isHeader(Reply.H_CONTENT_MD5)\r
+ || d.getName().equalsIgnoreCase("keep-alive"))\r
+ continue;\r
+ // This is an extra header:\r
+ a.put(d.getName(), reply.getValue(d));\r
+ }\r
+ setExtraHeaders(a);\r
+ // FIXME add the headers ;)\r
+ \r
+ }\r
+ }\r
+\r
+ /**\r
+ * This cached entry needs revalidation, it will modify the \r
+ * request to do that.\r
+ */\r
+ public Request setRequestRevalidation(Request request) {\r
+ Request origreq = (Request) request.getClone();\r
+ request.setState(CacheState.STATE_RESOURCE, this);\r
+ request.setState(CacheState.STATE_ORIGREQ, origreq);\r
+ // At this point, we use the suggested way of using date as etag:\r
+ request.setIfModifiedSince(getLastModified());\r
+ // But if we do have an etag, we also uses it, as recommended:\r
+ if ((etags == null) && (getETag() != null)) {\r
+ etags = new HttpEntityTag[1];\r
+ etags[0] = HttpFactory.parseETag(getETag());\r
+ }\r
+ request.setIfNoneMatch(etags);\r
+ // We have to remove all other conditionals here:\r
+ request.setIfRange(null);\r
+ request.setRange(null);\r
+ request.setIfUnmodifiedSince(-1);\r
+ request.setIfMatch(null);\r
+ return request;\r
+ } \r
+\r
+ /**\r
+ * A constructor for new resources that will get some data\r
+ * directly\r
+ * FIXME params\r
+ */\r
+ public EntityCachedResource(CacheFilter filter, Request req, Reply rep) {\r
+ invalidated = false;\r
+ setValue(ATTR_IDENTIFIER, req.getURL().toExternalForm());\r
+ // Keep fast track of the filter:\r
+ this.filter = filter;\r
+ // update the headers\r
+ updateInfo(req, rep);\r
+ // and do some calculation according to the validator\r
+ filter.getValidator().updateExpirationInfo(this, req, rep);\r
+ // Save the content of resource into the content cache:\r
+ setFile(filter.getStore().getNewEntryFile());\r
+ wantedsize = rep.getContentLength();\r
+ InputStream in;\r
+ try {\r
+ in = tryActiveCacheContent(rep.getInputStream());\r
+ if (in == null) {\r
+ // something bad happened\r
+ // in = cacheContent(reply.getInputStream());\r
+ }\r
+ rep.setStream(in);\r
+ } catch (IOException ex) {\r
+ // FIXME\r
+ } \r
+ }\r
+\r
+ public EntityCachedResource() {\r
+ super();\r
+ }\r
+}\r
+\r
+\r