You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by un...@apache.org on 2004/11/11 17:24:45 UTC

svn commit: rev 57444 - in cocoon/branches/BRANCH_2_1_X: . src/blocks/scratchpad/conf src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl

Author: unico
Date: Thu Nov 11 08:24:43 2004
New Revision: 57444

Added:
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java
Modified:
   cocoon/branches/BRANCH_2_1_X/blocks.properties
   cocoon/branches/BRANCH_2_1_X/gump.xml
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java
   cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java
   cocoon/branches/BRANCH_2_1_X/status.xml
Log:
port changes to CachingSource from trunk

Modified: cocoon/branches/BRANCH_2_1_X/blocks.properties
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/blocks.properties	(original)
+++ cocoon/branches/BRANCH_2_1_X/blocks.properties	Thu Nov 11 08:24:43 2004
@@ -59,7 +59,7 @@
 #-----[dependency]: "fop" is needed by "tour".
 #include.block.fop=false
 #-----[dependency]: "hsqldb" depends on "databases".
-#-----[dependency]: "hsqldb" is needed by "jms", "petstore".
+#-----[dependency]: "hsqldb" is needed by "jms", "ojb", "petstore".
 #include.block.hsqldb=false
 #-----[dependency]: "html" is needed by "portal".
 #include.block.html=false
@@ -82,7 +82,7 @@
 #-----[dependency]: "session-fw" depends on "xsp".
 #-----[dependency]: "session-fw" is needed by "authentication-fw", "portal", "portal-fw".
 #include.block.session-fw=false
-#-----[dependency]: "velocity" is needed by "petstore", "scratchpad".
+#-----[dependency]: "velocity" is needed by "petstore".
 #include.block.velocity=false
 #include.block.web3=false
 #-----[dependency]: "xmldb" depends on "databases".
@@ -110,10 +110,9 @@
 #include.block.cron=false
 #include.block.deli=false
 #-----[dependency]: "eventcache" depends on "jms", "xsp" (for samples).
-#-----[dependency]: "eventcache" is needed by "repository".
+#-----[dependency]: "eventcache" is needed by "repository", "scratchpad".
 #include.block.eventcache=false
 #-----[dependency]: "faces" depends on "portal", "taglib".
-#-----[dependency]: "faces" is needed by "scratchpad".
 #include.block.faces=false
 #-----[dependency]: "forms" depends on "xsp" (for samples).
 #-----[dependency]: "forms" is needed by "apples", "javaflow", "lucene", "ojb", "petstore", "tour".
@@ -124,10 +123,10 @@
 #-----[dependency]: "jms" is needed by "eventcache", "slide".
 #include.block.jms=false
 #include.block.linotype=false
-#-----[dependency]: "mail" depends on "asciiart", "scratchpad".
+#-----[dependency]: "mail" depends on "asciiart".
 #include.block.mail=false
 #include.block.midi=false
-#-----[dependency]: "ojb" depends on "databases", "forms" (for samples).
+#-----[dependency]: "ojb" depends on "databases" (for samples), "forms" (for samples), "hsqldb" (for samples).
 #-----[dependency]: "ojb" is needed by "javaflow".
 #include.block.ojb=false
 #-----[dependency]: "petstore" depends on "databases", "forms", "hsqldb", "velocity".
@@ -135,10 +134,9 @@
 #include.block.proxy=false
 #include.block.qdox=false
 #-----[dependency]: "repository" depends on "databases", "eventcache".
-#-----[dependency]: "repository" is needed by "slide", "webdav".
+#-----[dependency]: "repository" is needed by "scratchpad", "slide", "webdav".
 #include.block.repository=false
-#-----[dependency]: "scratchpad" depends on "axis", "batik" (for samples), "cron", "faces", "velocity", "xsp".
-#-----[dependency]: "scratchpad" is needed by "mail".
+#-----[dependency]: "scratchpad" depends on "axis", "batik" (for samples), "cron", "eventcache", "repository", "xsp".
 #include.block.scratchpad=false
 #include.block.serializers=false
 #-----[dependency]: "slide" depends on "jms", "repository".

Modified: cocoon/branches/BRANCH_2_1_X/gump.xml
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/gump.xml	(original)
+++ cocoon/branches/BRANCH_2_1_X/gump.xml	Thu Nov 11 08:24:43 2004
@@ -155,6 +155,8 @@
     <depend project="cocoon-block-axis"/>
     <depend project="cocoon-block-batik" type="samples"/>
     <depend project="cocoon-block-cron"/>
