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/03/22 18:38:25 UTC

cvs commit: cocoon-2.1/src/blocks/scratchpad/conf caching-source.xconf delay-refresher.xconf

unico       2004/03/22 09:38:25

  Modified:    src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl
                        CachingSourceFactory.java CachingSource.java
                        Refresher.java
  Added:       src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl
                        CachedSourceResponse.java
                        TraversableCachingSource.java DelayRefresher.java
               src/blocks/scratchpad/conf caching-source.xconf
                        delay-refresher.xconf
  Removed:     src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl
                        RefresherImpl.java AsyncCachingSource.java
  Log:
  Refactorings of CachingSource protocol. 
  Added TraversableCachingSource extension.
  Renamed RefresherImpl to DelayRefresher.
  Generalize Refresher interface.
  More to come.
  
  Revision  Changes    Path
  1.5       +234 -159  cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java
  
  Index: CachingSourceFactory.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- CachingSourceFactory.java	5 Mar 2004 10:07:25 -0000	1.4
  +++ CachingSourceFactory.java	22 Mar 2004 17:38:25 -0000	1.5
  @@ -17,75 +17,116 @@
   
   import java.io.IOException;
   import java.net.MalformedURLException;
  +import java.util.Iterator;
   import java.util.Map;
   
   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.container.ContainerUtil;
   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.caching.Cache;
  -import org.apache.cocoon.caching.CachedResponse;
   import org.apache.excalibur.source.Source;
   import org.apache.excalibur.source.SourceException;
   import org.apache.excalibur.source.SourceFactory;
  +import org.apache.excalibur.source.SourceParameters;
   import org.apache.excalibur.source.SourceResolver;
   import org.apache.excalibur.source.SourceUtil;
  +import org.apache.excalibur.source.TraversableSource;
   import org.apache.excalibur.source.URIAbsolutizer;
   
   /**
  - * 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 and can refresh the content async in the
  - * background.
  - *
  - * <h2>Example</h2>
  - * <pre>    
  - * &lt;component-instance name="cached" 
  - *      class="org.apache.cocoon.components.source.impl.CachingSourceFactory"/&gt;
  - * </pre>
  + * 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>
    * 
  - * <h2>Syntax for Protocol</h2>
  + * <h2>Protocol syntax</h2>
    * <p>
    * The URL needs to contain the URL of the cached source, an expiration
  - * period in second, and optionally a cache key: <code>cached://60@http://www.s-und-n.de</code>
  - * or <code>cached://60@main@http://www.s-und-n.de</code> 
  + * 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 show how the real source <code>http://www.s-und-n.de</code>
  + * 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 example extends the cache key with the string <code>main</code>
  - * allowing multiple cache entries for the same source.
  + * 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} 
  - * or {@link org.apache.cocoon.components.source.impl.AsyncCachingSource}
  - * depending on the <code>async</code> parameter.
  + * 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><th>cache-role</th><td>Role of component used as cache.</td><td>opt</td><td>String</td><td><code>{@link Cache.ROLE}</code></td></tr>
  - * <tr><th>refresher-role</th><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></tr>
  - * <tr><th>async</th><td>Indicated if the cached source should be refreshed asynchronously.</td><td>opt</td><td>String</td><td><code>false</code></td></tr>
  + * <tr>
  + *  <th>cache-role (String)</th>
  + *  <td>Role of component used as cache.</td>
  + *  <td>opt</td>
  + *  <td>String</td>
  + *  <td><code>{@link Cache.ROLE}</code></td>
  + * </tr>
  + * <tr>
  + *  <th>refresher-role (String)</th>
  + *  <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>
  + * </tr>
  + * <tr>
  + *  <th>async (boolean)</th>
  + *  <td>Indicated if the cached source should be refreshed asynchronously.</td>
  + *  <td>opt</td>
  + *  <td>String</td>
  + *  <td><code>false</code></td>
  + * </tr>
  + * <tr>
  + *  <th>default-expires (int)</th>
  + *  <td>Default expiration value for if it is not specified on the Source itself.</td>
  + *  <td>opt</td>
  + *  <td>String</td>
  + *  <td><code>-1</code></td>
  + * </tr>
    * </tbody></table>
    *  
  - * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
    * @version CVS $Id$
    * @since 2.1.1
    */
  -public final class CachingSourceFactory
  -    extends AbstractLogEnabled
  -    implements SourceFactory, ThreadSafe, Serviceable, URIAbsolutizer, Disposable, Parameterizable
  +public final class CachingSourceFactory extends AbstractLogEnabled
  +implements SourceFactory, URIAbsolutizer, Serviceable, Configurable, Disposable, ThreadSafe
   {
  +
  +    /** 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;
   
  @@ -93,100 +134,50 @@
       protected ServiceManager manager;
   
       /** The {@link SourceResolver} */
  -    protected SourceResolver   resolver;
  -
  -    /** The store */
  -    protected Cache cache;
  -    
  -    /** Async ? */
  -    protected boolean async;
  -    
  -    /** The role of the cache */
  -    protected String cacheRole;
  +    protected SourceResolver resolver;
       
       /** The refresher */
       protected Refresher refresher;
       
  -    /**
  -     * Serviceable
  -     */
  +    /** The cache */
  +    protected Cache cache;
  +    
  +    // ---------------------------------------------------- Lifecycle
  +    
  +    public CachingSourceFactory() {
  +    }
  +    
       public void service(ServiceManager manager) throws ServiceException {
           this.manager = manager;
  -        // due to cyclic dependencies we can't lookup the resolver or the refresher here
  +        // due to cyclic dependencies we can't lookup the resolver the refresher
  +        // or the cache until after the factory is initialized.
       }
  -
  -    /**
  -     * Get a <code>Source</code> object.
  -     * @param parameters This is optional.
  -     */
  -    public Source getSource( String location, Map parameters )
  -    throws MalformedURLException, IOException {
  -        if(  this.getLogger().isDebugEnabled() ) {
  -            this.getLogger().debug( "Creating source object for " + location );
  -        }
  -
  -        // we must do a lazy lookup because of cyclic dependencies
  -        if (!this.isInitialized) {
  -            lazyInitialize();
  -        }
  -
  -        CachingSource source;
  -        if ( this.async ) {
  -            source = this.getAsyncSource(location, parameters);
  -        } else {
  -            source = new CachingSource( location, parameters);
  +    
  +    public void configure(Configuration configuration) throws ConfigurationException {
  +        this.scheme = configuration.getAttribute("name");
  +        Parameters parameters = Parameters.fromConfiguration(configuration);
  +        
  +		// 'async' parameter
  +		this.async = parameters.getParameterAsBoolean("async", false);
  +		
  +        // 'cache-role' parameter
  +        this.cacheRole = parameters.getParameter("cache-role", Cache.ROLE);
  +        if (this.getLogger().isDebugEnabled()) {
  +            this.getLogger().debug("Using cache " + this.cacheRole);
           }
  -        ContainerUtil.enableLogging(source, this.getLogger());
  -        try {
  -            // call selected avalon lifecycle interfaces. Mmmh.
  -            ContainerUtil.service(source, this.manager);
  -            // we pass the components for performance reasons
  -            source.init(this.resolver, this.cache);
  -            ContainerUtil.initialize(source);                                  
  -        } catch (IOException ioe) {
  -            throw ioe;
  -        } catch (ServiceException se) {
  -            throw new SourceException("Unable to initialize source.", se);
  -        } catch (Exception e) {
  -            throw new SourceException("Unable to initialize source.", e);
  +        
  +        // 'refresher-role' parameter
  +        if (this.async) {
  +            this.refresherRole = parameters.getParameter("refresher-role", Refresher.ROLE);
  +            if (this.getLogger().isDebugEnabled()) {
  +                this.getLogger().debug("Using refresher " + this.refresherRole);
  +            }
           }
  -        return source;
  -    }
  -
  -    /**
  -     * Get an AsyncSource and register refresh period.
  -     * 
  -     * @param location a string holding a URI
  -     * @param parameters a map of additional parameters to pass to the source
  -     * @return a new AsyncCachingSource
  -     * @throws MalformedURLException
  -     * @throws SourceException
  -     */
  -    private CachingSource getAsyncSource(String location, Map parameters) throws MalformedURLException, SourceException {
  -        CachingSource source;
  -        source = new AsyncCachingSource( location, parameters);
  -        final long expires = source.getExpiration();
           
  -        CachedResponse response = this.cache.get( source.getCacheKey() );
  -        if ( response == null ) {
  -            
  -            // call the target the first time
  -            this.refresher.refresh(source.getCacheKey(),
  -                                   source.getSourceURI(),
  -                                   expires,
  -                                   this.cacheRole);
  -        
  -            response = this.cache.get( source.getCacheKey() );
  -        }
  -        ((AsyncCachingSource)source).setResponse(response);
  -        
  -        this.refresher.refreshPeriodically(source.getCacheKey(),
  -                               source.getSourceURI(),
  -                               expires,
  -                               this.cacheRole);
  -        return source;
  +        this.defaultExpires = parameters.getParameterAsInteger("default-expires",-1);
  +        
       }
  -
  +    
       /**
        * Lazy initialization of resolver and refresher because of
        * cyclic dependencies.
  @@ -199,85 +190,169 @@
               // we were waiting
               return;
           }
  -        if (this.resolver != null) {
  -            try {
  -                this.resolver = (SourceResolver)this.manager.lookup( SourceResolver.ROLE );
  -            } catch (ServiceException se) {
  -                throw new SourceException("SourceResolver is not available.", se);
  -            }
  +        try {
  +            this.resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
  +        } catch (ServiceException se) {
  +            throw new SourceException("Missing service dependency: " + SourceResolver.ROLE, se);
           }
  -        if ( this.refresher == null && this.async) {
  +        try {
  +            this.cache = (Cache) this.manager.lookup(this.cacheRole);
  +        } catch (ServiceException se) {
  +            throw new SourceException("Missing service dependency: " + SourceResolver.ROLE, se);
  +        }
  +        if (this.async) {
               try {
  -                this.refresher = (Refresher)this.manager.lookup(this.refresherRole);
  +                this.refresher = (Refresher) 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("Refesher is not available.", se);
  +                throw new SourceException("Missing service dependency: " + this.refresherRole, se);
               }
           }
           this.isInitialized = true;
       }
       
  -    /**
  -     * Release a {@link Source} object.
  -     */
  -    public void release( Source source ) {
  -        if ( source instanceof CachingSource) {
  -            if ( this.getLogger().isDebugEnabled() ) {
  -                this.getLogger().debug("Releasing source " + source.getURI());
  -            }
  -            ContainerUtil.dispose(source);
  -        }
  -    }
  -
  -    /*
  -     *  (non-Javadoc)
  -     * @see org.apache.excalibur.source.URIAbsolutizer#absolutize(java.lang.String, java.lang.String)
  -     */
  -    public String absolutize(String baseURI, String location) {
  -        return SourceUtil.absolutize(baseURI, location, true);
  -    }
  -
       /* (non-Javadoc)
        * @see org.apache.avalon.framework.activity.Disposable#dispose()
        */
       public void dispose() {
           if (this.manager != null) {
               this.manager.release(this.resolver);
  -            this.manager.release(this.cache);
               this.manager.release(this.refresher);
               this.refresher = null;
  -            this.cache = null;
               this.manager = null;
               this.resolver = null;
           }
       }
  -
  -    /* (non-Javadoc)
  -     * @see org.apache.avalon.framework.parameters.Parameterizable#parameterize(org.apache.avalon.framework.parameters.Parameters)
  +    
  +    // ---------------------------------------------------- SourceFactory implementation
  +    
  +    /**
  +     * Get a <code>Source</code> object.
  +     * @param parameters This is optional.
        */
  -    public void parameterize(Parameters parameters) throws ParameterException {
  -        this.cacheRole = parameters.getParameter("cache-role", Cache.ROLE);
  -        if ( this.getLogger().isDebugEnabled()) {
  -            this.getLogger().debug("Using cache " + this.cacheRole);
  +    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;
  +        index = uri.indexOf('?');
  +        if (index != -1) {
  +            queryString = uri.substring(index+1);
  +            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();
  +        if (sp != null) {
  +            final Iterator names = sp.getParameterNames();
  +            while (names.hasNext()) {
  +                String name = (String) names.next();
  +                if (name.startsWith("cocoon:")) {
  +                    params.setParameter(name.substring("cocoon:".length()),sp.getParameter(name));
  +                    sp.removeParameter(name);
  +                }
  +            }
  +            queryString = sp.getQueryString();
  +            if (queryString != null) {
  +                uri += "?" + queryString;
  +            }
           }
           
  +        final String cacheName = params.getParameter("cache-name", null);
  +        int expires = params.getParameterAsInteger("cache-expires", -1);
  +        if (expires == -1) {
  +            expires = this.defaultExpires;
  +            params.setParameter("cache-expires", String.valueOf(this.defaultExpires));
  +        }
  +        
  +        final Source wrappedSource = this.resolver.resolveURI(uri);
  +        CachingSource source;
  +        if (wrappedSource instanceof TraversableSource) {
  +            source = new TraversableCachingSource(scheme,
  +                                                  location,
  +                                                  uri,
  +                                                  cacheName,
  +                                                  expires,
  +                                                  parameters,
  +                                                  this.async);
  +        }
  +        else {
  +            source = new CachingSource(scheme,
  +                                       location,
  +                                       uri,
  +                                       cacheName,
  +                                       expires,
  +                                       parameters,
  +                                       this.async);
  +        }
  +
  +        if (this.async) {
  +            // schedule it with the refresher
  +            this.refresher.refresh(source.getCacheKey(), uri, this.cacheRole, params);
  +        }
  +
  +        // set the required components directly for speed
  +        source.cache = this.cache;
  +        source.resolver = this.resolver;
  +
  +        ContainerUtil.enableLogging(source, this.getLogger());
           try {
  -            this.cache = (Cache)this.manager.lookup(this.cacheRole);
  +            // call selected avalon lifecycle interfaces. Mmmh.
  +            ContainerUtil.service(source, this.manager);
  +            ContainerUtil.initialize(source);
           } catch (ServiceException se) {
  -            throw new ParameterException("Unable to lookup cache: " + this.cacheRole, se);
  +            throw new SourceException("Unable to initialize source.", se);
  +        } catch (Exception e) {
  +            throw new SourceException("Unable to initialize source.", e);
           }
  +        
  +        return source;
  +    }
   
  -        this.async = parameters.getParameterAsBoolean("async", false);
  -        if (this.async) {
  -            this.refresherRole = parameters.getParameter("refresher-role", Refresher.ROLE);
  -            if ( this.getLogger().isDebugEnabled()) {
  -                this.getLogger().debug("Using refresher " + this.refresherRole);
  +    /**
  +     * Release a {@link Source} object.
  +     */
  +    public void release(Source source) {
  +        if (source instanceof CachingSource) {
  +            if (this.getLogger().isDebugEnabled() ) {
  +                this.getLogger().debug("Releasing source " + source.getURI());
               }
  +            ((CachingSource) source).dispose();
           }
       }
  +    
  +	// ---------------------------------------------------- URIAbsolutizer implementation
  +
  +    /*
  +     *  (non-Javadoc)
  +     * @see org.apache.excalibur.source.URIAbsolutizer#absolutize(java.lang.String, java.lang.String)
  +     */
  +    public String absolutize(String baseURI, String location) {
  +        return SourceUtil.absolutize(baseURI, location, true);
  +    }
  +
   
   }
  
  
  
  1.7       +364 -256  cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java
  
  Index: CachingSource.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- CachingSource.java	6 Mar 2004 21:00:39 -0000	1.6
  +++ CachingSource.java	22 Mar 2004 17:38:25 -0000	1.7
  @@ -19,11 +19,10 @@
   import java.io.ByteArrayOutputStream;
   import java.io.IOException;
   import java.io.InputStream;
  +import java.io.Serializable;
   import java.net.MalformedURLException;
  -import java.util.Iterator;
   import java.util.Map;
   
  -import org.apache.avalon.framework.activity.Disposable;
   import org.apache.avalon.framework.activity.Initializable;
   import org.apache.avalon.framework.logger.AbstractLogEnabled;
   import org.apache.avalon.framework.service.ServiceException;
  @@ -32,7 +31,6 @@
   import org.apache.cocoon.CascadingIOException;
   import org.apache.cocoon.ProcessingException;
   import org.apache.cocoon.caching.Cache;
  -import org.apache.cocoon.caching.ExtendedCachedResponse;
   import org.apache.cocoon.caching.SimpleCacheKey;
   import org.apache.cocoon.components.sax.XMLDeserializer;
   import org.apache.cocoon.components.sax.XMLSerializer;
  @@ -44,10 +42,10 @@
   import org.apache.excalibur.source.SourceResolver;
   import org.apache.excalibur.source.SourceValidity;
   import org.apache.excalibur.source.impl.validity.ExpiresValidity;
  -import org.apache.excalibur.xml.sax.SAXParser;
  +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.InputSource;
   import org.xml.sax.SAXException;
   
   /**
  @@ -57,104 +55,243 @@
    * 
    * <h2>Syntax for Protocol</h2>
    * <p>
  - * The URL needs to contain the URL of the cached source, an expiration
  - * period in second, and optionally a cache key: <code>cached://60@http://www.s-und-n.de</code>
  - * or <code>cached://60@main@http://www.s-und-n.de</code> 
  + * 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.s-und-n.de</code>
  + * 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 example extends the cache key with the string <code>main</code>
  - * allowing multiple cache entries for the same source.
  + * 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>
  - * 
  - * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
  + * <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>
    * @version CVS $Id$
    */
  -public class CachingSource
  -extends AbstractLogEnabled
  -implements Source, Serviceable, Initializable, Disposable, XMLizable {
  -
  -    /** The current ServiceManager */
  +public class CachingSource extends AbstractLogEnabled
  +implements Source, Serviceable, Initializable, XMLizable {
  +    
  +    /** The ServiceManager */
       protected ServiceManager manager;
  -
  -    /** The current source resolver */
  +    
  +    /** The SourceResolver to resolve the wrapped Source */
       protected SourceResolver resolver;
  -
  +    
       /** The current cache */
       protected Cache cache;
  -
  -    /** The uri */
  +    
  +    /** 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 wrapped source uri */
  +    final protected String sourceURI;
  +    
       /** The used protocol */
       final protected String protocol;
  -
  -    /** The uri of the real source*/
  -    final protected String sourceURI;
       
  -    /** Parameters for the source */
  +    /** The key used in the store */
  +    final protected SimpleCacheKey cacheKey;
  +    
  +    /** number of seconds before cached object becomes invalid */
  +    final protected int expires;
  +    
  +    /** key name extension */
  +    final protected String cacheName;
  +    
  +    /** additional source parameters */
       final protected Map parameters;
       
  -    /** The expires information */
  -    final protected long expires;
  -   
  -    /** The key used in the store */
  -    final protected SimpleCacheKey streamKey;
  +    /** asynchronic refresh strategy ? */
  +    final protected boolean async;
       
  -    /** The cached response (if any) */
  -    protected ExtendedCachedResponse cachedResponse;
  +    /**
  +     * Construct a new object.
  +     */
  +    public CachingSource(final String protocol,
  +                         final String uri,
  +                         final Source source,
  +                         final String cacheName,
  +                         final int expires,
  +                         final Map parameters,
  +                         final boolean async) {
  +        this(protocol, uri, source.getURI(), cacheName, expires, parameters, async);
  +        this.source = source;
  +    }
       
  -    /** The source object for the real content */
  -    protected Source source;
  +    /**
  +     * Construct a new object.
  +     */
  +    public CachingSource(final String protocol,
  +                         final String uri,
  +                         final String sourceURI,
  +                         final String cacheName,
  +                         final int expires,
  +                         final Map parameters,
  +                         final boolean async) {
  +        this.protocol = protocol;
  +        this.uri = uri;
  +        this.sourceURI = sourceURI;
  +        this.cacheName = cacheName;
  +        this.expires = expires;
  +        this.parameters = parameters;
  +        this.async = async;
  +        
  +        String key = "source:" + sourceURI;
  +        if (cacheName != null) {
  +            key += ":" + cacheName;
  +        }
  +        this.cacheKey = new SimpleCacheKey(key, false);
  +    }
       
       /**
  -     * Construct a new object. Syntax for cached source:
  -     * "<code>cache-protocol</code>" + "<code>://</code>" + "<code>seconds</code>"
  -     * + "<code>@</code>" + "<code>wrapped-URL</code>".
  +     * Set the ServiceManager.
  +     */
  +    public void service(final ServiceManager manager) throws ServiceException {
  +        this.manager = manager;
  +    }
  +    
  +    /**
  +     * Initialize the Source.
  +     */
  +    public void initialize() throws Exception {
  +        this.response = (CachedSourceResponse) this.cache.get(this.cacheKey);
  +        if (this.response != null && (!this.async || expires == 0)) {
  +            // check if the source is cached, throw it out if invalid
  +            final SourceValidity validity = this.response.getValidityObjects()[0];
  +            if (expires != -1 && (expires == 0 || validity.isValid() != SourceValidity.VALID)) {
  +                if (getLogger().isDebugEnabled()) {
  +                    getLogger().debug("Invalid cached response for source " + getSourceURI());
  +                }
  +                this.response = null;
  +                // remove it if it no longer exists
  +                if (!exists()) {
  +                    this.cache.remove(this.cacheKey);
  +                }
  +            }
  +        }
  +    }
  +    
  +    /**
  +     * Cleanup.
  +     */
  +    public void dispose() {
  +        if (this.source != null) {
  +            this.resolver.release(this.source);
  +            this.source = null;
  +        }
  +        this.manager = null;
  +        this.resolver = null;
  +        this.cache = null;
  +    }
  +    
  +    /**
  +     * Initialize the cached response with meta info.
        * 
  +     * @param refresh  whether to force refresh
  +     * @throws IOException  if an the binary response could not be initialized
        */
  -    public CachingSource( String location,
  -                          Map    parameters) 
  -    throws MalformedURLException {
  -        int separatorPos = location.indexOf('@');
  -        if (separatorPos == -1) {
  -            throw new MalformedURLException("@ required in URI: " + location);
  -        }
  -        int protocolEnd = location.indexOf("://");
  -        if (protocolEnd == -1)
  -            throw new MalformedURLException("URI does not contain '://' : " + location);
  -
  -        final String expiresText = location.substring(protocolEnd+3, separatorPos);
  -        this.expires = Long.valueOf(expiresText).longValue() * 1000;
  -        this.protocol = location.substring(0, protocolEnd);
  -        String endString = location.substring(separatorPos+1);
  -        separatorPos = endString.indexOf('@');
  -        if ( separatorPos == -1 ) {
  -            this.sourceURI = endString;
  -            this.streamKey = new SimpleCacheKey("source:" + this.sourceURI, false);
  -        } else {
  -            this.sourceURI = endString.substring(separatorPos+1);
  -            this.streamKey = new SimpleCacheKey("source:" + endString.substring(0, separatorPos), false);
  +    protected void initMetaResponse(boolean refresh) throws IOException {
  +        boolean storeResponse = false;
  +        CachedSourceResponse response = this.response;
  +        if (response == null) {
  +            response = new CachedSourceResponse(new ExpiresValidity(getExpiration()));
  +            storeResponse = true;
  +        }
  +        if (response.getExtra() == null || refresh) {
  +            response.setExtra(readMeta());
  +            this.freshMeta = true;
  +        }
  +        if (storeResponse) {
  +            this.response = response;
  +            try {
  +                this.cache.store(this.cacheKey, this.response);
  +            }
  +            catch(ProcessingException e) {
  +                throw new CascadingIOException("Failure storing response.", e);
  +            }
           }
  -        this.uri = location;
  -        this.parameters = parameters;
       }
  -
  +    
       /**
  -     * Return the used key
  +     * Initialize the cached response with binary contents.
  +     * 
  +     * @param refresh  whether to force refresh
  +     * @throws IOException  if an the binary response could not be initialized
        */
  -    public SimpleCacheKey getCacheKey() {
  -        return this.streamKey;
  +    protected void initBinaryResponse(boolean refresh) 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(new ExpiresValidity(getExpiration()));
  +            storeResponse = true;
  +        }
  +        if (response.getBinaryResponse() == null || refresh) {
  +            response.setBinaryResponse(readBinaryResponse());
  +            if (!this.freshMeta) {
  +                /* always refresh meta in this case */
  +                response.setExtra(readMeta());
  +            }
  +        }
  +        if (storeResponse) {
  +            this.response = response;
  +            try {
  +                this.cache.store(this.cacheKey, this.response);
  +            }
  +            catch(ProcessingException e) {
  +                throw new CascadingIOException("Failure storing response.", e);
  +            }
  +        }
       }
       
       /**
  -     * Expires (in milli-seconds)
  +     * 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
  +     * @throws CascadingIOException  wraps all other exception types
        */
  -    public long getExpiration() {
  -        return this.expires;
  +    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(new ExpiresValidity(getExpiration()));
  +            storeResponse = true;
  +        }
  +        if (response.getXMLResponse() == null || refresh) {
  +            response.setXMLResponse(readXMLResponse());
  +            if (!this.freshMeta) {
  +                /* always refresh meta in this case */
  +                response.setExtra(readMeta());
  +            }
  +        }
  +        if (storeResponse) {
  +            this.response = response;
  +            try {
  +                this.cache.store(this.cacheKey, this.response);
  +            }
  +            catch(ProcessingException e) {
  +                throw new CascadingIOException("Failure storing response.", e);
  +            }
  +        }
       }
       
  +    // ---------------------------------------------------- Source implementation
  +    
       /**
        * Return the protocol identifier.
        */
  @@ -177,25 +314,37 @@
        */
       public long getLastModified() {
           try {
  -            this.initSource();
  +            initMetaResponse(false);
           } catch (IOException io) {
               return 0;
           }
  -
  -        return this.source.getLastModified();
  +        return ((SourceMeta) this.response.getExtra()).getLastModified();
       }
   
       /**
  +     * The mime-type of the content described by this object.
  +     * If the source is not able to determine the mime-type by itself
  +     * this can be null.
  +     */
  +    public String getMimeType() {
  +        try {
  +            initMetaResponse(false);
  +        } catch (IOException io) {
  +            return null;
  +        }
  +        return ((SourceMeta) this.response.getExtra()).getMimeType();
  +    }
  +    
  +    /**
        * Return an <code>InputStream</code> object to read from the source.
        */
  -    public InputStream getInputStream()
  -    throws IOException, SourceException {
  +    public InputStream getInputStream() throws IOException, SourceException {
           try {
  -            this.initCache(false);
  -        } catch (SAXException se) {
  -            throw new SourceException("Unable to init source", se);
  -        }   
  -        return new ByteArrayInputStream(this.cachedResponse.getResponse());
  +            initBinaryResponse(false);
  +        } catch (IOException se) {
  +            throw new SourceException("Failure getting input stream", se);
  +        }
  +        return new ByteArrayInputStream(this.response.getBinaryResponse());
       }
   
       /**
  @@ -206,23 +355,15 @@
       }
   
       /**
  -     * Return the unique identifer for the cached source
  -     */
  -    public String getSourceURI() {
  -        return this.sourceURI;
  -    }
  -
  -    /**
  -     * 
        * @see org.apache.excalibur.source.Source#exists()
        */
       public boolean exists() {
  -        try {
  -            this.initSource();
  -        } catch (IOException io) {
  -            return true;
  -        }
  -        return this.source.exists();
  +    	try {
  +			return this.getSource().exists();
  +    	}
  +    	catch (IOException e) {
  +    		return true;
  +    	}
       }
       
       /**
  @@ -232,150 +373,86 @@
        *  <code>null</code> is returned.
        */
       public SourceValidity getValidity() {
  -        try {
  -            this.initCache(true);
  -        } catch (SAXException se) {
  -            return null;
  -        } catch (IOException io) {
  -            return null;
  +        long lastModified = getLastModified();
  +        if (lastModified > 0) {
  +            return new TimeStampValidity(lastModified);
           }
  -        return this.cachedResponse.getValidityObjects()[0];
  +        return null;
       }
  -
  -    /**
  -     * The mime-type of the content described by this object.
  -     * If the source is not able to determine the mime-type by itself
  -     * this can be null.
  -     */
  -     public String getMimeType() {
  -         return null;
  -     }
  -
  +     
       /**
        * Refresh this object and update the last modified date
        * and content length.
        */
       public void refresh() {
  -        if ( this.source != null) {
  -            this.source.refresh();
  +        this.response = null;
  +        try {
  +            getSource().refresh();
  +        }
  +        catch (IOException ignore) {
           }
       }
  -
  +    
  +    // ---------------------------------------------------- XMLizable implementation
  +    
       /**
  -     * Get the value of a parameter.
  -     * Using this it is possible to get custom information provided by the
  -     * source implementation, like an expires date, HTTP headers etc.
  +     * Generates SAX events representing the object's state.
        */
  -    public String getParameter(String name) {
  -        return null;
  +    public void toSAX(ContentHandler contentHandler) throws SAXException {
  +        XMLDeserializer deserializer = null;
  +        try {
  +            initXMLResponse(false);
  +            deserializer = (XMLDeserializer) this.manager.lookup(XMLDeserializer.ROLE);
  +            if (contentHandler instanceof XMLConsumer) {
  +                deserializer.setConsumer((XMLConsumer) contentHandler);
  +            } else {
  +                deserializer.setConsumer(new ContentHandlerWrapper(contentHandler));
  +            }
  +            deserializer.deserialize(this.response.getXMLResponse());
  +        } catch(CascadingIOException e) {
  +            throw new SAXException(e.getMessage(), (Exception) e.getCause());
  +        } catch(IOException e) {
  +            throw new SAXException("Failure reading SAX response.", e);
  +        } catch (ServiceException se ) {
  +            throw new SAXException("Missing service dependency: XMLdeserializer.", se);
  +        } finally {
  +            this.manager.release(deserializer);
  +        }
       }
  -
  +    
  +    // ---------------------------------------------------- CachingSource specific accessors
  +    
       /**
  -     * Get the value of a parameter.
  -     * Using this it is possible to get custom information provided by the
  -     * source implementation, like an expires date, HTTP headers etc.
  +     * Return the uri of the cached source.
        */
  -    public long getParameterAsLong(String name) {
  -        return 0;
  +    protected String getSourceURI() {
  +        return this.sourceURI;
       }
   
       /**
  -     * Get parameter names
  -     * Using this it is possible to get custom information provided by the
  -     * source implementation, like an expires date, HTTP headers etc.
  -     */
  -    public Iterator getParameterNames() {
  -        return java.util.Collections.EMPTY_LIST.iterator();
  -    }
  -
  -
  -    /* (non-Javadoc)
  -     * @see org.apache.avalon.framework.component.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
  +     * Return the cached source itself.
        */
  -    public void service(ServiceManager manager) throws ServiceException {
  -        this.manager = manager;
  +    protected Source getSource() throws MalformedURLException, IOException {
  +        if (this.source == null) {
  +            this.source = resolver.resolveURI(sourceURI, null, parameters);
  +        }
  +        return this.source;
       }
  -
  +    
       /**
  -     * Set the required components
  -     * This is done for performance reasons, the components are only looked up
  -     * once by the factory
  +     * Return the used key.
        */
  -    public void init(SourceResolver resolver, Cache cache) {
  -        this.resolver = resolver;
  -        this.cache = cache;
  +    protected SimpleCacheKey getCacheKey() {
  +        return this.cacheKey;
       }
       
  -    /* (non-Javadoc)
  -     * @see org.apache.avalon.framework.activity.Initializable#initialize()
  -     */
  -    public void initialize() throws Exception {
  -        // check if the source is cached
  -        this.cachedResponse = (ExtendedCachedResponse)this.cache.get( this.streamKey );
  -        if ( this.cachedResponse != null ) {
  -            SourceValidity expiresValidity = this.cachedResponse.getValidityObjects()[0];
  -            if ( this.expires != -1
  -                 && (this.expires == 0 || expiresValidity.isValid() != SourceValidity.VALID)) {
  -                // TODO: according to SourceValidity.isValid() validity should be checked against 
  -                //       new validity object if UNKNOWN is returned
  -                //       maybe this is too expensive?
  -                
  -                // remove from cache if not valid anymore
  -                this.cache.remove( this.streamKey );
  -                this.cachedResponse = null;
  -            }
  -        }
  -    }
  -
       /**
  -     * Initialize the source
  +     * Expires (in milli-seconds)
        */
  -    protected void initSource() 
  -    throws IOException{
  -        if ( this.source == null ) {
  -            this.source = this.resolver.resolveURI(this.sourceURI, null, this.parameters);
  -        }
  +    protected long getExpiration() {
  +        return this.expires * 1000;
       }
  -    
  -    /** 
  -     * Initialize the cache
  -     */
  -    protected boolean initCache(boolean alternative)
  -    throws IOException, SAXException {
  -        this.initSource();
  -        boolean storeResponse = false;
  -        
  -        if ( this.cachedResponse == null
  -             && (!alternative || !(this.source instanceof XMLizable)) ) {
  -            
  -			this.cachedResponse = new ExtendedCachedResponse(
  -					new ExpiresValidity(this.expires), this.readBinaryResponse());
  -            storeResponse = true;                                                             
  -        } else if ( this.cachedResponse == null ) {
  -            this.cachedResponse = new ExtendedCachedResponse(new ExpiresValidity(this.expires), null);                                                            
  -        }
           
  -        // we cache both
  -        if ( alternative && this.cachedResponse.getAlternativeResponse() == null ) {
  -            this.cachedResponse.setAlternativeResponse(this.readXMLResponse());
  -            storeResponse = true;
  -        }
  -        
  -        if ( storeResponse && this.expires > 0 ) {
  -            try {
  -                this.cache.store(this.streamKey, this.cachedResponse);
  -                if (this.getLogger().isDebugEnabled()) {
  -                    this.getLogger().debug("Storing response for "+this.streamKey.getKey());
  -                }
  -            } catch (ProcessingException ignore) {
  -                if (this.getLogger().isDebugEnabled()) {
  -                    this.getLogger().debug("Ignoring exception when storing response.", ignore) ;
  -                }
  -            }
  -        }
  -        return storeResponse;
  -    }
  -    
       /**
        * Read XML content from source.
        * 
  @@ -385,27 +462,37 @@
   	 * @throws CascadingIOException
   	 */
   	protected byte[] readXMLResponse() throws SAXException, IOException, CascadingIOException {
  -		XMLSerializer serializer = null;
  -		SAXParser parser = null;
  +        XMLSerializer serializer = null;
  +        XMLizer xmlizer = null;
           byte[] result = null;
   		try {
  -		    serializer = (XMLSerializer)this.manager.lookup(XMLSerializer.ROLE);
  -		    if (this.source instanceof XMLizable) {
  -		        ((XMLizable)this.source).toSAX(serializer);
  -		    } else {
  -		        parser = (SAXParser)this.manager.lookup(SAXParser.ROLE);
  -		        
  -		        final InputSource inputSource = new InputSource(new ByteArrayInputStream(this.cachedResponse.getResponse()));
  -		        inputSource.setSystemId(this.source.getURI());
  -		        
  -		        parser.parse(inputSource, serializer);
  -		    }
  -		    
  -		    result = (byte[])serializer.getSAXFragment();
  +		    serializer = (XMLSerializer) this.manager.lookup(XMLSerializer.ROLE);
  +            
  +            if (this.source instanceof XMLizable) {
  +                ((XMLizable) this.source).toSAX(serializer);
  +            }
  +            else {
  +				byte[] binary = null;
  +            	if (this.response != null) {
  +            		binary = this.response.getBinaryResponse();
  +            	}
  +                if (binary == null) {
  +                    binary = readBinaryResponse();
  +                }
  +                final String mimeType = this.source.getMimeType();
  +                if (mimeType != null) {
  +                    xmlizer = (XMLizer) manager.lookup(XMLizer.ROLE);
  +                    xmlizer.toSAX(new ByteArrayInputStream(binary),
  +                                  mimeType,
  +                                  source.getURI(),
  +                                  serializer);
  +                }
  +            }
  +		    result = (byte[]) serializer.getSAXFragment();
   		} catch (ServiceException se) {
  -		    throw new CascadingIOException("Unable to lookup xml serializer.", se);
  +		    throw new CascadingIOException("Missing service dependencyj.", se);
   		} finally {
  -		    this.manager.release(parser);
  +            this.manager.release(xmlizer);
   		    this.manager.release(serializer);
   		}
   		return result;
  @@ -419,11 +506,10 @@
   	 * @throws SourceNotFoundException
   	 */
   	protected byte[] readBinaryResponse() throws IOException, SourceNotFoundException {
  -		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
  +        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
   		final byte[] buffer = new byte[2048];
   		final InputStream inputStream = this.source.getInputStream();
   		int length;
  -      
   		while ((length = inputStream.read(buffer)) > -1) {
   		    baos.write(buffer, 0, length);
   		}
  @@ -431,35 +517,57 @@
   		inputStream.close();
   		return baos.toByteArray();
   	}
  -
  -	/* (non-Javadoc)
  -     * @see org.apache.avalon.framework.activity.Disposable#dispose()
  +    
  +    /**
  +     * Read meta data from source.
  +     * 
  +     * @return source meta data
  +     * @throws IOException
        */
  -    public void dispose() {
  -        if ( this.source != null ) {
  -            this.resolver.release( this.source );
  -            this.source = null;
  +    protected SourceMeta readMeta() throws IOException {
  +        SourceMeta meta = new SourceMeta();
  +        long lastModified = getSource().getLastModified();
  +        if (lastModified <= 0) {
  +            lastModified = System.currentTimeMillis();
           }
  +        meta.setLastModified(lastModified);
  +        meta.setMimeType(getSource().getMimeType());
  +        return meta;
       }
  -
  -    /* (non-Javadoc)
  -     * @see org.apache.excalibur.xml.sax.XMLizable#toSAX(org.xml.sax.ContentHandler)
  +    
  +    /**
  +     * Data holder for caching Source meta info.
        */
  -    public void toSAX(ContentHandler contentHandler) throws SAXException {
  -       XMLDeserializer deserializer = null;
  -       try {
  -           deserializer = (XMLDeserializer) this.manager.lookup(XMLDeserializer.ROLE);
  -           if ( contentHandler instanceof XMLConsumer) {
  -               deserializer.setConsumer((XMLConsumer)contentHandler);
  -           } else {
  -               deserializer.setConsumer(new ContentHandlerWrapper(contentHandler));
  -           }
  -           deserializer.deserialize( this.cachedResponse.getAlternativeResponse() );
  -       } catch (ServiceException se ) {
  -           throw new SAXException("Unable to lookup xml deserializer.", se);
  -       } finally {
  -           this.manager.release(deserializer);
  -       }
  +    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;
  +        }
  +        
       }
   
   }
  
  
  
  1.4       +15 -23    cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/Refresher.java
  
  Index: Refresher.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/Refresher.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- Refresher.java	5 Mar 2004 10:07:25 -0000	1.3
  +++ Refresher.java	22 Mar 2004 17:38:25 -0000	1.4
  @@ -15,15 +15,19 @@
    */
   package org.apache.cocoon.components.source.impl;
   
  +import org.apache.avalon.framework.parameters.Parameters;
   import org.apache.cocoon.caching.SimpleCacheKey;
   import org.apache.excalibur.source.SourceException;
   
   /**
  - * The refresher is a component that updates uri and stores
  - * there response in a cache.
  - *  
  + * A Refresher is a component that updates the cached contents
  + * of a Source.
  + * <p>
  + * Implementations can for instance trigger updates based on a timeout value or
  + * in response to an external event.
  + * </p>
  + * 
    * @since 2.1.1
  - * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
    * @version CVS $Id$
    */
   public interface Refresher {
  @@ -31,29 +35,17 @@
       String ROLE = Refresher.class.getName();
   
       /**
  -     * Refresh the content now.
  +     * Add a uri to the Refresher.
        * 
  -     * @param cacheKey The key used to cache the content
  -     * @param uri      The uri to cache, every valid protocol can be used, except the Cocoon protocol!
  -     * @param expires  The time in seconds the cached content is valid
  +     * @param cacheKey  The key used to cache the content
  +     * @param uri       The uri to cache, every valid protocol can be used (except the Cocoon protocol!)
        * @param cacheRole The role of the cache component to store the content
  +     * @param params    Additional parameters such as a timout value
        */
       void refresh(SimpleCacheKey cacheKey,
                    String uri,
  -                 long   expires,
  -                 String cacheRole)
  +                 String cacheRole,
  +                 Parameters params)
       throws SourceException;
   
  -    /**
  -     * Refresh the content periodically in the background.
  -     * 
  -     * @param cacheKey The key used to cache the content
  -     * @param uri      The uri to cache, every valid protocol can be used, except the Cocoon protocol!
  -     * @param expires  The time in seconds the cached content is valid
  -     * @param cacheRole The role of the cache component to store the content
  -     */
  -    void refreshPeriodically(SimpleCacheKey cacheKey,
  -                             String uri,
  -                             long   expires,
  -                             String cacheRole);
   }
  
  
  
  1.1                  cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachedSourceResponse.java
  
  Index: CachedSourceResponse.java
  ===================================================================
  /*
   * 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.Serializable;
  
  import org.apache.cocoon.caching.CachedResponse;
  import org.apache.excalibur.source.SourceValidity;
  
  /**
   * Cached response for caching Source contents and extra meta information.
   */
  public final class CachedSourceResponse extends CachedResponse {
      
      private byte[] m_binary;
      private byte[] m_xml;
      private Serializable m_extra;
      
      public CachedSourceResponse(SourceValidity validity) {
          super(validity, null);
      }
      
      public byte[] getBinaryResponse() {
          return m_binary;
      }
      
      public void setBinaryResponse(byte[] binary) {
          m_binary = binary;
      }
      
      public byte[] getXMLResponse() {
          return m_xml;
      }
      
      public void setXMLResponse(byte[] xml) {
          m_xml = xml;
      }
      
      public void setExtra(Serializable extra) {
          m_extra = extra;
      }
      
      public Serializable getExtra() {
          return m_extra;
      }
      
  }
  
  
  
  1.1                  cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java
  
  Index: TraversableCachingSource.java
  ===================================================================
  /*
   * 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.net.MalformedURLException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.Map;
  
  import org.apache.avalon.framework.container.ContainerUtil;
  import org.apache.excalibur.source.Source;
  import org.apache.excalibur.source.SourceException;
  import org.apache.excalibur.source.TraversableSource;
  
  /**
   * Traversable version of {@link org.apache.cocoon.components.source.impl.CachingSource}.
   */
  public class TraversableCachingSource extends CachingSource implements TraversableSource {
  
  	// the Source children in case of a collection
      private TraversableSource[] m_children;
      
      public TraversableCachingSource(String protocol,
                                      String uri,
                                      String sourceURI,
                                      String cacheName,
                                      int expires,
                                      Map parameters,
                                      boolean async) {
          super(protocol, uri, sourceURI, cacheName, expires, parameters, async);
      }
      
      public TraversableCachingSource(String protocol,
                                      String location,
                                      TraversableSource source,
                                      String cacheName,
                                      int expires,
                                      Map parameters,
                                      boolean async) {
          super(protocol, location, source, cacheName, expires, parameters, async);
      }
      
      public void dispose() {
  		super.dispose();
  		m_children = null;
      }
      
      // ---------------------------------------------------- TraversableSource implementation
  
      public String getName() {
          
          try {
              initMetaResponse(false);
          }
          catch (IOException e) {
              if (getLogger().isDebugEnabled()) {
                  getLogger().debug("Failure initializing traversable response", e);
              }
              return null;
          }
          
          return ((TraversableSourceMeta) super.response.getExtra()).getName();
      }
  
      public boolean isCollection() {
          
          try {
              initMetaResponse(false);
          }
          catch (IOException e) {
              if (getLogger().isDebugEnabled()) {
                  getLogger().debug("Failure initializing traversable response", e);
              }
              return false;
          }
          
          return ((TraversableSourceMeta) super.response.getExtra()).isCollection();
      }
  
      public Source getChild(String name) throws SourceException {
          
          try {
              initMetaResponse(false);
          }
          catch (SourceException e) {
              throw e;
          }
          catch (IOException e) {
              throw new SourceException("Failure getting child", e);
          }
          
          if (!isCollection()) {
              throw new SourceException("Source is not a collection");
          }
          
          return createChildSource(name);
      }
  
      public Collection getChildren() throws SourceException {
          
          try {
              initMetaResponse(false);
          }
          catch (SourceException e) {
              throw e;
          }
          catch (IOException e) {
              throw new SourceException("Failure getting child", e);
          }
          
          if (!isCollection()) {
              throw new SourceException("Source is not a collection");
          }
  
          final Collection result = new ArrayList();
          if (m_children != null) {
              for (int i = 0; i < m_children.length; i++) {
                  result.add(createChildSource(m_children[i]));
              }
          }
          else {
              final String[] children = ((TraversableSourceMeta) super.response.getExtra()).getChildren();
              for (int i = 0; i < children.length; i++) {
                  result.add(createChildSource(children[i]));
              }
          }
          
          return result;
      }
  
      public Source getParent() throws SourceException {
          
          try {
              initMetaResponse(false);
          }
          catch (SourceException e) {
              throw e;
          }
          catch (IOException e) {
              throw new SourceException("Failure getting child", e);
          }
          
          return createSource(getParentURI(super.uri), getParentURI(super.sourceURI));
      }
  
  
      // ---------------------------------------------------- helper methods
      
      protected SourceMeta readMeta() throws IOException {
          final TraversableSourceMeta meta = new TraversableSourceMeta();
          
          meta.setName(getTraversableSource().getName());
          meta.setIsCollection(getTraversableSource().isCollection());
          meta.setLastModified(getTraversableSource().getLastModified());
          meta.setMimeType(getTraversableSource().getMimeType());
          
          if (meta.isCollection()) {
              final Collection children = getTraversableSource().getChildren();
              if (children != null) {
                  m_children = new TraversableSource[children.size()];
                  final String[] names = new String[children.size()];
                  final Iterator iter = children.iterator();
                  int count = 0;
                  while(iter.hasNext()) {
                      TraversableSource child = (TraversableSource) iter.next();
                      m_children[count] = child;
                      names[count] = child.getName();
                      count++;
                  }
                  meta.setChildren(names);
              }
          }
          return meta;
      }
      
      protected TraversableSource getTraversableSource() throws MalformedURLException, IOException {
          return (TraversableSource) getSource();
      }
      
      private TraversableCachingSource createChildSource(TraversableSource childSource)
      throws SourceException {
          return createSource(childSource, getChildURI(super.uri, childSource.getName()));
      }
  
      private TraversableCachingSource createSource(TraversableSource wrappedSource, String sourceURI)
      throws SourceException {
          final TraversableCachingSource child = 
              new TraversableCachingSource(super.protocol,
                                           sourceURI,
                                           wrappedSource,
                                           super.cacheName,
                                           super.expires,
                                           super.parameters,
                                           super.async);
          child.cache = super.cache;
          child.resolver = super.resolver;
          ContainerUtil.enableLogging(child,getLogger());
          try {
              ContainerUtil.service(child,super.manager);
              ContainerUtil.initialize(child);
          } catch (Exception e) {
              throw new SourceException("Unable to initialize source.", e);
          }
          return child;
      }
      
      private TraversableCachingSource createChildSource(String childName) 
      throws SourceException {
          return createSource(getChildURI(super.uri, childName), getChildURI(super.sourceURI, childName));
      }
  
      private TraversableCachingSource createSource(String uri, String sourceURI) 
      throws SourceException {
          final TraversableCachingSource source = 
              new TraversableCachingSource(super.protocol,
                                           uri,
                                           sourceURI,
                                           super.cacheName,
                                           super.expires,
                                           super.parameters,
                                           super.async);
          source.cache = super.cache;
          source.resolver = super.resolver;
          ContainerUtil.enableLogging(source, getLogger());
          try {
              ContainerUtil.service(source, super.manager);
              ContainerUtil.initialize(source);
          } catch (Exception e) {
              throw new SourceException("Unable to initialize source.", e);
          }
          return source;
      }
      
      /**
       * Calculate the cached child URI based on a parent URI
       * and a child name.
       */
      private String getChildURI(String parentURI, String childName) {
          
          // separate query string from rest of parentURI
          String rest, qs;
          int index = parentURI.indexOf('?');
          if (index != -1) {
              rest = parentURI.substring(0,index);
              qs = parentURI.substring(index);
          }
          else {
              rest = parentURI;
              qs = "";
          }
          
          // calculate qs-less child uri
          String childURI;
          if (rest.charAt(rest.length()-1) == '/') {
              childURI = rest + childName;
          }
          else {
              childURI = rest + "/" + childName;
          }
          
          return childURI + qs;
      }
  
      /**
       * Calculate the cached parent URI based on a child URI.
       */
      private String getParentURI(String childURI) {
          
          // separate query string from rest of uri
          String rest, qs;
          int index = childURI.indexOf('?');
          if (index != -1) {
              rest = childURI.substring(0, index);
              qs = childURI.substring(index);
          }
          else {
              rest = childURI;
              qs = "";
          }
          
          // calculate qs-less parent uri
          String parentUri;
          index = rest.lastIndexOf('/',rest.length()-2);
          if (index != -1) {
              parentUri = rest.substring(0,index);
          }
          else {
              parentUri = rest;
          }
          
          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;
          }
          
      }
  }
  
  
  
  1.1                  cocoon-2.1/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java
  
  Index: DelayRefresher.java
  ===================================================================
  /*
   * 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.File;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.Writer;
  import java.net.URLDecoder;
  import java.net.URLEncoder;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  
  import org.apache.avalon.framework.CascadingException;
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
  import org.apache.avalon.framework.context.Context;
  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.SimpleCacheKey;
  import org.apache.cocoon.components.cron.CronJob;
  import org.apache.cocoon.components.cron.JobScheduler;
  import org.apache.cocoon.components.source.SourceUtil;
  import org.apache.excalibur.source.ModifiableSource;
  import org.apache.excalibur.source.Source;
  import org.apache.excalibur.source.SourceException;
  import org.apache.excalibur.source.SourceResolver;
  
  /**
   * Default implementation of the refresher.
   * 
   * @since 2.1.1
   * @version CVS $Id: DelayRefresher.java,v 1.1 2004/03/22 17:38:25 unico Exp $
   */
  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  = "update-target";
      private static final String PARAM_WRITE_INTERVAL = "write-interval";
  	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_URI = "uri";
      
      // service dependencies
      protected ServiceManager manager;
      protected SourceResolver resolver;
  	protected JobScheduler scheduler;
      
      // the role name of the update CronJob
      protected String updateTarget;
      
      // 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;
      
      // whether anything changed to the entries since last persisting them
      protected boolean changed = false;
      
      
      // ---------------------------------------------------- Lifecycle
      
      public DelayRefresher() {
      }
  
  	/* (non-Javadoc)
  	 * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
  	 */
  	public void contextualize(Context context) throws ContextException {
  		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)
       */
      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);
      }
      
      /* (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, 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(parameters, writeInterval);
          }
          else {
          	if (getLogger().isInfoEnabled()) {
  				getLogger().info("Not writing update targets to file.");
          	}
          }
      }
  
      /* (non-Javadoc)
       * @see org.apache.avalon.framework.activity.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;
          }
      }
      
      // ---------------------------------------------------- Refresher implementation
      
      /* (non-Javadoc)
       * @see org.apache.cocoon.components.source.impl.Refresher#refresh(org.apache.cocoon.caching.SimpleCacheKey, java.lang.String, long, java.lang.String)
       */
      public void refresh(SimpleCacheKey cacheKey,
                          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
      
      /**
       * Persists the job configurations.
       */
      public void execute(String name) {
          if (this.changed && this.writeSource != null) {
              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);
              	}
              }
              
          }
      }
      
      /**
  	 * @param parameters
  	 * @param writeInterval
  	 */
  	private void registerSelfWithScheduler(Parameters parameters, int writeInterval) {
  		try {
  		    this.scheduler.addPeriodicJob(this.getClass().getName(), 
  		                                  this,
  		                                  (long) 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);
  						}
  		            }
  		        }
  		    }
  		}
  	}
  
  	/**
  	 * @param childs
  	 * @param i
  	 * @throws ConfigurationException
  	 * @throws CascadingException
  	 */
  	private void setupSingleRefreshJob(final Configuration conf) throws ConfigurationException, CascadingException {
  		
          final String uri = URLDecoder.decode(conf.getAttribute(ATTR_URI));
  		final String cache = conf.getAttribute(ATTR_CACHE);
          final int expires = conf.getAttributeAsInteger(ATTR_EXPIRES);
  		final String key = URLDecoder.decode(conf.getAttribute(ATTR_KEY));
  		final SimpleCacheKey cacheKey = new SimpleCacheKey(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);
  	}
  
  	/**
  	 * @return
  	 */
  	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 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());
  		}
  	}
  
      /**
  	 * @param writer
  	 * @param iter
  	 * @throws IOException
  	 */
  	private void writeRefreshJobConfiguration(Writer writer, final TargetConfiguration c) throws IOException {
          writer.write("<"+TAGNAME_TARGET+" "+ATTR_URI+"=\"");
          writer.write(URLEncoder.encode(c.parameters.getParameter(ATTR_URI, "")));
          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(URLEncoder.encode(((SimpleCacheKey) c.map.get(CACHE_KEY)).getKey()));
          writer.write("\"/>\n");
  	}
  	
  	/**
  	 * Configuration data holder for scheduled targets.
  	 */
  	class TargetConfiguration {
          
          final Map map;
  		Parameters parameters;
          
          TargetConfiguration(SimpleCacheKey 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);
          }
          
      }
  
  }
  
  
  
  1.1                  cocoon-2.1/src/blocks/scratchpad/conf/caching-source.xconf
  
  Index: caching-source.xconf
  ===================================================================
  <?xml version="1.0"?>
  <!--
    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.
  -->
  
  <xconf xpath="/cocoon/source-factories" unless="component-instance[@name='cached']">
  
      <!--+
          | Caching source protocol.
          | This protocol extension implements a proxy-like source
          | that can cache the contents of its delegate source.
          | 
          | The uri syntax is as follows:
          | cached:subprotocol:/path[?cocoon:cache-expires=60][&cache-name=main]
          | 
          | Where: 
          | - 'cached' is the caching source protocol scheme name as defined below.
          | - 'subprotcol:/path' identifies the source to get the actual contents from
          |   eg. http://cocoon.apache.org/doco.html?version=1.2
          | - optional querystring parameters that start with 'cocoon:' represent special 
          |   instructions to pass to the implementation.
          |
          | The protocol can be parametrized as follows:
          | - 'async' (false). When this is set to true cached source contents are updated
          |   in the background.
          | - 'cache-role' (org.apache.cocoon.caching.Cache). Use this if you want to use
          |   a Cache other than the default one.
          | - 'refresher-role' (org.apache.cocoon.components.source.impl.Refresher). Use this
          |   if you wish to use a Refresher other than the default one.
          |   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.
          +-->
      <component-instance name="cached"
                          class="org.apache.cocoon.components.source.impl.CachingSourceFactory"
                          logger="core.source.cached">
        <!--
        <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"/>
        -->
      </component-instance>
  
  </xconf>
  
  
  
  1.1                  cocoon-2.1/src/blocks/scratchpad/conf/delay-refresher.xconf
  
  Index: delay-refresher.xconf
  ===================================================================
  <?xml version="1.0"?>
  <!--
    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.
  -->
  
  <xconf xpath="/cocoon" unless="component[role='org.apache.cocoon.components.source.impl.Refresher/Delay']">
  
    <!--+
        | A Refresher is a component that is used by the CachingSource protocol.
        | It is responsible for updating the cached contents of sources that are
        | registered with it by the protocol.
        | The DelayRefresher is an implementation that periodically updates the
        | sources.
        | It recognizes the following parameters:
        | - 'write-interval' (0). The Refresher can persist its list of registered
        |   sources to update periodically. This parameter specifies how often
        |   it should write this list to file. Specifying a value of 0 (the default)
        |   means that target configurations will not be persisted at all.
        | - 'write-file' (refresher-targets.xml). Specifying a different value 
        |   than the default for this parameter only makes sense if you configure
        |   more than one DelayRefreshers. It represents the file name, relative to
        |   the cocoon working directory of the file the refresher writes its list
        |   of registered target configurations to.
        +-->
    <component role="org.apache.cocoon.components.source.impl.Refresher/Delay"
               class="org.apache.cocoon.components.source.impl.DelayRefresher"
               logger="core.refresher.delay">
      <!--
      <parameter name="write-interval" value="0"/>
      <parameter name="write-file" value="refresher-targets.xml"/>
      -->
    </component>
    
  </xconf>