You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by re...@apache.org on 2009/12/28 21:38:15 UTC

svn commit: r894242 - in /cocoon/cocoon3/trunk: cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/ cocoon-servlet/src/main/java/org/apache/cocoon/servlet/ cocoon-servlet/src/main/java/org/apache/cocoon/servlet/collector/ cocoon-servlet/src/main/r...

Author: reinhard
Date: Mon Dec 28 20:38:14 2009
New Revision: 894242

URL: http://svn.apache.org/viewvc?rev=894242&view=rev
Log:
Conditional GET (If-None-Match) support for ALL caching pipelines based on ETags. The ETag is the hex representation of the a pipeline's cache key hashcode.
Previously only cases where a cache key could be expressed as timestamp were supported (Last-Modified -> If-Modified-Since).

Modified:
    cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/JAXRSTest.java
    cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/ReaderTest.java
    cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/RequestProcessor.java
    cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/collector/ResponseHeaderCollector.java
    cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-collector.xml
    cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-component.xml

Modified: cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/JAXRSTest.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/JAXRSTest.java?rev=894242&r1=894241&r2=894242&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/JAXRSTest.java (original)
+++ cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/JAXRSTest.java Mon Dec 28 20:38:14 2009
@@ -61,11 +61,18 @@
     public void testConditionalGet() throws Exception {
         this.loadResponse("jax-rs/sample2/read/javascript-resource-implicit.js");
         String lastModified = this.response.getResponseHeaderValue("Last-Modified");
+        String eTag = this.response.getResponseHeaderValue("ETag");
         assertNotNull("Last-Modified has to be set at this point!", lastModified);
+        assertNotNull("ETag has to be set at this point!", eTag);
         assertFalse("Last-Modified has to be set at this point!", "".equals(lastModified));
+        assertFalse("ETag has to be set at this point!", "".equals(eTag));
 
         this.webClient.addRequestHeader("If-Modified-Since", lastModified);
         this.loadResponse("read/javascript-resource-implicit.js");
         assertEquals(304, this.response.getStatusCode());
+
+        this.webClient.addRequestHeader("If-None-Match", eTag);
+        this.loadResponse("read/javascript-resource-implicit.js");
+        assertEquals(304, this.response.getStatusCode());
     }
 }