+    <depend project="cocoon-block-eventcache"/>
+    <depend project="cocoon-block-repository"/>
     <depend project="cocoon-block-xsp"/>
     <depend project="apache-garbage"/>
 

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf	(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf	Thu Nov 11 08:24:43 2004
@@ -42,7 +42,7 @@
         |   A Refresher is used when asynchronic caching is turned on. It is responsible for
         |   updating the cached contents in the background.
         | - 'default-expires' (-1). The expires value if it is not specified on the source
-        |    itself.
+        |    itself. The default value of -1 means to never expire.
         +-->
     <component-instance name="cached"
                         class="org.apache.cocoon.components.source.impl.CachingSourceFactory"
@@ -50,9 +50,9 @@
       <!--
       <parameter name="async" value="true"/>
       <parameter name="cache-role" value="org.apache.cocoon.caching.Cache"/>
-      <parameter name="refresher-role" value="org.apache.cocoon.components.source.impl.Refresher/Delay"/>
       <parameter name="default-expires" value="-1"/>
       -->
+      <parameter name="refresher-role" value="org.apache.cocoon.components.source.impl.Refresher/Delay"/>
     </component-instance>
 
 </xconf>

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java	(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java	Thu Nov 11 08:24:43 2004
@@ -1,12 +1,12 @@
 /*
  * Copyright 1999-2004 The Apache Software Foundation.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "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
- * 
+ *
  * 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.
@@ -20,19 +20,19 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Serializable;
-import java.util.Collection;
-import java.util.Iterator;
 
 import org.apache.avalon.framework.activity.Initializable;
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
-import org.apache.avalon.framework.parameters.Parameters;
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
 import org.apache.cocoon.CascadingIOException;
 import org.apache.cocoon.ProcessingException;
 import org.apache.cocoon.caching.Cache;
+import org.apache.cocoon.caching.EventAware;
 import org.apache.cocoon.caching.IdentifierCacheKey;
+import org.apache.cocoon.caching.validity.EventValidity;
+import org.apache.cocoon.caching.validity.NamedEvent;
 import org.apache.cocoon.components.sax.XMLDeserializer;
 import org.apache.cocoon.components.sax.XMLSerializer;
 import org.apache.cocoon.xml.ContentHandlerWrapper;
@@ -40,9 +40,7 @@
 import org.apache.excalibur.source.Source;
 import org.apache.excalibur.source.SourceException;
 import org.apache.excalibur.source.SourceNotFoundException;
-import org.apache.excalibur.source.SourceResolver;
 import org.apache.excalibur.source.SourceValidity;
-import org.apache.excalibur.source.TraversableSource;
 import org.apache.excalibur.source.impl.validity.ExpiresValidity;
 import org.apache.excalibur.source.impl.validity.TimeStampValidity;
 import org.apache.excalibur.xml.sax.XMLizable;
@@ -54,7 +52,7 @@
  * This class implements a proxy like source that uses another source
  * to get the content. This implementation can cache the content for
  * a given period of time
- * 
+ *
  * <h2>Syntax for Protocol</h2>
  * <p>
  * cached:http://www.apache.org/[?cocoon:cache-expires=60&cocoon:cache-name=main]
@@ -62,95 +60,95 @@
  * <p>
  * The above examples show how the real source <code>http://www.apache.org</code>
  * is wrapped and the cached contents is used for <code>60</code> seconds.
- * The second querystring parameter instructs that the cache key be extended with the string 
+ * The second querystring parameter instructs that the cache key be extended with the string
  * <code>main</code>. This allows the use of multiple cache entries for the same source.
  * </p>
  * <p>
- * The value of the expires parameter holds some additional semantics. 
+ * The value of the expires parameter holds some additional semantics.
  * Specifying <code>-1</code> will yield the cached response to be considered valid
- * always. <code>0</code> can be used to achieve the exact opposite. That is to say, 
+ * always. <code>0</code> can be used to achieve the exact opposite. That is to say,
  * the cached contents will be thrown out and updated immediately and unconditionally.
  * <p>
- * @version CVS $Id: CachingSource.java,v 1.11 2004/04/15 08:05:56 cziegeler Exp $
+ * @version CVS $Id$
  */
 public class CachingSource extends AbstractLogEnabled
 implements Source, Serviceable, Initializable, XMLizable {
+
+    // ---------------------------------------------------- Constants
     
-    /** The ServiceManager */
-    protected ServiceManager manager;
+    public static final String CACHE_EXPIRES_PARAM = "cache-expires";
+    public static final String CACHE_NAME_PARAM = "cache-name";
     
-    /** The SourceResolver to resolve the wrapped Source */
-    protected SourceResolver resolver;
+    // ---------------------------------------------------- Instance variables
     
+    /** The ServiceManager */
+    protected ServiceManager manager;
+
     /** The current cache */
     protected Cache cache;
-    
-    /** The refresher for asynchronous updates */
-    protected Refresher refresher;
-    
+
     /** The source object for the real content */
     protected Source source;
-    
+
     /** The cached response (if any) */
     protected CachedSourceResponse response;
-    
+
     /** Did we just update meta info? */
     protected boolean freshMeta;
-    
+
     /** The full location string */
     final protected String uri;
-    
+
     /** The used protocol */
     final protected String protocol;
-    
+
     /** The key used in the store */
     final protected IdentifierCacheKey cacheKey;
     
     /** number of seconds before cached object becomes invalid */
     final protected int expires;
     
-    /** Parameters */
-    final protected Parameters parameters;
+    /** cache key extension */
+    final protected String cacheName;
     
     /** asynchronic refresh strategy ? */
     final protected boolean async;
-    
+
     /**
      * Construct a new object.
      */
     public CachingSource(final String protocol,
                          final String uri,
                          final Source source,
-                         final Parameters parameters,
                          final int expires,
+                         final String cacheName,
                          final boolean async) {
         this.protocol = protocol;
         this.uri = uri;
         this.source = source;
         this.expires = expires;
+        this.cacheName = cacheName;
         this.async = async;
-        this.parameters = parameters;
         
         String key = "source:" + source.getURI();
-        String cacheName = parameters.getParameter("cache-name", null);
         if (cacheName != null) {
             key += ":" + cacheName;
         }
         this.cacheKey = new IdentifierCacheKey(key, false);
     }
-        
+
     /**
      * Set the ServiceManager.
      */
     public void service(final ServiceManager manager) throws ServiceException {
         this.manager = manager;
     }
-    
+
     /**
      * Initialize the Source.
      */
     public void initialize() throws Exception {
-        
+
         boolean checkValidity = true;
         if (this.expires == -1) {
             if (getLogger().isDebugEnabled()) {
@@ -158,115 +156,75 @@
             }
             checkValidity = false;
         }
-        
+
         if (this.async && this.expires != 0) {
             if (getLogger().isDebugEnabled()) {
-                getLogger().debug("Not invalidating cached response " +
                    "for asynch source " + getSourceURI());
+                getLogger().debug("Using cached response if available.");
             }
             checkValidity = false;
         }
 
         this.response = (CachedSourceResponse) this.cache.get(this.cacheKey);
+
         if (this.response == null) {
             if (getLogger().isDebugEnabled()) {
-                getLogger().debug("No cached response found " +
                    "for source " + getSourceURI());
+                getLogger().debug("No cached response found.");
             }
             checkValidity = false;
         }
-        
-        if (checkValidity) {
-            
-            final ExpiresValidity cacheValidity = (ExpiresValidity) this.response.getValidityObjects()[0];
-            final SourceValidity sourceValidity = this.response.getValidityObjects()[1];
-            
-            boolean remove = false;
-            if (this.expires == 0) {
-                if (getLogger().isDebugEnabled()) {
-                    getLogger().debug("Force invalidation of cached response" +
                        " of source " + getSourceURI());
-                }
-                remove = true;
-            }
-            else {
-                boolean expired = cacheValidity.isValid() != SourceValidity.VALID;
-                if (expired) {
-                    if (getLogger().isDebugEnabled()) {
-                        getLogger().debug("Cached response of source " 
-                            + getSourceURI() + " is expired.");
-                    }
-                    boolean invalid = !isValid(sourceValidity, this.source);
-                    if (invalid) {
-                        if (getLogger().isDebugEnabled()) {
-                            getLogger().debug("Cached response of source "
-                                + getSourceURI() + " is invalid.");
-                        }
-                        remove = true;
-                    }
-                    else {
-                        if (getLogger().isDebugEnabled()) {
-                            getLogger().debug("Cached response of source " 
-                                + getSourceURI() + " is still valid.");
-                        }
-                        // set new expiration period
-                        this.response.getValidityObjects()[0] = new ExpiresValidity(getExpiration());
-                    }
-                }
-            }
-            
-            if (remove) {
-                this.response = null;
-                // remove it if it no longer exists
-                if (!exists()) {
-                    this.cache.remove(this.cacheKey);
-                }
+
+        if (this.expires == 0) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Not using cached response.");
             }
+            this.response = null;
+            checkValidity = false;
         }
-        if (this.async && this.expires > 0) {
-            // schedule it with the refresher
-            this.refresher.refresh(this.cacheKey,
-                                   getSourceURI(),
-                                   this.parameters.getParameter("cache-role", null),
-                                   this.parameters);
+
+        if (checkValidity && !checkValidity()) {
+            this.response = null;
+            // remove it if it no longer exists
+            if (!this.source.exists()) {
+                remove();
+            }
         }
+
     }
-    
+
     /**
      * Cleanup.
      */
     public void dispose() {
-        if (this.source != null) {
-            this.resolver.release(this.source);
-            this.source = null;
-        }
+        this.source = null;
         this.manager = null;
-        this.resolver = null;
         this.cache = null;
     }
-    
+
     /**
      * Initialize the cached response with meta info.
-     * 
+     *
      * @throws IOException  if an the binary response could not be initialized
      */
     protected void initMetaResponse() throws IOException {
         boolean storeResponse = false;
         CachedSourceResponse response = this.response;
         if (response == null) {
-            if (this.expires != 0) {
-                final SourceValidity cacheValidity = new ExpiresValidity(getExpiration());
-                final SourceValidity sourceValidity = source.getValidity();
-                response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
-                storeResponse = true;
+            SourceValidity[] validities;
+            if (this.cache instanceof EventAware) {
+                validities = new SourceValidity[] { new EventValidity(new NamedEvent(this.source.getURI())) };
             }
             else {
-                response = new CachedSourceResponse(null);
+                validities = new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity() };
             }
+            response = new CachedSourceResponse(validities);
+            storeResponse = true;
         }
         if (response.getExtra() == null) {
             response.setExtra(readMeta(this.source));
             this.freshMeta = true;
         }
+        this.response = response;
         if (storeResponse) {
-            this.response = response;
             try {
                 this.cache.store(this.cacheKey, this.response);
             }
@@ -275,10 +233,10 @@
             }
         }
     }
-    
+
     /**
      * Initialize the cached response with binary contents.
-     * 
+     *
      * @throws IOException  if an the binary response could not be initialized
      */
     protected void initBinaryResponse() throws IOException {
@@ -286,25 +244,19 @@
         /* delay caching the response until we have a valid new one */
         CachedSourceResponse response = this.response;
         if (response == null) {
-            if (this.expires != 0) {
-                final SourceValidity cacheValidity = new ExpiresValidity(getExpiration());
-                final SourceValidity sourceValidity = source.getValidity();
-                response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
-                storeResponse = true;
-            }
-            else {
-                response = new CachedSourceResponse(null);
-            }
+            response = new CachedSourceResponse(new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity()});
+            storeResponse = true;
         }
         if (response.getBinaryResponse() == null) {
             response.setBinaryResponse(readBinaryResponse(this.source));
             if (!this.freshMeta) {
                 /* always refresh meta in this case */
                 response.setExtra(readMeta(this.source));
+                this.freshMeta = true;
             }
         }
+        this.response = response;
         if (storeResponse) {
-            this.response = response;
             try {
                 this.cache.store(this.cacheKey, this.response);
             }
@@ -313,10 +265,10 @@
             }
         }
     }
-    
+
     /**
      * Initialize the cached response with XML contents.
-     * 
+     *
      * @param refresh  whether to force refresh.
      * @throws SAXException  if something happened during xml processing
      * @throws IOException  if an IO level error occured
@@ -327,15 +279,8 @@
         /* delay caching the response until we have a valid new one */
         CachedSourceResponse response = this.response;
         if (response == null) {
-            if (this.expires != 0) {
-                final SourceValidity cacheValidity = new ExpiresValidity(getExpiration());
-                final SourceValidity sourceValidity = source.getValidity();
-                response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
-                storeResponse = true;
-            }
-            else {
-                response = new CachedSourceResponse(null);
-            }
+            response = new CachedSourceResponse(new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity() });
+            storeResponse = true;
         }
         if (response.getXMLResponse() == null || refresh) {
             byte[] binary = response.getBinaryResponse();
@@ -343,10 +288,11 @@
             if (!this.freshMeta) {
                 /* always refresh meta in this case */
                 response.setExtra(readMeta(this.source));
+                this.freshMeta = true;
             }
         }
