cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vgritse...@apache.org
Subject svn commit: r433746 [1/2] - in /cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl: ./ src/main/java/org/apache/cocoon/components/source/impl/ src/main/resources/META-INF/legacy/xconf/ src/main/resources/org/apache/cocoon/components/ src/test...
Date Tue, 22 Aug 2006 20:46:57 GMT
Author: vgritsenko
Date: Tue Aug 22 13:46:55 2006
New Revision: 433746

URL: http://svn.apache.org/viewvc?rev=433746&view=rev
Log:
    <action dev="VG" type="update">
      CachingSource: Refactored to remove Cron block dependency, and more.
    </action>


Added:
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java   (contents, props changed)
      - copied, changed from r433250, cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelayRefresher.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/SourceRefresher.java   (contents, props changed)
      - copied, changed from r433250, cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/Refresher.java
Removed:
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelayRefresher.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/Refresher.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/UpdateTarget.java
Modified:
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSource.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/resources/META-INF/legacy/xconf/cocoon-scratchpad-caching-source.xconf
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/resources/org/apache/cocoon/components/readme.txt
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/test/java/org/apache/cocoon/components/source/impl/CachingSourceTestCase.java
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/test/resources/org/apache/cocoon/components/source/impl/CachingSourceTestCase.xtest
    cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/status.xml

Modified: cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSource.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSource.java?rev=433746&r1=433745&r2=433746&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSource.java (original)
+++ cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSource.java Tue Aug 22 13:46:55 2006
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
+ * Copyright 1999-2006 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.
@@ -26,6 +26,15 @@
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.impl.validity.ExpiresValidity;
+import org.apache.excalibur.source.impl.validity.TimeStampValidity;
+import org.apache.excalibur.xml.sax.XMLizable;
+import org.apache.excalibur.xmlizer.XMLizer;
+
 import org.apache.cocoon.CascadingIOException;
 import org.apache.cocoon.ProcessingException;
 import org.apache.cocoon.caching.Cache;
@@ -37,80 +46,80 @@
 import org.apache.cocoon.components.sax.XMLByteStreamInterpreter;
 import org.apache.cocoon.xml.ContentHandlerWrapper;
 import org.apache.cocoon.xml.XMLConsumer;
-import org.apache.excalibur.source.Source;
-import org.apache.excalibur.source.SourceException;
-import org.apache.excalibur.source.SourceNotFoundException;
-import org.apache.excalibur.source.SourceValidity;
-import org.apache.excalibur.source.impl.validity.ExpiresValidity;
-import org.apache.excalibur.source.impl.validity.TimeStampValidity;
-import org.apache.excalibur.xml.sax.XMLizable;
-import org.apache.excalibur.xmlizer.XMLizer;
+
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 /**
  * 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
+ * a given period of time.
  *
  * <h2>Syntax for Protocol</h2>
- * <p>
- * cached:http://www.apache.org/[?cocoon:cache-expires=60&cocoon:cache-name=main]
- * </p>
- * <p>
- * The above examples show how the real source <code>http://www.apache.org</code>
+ * <pre>
+ *   cached:http://www.apache.org/[?cocoon:cache-expires=60&cocoon:cache-name=main]
+ * </pre>
+ *
+ * <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
- * <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.
+ * <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.
  * 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,
- * the cached contents will be thrown out and updated immediately and unconditionally.
- * <p>
+ * always. Value <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 $Id$
  */
 public class CachingSource extends AbstractLogEnabled