Modified: cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/ReaderTest.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/ReaderTest.java?rev=894242&r1=894241&r2=894242&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/ReaderTest.java (original)
+++ cocoon/cocoon3/trunk/cocoon-sample-webapp/src/test/java/org/apache/cocoon/it/ReaderTest.java Mon Dec 28 20:38:14 2009
@@ -21,7 +21,7 @@
  */
 public class ReaderTest extends CocoonHtmlUnitTestCase {
 
-    /**
+     /**
      * Call a pipeline that explicitly sets the mime-type of the resource.
      */
     public void testReadingResourceWithExplicitMimeType() throws Exception {
@@ -54,11 +54,18 @@
     public void testConditionalGet() throws Exception {
         this.loadResponse("read/javascript-resource-implicit.js");
         String lastModified = this.response.getResponseHeaderValue("Last-Modified");
+        String eTag = this.response.getResponseHeaderValue("ETag");
         assertNotNull("Last-Modified has to be set at this point!", lastModified);
+        assertNotNull("ETag has to be set at this point!", eTag);
         assertFalse("Last-Modified has to be set at this point!", "".equals(lastModified));
+        assertFalse("ETag has to be set at this point!", "".equals(eTag));
 
         this.webClient.addRequestHeader("If-Modified-Since", lastModified);
         this.loadResponse("read/javascript-resource-implicit.js");
         assertEquals(304, this.response.getStatusCode());
+
+        this.webClient.addRequestHeader("If-None-Match", eTag);
+        this.loadResponse("read/javascript-resource-implicit.js");
+        assertEquals(304, this.response.getStatusCode());
     }
 }

Modified: cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/RequestProcessor.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/RequestProcessor.java?rev=894242&r1=894241&r2=894242&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/RequestProcessor.java (original)
+++ cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/RequestProcessor.java Mon Dec 28 20:38:14 2009
@@ -53,8 +53,6 @@
 
 public class RequestProcessor {
 
-    private static final long serialVersionUID = 1L;
-
     private URL baseURL;
     private BeanFactory beanFactory;
     private boolean inServletServiceFramework;
@@ -273,18 +271,42 @@
     private void sendSitemapResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
         Settings settings = (Settings) this.beanFactory.getBean(Settings.class.getName());
 
+        // read conditational GET relevant data
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        // read the 'If-Modified-Since' request header
+        long ifModifiedSince = request.getDateHeader("If-Modified-Since");
+        ResponseHeaderCollector.setIfLastModifiedSince(ifModifiedSince);
+
+        // read the 'If-None-Match' request header
+        String ifNoneMatch = request.getHeader("If-None-Match");
+        ResponseHeaderCollector.setIfNoneMatch(ifNoneMatch);
+
         // invoke the sitemap engine
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
         this.invoke(this.calcSitemapRequestURI(request), prepareParameters(request, response, settings,
                 this.servletContext), baos);
 
-        // collect meta information from the previous run of the sitemap engine
+        // read data after sitemap/pipeline execution
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        // read the produced status code
+        int statusCode = ResponseHeaderCollector.getStatusCode();
+
+        // collect meta information from the previous exeuction of the sitemap engine
         long lastModified = ResponseHeaderCollector.getLastModified();
-        int contentLengh = baos.size();
+        String etag = ResponseHeaderCollector.getETag();
 
-        // send the Last-Modified header
-        if (lastModified > -1) {
-            response.setDateHeader("Last-Modified", lastModified);
+        // set response headers
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        if (statusCode >= 200 && statusCode < 300) {
+            // send the ETag header
+            if (etag != null && !"".equals(etag)) {
+                response.setHeader("ETag", etag);
+            }
+            // send the Last-Modified
+            if (lastModified > -1) {
+                response.setDateHeader("Last-Modified", lastModified);
+            }
         }
 
         // set the X-Cocoon-Version header
@@ -292,20 +314,17 @@
             response.setHeader("X-Cocoon-Version", this.version);
         }
 
-        // conditional request support
-        long ifLastModifiedSince = request.getDateHeader("If-Modified-Since");
-        if (ifLastModifiedSince > 0 && lastModified > 0) {
-            if (ifLastModifiedSince / 1000 >= lastModified / 1000) {
-                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-
-                if (this.logger.isInfoEnabled()) {
-                    this.logger.info("The requested resource " + request.getRequestURI()
-                            + " hasn't changed: ifLastModifiedSince=" + ifLastModifiedSince + ", lastModified="
-                            + lastModified + ". Hence 304 (NOT_MODIFIED) was sent as status code.");
-                }
+        // conditional request support (no need to send an unmodified response)
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        if (!ResponseHeaderCollector.isModifiedResponse()) {
+            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 
-                return;
+            if (this.logger.isInfoEnabled()) {
+                this.logger.info("Going to send NOT MODIFIED response: statusCode=" + statusCode + ", lastModified="
+                        + lastModified);
             }
+
+            return;
         }
 
         // Content-Type handling
@@ -318,10 +337,10 @@
         }
 
         // Status code handling
-        int statusCode = ResponseHeaderCollector.getStatusCode();
         response.setStatus(statusCode);
 
         // write the sitemap result to the output stream
+        int contentLengh = baos.size();
         if (contentLengh > 0) {
             response.setContentLength(contentLengh);
             if (this.logger.isInfoEnabled()) {

Modified: cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/collector/ResponseHeaderCollector.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/collector/ResponseHeaderCollector.java?rev=894242&r1=894241&r2=894242&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/collector/ResponseHeaderCollector.java (original)
+++ cocoon/cocoon3/trunk/cocoon-servlet/src/main/java/org/apache/cocoon/servlet/collector/ResponseHeaderCollector.java Mon Dec 28 20:38:14 2009
@@ -18,7 +18,9 @@
 
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.cocoon.pipeline.CachingPipeline;
 import org.apache.cocoon.pipeline.Pipeline;
+import org.apache.cocoon.pipeline.caching.CacheKey;
 import org.apache.cocoon.pipeline.component.PipelineComponent;
 import org.apache.cocoon.sitemap.Invocation;
 import org.apache.cocoon.sitemap.node.InvocationResult;
@@ -30,24 +32,68 @@
 @Aspect
 public class ResponseHeaderCollector {
 
-    private static CollectorDataStore collectorDataStore = new ThreadLocalCollectorDataStore();
-
+    private static final String KEY_ETAG = ResponseHeaderCollector.class.getName() + "/etag";
+    private static final String KEY_IF_MODIFIED_SINCE = ResponseHeaderCollector.class.getName() + "/if-modified-since";
+    private static final String KEY_IF_NONE_MATCH = ResponseHeaderCollector.class.getName() + "/if-none-match";
     private static final String KEY_LAST_MODIFIED = ResponseHeaderCollector.class.getName() + "/last-modified";
     private static final String KEY_MIME_TYPE = ResponseHeaderCollector.class.getName() + "/mime-type";
+    private static final String KEY_PIPELINE_EXECUTED = ResponseHeaderCollector.class.getName() + "/pipeline-executed";
     private static final String KEY_STATUS_CODE = ResponseHeaderCollector.class.getName() + "/status-code";
 
+    private static CollectorDataStore collectorDataStore = new ThreadLocalCollectorDataStore();
+
     @SuppressWarnings("unchecked")
     @Around("execution(* org.apache.cocoon.pipeline.Pipeline.execute(..))")
     public Object interceptInvoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
-        Object result = proceedingJoinPoint.proceed();
+        // prepare data
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        setPipelineExecuted(false);
         Pipeline<PipelineComponent> pipeline = (Pipeline<PipelineComponent>) proceedingJoinPoint.getTarget();
 
-        // last modified
-        long lastModified = pipeline.getLastModified();
+        // collect last-modified data
+        final long ifModifiedSince = getIfLastModifiedSince();
+        final long lastModified = pipeline.getLastModified();
+
+        // collect eTag
+        final String noneMatch = getIfNoneMatch();
+        if (pipeline instanceof CachingPipeline) {
+            CachingPipeline<PipelineComponent> cachingPipeline = (CachingPipeline<PipelineComponent>) pipeline;
+            CacheKey cacheKey = cachingPipeline.getCacheKey();
+            if (cacheKey != null) {
+                setETag(Integer.toHexString(cacheKey.hashCode()));
+            }
+        }
+
+        // set last-modified
         if (lastModified > -1 || getLastModified() <= 0) {
             setLastModified(lastModified);
         }
 
+        // pipeline execution
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        Object result = null;
+        boolean repeatPipeline = true;
+
+        // check the eTag (if ETag and If-None-Match parameters match, there is no need to
+        // re-execute the pipeline
+        String eTag = getETag();
+        if (eTag != null && noneMatch != null && noneMatch.equals(eTag)) {
+            repeatPipeline = false;
+        }
+
+        // only check for last modification if eTag doesn't match
+        if (repeatPipeline && ifModifiedSince > 0 && lastModified > 0) {
+            if (ifModifiedSince / 1000 >= lastModified / 1000) {
+                repeatPipeline = false;
+            }
+        }
+
+        // repeat the pipeline
+        if (repeatPipeline) {
+            result = proceedingJoinPoint.proceed();
+            setPipelineExecuted(true);
+        }
+
         // mime type
         String newValue = pipeline.getContentType();
         if (newValue != null) {
@@ -78,6 +124,24 @@
         ResponseHeaderCollector.collectorDataStore = collectorDataStore;
     }
 
+    public static String getETag() {
+        return (String) collectorDataStore.get(KEY_ETAG);
+    }
+
+    public static long getIfLastModifiedSince() {
+        Number attribute = (Number) collectorDataStore.get(KEY_IF_MODIFIED_SINCE);
+
+        if (attribute == null) {
+            return -1;
+        }
+
+        return attribute.longValue();
+    }
+
+    public static String getIfNoneMatch() {
+        return (String) collectorDataStore.get(KEY_IF_NONE_MATCH);
+    }
+
     public static long getLastModified() {
         Object lastModified = collectorDataStore.get(KEY_LAST_MODIFIED);
 
@@ -102,6 +166,22 @@
         return statusCode;
     }
 
+    public static boolean isModifiedResponse() {
+        return (Boolean) collectorDataStore.get(KEY_PIPELINE_EXECUTED);
+    }
+
+    public static void setETag(String etag) {
+        collectorDataStore.set(KEY_ETAG, etag);
+    }
+
+    public static void setIfLastModifiedSince(long ifLastModifiedSince) {
+        collectorDataStore.set(KEY_IF_MODIFIED_SINCE, ifLastModifiedSince);
+    }
+
+    public static void setIfNoneMatch(String ifNoneMatch) {
+        collectorDataStore.set(KEY_IF_NONE_MATCH, ifNoneMatch);
+    }
+
     public static void setLastModified(Long lastModified) {
         collectorDataStore.set(KEY_LAST_MODIFIED, lastModified);
     }
@@ -110,6 +190,10 @@
         collectorDataStore.set(KEY_MIME_TYPE, mimeType);
     }
 
+    public static void setPipelineExecuted(boolean executed) {
+        collectorDataStore.set(KEY_PIPELINE_EXECUTED, executed);
+    }
+
     public static void setStatusCode(int statusCode) {
         collectorDataStore.set(KEY_STATUS_CODE, statusCode);
     }

Modified: cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-collector.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-collector.xml?rev=894242&r1=894241&r2=894242&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-collector.xml (original)
+++ cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-collector.xml Mon Dec 28 20:38:14 2009
@@ -8,15 +8,15 @@
   "License"); you may not use this file except in compliance
   with the License.  You may obtain a copy of the License at
 
-   http://www.apache.org/licenses/LICENSE-2.0
+      http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing,
   software distributed under the License is distributed on an
   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   KIND, either express or implied.  See the License for the
   specific language governing permissions and limitations
-  und
--->
+  under the License.
+ -->
 <!-- $Id: cocoon-servlet-node.xml 647814 2008-04-14 14:28:00Z reinhard $ -->
 <beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -25,7 +25,7 @@
                       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
   <aop:aspectj-autoproxy proxy-target-class="false" />
- 
+  
   <bean id="org.apache.cocoon.servlet.collector.ResponseHeaderCollector" class="org.apache.cocoon.servlet.collector.ResponseHeaderCollector">
     <property name="collectorDataStore">
       <bean class="org.apache.cocoon.servlet.collector.CallStackCollectorDataStore" />

Modified: cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-component.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-component.xml?rev=894242&r1=894241&r2=894242&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-component.xml (original)
+++ cocoon/cocoon3/trunk/cocoon-servlet/src/main/resources/META-INF/cocoon/spring/cocoon-servlet-component.xml Mon Dec 28 20:38:14 2009
@@ -8,15 +8,15 @@
   "License"); you may not use this file except in compliance
   with the License.  You may obtain a copy of the License at
 
-   http://www.apache.org/licenses/LICENSE-2.0
+      http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing,
   software distributed under the License is distributed on an
   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   KIND, either express or implied.  See the License for the
   specific language governing permissions and limitations
-  und
--->
+  under the License.
+ -->
 <!-- $Id: cocoon-servlet-component.xml 647856 2008-04-14 15:40:50Z reinhard $ -->
 <beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"