+        this.response = response;
         if (storeResponse) {
-            this.response = response;
             try {
                 this.cache.store(this.cacheKey, this.response);
             }
@@ -355,9 +301,9 @@
             }
         }
     }
-    
+
     // ---------------------------------------------------- Source implementation
-    
+
     /**
      * Return the protocol identifier.
      */
@@ -400,7 +346,7 @@
         }
         return ((SourceMeta) this.response.getExtra()).getMimeType();
     }
-    
+
     /**
      * Return an <code>InputStream</code> object to read from the source.
      */
@@ -424,9 +370,9 @@
      * @see org.apache.excalibur.source.Source#exists()
      */
     public boolean exists() {
-		return this.source.exists();
+        return this.source.exists();
     }
-    
+
     /**
      *  Get the Validity object. This can either wrap the last modification
      *  date or the expires information or...
@@ -440,18 +386,62 @@
         }
         return null;
     }
-     
+
     /**
      * Refresh this object and update the last modified date
-     * and content length.
+     * and content length. This method will try to refresh the
+     * cached contents.
      */
     public void refresh() {
-        this.response = null;
         this.source.refresh();
+        if (response != null && checkValidity()) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Cached response is still valid for source " + this.uri + ".");
+            }
+        }
+        else {
+            if (this.source.exists()) {
+                CachedSourceResponse response = this.response;
+                try {
+                    if (response == null) {
+                        // create a new cached response
+                        response = new CachedSourceResponse(new SourceValidity[] { 
+                                new ExpiresValidity(getExpiration()), source.getValidity()});
+                    }
+                    // only create objects that are cached
+                    if (response.getBinaryResponse() != null) {
+                        response.setBinaryResponse(readBinaryResponse(source));
+                    }
+                    if (response.getXMLResponse() != null) {
+                        response.setXMLResponse(readXMLResponse(
+                                source, response.getBinaryResponse(), this.manager));
+                    }
+                    // always refresh meta data
+                    response.setExtra(readMeta(source));
+                    this.response = response;
+                    cache.store(this.cacheKey, response);
+                }
+                catch (Exception e) {
+                    getLogger().warn("Error refreshing source " + this.uri +
+                        "Cached response (if any) may be stale.", e);
+                }
+            }
+            else if (this.response != null) {
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("Source " + this.uri + " no longer exists." +
+                        " Throwing out cached response.");
+                }
+                remove();
+            }
+        }
     }
     
-    // ---------------------------------------------------- XMLizable implementation
+    protected void remove() {
+        this.cache.remove(this.cacheKey);
+    }
     
+    // ---------------------------------------------------- XMLizable implementation
+
     /**
      * Generates SAX events representing the object's state.
      */
@@ -476,46 +466,46 @@
             this.manager.release(deserializer);
         }
     }
-    
+
     // ---------------------------------------------------- CachingSource specific accessors
-    
+
     /**
      * Return the uri of the cached source.
      */
     protected String getSourceURI() {
         return this.source.getURI();
     }
-    
+
     /**
      * Return the used key.
      */
     protected IdentifierCacheKey getCacheKey() {
         return this.cacheKey;
     }
-    
+
     /**
      * Expires (in milli-seconds)
      */
     protected long getExpiration() {
         return this.expires * 1000;
     }
-    
+
     /**
      * Read XML content from source.
-     * 
-	 * @return content from source
-	 * @throws SAXException
-	 * @throws IOException
-	 * @throws CascadingIOException
-	 */
-	protected static byte[] readXMLResponse(Source source, byte[] binary, ServiceManager manager) 
+     *
+     * @return content from source
+     * @throws SAXException
+     * @throws IOException
+     * @throws CascadingIOException
+     */
+    protected byte[] readXMLResponse(Source source, byte[] binary, ServiceManager manager)
     throws SAXException, IOException, CascadingIOException {
         XMLSerializer serializer = null;
         XMLizer xmlizer = null;
         byte[] result = null;
-		try {
-		    serializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE);
-            
+        try {
+            serializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE);
+
             if (source instanceof XMLizable) {
                 ((XMLizable) source).toSAX(serializer);
             }
@@ -532,75 +522,54 @@
                                   serializer);
                 }
             }