-implements Source, Serviceable, Initializable, XMLizable {
+                           implements Serviceable, Initializable, XMLizable,
+                                      Source {
 
     // ---------------------------------------------------- Constants
-    
+
     public static final String CACHE_EXPIRES_PARAM = "cache-expires";
     public static final String CACHE_NAME_PARAM = "cache-name";
-    
+
+    private static final SourceMeta DUMMY = new SourceMeta();
+
     // ---------------------------------------------------- Instance variables
-    
+
+    /** The used protocol */
+    final protected String protocol;
+
+    /** The full URI string */
+    final protected String uri;
+
+    /** The full URI string of the underlying source */
+    final protected String sourceUri;
+
+    /** The source object for the real content */
+    protected Source source;
+
+
     /** The ServiceManager */
     protected ServiceManager manager;
 
     /** The current cache */
     protected Cache cache;
 
-    /** The source object for the real content */
-    protected Source source;
 
     /** The cached response (if any) */
-    protected CachedSourceResponse response;
+    private 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;
+    private boolean freshMeta;
 
     /** The key used in the store */
     final protected IdentifierCacheKey cacheKey;
-    
+
     /** number of seconds before cached object becomes invalid */
     final protected int expires;
-    
+
     /** cache key extension */
     final protected String cacheName;
-    
+
     /** asynchronic refresh strategy ? */
     final protected boolean async;
 
@@ -121,6 +130,7 @@
      */
     public CachingSource(final String protocol,
                          final String uri,
+                         final String sourceUri,
                          final Source source,
                          final int expires,
                          final String cacheName,
@@ -128,6 +138,7 @@
                          final boolean eventAware) {
         this.protocol = protocol;
         this.uri = uri;
+        this.sourceUri = sourceUri;
         this.source = source;
         this.expires = expires;
         this.cacheName = cacheName;
@@ -141,6 +152,8 @@
         this.cacheKey = new IdentifierCacheKey(key, false);
     }
 
+    // ---------------------------------------------------- Lifecycle
+
     /**
      * Set the ServiceManager.
      */
@@ -152,16 +165,8 @@
      * Initialize the Source.
      */
     public void initialize() throws Exception {
-
         boolean checkValidity = true;
-        if (this.expires == -1) {
-            if (getLogger().isDebugEnabled()) {
-                getLogger().debug("Using cached response if available.");
-            }
-            checkValidity = false;
-        }
-
-        if (this.async && this.expires != 0) {
+        if (this.async && this.expires > 0 || this.expires == -1) {
             if (getLogger().isDebugEnabled()) {
                 getLogger().debug("Using cached response if available.");
             }
@@ -175,9 +180,7 @@
                 getLogger().debug("No cached response found.");
             }
             checkValidity = false;
-        }
-
-        if (this.expires == 0) {
+        } else if (this.expires == 0) {
             if (getLogger().isDebugEnabled()) {
                 getLogger().debug("Not using cached response.");
             }
@@ -186,13 +189,9 @@
         }
 
         if (checkValidity && !checkValidity()) {
-            this.response = null;
-            // remove it if it no longer exists
-            if (!this.source.exists()) {
-                remove();
-            }
+            // remove invalid response
+            clearResponse();
         }
-
     }
 
     /**
@@ -204,98 +203,106 @@
         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 object management
+
+    private CachedSourceResponse getResponse() {
         CachedSourceResponse response = this.response;
         if (response == null) {
             response = new CachedSourceResponse(getCacheValidities());
-            storeResponse = true;
-        }
-        if (response.getExtra() == null) {
-            response.setExtra(readMeta(this.source));
-            this.freshMeta = true;
         }
+        return response;
+    }
+
+    private void setResponse(CachedSourceResponse response) throws IOException {
         this.response = response;
-        if (storeResponse) {
+        if (this.expires != 0) {
             try {
                 this.cache.store(this.cacheKey, this.response);
-            }
-            catch(ProcessingException e) {
+            } catch (ProcessingException e) {
                 throw new CascadingIOException("Failure storing response.", e);
             }
         }
     }
 
+    private void clearResponse() {
+        this.response = null;
+        this.cache.remove(this.cacheKey);
+    }
+
     /**
-     * Initialize the cached response with binary contents.
+     * Initialize the cached response with meta info.
      *
      * @throws IOException  if an the binary response could not be initialized
      */
-    protected void initBinaryResponse() throws IOException {
-        boolean storeResponse = false;
-        /* delay caching the response until we have a valid new one */
-        CachedSourceResponse response = this.response;
-        if (response == null) {
-            response = new CachedSourceResponse(getCacheValidities());
-            storeResponse = true;
+    protected SourceMeta getResponseMeta() throws IOException {
+        CachedSourceResponse response = getResponse();
+
+        if (response.getExtra() == null) {
+            response.setExtra(readMeta(this.source));
+            this.freshMeta = true;
+            setResponse(response);
         }
+
+        return (SourceMeta) response.getExtra();
+    }
+
+    /**
+     * Initialize the cached response with meta and binary contents.
+     *
+     * @throws IOException  if an the binary response could not be initialized
+     */
+    protected byte[] getBinaryResponse() throws IOException {
+        CachedSourceResponse response = getResponse();
+
         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) {
-            try {
-                this.cache.store(this.cacheKey, this.response);
-            }
-            catch(ProcessingException e) {
-                throw new CascadingIOException("Failure storing response.", e);
+            if (((SourceMeta) response.getExtra()).exists()) {
+                response.setBinaryResponse(readBinaryResponse(this.source));
             }
+            setResponse(response);
         }
+
+        return response.getBinaryResponse();
     }
 
     /**
-     * Initialize the cached response with XML contents.
+     * Initialize the cached response with meta, binary, and XML contents.
      *
-     * @param refresh  whether to force refresh.
      * @throws SAXException  if something happened during xml processing
      * @throws IOException  if an IO level error occured
      * @throws CascadingIOException  wraps all other exception types
      */
-    protected void initXMLResponse(boolean refresh) throws SAXException, IOException, CascadingIOException {
-        boolean storeResponse = false;
-        /* delay caching the response until we have a valid new one */
-        CachedSourceResponse response = this.response;
-        if (response == null) {
-            response = new CachedSourceResponse(getCacheValidities());
-            storeResponse = true;
-        }
-        if (response.getXMLResponse() == null || refresh) {
-            byte[] binary = response.getBinaryResponse();
-            response.setXMLResponse(readXMLResponse(this.source, binary, this.manager));
+    protected byte[] getXMLResponse() throws SAXException, IOException, CascadingIOException {
+        CachedSourceResponse response = getResponse();
+
+        if (response.getXMLResponse() == null) {
             if (!this.freshMeta) {
                 /* always refresh meta in this case */
                 response.setExtra(readMeta(this.source));
                 this.freshMeta = true;
             }
-        }
-        this.response = response;
-        if (storeResponse) {
-            try {
-                this.cache.store(this.cacheKey, this.response);
-            }
-            catch(ProcessingException e) {
-                throw new CascadingIOException("Failure storing response.", e);
+            if (((SourceMeta) response.getExtra()).exists()) {
+                if (response.getBinaryResponse() == null) {
+                    response.setBinaryResponse(readBinaryResponse(this.source));
+                }
+                response.setXMLResponse(readXMLResponse(this.source, response.getBinaryResponse(), this.manager));
             }
+            setResponse(response);
+        }
+
+        return response.getXMLResponse();
+    }
+
+    private SourceMeta getMeta() {
+        try {
+            return getResponseMeta();
+        } catch (IOException e) {
+            // Could not initialize meta. Return default meta values.
+            return DUMMY;
         }
     }
 
@@ -313,7 +320,7 @@
      * is not possible to determine the length.
      */
     public long getContentLength() {
-        return -1;
+        return getMeta().getContentLength();
     }
 
     /**
@@ -322,12 +329,7 @@
      *         or 0 if it is unknown
      */
     public long getLastModified() {
-        try {
-            initMetaResponse();
-        } catch (IOException io) {
-            return 0;
-        }
-        return ((SourceMeta) this.response.getExtra()).getLastModified();
+        return getMeta().getLastModified();
     }
 
     /**
@@ -336,12 +338,7 @@
      * this can be null.
      */
     public String getMimeType() {
-        try {
-            initMetaResponse();
-        } catch (IOException io) {
-            return null;
-        }
-        return ((SourceMeta) this.response.getExtra()).getMimeType();
+        return getMeta().getMimeType();
     }
 
     /**
@@ -349,11 +346,10 @@
      */
     public InputStream getInputStream() throws IOException, SourceException {
         try {
-            initBinaryResponse();
+            return new ByteArrayInputStream(getBinaryResponse());
         } catch (IOException se) {
             throw new SourceException("Failure getting input stream", se);
         }
-        return new ByteArrayInputStream(this.response.getBinaryResponse());
     }
 
     /**
@@ -367,7 +363,7 @@
      * @see org.apache.excalibur.source.Source#exists()
      */
     public boolean exists() {
-        return this.source.exists();
+        return getMeta().exists();
     }
 
     /**
@@ -386,57 +382,49 @@
 
     /**
      * Refresh this object and update the last modified date
-     * and content length. This method will try to refresh the
-     * cached contents.
+     * and content length.
+     *
+     * This method will try to refresh the cached meta data
+     * and content only if cached content is expired.
      */
     public void refresh() {
-        this.source.refresh();
         if (response != null && checkValidity()) {
-            if (getLogger().isDebugEnabled()) {
-                getLogger().debug("Cached response is still valid for source " + this.uri + ".");
-            }
+            return;
         }
-        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);
+
+        this.source.refresh();
+
+        CachedSourceResponse response = getResponse();
+        try {
+            // always refresh meta data
+            SourceMeta meta = readMeta(source);
+            response.setExtra(meta);
+
+            if (meta.exists()) {
+                // only create objects that are cached
+                if (response.getBinaryResponse() != null) {
+                    response.setBinaryResponse(readBinaryResponse(source));
                 }
-                catch (Exception e) {
-                    getLogger().warn("Error refreshing source " + this.uri +
-                        "Cached response (if any) may be stale.", e);
+                if (response.getXMLResponse() != null) {
+                    response.setXMLResponse(readXMLResponse(source, response.getBinaryResponse(), this.manager));
                 }
-            }
-            else if (this.response != null) {
+            } else {
                 if (getLogger().isDebugEnabled()) {
-                    getLogger().debug("Source " + this.uri + " no longer exists." +
-                        " Throwing out cached response.");
+                    getLogger().debug("Source " + this.uri + " does not exist.");
                 }
-                remove();
+                // clear cached data
+                response.setBinaryResponse(null);
+                response.setXMLResponse(null);
             }
+
+            // Even if source does not exist, cache that fact.
+            setResponse(response);
+        } catch (Exception e) {
+            getLogger().warn("Error refreshing source " + this.uri +
+                             ". Cached response (if any) may be stale.", e);
         }
     }
-    
-    protected void remove() {
-        this.cache.remove(this.cacheKey);
-    }
-    
+
     // ---------------------------------------------------- XMLizable implementation
 
     /**
@@ -444,17 +432,16 @@
      */
     public void toSAX(ContentHandler contentHandler) throws SAXException {
         try {
-            initXMLResponse(false);
             XMLByteStreamInterpreter deserializer = new XMLByteStreamInterpreter();
             if (contentHandler instanceof XMLConsumer) {
                 deserializer.setConsumer((XMLConsumer) contentHandler);
             } else {
                 deserializer.setConsumer(new ContentHandlerWrapper(contentHandler));
             }
-            deserializer.deserialize(this.response.getXMLResponse());
-        } catch(CascadingIOException e) {
+            deserializer.deserialize(getXMLResponse());
+        } catch (CascadingIOException e) {
             throw new SAXException(e.getMessage(), (Exception) e.getCause());
-        } catch(IOException e) {
+        } catch (IOException e) {
             throw new SAXException("Failure reading SAX response.", e);
         }
     }
@@ -465,14 +452,14 @@
      * Return the uri of the cached source.
      */
     protected String getSourceURI() {
-        return this.source.getURI();
+        return this.sourceUri;
     }
 
     /**
      * Return the used key.
      */
-    protected IdentifierCacheKey getCacheKey() {
-        return this.cacheKey;
+    protected String getCacheKey() {
+        return this.cacheKey.getKey();
     }
 
     /**
@@ -493,17 +480,12 @@
     protected byte[] readXMLResponse(Source source, byte[] binary, ServiceManager manager)
     throws SAXException, IOException, CascadingIOException {
         XMLizer xmlizer = null;
-        byte[] result = null;
         try {
             XMLByteStreamCompiler serializer = new XMLByteStreamCompiler();
 
             if (source instanceof XMLizable) {
                 ((XMLizable) source).toSAX(serializer);
-            }
-            else {
-                if (binary == null) {
-                    binary = readBinaryResponse(source);
-                }
+            } else {
                 final String mimeType = source.getMimeType();
                 if (mimeType != null) {
                     xmlizer = (XMLizer) manager.lookup(XMLizer.ROLE);
@@ -513,13 +495,15 @@
                                   serializer);
                 }
             }
-            result = (byte[]) serializer.getSAXFragment();
-        } catch (ServiceException se) {
-            throw new CascadingIOException("Missing service dependency.", se);
+
+            return (byte[]) serializer.getSAXFragment();
+        } catch (ServiceException e) {
+            throw new CascadingIOException("Missing service dependency.", e);
         } finally {
-            manager.release(xmlizer);
+            if (xmlizer != null) {
+                manager.release(xmlizer);
+            }
         }
-        return result;
     }
 
     /**
@@ -545,69 +529,51 @@
 
     /**
      * Read meta data from source.
-     *
-     * @return source meta data
-     * @throws IOException
      */
-    protected final SourceMeta readMeta(Source source) throws IOException {
-        SourceMeta meta = createMeta();
-        initMeta(meta, source);
-        return meta;
-    }
-
-    protected SourceMeta createMeta() {
-        return new SourceMeta();
+    protected SourceMeta readMeta(Source source) throws SourceException {
+        return new SourceMeta(source);
     }
 
-    protected void initMeta(SourceMeta meta, Source source) throws IOException {
-        final long lastModified = source.getLastModified();
-        if (lastModified > 0) {
-            meta.setLastModified(lastModified);
-        }
-        else {
-            meta.setLastModified(System.currentTimeMillis());
+    private boolean checkValidity() {
+        if (this.response == null) {
+            return false;
         }
-        meta.setMimeType(source.getMimeType());
-    }
 
-    private boolean checkValidity() {
-        if (this.response == null) return false;
+        if (eventAware) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Cached response of source does not expire");
+            }
+            return true;
+        }
 
         final SourceValidity[] validities = this.response.getValidityObjects();
         boolean valid = true;
-        if (! eventAware) {
-            final ExpiresValidity expiresValidity = (ExpiresValidity) validities[0];
-            final SourceValidity sourceValidity = validities[1];
 
-            if (expiresValidity.isValid() != SourceValidity.VALID) {
+        final ExpiresValidity expiresValidity = (ExpiresValidity) validities[0];
+        final SourceValidity sourceValidity = validities[1];
+
+        if (expiresValidity.isValid() != SourceValidity.VALID) {
+            int validity = sourceValidity != null? sourceValidity.isValid() : SourceValidity.INVALID;
+            if (validity == SourceValidity.INVALID ||
+                    validity == SourceValidity.UNKNOWN &&
+                            sourceValidity.isValid(source.getValidity()) != SourceValidity.VALID) {
                 if (getLogger().isDebugEnabled()) {
-                    getLogger().debug("Cached response of source " + getSourceURI() + " is expired.");
+                    getLogger().debug("Response expired, invalid for " + getSourceURI());
                 }
-                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 {
+                valid = false;
+            } else {
                 if (getLogger().isDebugEnabled()) {
-                    getLogger().debug("Cached response of source " + getSourceURI() + " is NOT expired.");
+                    getLogger().debug("Response expired, still valid for " + getSourceURI());
                 }
+                // set new expiration period
+                validities[0] = new ExpiresValidity(getExpiration());
             }
         } else {
-            // assert(validities.length == 1 && validities[0] instanceof EventValidity)
             if (getLogger().isDebugEnabled()) {
-                getLogger().debug("Cached response of source does not expire");
+                getLogger().debug("Response not expired for " + getSourceURI());
             }
         }
+
         return valid;
     }
 
@@ -615,56 +581,71 @@
         if (this.cache instanceof EventAware) {
             // use event caching strategy, the associated event is the source uri
             return new SourceValidity[] { new EventValidity(new NamedEvent(this.source.getURI())) };
-        }
-        else {
+        } else {
             // we need to store both the cache expiration and the original source validity
             // the former is to determine whether to recheck the latter (see checkValidity)
             return new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity() };
         }
     }
 
-    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 boolean exists;
+        private long contentLength;
+        private String mimeType;
+        private long lastModified;
+
+        public SourceMeta() {
+        }
+
+        public SourceMeta(Source source) {
+            setExists(source.exists());
+            if (exists()) {
+                setContentLength(source.getContentLength());
+                final long lastModified = source.getLastModified();
+                if (lastModified > 0) {
+                    setLastModified(lastModified);
+                } else {
+                    setLastModified(System.currentTimeMillis());
+                }
+                setMimeType(source.getMimeType());
+            } else {
+                contentLength = -1;
+            }
+        }
+
+        protected boolean exists() {
+            return exists;
+        }
 
-        private String m_mimeType;
-        private long m_lastModified;
-        private boolean m_exists;
+        protected void setExists(boolean exists) {
+            this.exists = exists;
+        }
 
-        protected String getMimeType() {
-            return m_mimeType;
+        protected long getContentLength() {
+            return contentLength;
         }
 
-        protected void setMimeType(String mimeType) {
-            m_mimeType = mimeType;
+        protected void setContentLength(long contentLength) {
+            this.contentLength = contentLength;
         }
 
         protected long getLastModified() {
-            return m_lastModified;
+            return lastModified;
         }
 
         protected void setLastModified(long lastModified) {
-            m_lastModified = lastModified;
+            this.lastModified = lastModified;
         }
 
-        protected boolean exists() {
-            return m_exists;
+        protected String getMimeType() {
+            return mimeType;
         }
 
-        protected void setExists(boolean exists) {
-            m_exists = exists;
+        protected void setMimeType(String mimeType) {
+            this.mimeType = mimeType;
         }
-
     }
-
-	public void setCache(Cache cache) {
-		this.cache = cache;
-	}
 }

Modified: cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java?rev=433746&r1=433745&r2=433746&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java (original)
+++ cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java Tue Aug 22 13:46:55 2006
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
+ * Copyright 1999-2006 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.
@@ -26,14 +26,11 @@
 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,19 +40,23 @@
 import org.apache.excalibur.source.TraversableSource;
 import org.apache.excalibur.source.URIAbsolutizer;
 
+import org.apache.cocoon.caching.Cache;
+import org.apache.cocoon.components.source.InspectableSource;
+
 /**
  * This class implements a proxy like source caches the contents of the source
- * 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
- * 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>
+ * 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>
+ *
+ * <ul>
+ * <li>Synchronously. This means that the cached contents are checked for validity
+ * and thrown out on the current thread.
+ * <li>Asynchronously. A runnable task is created to invalidate and update the
+ * cached response in the backgound.
+ * </ul>
  *
  * <h2>Protocol syntax</h2>
  * <p>
@@ -89,7 +90,7 @@
  *  <td>Role of component used for refreshing sources.</td>
  *  <td>opt</td>
  *  <td>String</td>
- *  <td><code>{@link org.apache.cocoon.components.source.impl.Refresher#ROLE}</code></td>
+ *  <td><code>{@link org.apache.cocoon.components.source.impl.SourceRefresher#ROLE}</code></td>
  * </tr>
  * <tr>
  *  <th>async (boolean)</th>
@@ -118,17 +119,16 @@
  * @since 2.1.1
  */
 public class CachingSourceFactory extends AbstractLogEnabled
-implements SourceFactory, URIAbsolutizer, Serviceable, Configurable, Disposable, ThreadSafe
-{
+                                  implements Serviceable, Configurable, Disposable,
+                                             ThreadSafe, URIAbsolutizer, SourceFactory {
 
     // ---------------------------------------------------- Constants
-    
-    public static final String ASYNC_PARAM = "async";
-    public static final String EVENT_AWARE_PARAM = "event-aware";
-    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";
+
+    private static final String ASYNC_PARAM = "async";
+    private static final String EVENT_AWARE_PARAM = "event-aware";
+    private static final String CACHE_ROLE_PARAM = "cache-role";
+    private static final String REFRESHER_ROLE_PARAM = "refresher-role";
+    private static final String DEFAULT_EXPIRES_PARAM = "default-expires";
 
     // ---------------------------------------------------- Instance variables
 
@@ -151,7 +151,7 @@
     private int defaultExpires;
 
     /** Has the lazy initialization been done? */
-    private boolean isInitialized;
+    private volatile boolean isInitialized;
 
     /** The <code>ServiceManager</code> */
     protected ServiceManager manager;
@@ -160,7 +160,7 @@
     protected SourceResolver resolver;
 
     /** The refresher */
-    protected Refresher refresher;
+    protected SourceRefresher refresher;
 
     /** The cache */
     protected Cache cache;
@@ -189,19 +189,20 @@
 
         // 'cache-role' parameter
         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_PARAM, Refresher.ROLE);
-            if (this.getLogger().isDebugEnabled()) {
-                this.getLogger().debug("Using refresher " + this.refresherRole);
-            }
+            this.refresherRole = parameters.getParameter(REFRESHER_ROLE_PARAM, SourceRefresher.ROLE);
         }
 
         this.defaultExpires = parameters.getParameterAsInteger(DEFAULT_EXPIRES_PARAM, -1);
+
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Using cache " + this.cacheRole);
+            if (this.async) {
+                getLogger().debug("Using refresher " + this.refresherRole);
+            }
+        }
     }
 
     /**
@@ -216,46 +217,59 @@
             // we were waiting
             return;
         }
+
         try {
             this.resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
         } catch (ServiceException se) {
             throw new SourceException("Missing service dependency: " + SourceResolver.ROLE, se);
         }
+
         try {
             this.cache = (Cache) this.manager.lookup(this.cacheRole);
         } catch (ServiceException se) {
             throw new SourceException("Missing service dependency: " + this.cacheRole, se);
         }
+
         if (this.async) {
             try {
-                this.refresher = (Refresher) this.manager.lookup(this.refresherRole);
+                this.refresher = (SourceRefresher) this.manager.lookup(this.refresherRole);
             } catch (ServiceException se) {
-                // clean up
-                if (this.resolver != null){
-                    this.manager.release(this.resolver);
-                    this.resolver = null;
-                }
                 throw new SourceException("Missing service dependency: " + this.refresherRole, se);
             }
         }
+
         this.isInitialized = true;
     }
 
     /* (non-Javadoc)
-     * @see org.apache.avalon.framework.activity.Disposable#dispose()
+     * @see Disposable#dispose()
      */
     public void dispose() {
-        if (this.manager != null) {
-            this.manager.release(this.resolver);
+        if (this.refresher != null) {
             this.manager.release(this.refresher);
             this.refresher = null;
-            this.manager = null;
+        }
+        if (this.cache != null) {
+            this.manager.release(this.cache);
+            this.cache = null;
+        }
+        if (this.resolver != null) {
+            this.manager.release(this.resolver);
             this.resolver = null;
         }
+        this.manager = null;
     }
 
     // ---------------------------------------------------- SourceFactory implementation
 
+    protected String getScheme() {
+        return this.scheme;
+    }
+
+    protected boolean isAsync() {
+        return this.async;
+    }
+
     /**
      * Get a <code>Source</code> object.
      * @param parameters This is optional.
@@ -263,8 +277,8 @@
     public Source getSource(final String location, final Map parameters)
     throws MalformedURLException, IOException {
 
-        if (this.getLogger().isDebugEnabled() ) {
-            this.getLogger().debug("Creating source object for " + location);
+        if (getLogger().isDebugEnabled() ) {
+            getLogger().debug("Creating source " + location);
         }
 
         // we must do lazy initialization because of cyclic dependencies
@@ -277,23 +291,22 @@
         if (index == -1) {
             throw new MalformedURLException("This Source requires a subprotocol to be specified.");
         }
-        String uri = location.substring(index+1);
+
+        String uri = location.substring(index + 1);
 
         // parse the query string
         SourceParameters sp = null;
-        String queryString = null;
         index = uri.indexOf('?');
         if (index != -1) {
-            queryString = uri.substring(index+1);
-            uri = uri.substring(0,index);
-            sp = new SourceParameters(queryString);
+            sp = new SourceParameters(uri.substring(index + 1));
+            uri = uri.substring(0, index);
         }
 
         // put caching source specific query string parameters
         // into a Parameters object
         final Parameters params = new Parameters();
-        SourceParameters remainingParameters = (SourceParameters) sp.clone();
         if (sp != null) {
+            SourceParameters remainingParameters = (SourceParameters) sp.clone();
             final Iterator names = sp.getParameterNames();
             while (names.hasNext()) {
                 String name = (String) names.next();
@@ -302,113 +315,86 @@
                     remainingParameters.removeParameter(name);
                 }
             }
-            queryString = remainingParameters.getEncodedQueryString();
+            String queryString = remainingParameters.getEncodedQueryString();
             if (queryString != null) {
                 uri += "?" + queryString;
             }
         }
 
-        int expires = params.getParameterAsInteger(CachingSource.CACHE_EXPIRES_PARAM, this.defaultExpires);
+        int expires = params.getParameterAsInteger(CachingSource.CACHE_EXPIRES_PARAM, defaultExpires);
         String cacheName = params.getParameter(CachingSource.CACHE_NAME_PARAM, null);
 
-        final CachingSource source = createCachingSource(this.resolver.resolveURI(uri),
-                                                      this.scheme,
-                                                      location,
-                                                      expires,
-                                                      cacheName,
-                                                      this.async,
-                                                      this.cache,
-                                                      getLogger(),
-                                                      manager);
-        
-        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);
-        }
-
-        return source;
+        Source source = this.resolver.resolveURI(uri);
+        return createCachingSource(location, uri, source, expires, cacheName);
     }
 
     /**
-     * Factory method for creating a new CachingSource. Delegates to createCachingSource()
-     */
-    public static CachingSource newCachingSource(Source wrappedSource, 
-                                                 String scheme,
-                                                 String uri,
-                                                 int expires,
-                                                 String cacheName,
-                                                 boolean async,
-                                                 Cache cache,
-                                                 Logger logger,
-                                                 ServiceManager manager)
-                                                 throws SourceException {
-        return new CachingSourceFactory().createCachingSource(wrappedSource, scheme, uri, expires, cacheName, async, cache, logger, manager);
-    }
-    
-    /**
      * Actually creates a new CachingSource. Can be overriden in subclasses
      */
-    protected CachingSource createCachingSource(Source wrappedSource, 
-                                                 String scheme,
-                                                 String uri,
-                                                 int expires,
-                                                 String cacheName,
-                                                 boolean async,
-                                                 Cache cache,
-                                                 Logger logger,
-                                                 ServiceManager manager)
+    protected CachingSource createCachingSource(String uri,
+                                                String wrappedUri,
+                                                Source wrappedSource,
+                                                int expires,
+                                                String cacheName)
     throws SourceException {
-        
+
         CachingSource source;
+        
         if (wrappedSource instanceof TraversableSource) {
             if (wrappedSource instanceof InspectableSource) {
-                source = new InspectableTraversableCachingSource(scheme,
+                source = new InspectableTraversableCachingSource(this,
+                                                                 getScheme(),
                                                                  uri,
+                                                                 wrappedUri,
                                                                  (InspectableSource) wrappedSource,
                                                                  expires,
                                                                  cacheName,
-                                                                 async,
+                                                                 isAsync(),
                                                                  eventAware);
             } else {
-                source = new TraversableCachingSource(scheme,
+                source = new TraversableCachingSource(this,
+                                                      getScheme(),
                                                       uri,
+                                                      wrappedUri,
                                                       (TraversableSource) wrappedSource,
                                                       expires,
                                                       cacheName,
-                                                      async,
+                                                      isAsync(),
                                                       eventAware);
             }
         } else {
-            source = new CachingSource(scheme,
+            source = new CachingSource(getScheme(),
                                        uri,
+                                       wrappedUri,
                                        wrappedSource,
                                        expires,
                                        cacheName,
-                                       async,
+                                       isAsync(),
                                        eventAware);
         }
 
         // set the required components directly for speed
-        source.cache = cache;
+        source.cache = this.cache;
 
-        ContainerUtil.enableLogging(source, logger);
+        ContainerUtil.enableLogging(source, getLogger());
         try {
             // call selected avalon lifecycle interfaces. Mmmh.
-            ContainerUtil.service(source, manager);
+            ContainerUtil.service(source, this.manager);
             ContainerUtil.initialize(source);
-        } catch (ServiceException se) {
-            throw new SourceException("Unable to initialize source.", se);
+        } catch (ServiceException e) {
+            throw new SourceException("Unable to initialize source.", e);
         } catch (Exception e) {
             throw new SourceException("Unable to initialize source.", e);
         }
+
+        if (this.async && expires > 0) {
+            // schedule it with the refresher
+            final Parameters params = new Parameters();
+            params.setParameter(SourceRefresher.PARAM_CACHE_INTERVAL,
+                                String.valueOf(source.getExpiration()));
+            this.refresher.refresh(source.getCacheKey(), source.getURI(), params);
+        }
+
         return source;
     }
 
@@ -417,11 +403,12 @@
      */
     public void release(Source source) {
         if (source instanceof CachingSource) {
-            if (this.getLogger().isDebugEnabled() ) {
-                this.getLogger().debug("Releasing source " + source.getURI());
+            if (getLogger().isDebugEnabled() ) {
+                getLogger().debug("Releasing source " + source.getURI());
             }
-            resolver.release(((CachingSource) source).source);
-            ((CachingSource) source).dispose();
+            CachingSource caching = (CachingSource) source;
+            resolver.release(caching.source);
+            caching.dispose();
         }
     }
 
@@ -434,6 +421,5 @@
     public String absolutize(String baseURI, String location) {
         return SourceUtil.absolutize(baseURI, location, true);
     }
-
 
 }

Copied: cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java (from r433250, cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelayRefresher.java)
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java?p2=cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java&p1=cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelayRefresher.java&r1=433250&r2=433746&rev=433746&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelayRefresher.java (original)
+++ cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java Tue Aug 22 13:46:55 2006
@@ -1,12 +1,12 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
- * 
+ * Copyright 1999-2006 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.
@@ -16,18 +16,21 @@
 package org.apache.cocoon.components.source.impl;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
 import java.util.Collections;
+import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
 import org.apache.avalon.framework.CascadingException;
+import org.apache.avalon.framework.CascadingRuntimeException;
 import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.configuration.Configurable;
 import org.apache.avalon.framework.configuration.Configuration;
 import org.apache.avalon.framework.configuration.ConfigurationException;
 import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
@@ -35,343 +38,389 @@
 import org.apache.avalon.framework.context.ContextException;
 import org.apache.avalon.framework.context.Contextualizable;
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
-import org.apache.avalon.framework.parameters.ParameterException;
-import org.apache.avalon.framework.parameters.Parameterizable;
 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.Constants;
-import org.apache.cocoon.caching.IdentifierCacheKey;
-import org.apache.cocoon.components.cron.CronJob;
-import org.apache.cocoon.components.cron.JobScheduler;
-import org.apache.cocoon.components.source.SourceUtil;
-import org.apache.cocoon.util.NetUtils;
-import org.apache.excalibur.source.ModifiableSource;
 import org.apache.excalibur.source.Source;
 import org.apache.excalibur.source.SourceException;
 import org.apache.excalibur.source.SourceResolver;
 
+import org.apache.cocoon.Constants;
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.Processor;
+import org.apache.cocoon.components.source.SourceUtil;
+import org.apache.cocoon.components.thread.RunnableManager;
+import org.apache.cocoon.environment.background.BackgroundEnvironment;
+import org.apache.cocoon.environment.internal.EnvironmentHelper;
+import org.apache.cocoon.util.NetUtils;
+
 /**
  * Default implementation of the refresher.
- * 
+ *
  * @since 2.1.1
  * @version $Id$
  */
-public class DelayRefresher extends AbstractLogEnabled
-implements Contextualizable, Serviceable, Parameterizable, Disposable, ThreadSafe, Refresher, CronJob {
-    
-    private static final String PARAM_CACHE_ROLE          = "cache-role";
-    private static final String PARAM_CACHE_EXPIRES       = "cache-expires";
-    private static final String PARAM_UPDATE_TARGET_ROLE  = "update-target-role";
-    private static final String PARAM_WRITE_INTERVAL      = "write-interval";
-	private static final String PARAM_WRITE_FILE          = "write-file";
-	
+public class DelaySourceRefresher extends AbstractLogEnabled
+                                  implements Contextualizable, Serviceable, Configurable,
+                                             Disposable, ThreadSafe, SourceRefresher {
+
+    private static final String PARAM_WRITE_FILE          = "write-file";
+
 	private static final String DEFAULT_WRITE_FILE        = "refresher-targets.xml";
-	
-    private static final String CACHE_KEY                 = "cache-key";
-	
+
     private static final String TAGNAME_TARGET            = "target";
-	private static final String ATTR_CACHE                = "cache";
-	private static final String ATTR_EXPIRES              = "expires";
-    private static final String ATTR_KEY                  = "key";
+	private static final String ATTR_KEY                  = "key";
 	private static final String ATTR_URI                  = "uri";
-    
+    private static final String ATTR_INTERVAL             = "interval";
+
+
+    protected Context context;
+
     // service dependencies
     protected ServiceManager manager;
     protected SourceResolver resolver;
-	protected JobScheduler scheduler;
-    
-    // the role name of the update CronJob
-    protected String updateTarget;
-    
+    protected RunnableManager runnable;
+
     // the scheduled targets to be persisted and recovered upon restart
     protected Map entries = Collections.synchronizedMap(new HashMap());
-    
+
     // the cocoon working directory
     protected File workDir;
-    
-    // the source to persist entries to
-    protected Source writeSource;
-    
+
+    /** The source to persist refresher entries into */
+    protected File configFile;
+
     // whether anything changed to the entries since last persisting them
-    protected boolean changed = false;
-    
-    
+    protected volatile boolean changed;
+
+    protected ConfigurationTask configurationTask;
+
+
     // ---------------------------------------------------- Lifecycle
-    
-    public DelayRefresher() {
-    }
 
 	/* (non-Javadoc)
-	 * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
+	 * @see Contextualizable#contextualize(Context)
 	 */
 	public void contextualize(Context context) throws ContextException {
-		this.workDir = (File) context.get(Constants.CONTEXT_WORK_DIR);
+        this.context = context;
+        this.workDir = (File) context.get(Constants.CONTEXT_WORK_DIR);
 	}
-    
+
     /* (non-Javadoc)
-     * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
+     * @see Serviceable#service(ServiceManager)
      */
     public void service(ServiceManager manager) throws ServiceException {
         this.manager = manager;
-        this.scheduler = (JobScheduler) this.manager.lookup(JobScheduler.ROLE);
         this.resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
+        this.runnable = (RunnableManager) this.manager.lookup(RunnableManager.ROLE);
     }
-    
-    /* (non-Javadoc)
-     * @see org.apache.avalon.framework.parameters.Parameterizable#parameterize(org.apache.avalon.framework.parameters.Parameters)
-     */
-    public void parameterize(Parameters parameters) throws ParameterException {
-        this.updateTarget = parameters.getParameter(PARAM_UPDATE_TARGET_ROLE, CronJob.ROLE + "/UpdateTarget");
-        int writeInterval = parameters.getParameterAsInteger(PARAM_WRITE_INTERVAL, 0);
-        if (writeInterval > 0) {
-            this.setupRefreshJobSource(parameters);
-            final Configuration conf = this.readRefreshJobConfiguration();
-            this.setupRefreshJobs(conf);
-            this.registerSelfWithScheduler(writeInterval);
-        }
-        else {
+
+    public void configure(Configuration configuration) throws ConfigurationException {
+        Parameters parameters = Parameters.fromConfiguration(configuration);
+        long interval = parameters.getParameterAsLong("interval", 0);
+        if (interval > 0) {
+            String fileName = parameters.getParameter(PARAM_WRITE_FILE, DEFAULT_WRITE_FILE);
+            this.configFile = new File(this.workDir, fileName);
+            if (this.configFile.exists() && !this.configFile.canWrite()) {
+                throw new ConfigurationException("Parameter 'write-source' resolves to not modifiable file: " +
+                                                 this.configFile);
+            }
+            if (!this.configFile.getParentFile().exists() && !this.configFile.getParentFile().mkdirs()) {
+                throw new ConfigurationException("Can not create parent directory for: " +
+                                                 this.configFile);
+            }
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Write source location: " + this.configFile);
+            }
+
+            setupRefreshJobs(readRefreshJobConfiguration());
+            startConfigurationTask(interval);
+        } else {
         	if (getLogger().isInfoEnabled()) {
 				getLogger().info("Not writing update targets to file.");
         	}
         }
+
+        // Setup any in-line configured tasks
+        setupRefreshJobs(configuration);
     }
 
     /* (non-Javadoc)
-     * @see org.apache.avalon.framework.activity.Disposable#dispose()
+     * @see Disposable#dispose()
      */
     public void dispose() {
-    	execute(null);
-        if (this.manager != null) {
-            this.manager.release(this.scheduler);
-            this.scheduler = null;
-            if (this.resolver != null) {
-				this.resolver.release(this.writeSource);
-                this.writeSource = null;
-                this.manager.release(this.resolver);
-                this.resolver = null;
-            }
-            this.manager = null;
+    	stopConfigurationTask();
+        if (this.runnable != null) {
+            this.manager.release(this.runnable);
+            this.runnable = null;
         }
+        if (this.resolver != null) {
+            this.manager.release(this.resolver);
+            this.resolver = null;
+        }
+        this.manager = null;
     }
-    
-    // ---------------------------------------------------- Refresher implementation
-    
+
+    // ---------------------------------------------------- SourceRefresher implementation
+
     /* (non-Javadoc)
-     * @see org.apache.cocoon.components.source.impl.Refresher#refresh(org.apache.cocoon.caching.SimpleCacheKey, java.lang.String, long, java.lang.String)
+     * @see SourceRefresher#refresh
      */
-    public void refresh(IdentifierCacheKey cacheKey,
+    public void refresh(String name,
                         String uri,
-                        String cacheRole,
                         Parameters parameters)
     throws SourceException {
-        
-        final String name = cacheKey.getKey();
-		final int expires = parameters.getParameterAsInteger(PARAM_CACHE_EXPIRES, -1);
-		
-		if (expires > 0) {
-			TargetConfiguration conf = (TargetConfiguration) this.entries.get(name);
-			if (conf == null) {
-				conf = new TargetConfiguration(cacheKey, uri, cacheRole, parameters);
-				try {
-					this.scheduler.addPeriodicJob(name,
-												  this.updateTarget,
-												  expires,
-												  true,
-												  conf.parameters,
-												  conf.map);
-				} catch (CascadingException e) {
-					throw new SourceException("Failure scheduling update job.", e);
-				}
-				this.entries.put(name, conf);
-			} else {
-				conf.update(uri, cacheRole, parameters);
-			}
-			
-			this.changed = true;
-		}
-    }
-    
-    // ---------------------------------------------------- CronJob implementation
-    
+		final long interval = parameters.getParameterAsLong(PARAM_CACHE_INTERVAL, -1);
+		if (uri != null && interval > 0) {
+            addRefreshSource(name, uri, interval, interval);
+		} else {
+            removeRefreshSource(name);
+        }
+    }
+
+    protected void addRefreshSource(String key, String uri, long delay, long interval) {
+        RefresherTask task = (RefresherTask) this.entries.get(key);
+        if (task == null) {
+            // New source added.
+            task = new RefresherTask(key, uri, interval);
+            task.enableLogging(getLogger());
+            this.entries.put(key, task);
+            this.runnable.execute(task, interval, interval);
+            this.changed = true;
+        } else if (task.interval != interval) {
+            // Existing source refresh interval updated.
+            task.update(uri, interval);
+            this.runnable.remove(task);
+            this.runnable.execute(task, interval, interval);
+            this.changed = true;
+        } else {
+            // No change.
+        }
+    }
+
+    protected void removeRefreshSource(String key) {
+        RefresherTask task = (RefresherTask) this.entries.get(key);
+        if (task != null) {
+            this.entries.remove(key);
+            this.runnable.remove(task);
+            this.changed = true;
+        }
+    }
+
+    // ---------------------------------------------------- Implementation
+
     /**
-     * Persists the job configurations.
+     *
      */
-    public void execute(String name) {
-        if (this.changed && this.writeSource != null) {
-        	// TODO when an error occurs during write, we wouldn't try again although the
-        	// list is still dirty. In addition, the access through the Iterator is not
-        	// synchronized -- Collections.synchronizedMap() doesn't suffice here. Thus,
-        	// modifications to the collection of sources while writing them to a file 
-        	// ay result in undeterministic behaviour (quoting the javadocs)
-            this.changed = false;
-            try {
-                final OutputStream stream = ((ModifiableSource) this.writeSource).getOutputStream();
-                final Writer writer = new OutputStreamWriter(stream);
-                
-                writer.write("<targets>\n");
-                final Iterator iter = this.entries.values().iterator();
-                while (iter.hasNext()) {
-                    this.writeRefreshJobConfiguration(writer, (TargetConfiguration) iter.next());
-                }
-                writer.write("</targets>\n");
-                writer.flush();
-                writer.close();
-            } catch (IOException e) {
-            	if (getLogger().isDebugEnabled()) {
-            		getLogger().debug("Error writing targets to file.", e);
-            	}
+    private Configuration readRefreshJobConfiguration() {
+        Source source = null;
+        SAXConfigurationHandler b = new SAXConfigurationHandler();
+        try {
+            if (this.configFile.exists()) {
+                source = this.resolver.resolveURI(this.configFile.toURL().toString());
+                SourceUtil.toSAX(this.manager, source, source.getMimeType(), b);
+            }
+        } catch (Exception ignore) {
+            getLogger().warn("Unable to read configuration from " + this.configFile);
+        } finally {
+            if (source != null) {
+                this.resolver.release(source);
             }
-            
         }
+        return b.getConfiguration();
     }
-    
-    /**
-	 * @param writeInterval
-	 */
-	private void registerSelfWithScheduler(int writeInterval) {
-		try {
-		    this.scheduler.addPeriodicJob(this.getClass().getName(), 
-		                                  this,
-		                                  writeInterval,
-		                                  true,
-		                                  null,
-		                                  null);
-		} catch (CascadingException ignore) {
-            if (this.getLogger().isDebugEnabled()) {
-				this.getLogger().debug("Registering self with scheduler, ignoring exception:", ignore);
-			}
-		}
-	}
 
-	/**
+
+    /**
 	 * @param conf
 	 */
 	private void setupRefreshJobs(final Configuration conf) {
-		if ( conf != null ) {
-		    final Configuration[] childs = conf.getChildren(TAGNAME_TARGET);
-		    if ( childs != null ) {
-		        for(int i=0; i < childs.length; i++) {
-		            try {
-		                this.setupSingleRefreshJob(childs[i]);
-		            } catch (CascadingException ignore) {
-		                if (this.getLogger().isDebugEnabled()) {
-							this.getLogger().debug("Setting up refresh job, ignoring exception:", ignore);
-						}
-		            }
-		        }
-		    }
-		}
-	}
+        if (conf != null) {
+            final Configuration[] children = conf.getChildren(TAGNAME_TARGET);
+            if (children != null) {
+                for (int i = 0; i < children.length; i++) {
+                    try {
+                        setupSingleRefreshJob(children[i]);
+                    } catch (CascadingException ignore) {
+                        if (getLogger().isDebugEnabled()) {
+                            getLogger().debug("Setting up refresh job, ignoring exception:", ignore);
+                        }
+                    }
+                }
+            }
+        }
+    }
 
-	/**
+    /**
 	 * @param conf
 	 * @throws ConfigurationException
-	 * @throws CascadingException
 	 */
-	private void setupSingleRefreshJob(final Configuration conf) throws ConfigurationException, CascadingException {
+	private void setupSingleRefreshJob(final Configuration conf) throws ConfigurationException {
 		try {
-            final String uri = NetUtils.decode(conf.getAttribute(ATTR_URI), "utf-8");
-    		final String cache = conf.getAttribute(ATTR_CACHE);
-            final int expires = conf.getAttributeAsInteger(ATTR_EXPIRES);
-    		final String key = NetUtils.decode(conf.getAttribute(ATTR_KEY), "utf-8");
-    		final IdentifierCacheKey cacheKey = new IdentifierCacheKey(key, false);
-    		
-            final Parameters parameters = Parameters.fromConfiguration(conf);
-            
-            final TargetConfiguration tc = new TargetConfiguration(cacheKey, uri, cache, parameters);
-    		
-            this.entries.put(key, tc);
-    		final String name = cacheKey.getKey();
-    		
-    		this.scheduler.addPeriodicJob(name,
-                                          this.updateTarget,
-    		                              expires,
-    		                              true,
-    		                              tc.parameters,
-    		                              tc.map);
-        } catch (UnsupportedEncodingException uee) {
-            throw new ConfigurationException("Unsupported encoding", uee);
+            String key = NetUtils.decode(conf.getAttribute(ATTR_KEY), "utf-8");
+            String uri = NetUtils.decode(conf.getAttribute(ATTR_URI), "utf-8");
+            long interval = conf.getAttributeAsLong(ATTR_INTERVAL);
+            addRefreshSource(key, uri, 10, interval);
+        } catch (UnsupportedEncodingException e) {
+            /* Won't happen */
         }
 	}
 
-	/**
-	 *
-	 */
-	private Configuration readRefreshJobConfiguration() {
-		SAXConfigurationHandler b = new SAXConfigurationHandler();
-		try {
-		    SourceUtil.toSAX(this.manager, this.writeSource, this.writeSource.getMimeType(), b);
-		} catch (Exception ignore) {
-		    this.getLogger().warn("Unable to read configuration from " + this.writeSource.getURI());
-		}
-		final Configuration conf = b.getConfiguration();
-		return conf;
-	}
+    /**
+     * @param interval
+     */
+    protected void startConfigurationTask(long interval) {
+        configurationTask = new ConfigurationTask();
+        configurationTask.enableLogging(getLogger());
+        runnable.execute(configurationTask, interval, interval);
+    }
 
-	/**
-	 * @param parameters
-	 * @throws ParameterException
-	 */
-	private void setupRefreshJobSource(Parameters parameters) throws ParameterException {
-		try {
-			final String fileName = parameters.getParameter(PARAM_WRITE_FILE, DEFAULT_WRITE_FILE);
-			final File file = new File(workDir, fileName);
-		    this.writeSource = this.resolver.resolveURI(file.toString());
-		} catch (IOException ioe) {
-		    throw new ParameterException("Error getting write-source.", ioe);
-		}
-		if (!(this.writeSource instanceof ModifiableSource)) {
-		    throw new ParameterException("Write-source is not modifiable.");
-		}
-		if (getLogger().isDebugEnabled()) {
-			getLogger().debug("Write source location: " + this.writeSource.getURI());
-		}
-	}
+    protected void stopConfigurationTask() {
+        if (this.configurationTask != null) {
+            this.runnable.remove(this.configurationTask);
+            this.configurationTask.run();
+            this.configurationTask = null;
+        }
+    }
 
     /**
-	 * @param writer
-	 * @param c
-	 * @throws IOException
-	 */
-	private void writeRefreshJobConfiguration(Writer writer, final TargetConfiguration c) throws IOException {
-        writer.write("<"+TAGNAME_TARGET+" "+ATTR_URI+"=\"");
-        writer.write(NetUtils.encode(c.parameters.getParameter(ATTR_URI, ""), "utf-8"));
-        writer.write("\" "+ATTR_EXPIRES+"=\"");
-        writer.write(c.parameters.getParameter(PARAM_CACHE_EXPIRES, "0"));
-        writer.write("\" "+ATTR_CACHE+"=\"");
-        writer.write(c.parameters.getParameter(PARAM_CACHE_ROLE, ""));
-        writer.write("\" "+ATTR_KEY+"=\"");
-        writer.write(NetUtils.encode(((IdentifierCacheKey) c.map.get(CACHE_KEY)).getKey(), "utf-8"));
-        writer.write("\"/>\n");
-	}
-	
-	/**
-	 * Configuration data holder for scheduled targets.
-	 */
-	static class TargetConfiguration {
-        
-        final Map map;
-		Parameters parameters;
-        
-        TargetConfiguration(IdentifierCacheKey cacheKey,
-                            String uri,
-                            String cacheRole,
-                            Parameters parameters) {
-            this.map = new HashMap();
-            this.map.put(CACHE_KEY, cacheKey);
-            update(uri, cacheRole, parameters);
-            
-        }
-        
-        void update(String uri, String cacheRole, Parameters parameters) {
-            this.parameters = parameters;
-            this.parameters.setParameter(ATTR_URI, uri);
-            this.parameters.setParameter(PARAM_CACHE_ROLE, cacheRole);
+     * Task which writes refresher configuraiton into the source.
+     */
+    protected class ConfigurationTask extends AbstractLogEnabled
+                                      implements Runnable {
+        public void run() {
+            if (changed) {
+                // Reset the flag.
+                changed = false;
+
+                boolean success = true;
+                Writer writer = null;
+                try {
+                    writer = new OutputStreamWriter(new FileOutputStream(configFile), "utf-8");
+                    writer.write("<targets>\n");
+
+                    try {
+                        final Iterator i = entries.values().iterator();
+                        while (i.hasNext()) {
+                            RefresherTask task = (RefresherTask) i.next();
+                            writer.write(task.toXML());
+                        }
+                    } catch (ConcurrentModificationException e) {
+                        // List of targets has been changed, unable to save it completely.
+                        // Will re-try writing the list next time.
+                        success = false;
+                    }
+
+                    writer.write("</targets>\n");
+                } catch (IOException e) {
+                    // Got I/O exception while writing the list.
+                    // Will re-try writing the list next time.
+                    success = false;
+                    if (getLogger().isDebugEnabled()) {
+                        getLogger().debug("Error writing targets to file.", e);
+                    }
+                } finally {
+                    if (writer != null) {
+                        try {
+                            writer.close();
+                        } catch (IOException e) { /* ignored */ }
+                    }
+                }
+
+                // Set the flag to run next time if failed this time
+                if (!success) {
+                    changed = true;
+                }
+            }
         }
-        
     }
 
+    protected class RefresherTask extends AbstractLogEnabled
+                                  implements Runnable {
+        private String key;
+        private String uri;
+        private long interval;
+
+
+        public RefresherTask(String key, String uri, long interval) {
+            this.key = key;
+            this.uri = uri;
+            this.interval = interval;
+        }
+
+        public void run() {
+            if (this.uri != null) {
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("Refreshing " + this.uri);
+                }
+
+                // Setup Environment
+                final BackgroundEnvironment env;
+                try {
+                    org.apache.cocoon.environment.Context ctx =
+                            (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
+                    env = new BackgroundEnvironment(getLogger(), ctx);
+                } catch (ContextException e) {
+                    throw new CascadingRuntimeException("No context found", e);
+                }
+                Processor processor;
+                try {
+                    processor = (Processor) manager.lookup(Processor.ROLE);
+                } catch (ServiceException e) {
+                    throw new CascadingRuntimeException("No processor found", e);
+                }
+
+                env.startingProcessing();
+                try {
+                    EnvironmentHelper.enterProcessor(processor, env);
+                } catch (ProcessingException e) {
+                    throw new CascadingRuntimeException("Can not enter processor", e);
+                }
+                try {
+                    // Refresh Source
+                    Source source = null;
+                    try {
+                        source = resolver.resolveURI(uri);
+                        source.refresh();
+                    } catch (IOException e) {
+                        getLogger().error("Error refreshing source", e);
+                    } finally {
+                        if (source != null) {
+                            resolver.release(source);
+                        }
+                    }
+                } finally {
+                    EnvironmentHelper.leaveProcessor();
+                    env.finishingProcessing();
+                    if (manager != null) {
+                        manager.release(processor);
+                    }
+                }
+            }
+        }
+
+        public void update(String uri, long interval) {
+            this.uri = uri;
+            this.interval = interval;
+        }
+
+        public String toXML() {
+            String key = null;
+            String uri = null;
+            try {
+                key = NetUtils.encode(this.key, "utf-8");
+                uri = NetUtils.encode(this.uri, "utf-8");
+            } catch (UnsupportedEncodingException e) {
+                /* Won't happen */
+            }
+            StringBuffer s = new StringBuffer();
+            s.append('<').append(TAGNAME_TARGET).append(' ');
+            s.append(ATTR_KEY).append("=\"").append(key).append("\" ");
+            s.append(ATTR_URI).append("=\"").append(uri).append("\" ");
+            s.append(ATTR_INTERVAL).append("=\"").append(interval).append("\" />\n");
+            return s.toString();
+        }
+    }
 }

Propchange: cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java
------------------------------------------------------------------------------
    eol-style = native

Propchange: cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/trunk/blocks/cocoon-scratchpad/cocoon-scratchpad-impl/src/main/java/org/apache/cocoon/components/source/impl/DelaySourceRefresher.java
------------------------------------------------------------------------------
    svn:keywords = Id



Mime
View raw message