-		    result = (byte[]) serializer.getSAXFragment();
-		} catch (ServiceException se) {
-		    throw new CascadingIOException("Missing service dependency.", se);
-		} finally {
+            result = (byte[]) serializer.getSAXFragment();
+        } catch (ServiceException se) {
+            throw new CascadingIOException("Missing service dependency.", se);
+        } finally {
             manager.release(xmlizer);
-		    manager.release(serializer);
-		}
-		return result;
-	}
+            manager.release(serializer);
+        }
+        return result;
+    }
 
-	/**
+    /**
      * Read binary content from source.
-     * 
-	 * @return content from source
-	 * @throws IOException
-	 * @throws SourceNotFoundException
-	 */
-	protected static byte[] readBinaryResponse(Source source) 
+     *
+     * @return content from source
+     * @throws IOException
+     * @throws SourceNotFoundException
+     */
+    protected byte[] readBinaryResponse(Source source)
     throws IOException, SourceNotFoundException {
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-		final byte[] buffer = new byte[2048];
-		final InputStream inputStream = source.getInputStream();
-		int length;
-		while ((length = inputStream.read(buffer)) > -1) {
-		    baos.write(buffer, 0, length);
-		}
-		baos.flush();
-		inputStream.close();
-		return baos.toByteArray();
-	}
-    
+        final byte[] buffer = new byte[2048];
+        final InputStream inputStream = source.getInputStream();
+        int length;
+        while ((length = inputStream.read(buffer)) > -1) {
+            baos.write(buffer, 0, length);
+        }
+        baos.flush();
+        inputStream.close();
+        return baos.toByteArray();
+    }
+
     /**
      * Read meta data from source.
-     * 
+     *
      * @return source meta data
      * @throws IOException
      */
-    protected static SourceMeta readMeta(Source source) throws IOException {
-        SourceMeta meta;
-        
-        if (source instanceof TraversableSource) {
-            
-            final TraversableSourceMeta tmeta = new TraversableSourceMeta();
-            final TraversableSource tsource = (TraversableSource) source;
-            
-            tmeta.setName(tsource.getName());
-            tmeta.setIsCollection(tsource.isCollection());
-            
-            if (tmeta.isCollection()) {
-                final Collection children = tsource.getChildren();
-                if (children != null) {
-                    final String[] names = new String[children.size()];
-                    final Iterator iter = children.iterator();
-                    int count = 0;
-                    while(iter.hasNext()) {
-                        TraversableSource child = (TraversableSource) iter.next();
-                        names[count] = child.getName();
-                        count++;
-                    }
-                    tmeta.setChildren(names);
-                }
-            }
-            
-            meta = tmeta;
-        }
-        else {
-            meta = new SourceMeta();
-        }
-        
+    protected final SourceMeta readMeta(Source source) throws IOException {
+        SourceMeta meta = createMeta();
+        initMeta(meta, source);
+        return meta;
+    }
+    
+    protected SourceMeta createMeta() {
+        return new SourceMeta();
+    }
+    
+    protected void initMeta(SourceMeta meta, Source source) throws IOException {
         final long lastModified = source.getLastModified();
         if (lastModified > 0) {
             meta.setLastModified(lastModified);
@@ -609,80 +578,89 @@
             meta.setLastModified(System.currentTimeMillis());
         }
         meta.setMimeType(source.getMimeType());
+    }
+
+    private boolean checkValidity() {
+        if (this.response == null) return false;
         
-        return meta;
+        final SourceValidity[] validities = this.response.getValidityObjects();
+        boolean valid = true;
+        if (validities.length == 2) {
+            final ExpiresValidity expiresValidity = (ExpiresValidity) validities[0];
+            final SourceValidity sourceValidity = validities[1];
+
+            if (expiresValidity.isValid() != SourceValidity.VALID) {
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("Cached response of source " + getSourceURI() + " is expired.");
+                }
+                if (!isValid(sourceValidity, source.getValidity())) {
+                    if (getLogger().isDebugEnabled()) {
+                        getLogger().debug("Cached response of source " + getSourceURI() + " is invalid.");
+                    }
+                    valid = false;
+                }
+                else {
+                    if (getLogger().isDebugEnabled()) {
+                        getLogger().debug("Cached response of source " + getSourceURI() + " is still valid.");
+                    }
+                    // set new expiration period
+                    this.response.getValidityObjects()[0] = new ExpiresValidity(getExpiration());
+                }
+            }
+            else {
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("Cached response of source " + getSourceURI() + " is NOT expired.");
+                }
+            }
+        }
+        else {
+            // assert(validities.length == 1 && validities[0] instanceof EventValidity)
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Cached response of source does not expire");
+            }
+        }
+        return valid;
     }
     
-    protected static boolean isValid(SourceValidity validity, Source source) {
-        if (validity == null) return false;
-        return validity.isValid() == SourceValidity.VALID || 
-              (validity.isValid() == SourceValidity.UNKNOWN && 
-               validity.isValid(source.getValidity()) == SourceValidity.VALID);
+    private static boolean isValid(SourceValidity oldValidity, SourceValidity newValidity) {
+        return (oldValidity.isValid() == SourceValidity.VALID ||
+                (oldValidity.isValid() == SourceValidity.UNKNOWN &&
+                 oldValidity.isValid(newValidity) == SourceValidity.VALID));
     }
-    
+
     /**
      * Data holder for caching Source meta info.
      */
     protected static class SourceMeta implements Serializable {
-        
+
         private String m_mimeType;
         private long m_lastModified;
         private boolean m_exists;
-        
+
         protected String getMimeType() {
             return m_mimeType;
         }
-        
+
         protected void setMimeType(String mimeType) {
             m_mimeType = mimeType;
         }
-        
+
         protected long getLastModified() {
             return m_lastModified;
         }
-        
+
         protected void setLastModified(long lastModified) {
             m_lastModified = lastModified;
         }
-        
+
         protected boolean exists() {
             return m_exists;
         }
-        
+
         protected void setExists(boolean exists) {
             m_exists = exists;
         }
-        
-    }
-    
-    protected static class TraversableSourceMeta extends SourceMeta {
-        private String   m_name;
-        private boolean  m_isCollection;
-        private String[] m_children;
-        
-        protected String getName() {
-            return m_name;
-        }
-        
-        protected void setName(String name) {
-            m_name = name;
-        }
-        
-        protected boolean isCollection() {
-            return m_isCollection;
-        }
-        
-        protected void setIsCollection(boolean isCollection) {
-            m_isCollection = isCollection;
-        }
-        
-        protected String[] getChildren() {
-            return m_children;
-        }
-        
-        protected void setChildren(String[] children) {
-            m_children = children;
-        }
+
     }
 
 }

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java	(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java	Thu Nov 11 08:24:43 2004
@@ -1,12 +1,12 @@
 /*
  * Copyright 1999-2004 The Apache Software Foundation.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "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
- * 
+ *
  * 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.
@@ -26,12 +26,14 @@
 import org.apache.avalon.framework.configuration.ConfigurationException;
 import org.apache.avalon.framework.container.ContainerUtil;
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.logger.Logger;
 import org.apache.avalon.framework.parameters.Parameters;
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
 import org.apache.avalon.framework.thread.ThreadSafe;
 import org.apache.cocoon.caching.Cache;
+import org.apache.cocoon.components.source.InspectableSource;
 import org.apache.excalibur.source.Source;
 import org.apache.excalibur.source.SourceException;
 import org.apache.excalibur.source.SourceFactory;
@@ -43,36 +45,36 @@
 
 /**
  * This class implements a proxy like source caches the contents of the source
- * it wraps. This implementation can cache the content either 
+ * it wraps. This implementation can cache the content either
  * for a given period of time or until an external event invalidates
  * the cached response.
  * <p>
  * When using the timeout approach you have a choice between two separate
  * revalidation strategies.
  * </p>
- * 1) Synchronously. This means that the cached contents are checked for validity 
+ * 1) Synchronously. This means that the cached contents are checked for validity
  * and thrown out on the current thread.<br>
  * 2) Asynchronously. A cronjob is scheduled to invalidate and update the cached response
  * in the backgound.<br><br>
- * 
+ *
  * <h2>Protocol syntax</h2>
  * <p>
  * The URL needs to contain the URL of the cached source, an expiration
- * period in seconds, and optionally a cache key: 
+ * period in seconds, and optionally a cache key:
  * <code>cached:http://www.apache.org/[?cocoon:cache-expires=60][&cocoon:cache-name=main]</code>.
  * </p>
  * <p>
  * The above examples shows how the real source <code>http://www.apache.org/</code>
  * is wrapped and the cached contents is used for <code>60</code> seconds.
- * The second querystring parameter instructs that the cache key be extended with the string 
+ * The second querystring parameter instructs that the cache key be extended with the string
  * <code>main</code>. This allows the use of multiple cache entries for the same source.
  * </p>
  * <p>
- * This factory creates either instances of {@link org.apache.cocoon.components.source.impl.CachingSource} 
+ * This factory creates either instances of {@link org.apache.cocoon.components.source.impl.CachingSource}
  * or {@link org.apache.cocoon.components.source.impl.TraversableCachingSource}
  * depending on the whether the wrapped Source is an instance of TraversableSource.
  * </p>
- * 
+ *
  * <h2>Parameters</h2>
  * <table><tbody>
  * <tr>
@@ -104,29 +106,39 @@
  *  <td><code>-1</code></td>
  * </tr>
  * </tbody></table>
- *  
- * @version CVS $Id: CachingSourceFactory.java,v 1.10 2004/05/07 17:32:59 joerg Exp $
+ *
+ * @version CVS $Id$
  * @since 2.1.1
  */
 public final class CachingSourceFactory extends AbstractLogEnabled
 implements SourceFactory, URIAbsolutizer, Serviceable, Configurable, Disposable, ThreadSafe
 {
 
+    // ---------------------------------------------------- Constants
+    
+    public static final String ASYNC_PARAM = "async";
+    public static final String FAILSAFE_PARAM = "failsafe";
+    public static final String CACHE_ROLE_PARAM = "cache-role";
+    public static final String REFRESHER_ROLE_PARAM = "refresher-role";
+    public static final String DEFAULT_EXPIRES_PARAM = "default-expires";
+
+    // ---------------------------------------------------- Instance variables
+
     /** Protocol prefix / factory name */
     private String scheme;
-    
+
     /** Asynchronous ? */
     private boolean async;
-    
+
     /** The role of the cache */
     private String cacheRole;
- 
+
     /** The role of the refresher */
     private String refresherRole;
-    
+
     /** Default expires value */
     private int defaultExpires;
-    
+
     /** Has the lazy initialization been done? */
     private boolean isInitialized;
 
@@ -135,53 +147,53 @@
 
     /** The {@link SourceResolver} */
     protected SourceResolver resolver;
-    
+
     /** The refresher */
     protected Refresher refresher;
-    
+
     /** The cache */
     protected Cache cache;
-    
+
     // ---------------------------------------------------- Lifecycle
-    
+
     public CachingSourceFactory() {
     }
-    
-    public void service(ServiceManager manager) throws ServiceException {
+
+    public void service(ServiceManager manager) {
         this.manager = manager;
-        // due to cyclic dependencies we can't lookup the resolver the refresher
-        // or the cache until after the factory is initialized.
+        // Due to cyclic dependencies we can't lookup the resolver,
+        // the refresher or the cache until after the factory is
+        // initialized.
     }
-    
+
     public void configure(Configuration configuration) throws ConfigurationException {
         this.scheme = configuration.getAttribute("name");
         Parameters parameters = Parameters.fromConfiguration(configuration);
+
+        // 'async' parameter
+        this.async = parameters.getParameterAsBoolean(ASYNC_PARAM, false);
         
-		// 'async' parameter
-		this.async = parameters.getParameterAsBoolean("async", false);
-		
         // 'cache-role' parameter
-        this.cacheRole = parameters.getParameter("cache-role", Cache.ROLE);
+        this.cacheRole = parameters.getParameter(CACHE_ROLE_PARAM, Cache.ROLE);
         if (this.getLogger().isDebugEnabled()) {
             this.getLogger().debug("Using cache " + this.cacheRole);
         }
-        
+
         // 'refresher-role' parameter
         if (this.async) {
-            this.refresherRole = parameters.getParameter("refresher-role", Refresher.ROLE);
+            this.refresherRole = parameters.getParameter(REFRESHER_ROLE_PARAM, Refresher.ROLE);
             if (this.getLogger().isDebugEnabled()) {
                 this.getLogger().debug("Using refresher " + this.refresherRole);
             }
         }
-        
-        this.defaultExpires = parameters.getParameterAsInteger("default-expires",-1);
-        
+
+        this.defaultExpires = parameters.getParameterAsInteger(DEFAULT_EXPIRES_PARAM, -1);
     }
-    
+
     /**
      * Lazy initialization of resolver and refresher because of
      * cyclic dependencies.
-     * 
+     *
      * @throws SourceException
      */
     private synchronized void lazyInitialize() throws SourceException {
@@ -214,7 +226,7 @@
         }
         this.isInitialized = true;
     }
-    
+
     /* (non-Javadoc)
      * @see org.apache.avalon.framework.activity.Disposable#dispose()
      */
@@ -227,32 +239,32 @@
             this.resolver = null;
         }
     }
-    
+
     // ---------------------------------------------------- SourceFactory implementation
-    
+
     /**
      * Get a <code>Source</code> object.
      * @param parameters This is optional.
      */
     public Source getSource(final String location, final Map parameters)
     throws MalformedURLException, IOException {
-        
+
         if (this.getLogger().isDebugEnabled() ) {
             this.getLogger().debug("Creating source object for " + location);
         }
-        
+
         // we must do lazy initialization because of cyclic dependencies
         if (!this.isInitialized) {
             lazyInitialize();
         }
-        
+
         // snip the cache protocol
         int index = location.indexOf(':');
         if (index == -1) {
             throw new MalformedURLException("This Source requires a subprotocol to be specified.");
         }
         String uri = location.substring(index+1);
-        
+
         // parse the query string
         SourceParameters sp = null;
         String queryString = null;
@@ -262,7 +274,7 @@
             uri = uri.substring(0,index);
             sp = new SourceParameters(queryString);
         }
-        
+
         // put caching source specific query string parameters
         // into a Parameters object
         final Parameters params = new Parameters();
@@ -280,52 +292,92 @@
                 uri += "?" + queryString;
             }
         }
+
+        int expires = params.getParameterAsInteger(CachingSource.CACHE_EXPIRES_PARAM, this.defaultExpires);
+        String cacheName = params.getParameter(CachingSource.CACHE_NAME_PARAM, null);
+
+        final CachingSource source = newCachingSource(this.resolver.resolveURI(uri),
+                                                      this.scheme,
+                                                      location,
+                                                      expires,
+                                                      cacheName,
+                                                      this.async,
+                                                      this.cache,
+                                                      getLogger(),
+                                                      manager);
         
-        int expires = params.getParameterAsInteger("cache-expires", -1);
-        if (expires == -1) {
-            expires = this.defaultExpires;
-            params.setParameter("cache-expires", String.valueOf(this.defaultExpires));
+        if (this.async && expires > 0) {
+
+            params.setParameter(CachingSource.CACHE_EXPIRES_PARAM, String.valueOf(expires));
+            params.setParameter(CachingSource.CACHE_NAME_PARAM, cacheName);
+            params.setParameter(CACHE_ROLE_PARAM, this.cacheRole);
+
+            // schedule it with the refresher
+            this.refresher.refresh(source.getCacheKey(),
+                                   source.getSourceURI(),
+                                   this.cacheRole,
+                                   params);
         }
-        params.setParameter("cache-role", this.cacheRole);
         
-        final Source wrappedSource = this.resolver.resolveURI(uri);
+        return source;
+    }
+    
+    /**
+     * Factory method for creating a new CachingSource.
+     */
+    public static CachingSource newCachingSource(Source wrappedSource, 
+                                                 String scheme,
+                                                 String uri,
+                                                 int expires,
+                                                 String cacheName,
+                                                 boolean async,
+                                                 Cache cache,
+                                                 Logger logger,
+                                                 ServiceManager manager)
+    throws SourceException {
+        
         CachingSource source;
         if (wrappedSource instanceof TraversableSource) {
-            source = new TraversableCachingSource(scheme,
-                                                  location,
-                                                  (TraversableSource) wrappedSource,
-                                                  params,
-                                                  expires,
-                                                  this.async);
-        }
-        else {
+            if (wrappedSource instanceof InspectableSource) {
+                source = new InspectableTraversableCachingSource(scheme,
+                                                                 uri,
+                                                                 (InspectableSource) wrappedSource,
+                                                                 expires,
+                                                                 cacheName,
+                                                                 async);
+            } else {
+                source = new TraversableCachingSource(scheme,
+                                                      uri,
+                                                      (TraversableSource) wrappedSource,
+                                                      expires,
+                                                      cacheName,
+                                                      async);
+            }
+        } else {
             source = new CachingSource(scheme,
-                                       location,
+                                       uri,
                                        wrappedSource,
-                                       params,
                                        expires,
-                                       this.async);
+                                       cacheName,
+                                       async);
         }
 
         // set the required components directly for speed
-        source.cache = this.cache;
-        source.resolver = this.resolver;
-        source.refresher = this.refresher;
+        source.cache = cache;
 
-        ContainerUtil.enableLogging(source, this.getLogger());
+        ContainerUtil.enableLogging(source, logger);
         try {
             // call selected avalon lifecycle interfaces. Mmmh.
-            ContainerUtil.service(source, this.manager);
+            ContainerUtil.service(source, manager);
             ContainerUtil.initialize(source);
         } catch (ServiceException se) {
             throw new SourceException("Unable to initialize source.", se);
         } catch (Exception e) {
             throw new SourceException("Unable to initialize source.", e);
         }
-        
         return source;
     }
-
+    
     /**
      * Release a {@link Source} object.
      */
@@ -334,11 +386,12 @@
             if (this.getLogger().isDebugEnabled() ) {
                 this.getLogger().debug("Releasing source " + source.getURI());
             }
+            resolver.release(((CachingSource) source).source);
             ((CachingSource) source).dispose();
         }
     }
-    
-	// ---------------------------------------------------- URIAbsolutizer implementation
+
+    // ---------------------------------------------------- URIAbsolutizer implementation
 
     /*
      *  (non-Javadoc)

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java	(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java	Thu Nov 11 08:24:43 2004
@@ -57,7 +57,7 @@
  * Default implementation of the refresher.
  * 
  * @since 2.1.1
- * @version CVS $Id: DelayRefresher.java,v 1.6 2004/04/25 20:01:36 haul Exp $
+ * @version CVS $Id$
  */
 public class DelayRefresher extends AbstractLogEnabled
 implements Contextualizable, Serviceable, Parameterizable, Disposable, ThreadSafe, Refresher, CronJob {
@@ -267,8 +267,7 @@
 	}
 
 	/**
-	 * @param childs
-	 * @param i
+	 * @param conf
 	 * @throws ConfigurationException
 	 * @throws CascadingException
 	 */
@@ -331,7 +330,7 @@
 
     /**
 	 * @param writer
-	 * @param iter
+	 * @param c
 	 * @throws IOException
 	 */
 	private void writeRefreshJobConfiguration(Writer writer, final TargetConfiguration c) throws IOException {

Added: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java
==============================================================================
--- (empty file)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java	Thu Nov 11 08:24:43 2004
@@ -0,0 +1,184 @@
+/*
+ * Copyright 1999-2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "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
+ * 
+ * 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 under the License.
+ */
+package org.apache.cocoon.components.source.impl;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cocoon.components.source.InspectableSource;
+import org.apache.cocoon.components.source.helpers.SourceProperty;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.TraversableSource;
+
+/**
+ * TraversableCachingSource that adds support for SourceProperty caching.
+ */
+public class InspectableTraversableCachingSource extends TraversableCachingSource
+implements InspectableSource {
+
+    private InspectableSource isource;
+
+    public InspectableTraversableCachingSource(String protocol,
+                                               String uri,
+                                               InspectableSource source, 
+                                               int expires,
+                                               String cacheName,
+                                               boolean async) {
+        super(protocol, uri, (TraversableSource) source, expires, cacheName, async);
+        this.isource = source;
+    }
+
+    public SourceProperty getSourceProperty(String namespace, String name) throws SourceException {
+        try {
+            initMetaResponse();
+        }
+        catch (IOException e) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Failure initializing inspectable response", e);
+            }
+            return null;
+        }
+        final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra());
+        SourceProperty property = imeta.getSourceProperty(namespace, name);
+        if (property == null) {
+            // In the case of webdav the source cannot
+            // determine all available properties beforehand.
+            // Therefore, although we initialized the cached
+            // response by calling getSourceProperties(),
+            // this does not mean this particular property
+            // was returned and cached. Hence we try to 
+            // get it here still and remember if it was null.
+            property = isource.getSourceProperty(namespace, name);
+            if (property == null) {
+                // remember that this property is null
+                property = InspectableSourceMeta.NULL_PROPERTY;
+            }
+            imeta.setSourceProperty(property);
+        }
+        if (InspectableSourceMeta.NULL_PROPERTY.equals(property)) {
+            return null;
+        }
+        return property;
+    }
+
+    public void setSourceProperty(SourceProperty property) throws SourceException {
+        isource.setSourceProperty(property);
+        try {
+            initMetaResponse();
+        }
+        catch (IOException e) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Failure initializing inspectable response", e);
+            }
+        }
+        final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra());
+        imeta.setSourceProperty(property);
+    }
+
+    public SourceProperty[] getSourceProperties() throws SourceException {
+        try {
+            initMetaResponse();
+        }
+        catch (IOException e) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Failure initializing inspectable response", e);
+            }
+            return null;
+        }
+        final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra());
+        return imeta.getSourceProperties();
+    }
+
+    public void removeSourceProperty(String namespace, String name) throws SourceException {
+        isource.removeSourceProperty(namespace, name);
+        try {
+            initMetaResponse();
+        }
+        catch (IOException e) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Failure initializing inspectable response", e);
+            }
+        }
+        final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra());
+        imeta.removeSourceProperty(namespace, name);
+    }
+    
+    protected SourceMeta createMeta() {
+        return new InspectableSourceMeta();
+    }
+
+    protected void initMeta(SourceMeta meta, Source source) throws IOException {
+        super.initMeta(meta, source);
+        final InspectableSourceMeta imeta = ((InspectableSourceMeta) meta);
+        imeta.setSourceProperties(isource.getSourceProperties());
+    }
+
+    protected TraversableCachingSource newSource(String uri, Source wrapped) {
+        return  new InspectableTraversableCachingSource(super.protocol,
+                                                        uri,
+                                                        (InspectableSource) wrapped,
+                                                        super.expires,
+                                                        super.cacheName,
+                                                        super.async);
+    }
+    
+    protected static class InspectableSourceMeta extends TraversableSourceMeta {
+        
+        protected static final SourceProperty NULL_PROPERTY = new SourceProperty("cocoon", "isnull");
+        
+        private Map properties;
+        
+        protected SourceProperty getSourceProperty(String namespace, String name) {
+            if (properties == null) return null;
+            final String key = namespace + "#" + name;
+            return (SourceProperty) properties.get(key);
+        }
+        
+        protected void setSourceProperty(SourceProperty property) {
+            if (this.properties == null) {
+                this.properties = Collections.synchronizedMap(new HashMap(11));
+            }
+            final String key = property.getNamespace() + "#" + property.getName();
+            properties.put(key, property);
+        }
+
+        protected SourceProperty[] getSourceProperties() {
+            if (this.properties == null) return null;
+            final Collection values = this.properties.values();
+            return (SourceProperty[]) values.toArray(new SourceProperty[values.size()]);
+        }
+
+        protected void setSourceProperties(SourceProperty[] props) {
+            if (this.properties == null) {
+                this.properties = Collections.synchronizedMap(new HashMap(props.length));
+            }
+            for (int i = 0; i < props.length; i++) {
+                setSourceProperty(props[i]);
+            }
+        }
+        
+        protected void removeSourceProperty(String namespace, String name) {
+            if (this.properties != null) {
+                final String key = namespace + "#" + name;
+                properties.remove(key);
+            }
+        }
+    }
+}

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java	(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java	Thu Nov 11 08:24:43 2004
@@ -18,9 +18,9 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 
 import org.apache.avalon.framework.container.ContainerUtil;
-import org.apache.avalon.framework.parameters.Parameters;
 import org.apache.excalibur.source.Source;
 import org.apache.excalibur.source.SourceException;
 import org.apache.excalibur.source.TraversableSource;
@@ -33,12 +33,12 @@
     private TraversableSource tsource;
     
     public TraversableCachingSource(String protocol,
-                                    String location,
+                                    String uri,
                                     TraversableSource source,
-                                    Parameters params,
                                     int expires,
+                                    String cacheName,
                                     boolean async) {
-        super(protocol, location, source, params, expires, async);
+        super(protocol, uri, source, expires, cacheName, async);
         this.tsource = source;
     }
     
@@ -148,18 +148,26 @@
 
     // ---------------------------------------------------- helper methods
 
-    private TraversableCachingSource createSource(String uri, Source wrapped) 
+    
+    
+    protected final TraversableCachingSource createSource(String uri, Source wrapped) 
     throws SourceException {
-        final TraversableCachingSource source = 
-            new TraversableCachingSource(super.protocol,
-                                         uri,
-                                         (TraversableSource) wrapped,
-                                         new Parameters().merge(super.parameters),
-                                         super.expires,
-                                         super.async);
+        final TraversableCachingSource source = newSource(uri, wrapped);
+        initializeSource(source);
+        return source;
+    }
+
+    protected TraversableCachingSource newSource(String uri, Source wrapped) {
+        return  new TraversableCachingSource(super.protocol,
+                                             uri,
+                                             (TraversableSource) wrapped,
+                                             super.expires,
+                                             super.cacheName,
+                                             super.async);
+    }
+
+    protected void initializeSource(TraversableCachingSource source) throws SourceException {
         source.cache = super.cache;
-        source.resolver = super.resolver;
-        source.refresher = super.refresher;
         ContainerUtil.enableLogging(source, getLogger());
         try {
             ContainerUtil.service(source, super.manager);
@@ -167,14 +175,64 @@
         } catch (Exception e) {
             throw new SourceException("Unable to initialize source.", e);
         }
-        return source;
     }
     
+    protected SourceMeta createMeta() {
+        return new TraversableSourceMeta();
+    }
+    
+    protected void initMeta(SourceMeta meta, Source source) throws IOException {
+        super.initMeta(meta, source);
+
+        final TraversableSource tsource = (TraversableSource) source;
+        final TraversableSourceMeta tmeta = (TraversableSourceMeta) meta;
+
+        tmeta.setName(tsource.getName());
+        tmeta.setIsCollection(tsource.isCollection());
+
+        if (tmeta.isCollection()) {
+            final Collection children = tsource.getChildren();
+            if (children != null) {
+                final String[] names = new String[children.size()];
+                final Iterator iter = children.iterator();
+                int count = 0;
+                while(iter.hasNext()) {
+                    TraversableSource child = (TraversableSource) iter.next();
+                    names[count] = child.getName();
+                    count++;
+                }
+                tmeta.setChildren(names);
+            }
+        }
+
+    }
+    
+    protected void remove() {
+        remove(true);
+    }
+
+    /**
+     * The parent's cached response needs to be removed from cache
+     * as well because it's cached list of children is no longer valid.
+     */
+    private void remove(boolean flag) {
+        super.remove();
+        if (flag) {
+            try {
+                TraversableCachingSource parent = (TraversableCachingSource) getParent();
+                parent.remove(false);
+            }
+            catch (SourceException e) {
+                getLogger().error("Error removing parent's cached response");
+            }
+        }
+    }
+
     /**
      * Calculate the cached child URI based on a parent URI
      * and a child name.
      */
-    private String getChildURI(String parentURI, String childName) {
+    private static String getChildURI(String parentURI, String childName) {
         
         // separate query string from rest of parentURI
         String rest, qs;
@@ -203,7 +261,7 @@
     /**
      * Calculate the cached parent URI based on a child URI.
      */
-    private String getParentURI(String childURI) {
+    private static String getParentURI(String childURI) {
         
         // separate query string from rest of uri
         String rest, qs;
@@ -228,6 +286,36 @@
         }
         
         return parentUri + qs;
+    }
+
+    protected static class TraversableSourceMeta extends SourceMeta {
+        private String   m_name;
+        private boolean  m_isCollection;
+        private String[] m_children;
+
+        protected String getName() {
+            return m_name;
+        }
+
+        protected void setName(String name) {
+            m_name = name;
+        }
+
+        protected boolean isCollection() {
+            return m_isCollection;
+        }
+
+        protected void setIsCollection(boolean isCollection) {
+            m_isCollection = isCollection;
+        }
+
+        protected String[] getChildren() {
+            return m_children;
+        }
+
+        protected void setChildren(String[] children) {
+            m_children = children;
+        }
     }
 
 }

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java	(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java	Thu Nov 11 08:24:43 2004
@@ -15,6 +15,7 @@
  */
 package org.apache.cocoon.components.source.impl;
 
+import java.io.IOException;
 import java.util.Map;
 
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
@@ -23,12 +24,8 @@
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
 import org.apache.cocoon.caching.Cache;
-import org.apache.cocoon.caching.IdentifierCacheKey;
 import org.apache.cocoon.components.cron.ConfigurableCronJob;
-import org.apache.excalibur.source.Source;
 import org.apache.excalibur.source.SourceResolver;
-import org.apache.excalibur.source.SourceValidity;
-import org.apache.excalibur.source.impl.validity.ExpiresValidity;
 
 /**
  * A target updating a cache entry.
@@ -48,17 +45,13 @@
  *  The time in seconds the cached content is valid
  * </li>
  * <li>
- *  <code>fail-safe (boolean)</code>
- *  Whether to invalidate the cached response when updating it failed.
- * </li>
- * <li>
- *  <code>cache-key (SimpleCacheKey)</code>: 
+ *  <code>cache-name (String)</code>: 
  *  The key used to cache the content
  * </li>
  * </ul>
  *  
  * @since 2.1.1
- * @version CVS $Id: UpdateTarget.java,v 1.6 2004/04/15 08:05:56 cziegeler Exp $
+ * @version CVS $Id$
  */
 public class UpdateTarget extends AbstractLogEnabled
 implements Serviceable, ConfigurableCronJob {
@@ -71,10 +64,7 @@
     private String uri;
     private String cacheRole;
     private int expires;
-    private boolean failSafe;
-    
-    // the key under which to store the CachedResponse in the Cache
-    private IdentifierCacheKey cacheKey;
+    private String cacheName;
     
         
     // ---------------------------------------------------- Lifecycle
@@ -96,14 +86,13 @@
     public void setup(Parameters pars, Map objects) {
         this.uri = pars.getParameter("uri", null);
         this.cacheRole = pars.getParameter("cache-role", Cache.ROLE);
-        this.expires = pars.getParameterAsInteger("cache-expires", 60);
-        this.failSafe = pars.getParameterAsBoolean("fail-safe", true);
-        this.cacheKey = (IdentifierCacheKey) objects.get("cache-key");
+        this.expires = pars.getParameterAsInteger("cache-expires", 0);
+        this.cacheName = pars.getParameter("cache-name", null);
     }
     
     
     // ---------------------------------------------------- CronJob implementation
-    
+
     /* (non-Javadoc)
      * @see org.apache.avalon.cornerstone.services.scheduler.Target#targetTriggered(java.lang.String)
      */
@@ -112,78 +101,36 @@
             if (this.getLogger().isDebugEnabled()) {
                 this.getLogger().debug("Refreshing " + this.uri);
             }
-            
-            Source source = null;
             Cache cache = null;
+            CachingSource source = null;
             try {
-                
-                cache = (Cache) this.manager.lookup(this.cacheRole);
-                source = this.resolver.resolveURI(this.uri);
-                
-                // check if the source is really expired and invalid
-                CachedSourceResponse response = (CachedSourceResponse) cache.get(this.cacheKey);
-                if (response != null) {
-                    final SourceValidity sourceValidity = response.getValidityObjects()[1];
-                    if (CachingSource.isValid(sourceValidity, source)) {
-                        if (getLogger().isDebugEnabled()) {
-                            getLogger().debug("Cached response is still valid " +
                                "for source " + this.uri + ".");
-                        }
-                        response.getValidityObjects()[0] = new ExpiresValidity(this.expires * 1000);
-                        return;
-                    }
-                }
-                
-                if (source.exists()) {
-                    
-                    // what is in the cached response?
-                    byte[] binary = null;
-                    byte[] xml = null;
-                    if (response != null) {
-                        binary = response.getBinaryResponse();
-                        xml = response.getXMLResponse();
-                    }
-                    
-                    // create a new cached response
-                    final ExpiresValidity cacheValidity = new ExpiresValidity(this.expires * 1000);
-                    final SourceValidity sourceValidity = source.getValidity();
-                    response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity});
-                    
-                    // only create objects that have previously been used
-                    if (binary != null) {
-                        binary = CachingSource.readBinaryResponse(source);
-                        response.setBinaryResponse(binary);
-                    }
-                    if (xml != null) {
-                        xml = CachingSource.readXMLResponse(source, binary, this.manager);
-                        response.setXMLResponse(xml);
-                    }
-                    // meta info is always set
-                    response.setExtra(CachingSource.readMeta(source));
-                    cache.store(this.cacheKey, response);
-                }
-                else if (response != null) {
-                    // FIXME: There is a potential problem when the parent
-                    // source has not yet been updated thus listing this
-                    // source still as one of its children. We'll have to remove 
-                    // the parent's cached response here too.
-                    if (getLogger().isDebugEnabled()) {
-                        getLogger().debug("Source " + this.uri + " no longer exists." +
                            " Throwing out cached response.");
-                    }
-                    cache.remove(this.cacheKey);
-                }
-            } catch (Exception e) {
-                if (!failSafe) {
-                    // the content expires, so remove it
-                    cache.remove(cacheKey);
-                    getLogger().warn("Exception during updating of source " + this.uri, e);
+                cache = (Cache) manager.lookup(cacheRole);
+                source = CachingSourceFactory.newCachingSource(
+                                               this.resolver.resolveURI(this.uri),
+                                               "cached",
+                                               "cached:" + uri,
+                                               expires,
+                                               cacheName,
+                                               true,
+                                               cache,
+                                               getLogger(),
+                                               this.manager);
+                                               
+                source.refresh();
+            }
+            catch (IOException e) {
+                getLogger().error("Error refreshing source", e);
+            }
+            catch (ServiceException e) {
+                getLogger().error("Error refreshing source", e);
+            }
+            finally {
+                if (cache != null) {
+                    manager.release(cache);
                 }
-                else {
-                    getLogger().warn("Updating of source " + this.uri + " failed. " +
-                        "Cached response (if any) will be stale.", e);
+                if (source != null) {
+                    this.resolver.release(source);
                 }
-            } finally {
-                this.resolver.release(source);
-                this.manager.release(cache);
             }
         }
     }

Modified: cocoon/branches/BRANCH_2_1_X/status.xml
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/status.xml	(original)
+++ cocoon/branches/BRANCH_2_1_X/status.xml	Thu Nov 11 08:24:43 2004
@@ -465,6 +465,11 @@
      AbstractMessageListener and AbstractMessagePublisher should be used as basis for
      custom publish/subscribe components.
    </action>
+   <action dev="UH" type="add">
+     Still in the scratchpad area at the time of this writing, added a
+     CachedSource proxy subclass for Sources that implement TraversableSource and
+     InspectableSource (for instance WebDAVSource).
+   </action> 
    <action dev="TC" type="add" fixes-bug="29935" due-to="Leszek Gawron" due-to-email="ouzo@wlkp.org">
      Added support for stripping root elements in the CIncludeTransformer.
    </action>