Author: unico Date: Thu Nov 11 08:24:43 2004 New Revision: 57444 Added: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java Modified: cocoon/branches/BRANCH_2_1_X/blocks.properties cocoon/branches/BRANCH_2_1_X/gump.xml cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java cocoon/branches/BRANCH_2_1_X/status.xml Log: port changes to CachingSource from trunk
Modified: cocoon/branches/BRANCH_2_1_X/blocks.properties ============================================================================== --- cocoon/branches/BRANCH_2_1_X/blocks.properties (original) +++ cocoon/branches/BRANCH_2_1_X/blocks.properties Thu Nov 11 08:24:43 2004 @@ -59,7 +59,7 @@ #-----[dependency]: "fop" is needed by "tour". #include.block.fop=false #-----[dependency]: "hsqldb" depends on "databases". -#-----[dependency]: "hsqldb" is needed by "jms", "petstore". +#-----[dependency]: "hsqldb" is needed by "jms", "ojb", "petstore". #include.block.hsqldb=false #-----[dependency]: "html" is needed by "portal". #include.block.html=false @@ -82,7 +82,7 @@ #-----[dependency]: "session-fw" depends on "xsp". #-----[dependency]: "session-fw" is needed by "authentication-fw", "portal", "portal-fw". #include.block.session-fw=false -#-----[dependency]: "velocity" is needed by "petstore", "scratchpad". +#-----[dependency]: "velocity" is needed by "petstore". #include.block.velocity=false #include.block.web3=false #-----[dependency]: "xmldb" depends on "databases". @@ -110,10 +110,9 @@ #include.block.cron=false #include.block.deli=false #-----[dependency]: "eventcache" depends on "jms", "xsp" (for samples). -#-----[dependency]: "eventcache" is needed by "repository". +#-----[dependency]: "eventcache" is needed by "repository", "scratchpad". #include.block.eventcache=false #-----[dependency]: "faces" depends on "portal", "taglib". -#-----[dependency]: "faces" is needed by "scratchpad". #include.block.faces=false #-----[dependency]: "forms" depends on "xsp" (for samples). #-----[dependency]: "forms" is needed by "apples", "javaflow", "lucene", "ojb", "petstore", "tour". @@ -124,10 +123,10 @@ #-----[dependency]: "jms" is needed by "eventcache", "slide". #include.block.jms=false #include.block.linotype=false -#-----[dependency]: "mail" depends on "asciiart", "scratchpad". +#-----[dependency]: "mail" depends on "asciiart". #include.block.mail=false #include.block.midi=false -#-----[dependency]: "ojb" depends on "databases", "forms" (for samples). +#-----[dependency]: "ojb" depends on "databases" (for samples), "forms" (for samples), "hsqldb" (for samples). #-----[dependency]: "ojb" is needed by "javaflow". #include.block.ojb=false #-----[dependency]: "petstore" depends on "databases", "forms", "hsqldb", "velocity". @@ -135,10 +134,9 @@ #include.block.proxy=false #include.block.qdox=false #-----[dependency]: "repository" depends on "databases", "eventcache". -#-----[dependency]: "repository" is needed by "slide", "webdav". +#-----[dependency]: "repository" is needed by "scratchpad", "slide", "webdav". #include.block.repository=false -#-----[dependency]: "scratchpad" depends on "axis", "batik" (for samples), "cron", "faces", "velocity", "xsp". -#-----[dependency]: "scratchpad" is needed by "mail". +#-----[dependency]: "scratchpad" depends on "axis", "batik" (for samples), "cron", "eventcache", "repository", "xsp". #include.block.scratchpad=false #include.block.serializers=false #-----[dependency]: "slide" depends on "jms", "repository". Modified: cocoon/branches/BRANCH_2_1_X/gump.xml ============================================================================== --- cocoon/branches/BRANCH_2_1_X/gump.xml (original) +++ cocoon/branches/BRANCH_2_1_X/gump.xml Thu Nov 11 08:24:43 2004 @@ -155,6 +155,8 @@ <depend project="cocoon-block-axis"/> <depend project="cocoon-block-batik" type="samples"/> <depend project="cocoon-block-cron"/> + <depend project="cocoon-block-eventcache"/> + <depend project="cocoon-block-repository"/> <depend project="cocoon-block-xsp"/> <depend project="apache-garbage"/> Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/conf/caching-source.xconf Thu Nov 11 08:24:43 2004 @@ -42,7 +42,7 @@ | A Refresher is used when asynchronic caching is turned on. It is responsible for | updating the cached contents in the background. | - 'default-expires' (-1). The expires value if it is not specified on the source - | itself. + | itself. The default value of -1 means to never expire. +--> <component-instance name="cached" class="org.apache.cocoon.components.source.impl.CachingSourceFactory" @@ -50,9 +50,9 @@ <!-- <parameter name="async" value="true"/> <parameter name="cache-role" value="org.apache.cocoon.caching.Cache"/> - <parameter name="refresher-role" value="org.apache.cocoon.components.source.impl.Refresher/Delay"/> <parameter name="default-expires" value="-1"/> --> + <parameter name="refresher-role" value="org.apache.cocoon.components.source.impl.Refresher/Delay"/> </component-instance> </xconf> Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSource.java Thu Nov 11 08:24:43 2004 @@ -1,12 +1,12 @@ /* * Copyright 1999-2004 The Apache Software Foundation. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,19 +20,19 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.logger.AbstractLogEnabled; -import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.cocoon.CascadingIOException; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.caching.Cache; +import org.apache.cocoon.caching.EventAware; import org.apache.cocoon.caching.IdentifierCacheKey; +import org.apache.cocoon.caching.validity.EventValidity; +import org.apache.cocoon.caching.validity.NamedEvent; import org.apache.cocoon.components.sax.XMLDeserializer; import org.apache.cocoon.components.sax.XMLSerializer; import org.apache.cocoon.xml.ContentHandlerWrapper; @@ -40,9 +40,7 @@ import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceNotFoundException; -import org.apache.excalibur.source.SourceResolver; import org.apache.excalibur.source.SourceValidity; -import org.apache.excalibur.source.TraversableSource; import org.apache.excalibur.source.impl.validity.ExpiresValidity; import org.apache.excalibur.source.impl.validity.TimeStampValidity; import org.apache.excalibur.xml.sax.XMLizable; @@ -54,7 +52,7 @@ * This class implements a proxy like source that uses another source * to get the content. This implementation can cache the content for * a given period of time - * + * * <h2>Syntax for Protocol</h2> * <p> * cached:http://www.apache.org/[?cocoon:cache-expires=60&cocoon:cache-name=main] @@ -62,95 +60,95 @@ * <p> * The above examples show how the real source <code>http://www.apache.org</code> * is wrapped and the cached contents is used for <code>60</code> seconds. - * The second querystring parameter instructs that the cache key be extended with the string + * The second querystring parameter instructs that the cache key be extended with the string * <code>main</code>. This allows the use of multiple cache entries for the same source. * </p> * <p> - * The value of the expires parameter holds some additional semantics. + * The value of the expires parameter holds some additional semantics. * Specifying <code>-1</code> will yield the cached response to be considered valid - * always. <code>0</code> can be used to achieve the exact opposite. That is to say, + * always. <code>0</code> can be used to achieve the exact opposite. That is to say, * the cached contents will be thrown out and updated immediately and unconditionally. * <p> - * @version CVS $Id: CachingSource.java,v 1.11 2004/04/15 08:05:56 cziegeler Exp $ + * @version CVS $Id$ */ public class CachingSource extends AbstractLogEnabled implements Source, Serviceable, Initializable, XMLizable { + + // ---------------------------------------------------- Constants - /** The ServiceManager */ - protected ServiceManager manager; + public static final String CACHE_EXPIRES_PARAM = "cache-expires"; + public static final String CACHE_NAME_PARAM = "cache-name"; - /** The SourceResolver to resolve the wrapped Source */ - protected SourceResolver resolver; + // ---------------------------------------------------- Instance variables + /** The ServiceManager */ + protected ServiceManager manager; + /** The current cache */ protected Cache cache; - - /** The refresher for asynchronous updates */ - protected Refresher refresher; - + /** The source object for the real content */ protected Source source; - + /** The cached response (if any) */ protected CachedSourceResponse response; - + /** Did we just update meta info? */ protected boolean freshMeta; - + /** The full location string */ final protected String uri; - + /** The used protocol */ final protected String protocol; - + /** The key used in the store */ final protected IdentifierCacheKey cacheKey; /** number of seconds before cached object becomes invalid */ final protected int expires; - /** Parameters */ - final protected Parameters parameters; + /** cache key extension */ + final protected String cacheName; /** asynchronic refresh strategy ? */ final protected boolean async; - + /** * Construct a new object. */ public CachingSource(final String protocol, final String uri, final Source source, - final Parameters parameters, final int expires, + final String cacheName, final boolean async) { this.protocol = protocol; this.uri = uri; this.source = source; this.expires = expires; + this.cacheName = cacheName; this.async = async; - this.parameters = parameters; String key = "source:" + source.getURI(); - String cacheName = parameters.getParameter("cache-name", null); if (cacheName != null) { key += ":" + cacheName; } this.cacheKey = new IdentifierCacheKey(key, false); } - + /** * Set the ServiceManager. */ public void service(final ServiceManager manager) throws ServiceException { this.manager = manager; } - + /** * Initialize the Source. */ public void initialize() throws Exception { - + boolean checkValidity = true; if (this.expires == -1) { if (getLogger().isDebugEnabled()) { @@ -158,115 +156,75 @@ } checkValidity = false; } - + if (this.async && this.expires != 0) { if (getLogger().isDebugEnabled()) { - getLogger().debug("Not invalidating cached response " + "for asynch source " + getSourceURI()); + getLogger().debug("Using cached response if available."); } checkValidity = false; } this.response = (CachedSourceResponse) this.cache.get(this.cacheKey); + if (this.response == null) { if (getLogger().isDebugEnabled()) { - getLogger().debug("No cached response found " + "for source " + getSourceURI()); + getLogger().debug("No cached response found."); } checkValidity = false; } - - if (checkValidity) { - - final ExpiresValidity cacheValidity = (ExpiresValidity) this.response.getValidityObjects()[0]; - final SourceValidity sourceValidity = this.response.getValidityObjects()[1]; - - boolean remove = false; - if (this.expires == 0) { - if (getLogger().isDebugEnabled()) { - getLogger().debug("Force invalidation of cached response" + " of source " + getSourceURI()); - } - remove = true; - } - else { - boolean expired = cacheValidity.isValid() != SourceValidity.VALID; - if (expired) { - if (getLogger().isDebugEnabled()) { - getLogger().debug("Cached response of source " - + getSourceURI() + " is expired."); - } - boolean invalid = !isValid(sourceValidity, this.source); - if (invalid) { - if (getLogger().isDebugEnabled()) { - getLogger().debug("Cached response of source " - + getSourceURI() + " is invalid."); - } - remove = true; - } - else { - if (getLogger().isDebugEnabled()) { - getLogger().debug("Cached response of source " - + getSourceURI() + " is still valid."); - } - // set new expiration period - this.response.getValidityObjects()[0] = new ExpiresValidity(getExpiration()); - } - } - } - - if (remove) { - this.response = null; - // remove it if it no longer exists - if (!exists()) { - this.cache.remove(this.cacheKey); - } + + if (this.expires == 0) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Not using cached response."); } + this.response = null; + checkValidity = false; } - if (this.async && this.expires > 0) { - // schedule it with the refresher - this.refresher.refresh(this.cacheKey, - getSourceURI(), - this.parameters.getParameter("cache-role", null), - this.parameters); + + if (checkValidity && !checkValidity()) { + this.response = null; + // remove it if it no longer exists + if (!this.source.exists()) { + remove(); + } } + } - + /** * Cleanup. */ public void dispose() { - if (this.source != null) { - this.resolver.release(this.source); - this.source = null; - } + this.source = null; this.manager = null; - this.resolver = null; this.cache = null; } - + /** * Initialize the cached response with meta info. - * + * * @throws IOException if an the binary response could not be initialized */ protected void initMetaResponse() throws IOException { boolean storeResponse = false; CachedSourceResponse response = this.response; if (response == null) { - if (this.expires != 0) { - final SourceValidity cacheValidity = new ExpiresValidity(getExpiration()); - final SourceValidity sourceValidity = source.getValidity(); - response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity}); - storeResponse = true; + SourceValidity[] validities; + if (this.cache instanceof EventAware) { + validities = new SourceValidity[] { new EventValidity(new NamedEvent(this.source.getURI())) }; } else { - response = new CachedSourceResponse(null); + validities = new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity() }; } + response = new CachedSourceResponse(validities); + storeResponse = true; } if (response.getExtra() == null) { response.setExtra(readMeta(this.source)); this.freshMeta = true; } + this.response = response; if (storeResponse) { - this.response = response; try { this.cache.store(this.cacheKey, this.response); } @@ -275,10 +233,10 @@ } } } - + /** * Initialize the cached response with binary contents. - * + * * @throws IOException if an the binary response could not be initialized */ protected void initBinaryResponse() throws IOException { @@ -286,25 +244,19 @@ /* delay caching the response until we have a valid new one */ CachedSourceResponse response = this.response; if (response == null) { - if (this.expires != 0) { - final SourceValidity cacheValidity = new ExpiresValidity(getExpiration()); - final SourceValidity sourceValidity = source.getValidity(); - response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity}); - storeResponse = true; - } - else { - response = new CachedSourceResponse(null); - } + response = new CachedSourceResponse(new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity()}); + storeResponse = true; } if (response.getBinaryResponse() == null) { response.setBinaryResponse(readBinaryResponse(this.source)); if (!this.freshMeta) { /* always refresh meta in this case */ response.setExtra(readMeta(this.source)); + this.freshMeta = true; } } + this.response = response; if (storeResponse) { - this.response = response; try { this.cache.store(this.cacheKey, this.response); } @@ -313,10 +265,10 @@ } } } - + /** * Initialize the cached response with XML contents. - * + * * @param refresh whether to force refresh. * @throws SAXException if something happened during xml processing * @throws IOException if an IO level error occured @@ -327,15 +279,8 @@ /* delay caching the response until we have a valid new one */ CachedSourceResponse response = this.response; if (response == null) { - if (this.expires != 0) { - final SourceValidity cacheValidity = new ExpiresValidity(getExpiration()); - final SourceValidity sourceValidity = source.getValidity(); - response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity}); - storeResponse = true; - } - else { - response = new CachedSourceResponse(null); - } + response = new CachedSourceResponse(new SourceValidity[] { new ExpiresValidity(getExpiration()), source.getValidity() }); + storeResponse = true; } if (response.getXMLResponse() == null || refresh) { byte[] binary = response.getBinaryResponse(); @@ -343,10 +288,11 @@ if (!this.freshMeta) { /* always refresh meta in this case */ response.setExtra(readMeta(this.source)); + this.freshMeta = true; } } + this.response = response; if (storeResponse) { - this.response = response; try { this.cache.store(this.cacheKey, this.response); } @@ -355,9 +301,9 @@ } } } - + // ---------------------------------------------------- Source implementation - + /** * Return the protocol identifier. */ @@ -400,7 +346,7 @@ } return ((SourceMeta) this.response.getExtra()).getMimeType(); } - + /** * Return an <code>InputStream</code> object to read from the source. */ @@ -424,9 +370,9 @@ * @see org.apache.excalibur.source.Source#exists() */ public boolean exists() { - return this.source.exists(); + return this.source.exists(); } - + /** * Get the Validity object. This can either wrap the last modification * date or the expires information or... @@ -440,18 +386,62 @@ } return null; } - + /** * Refresh this object and update the last modified date - * and content length. + * and content length. This method will try to refresh the + * cached contents. */ public void refresh() { - this.response = null; this.source.refresh(); + if (response != null && checkValidity()) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Cached response is still valid for source " + this.uri + "."); + } + } + else { + if (this.source.exists()) { + CachedSourceResponse response = this.response; + try { + if (response == null) { + // create a new cached response + response = new CachedSourceResponse(new SourceValidity[] { + new ExpiresValidity(getExpiration()), source.getValidity()}); + } + // only create objects that are cached + if (response.getBinaryResponse() != null) { + response.setBinaryResponse(readBinaryResponse(source)); + } + if (response.getXMLResponse() != null) { + response.setXMLResponse(readXMLResponse( + source, response.getBinaryResponse(), this.manager)); + } + // always refresh meta data + response.setExtra(readMeta(source)); + this.response = response; + cache.store(this.cacheKey, response); + } + catch (Exception e) { + getLogger().warn("Error refreshing source " + this.uri + + "Cached response (if any) may be stale.", e); + } + } + else if (this.response != null) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Source " + this.uri + " no longer exists." + + " Throwing out cached response."); + } + remove(); + } + } } - // ---------------------------------------------------- XMLizable implementation + protected void remove() { + this.cache.remove(this.cacheKey); + } + // ---------------------------------------------------- XMLizable implementation + /** * Generates SAX events representing the object's state. */ @@ -476,46 +466,46 @@ this.manager.release(deserializer); } } - + // ---------------------------------------------------- CachingSource specific accessors - + /** * Return the uri of the cached source. */ protected String getSourceURI() { return this.source.getURI(); } - + /** * Return the used key. */ protected IdentifierCacheKey getCacheKey() { return this.cacheKey; } - + /** * Expires (in milli-seconds) */ protected long getExpiration() { return this.expires * 1000; } - + /** * Read XML content from source. - * - * @return content from source - * @throws SAXException - * @throws IOException - * @throws CascadingIOException - */ - protected static byte[] readXMLResponse(Source source, byte[] binary, ServiceManager manager) + * + * @return content from source + * @throws SAXException + * @throws IOException + * @throws CascadingIOException + */ + protected byte[] readXMLResponse(Source source, byte[] binary, ServiceManager manager) throws SAXException, IOException, CascadingIOException { XMLSerializer serializer = null; XMLizer xmlizer = null; byte[] result = null; - try { - serializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE); - + try { + serializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE); + if (source instanceof XMLizable) { ((XMLizable) source).toSAX(serializer); } @@ -532,75 +522,54 @@ serializer); } } - result = (byte[]) serializer.getSAXFragment(); - } catch (ServiceException se) { - throw new CascadingIOException("Missing service dependency.", se); - } finally { + result = (byte[]) serializer.getSAXFragment(); + } catch (ServiceException se) { + throw new CascadingIOException("Missing service dependency.", se); + } finally { manager.release(xmlizer); - manager.release(serializer); - } - return result; - } + manager.release(serializer); + } + return result; + } - /** + /** * Read binary content from source. - * - * @return content from source - * @throws IOException - * @throws SourceNotFoundException - */ - protected static byte[] readBinaryResponse(Source source) + * + * @return content from source + * @throws IOException + * @throws SourceNotFoundException + */ + protected byte[] readBinaryResponse(Source source) throws IOException, SourceNotFoundException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final byte[] buffer = new byte[2048]; - final InputStream inputStream = source.getInputStream(); - int length; - while ((length = inputStream.read(buffer)) > -1) { - baos.write(buffer, 0, length); - } - baos.flush(); - inputStream.close(); - return baos.toByteArray(); - } - + final byte[] buffer = new byte[2048]; + final InputStream inputStream = source.getInputStream(); + int length; + while ((length = inputStream.read(buffer)) > -1) { + baos.write(buffer, 0, length); + } + baos.flush(); + inputStream.close(); + return baos.toByteArray(); + } + /** * Read meta data from source. - * + * * @return source meta data * @throws IOException */ - protected static SourceMeta readMeta(Source source) throws IOException { - SourceMeta meta; - - if (source instanceof TraversableSource) { - - final TraversableSourceMeta tmeta = new TraversableSourceMeta(); - final TraversableSource tsource = (TraversableSource) source; - - tmeta.setName(tsource.getName()); - tmeta.setIsCollection(tsource.isCollection()); - - if (tmeta.isCollection()) { - final Collection children = tsource.getChildren(); - if (children != null) { - final String[] names = new String[children.size()]; - final Iterator iter = children.iterator(); - int count = 0; - while(iter.hasNext()) { - TraversableSource child = (TraversableSource) iter.next(); - names[count] = child.getName(); - count++; - } - tmeta.setChildren(names); - } - } - - meta = tmeta; - } - else { - meta = new SourceMeta(); - } - + protected final SourceMeta readMeta(Source source) throws IOException { + SourceMeta meta = createMeta(); + initMeta(meta, source); + return meta; + } + + protected SourceMeta createMeta() { + return new SourceMeta(); + } + + protected void initMeta(SourceMeta meta, Source source) throws IOException { final long lastModified = source.getLastModified(); if (lastModified > 0) { meta.setLastModified(lastModified); @@ -609,80 +578,89 @@ meta.setLastModified(System.currentTimeMillis()); } meta.setMimeType(source.getMimeType()); + } + + private boolean checkValidity() { + if (this.response == null) return false; - return meta; + final SourceValidity[] validities = this.response.getValidityObjects(); + boolean valid = true; + if (validities.length == 2) { + final ExpiresValidity expiresValidity = (ExpiresValidity) validities[0]; + final SourceValidity sourceValidity = validities[1]; + + if (expiresValidity.isValid() != SourceValidity.VALID) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Cached response of source " + getSourceURI() + " is expired."); + } + if (!isValid(sourceValidity, source.getValidity())) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Cached response of source " + getSourceURI() + " is invalid."); + } + valid = false; + } + else { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Cached response of source " + getSourceURI() + " is still valid."); + } + // set new expiration period + this.response.getValidityObjects()[0] = new ExpiresValidity(getExpiration()); + } + } + else { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Cached response of source " + getSourceURI() + " is NOT expired."); + } + } + } + else { + // assert(validities.length == 1 && validities[0] instanceof EventValidity) + if (getLogger().isDebugEnabled()) { + getLogger().debug("Cached response of source does not expire"); + } + } + return valid; } - protected static boolean isValid(SourceValidity validity, Source source) { - if (validity == null) return false; - return validity.isValid() == SourceValidity.VALID || - (validity.isValid() == SourceValidity.UNKNOWN && - validity.isValid(source.getValidity()) == SourceValidity.VALID); + private static boolean isValid(SourceValidity oldValidity, SourceValidity newValidity) { + return (oldValidity.isValid() == SourceValidity.VALID || + (oldValidity.isValid() == SourceValidity.UNKNOWN && + oldValidity.isValid(newValidity) == SourceValidity.VALID)); } - + /** * Data holder for caching Source meta info. */ protected static class SourceMeta implements Serializable { - + private String m_mimeType; private long m_lastModified; private boolean m_exists; - + protected String getMimeType() { return m_mimeType; } - + protected void setMimeType(String mimeType) { m_mimeType = mimeType; } - + protected long getLastModified() { return m_lastModified; } - + protected void setLastModified(long lastModified) { m_lastModified = lastModified; } - + protected boolean exists() { return m_exists; } - + protected void setExists(boolean exists) { m_exists = exists; } - - } - - protected static class TraversableSourceMeta extends SourceMeta { - private String m_name; - private boolean m_isCollection; - private String[] m_children; - - protected String getName() { - return m_name; - } - - protected void setName(String name) { - m_name = name; - } - - protected boolean isCollection() { - return m_isCollection; - } - - protected void setIsCollection(boolean isCollection) { - m_isCollection = isCollection; - } - - protected String[] getChildren() { - return m_children; - } - - protected void setChildren(String[] children) { - m_children = children; - } + } } Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/CachingSourceFactory.java Thu Nov 11 08:24:43 2004 @@ -1,12 +1,12 @@ /* * Copyright 1999-2004 The Apache Software Foundation. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,12 +26,14 @@ import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.container.ContainerUtil; import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.logger.Logger; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.framework.thread.ThreadSafe; import org.apache.cocoon.caching.Cache; +import org.apache.cocoon.components.source.InspectableSource; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceFactory; @@ -43,36 +45,36 @@ /** * This class implements a proxy like source caches the contents of the source - * it wraps. This implementation can cache the content either + * it wraps. This implementation can cache the content either * for a given period of time or until an external event invalidates * the cached response. * <p> * When using the timeout approach you have a choice between two separate * revalidation strategies. * </p> - * 1) Synchronously. This means that the cached contents are checked for validity + * 1) Synchronously. This means that the cached contents are checked for validity * and thrown out on the current thread.<br> * 2) Asynchronously. A cronjob is scheduled to invalidate and update the cached response * in the backgound.<br><br> - * + * * <h2>Protocol syntax</h2> * <p> * The URL needs to contain the URL of the cached source, an expiration - * period in seconds, and optionally a cache key: + * period in seconds, and optionally a cache key: * <code>cached:http://www.apache.org/[?cocoon:cache-expires=60][&cocoon:cache-name=main]</code>. * </p> * <p> * The above examples shows how the real source <code>http://www.apache.org/</code> * is wrapped and the cached contents is used for <code>60</code> seconds. - * The second querystring parameter instructs that the cache key be extended with the string + * The second querystring parameter instructs that the cache key be extended with the string * <code>main</code>. This allows the use of multiple cache entries for the same source. * </p> * <p> - * This factory creates either instances of [EMAIL PROTECTED] org.apache.cocoon.components.source.impl.CachingSource} + * This factory creates either instances of [EMAIL PROTECTED] org.apache.cocoon.components.source.impl.CachingSource} * or [EMAIL PROTECTED] org.apache.cocoon.components.source.impl.TraversableCachingSource} * depending on the whether the wrapped Source is an instance of TraversableSource. * </p> - * + * * <h2>Parameters</h2> * <table><tbody> * <tr> @@ -104,29 +106,39 @@ * <td><code>-1</code></td> * </tr> * </tbody></table> - * - * @version CVS $Id: CachingSourceFactory.java,v 1.10 2004/05/07 17:32:59 joerg Exp $ + * + * @version CVS $Id$ * @since 2.1.1 */ public final class CachingSourceFactory extends AbstractLogEnabled implements SourceFactory, URIAbsolutizer, Serviceable, Configurable, Disposable, ThreadSafe { + // ---------------------------------------------------- Constants + + public static final String ASYNC_PARAM = "async"; + public static final String FAILSAFE_PARAM = "failsafe"; + public static final String CACHE_ROLE_PARAM = "cache-role"; + public static final String REFRESHER_ROLE_PARAM = "refresher-role"; + public static final String DEFAULT_EXPIRES_PARAM = "default-expires"; + + // ---------------------------------------------------- Instance variables + /** Protocol prefix / factory name */ private String scheme; - + /** Asynchronous ? */ private boolean async; - + /** The role of the cache */ private String cacheRole; - + /** The role of the refresher */ private String refresherRole; - + /** Default expires value */ private int defaultExpires; - + /** Has the lazy initialization been done? */ private boolean isInitialized; @@ -135,53 +147,53 @@ /** The [EMAIL PROTECTED] SourceResolver} */ protected SourceResolver resolver; - + /** The refresher */ protected Refresher refresher; - + /** The cache */ protected Cache cache; - + // ---------------------------------------------------- Lifecycle - + public CachingSourceFactory() { } - - public void service(ServiceManager manager) throws ServiceException { + + public void service(ServiceManager manager) { this.manager = manager; - // due to cyclic dependencies we can't lookup the resolver the refresher - // or the cache until after the factory is initialized. + // Due to cyclic dependencies we can't lookup the resolver, + // the refresher or the cache until after the factory is + // initialized. } - + public void configure(Configuration configuration) throws ConfigurationException { this.scheme = configuration.getAttribute("name"); Parameters parameters = Parameters.fromConfiguration(configuration); + + // 'async' parameter + this.async = parameters.getParameterAsBoolean(ASYNC_PARAM, false); - // 'async' parameter - this.async = parameters.getParameterAsBoolean("async", false); - // 'cache-role' parameter - this.cacheRole = parameters.getParameter("cache-role", Cache.ROLE); + this.cacheRole = parameters.getParameter(CACHE_ROLE_PARAM, Cache.ROLE); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Using cache " + this.cacheRole); } - + // 'refresher-role' parameter if (this.async) { - this.refresherRole = parameters.getParameter("refresher-role", Refresher.ROLE); + this.refresherRole = parameters.getParameter(REFRESHER_ROLE_PARAM, Refresher.ROLE); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Using refresher " + this.refresherRole); } } - - this.defaultExpires = parameters.getParameterAsInteger("default-expires",-1); - + + this.defaultExpires = parameters.getParameterAsInteger(DEFAULT_EXPIRES_PARAM, -1); } - + /** * Lazy initialization of resolver and refresher because of * cyclic dependencies. - * + * * @throws SourceException */ private synchronized void lazyInitialize() throws SourceException { @@ -214,7 +226,7 @@ } this.isInitialized = true; } - + /* (non-Javadoc) * @see org.apache.avalon.framework.activity.Disposable#dispose() */ @@ -227,32 +239,32 @@ this.resolver = null; } } - + // ---------------------------------------------------- SourceFactory implementation - + /** * Get a <code>Source</code> object. * @param parameters This is optional. */ public Source getSource(final String location, final Map parameters) throws MalformedURLException, IOException { - + if (this.getLogger().isDebugEnabled() ) { this.getLogger().debug("Creating source object for " + location); } - + // we must do lazy initialization because of cyclic dependencies if (!this.isInitialized) { lazyInitialize(); } - + // snip the cache protocol int index = location.indexOf(':'); if (index == -1) { throw new MalformedURLException("This Source requires a subprotocol to be specified."); } String uri = location.substring(index+1); - + // parse the query string SourceParameters sp = null; String queryString = null; @@ -262,7 +274,7 @@ uri = uri.substring(0,index); sp = new SourceParameters(queryString); } - + // put caching source specific query string parameters // into a Parameters object final Parameters params = new Parameters(); @@ -280,52 +292,92 @@ uri += "?" + queryString; } } + + int expires = params.getParameterAsInteger(CachingSource.CACHE_EXPIRES_PARAM, this.defaultExpires); + String cacheName = params.getParameter(CachingSource.CACHE_NAME_PARAM, null); + + final CachingSource source = newCachingSource(this.resolver.resolveURI(uri), + this.scheme, + location, + expires, + cacheName, + this.async, + this.cache, + getLogger(), + manager); - int expires = params.getParameterAsInteger("cache-expires", -1); - if (expires == -1) { - expires = this.defaultExpires; - params.setParameter("cache-expires", String.valueOf(this.defaultExpires)); + if (this.async && expires > 0) { + + params.setParameter(CachingSource.CACHE_EXPIRES_PARAM, String.valueOf(expires)); + params.setParameter(CachingSource.CACHE_NAME_PARAM, cacheName); + params.setParameter(CACHE_ROLE_PARAM, this.cacheRole); + + // schedule it with the refresher + this.refresher.refresh(source.getCacheKey(), + source.getSourceURI(), + this.cacheRole, + params); } - params.setParameter("cache-role", this.cacheRole); - final Source wrappedSource = this.resolver.resolveURI(uri); + return source; + } + + /** + * Factory method for creating a new CachingSource. + */ + public static CachingSource newCachingSource(Source wrappedSource, + String scheme, + String uri, + int expires, + String cacheName, + boolean async, + Cache cache, + Logger logger, + ServiceManager manager) + throws SourceException { + CachingSource source; if (wrappedSource instanceof TraversableSource) { - source = new TraversableCachingSource(scheme, - location, - (TraversableSource) wrappedSource, - params, - expires, - this.async); - } - else { + if (wrappedSource instanceof InspectableSource) { + source = new InspectableTraversableCachingSource(scheme, + uri, + (InspectableSource) wrappedSource, + expires, + cacheName, + async); + } else { + source = new TraversableCachingSource(scheme, + uri, + (TraversableSource) wrappedSource, + expires, + cacheName, + async); + } + } else { source = new CachingSource(scheme, - location, + uri, wrappedSource, - params, expires, - this.async); + cacheName, + async); } // set the required components directly for speed - source.cache = this.cache; - source.resolver = this.resolver; - source.refresher = this.refresher; + source.cache = cache; - ContainerUtil.enableLogging(source, this.getLogger()); + ContainerUtil.enableLogging(source, logger); try { // call selected avalon lifecycle interfaces. Mmmh. - ContainerUtil.service(source, this.manager); + ContainerUtil.service(source, manager); ContainerUtil.initialize(source); } catch (ServiceException se) { throw new SourceException("Unable to initialize source.", se); } catch (Exception e) { throw new SourceException("Unable to initialize source.", e); } - return source; } - + /** * Release a [EMAIL PROTECTED] Source} object. */ @@ -334,11 +386,12 @@ if (this.getLogger().isDebugEnabled() ) { this.getLogger().debug("Releasing source " + source.getURI()); } + resolver.release(((CachingSource) source).source); ((CachingSource) source).dispose(); } } - - // ---------------------------------------------------- URIAbsolutizer implementation + + // ---------------------------------------------------- URIAbsolutizer implementation /* * (non-Javadoc) Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/DelayRefresher.java Thu Nov 11 08:24:43 2004 @@ -57,7 +57,7 @@ * Default implementation of the refresher. * * @since 2.1.1 - * @version CVS $Id: DelayRefresher.java,v 1.6 2004/04/25 20:01:36 haul Exp $ + * @version CVS $Id$ */ public class DelayRefresher extends AbstractLogEnabled implements Contextualizable, Serviceable, Parameterizable, Disposable, ThreadSafe, Refresher, CronJob { @@ -267,8 +267,7 @@ } /** - * @param childs - * @param i + * @param conf * @throws ConfigurationException * @throws CascadingException */ @@ -331,7 +330,7 @@ /** * @param writer - * @param iter + * @param c * @throws IOException */ private void writeRefreshJobConfiguration(Writer writer, final TargetConfiguration c) throws IOException { Added: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java ============================================================================== --- (empty file) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/InspectableTraversableCachingSource.java Thu Nov 11 08:24:43 2004 @@ -0,0 +1,184 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cocoon.components.source.impl; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cocoon.components.source.InspectableSource; +import org.apache.cocoon.components.source.helpers.SourceProperty; +import org.apache.excalibur.source.Source; +import org.apache.excalibur.source.SourceException; +import org.apache.excalibur.source.TraversableSource; + +/** + * TraversableCachingSource that adds support for SourceProperty caching. + */ +public class InspectableTraversableCachingSource extends TraversableCachingSource +implements InspectableSource { + + private InspectableSource isource; + + public InspectableTraversableCachingSource(String protocol, + String uri, + InspectableSource source, + int expires, + String cacheName, + boolean async) { + super(protocol, uri, (TraversableSource) source, expires, cacheName, async); + this.isource = source; + } + + public SourceProperty getSourceProperty(String namespace, String name) throws SourceException { + try { + initMetaResponse(); + } + catch (IOException e) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Failure initializing inspectable response", e); + } + return null; + } + final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra()); + SourceProperty property = imeta.getSourceProperty(namespace, name); + if (property == null) { + // In the case of webdav the source cannot + // determine all available properties beforehand. + // Therefore, although we initialized the cached + // response by calling getSourceProperties(), + // this does not mean this particular property + // was returned and cached. Hence we try to + // get it here still and remember if it was null. + property = isource.getSourceProperty(namespace, name); + if (property == null) { + // remember that this property is null + property = InspectableSourceMeta.NULL_PROPERTY; + } + imeta.setSourceProperty(property); + } + if (InspectableSourceMeta.NULL_PROPERTY.equals(property)) { + return null; + } + return property; + } + + public void setSourceProperty(SourceProperty property) throws SourceException { + isource.setSourceProperty(property); + try { + initMetaResponse(); + } + catch (IOException e) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Failure initializing inspectable response", e); + } + } + final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra()); + imeta.setSourceProperty(property); + } + + public SourceProperty[] getSourceProperties() throws SourceException { + try { + initMetaResponse(); + } + catch (IOException e) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Failure initializing inspectable response", e); + } + return null; + } + final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra()); + return imeta.getSourceProperties(); + } + + public void removeSourceProperty(String namespace, String name) throws SourceException { + isource.removeSourceProperty(namespace, name); + try { + initMetaResponse(); + } + catch (IOException e) { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Failure initializing inspectable response", e); + } + } + final InspectableSourceMeta imeta = ((InspectableSourceMeta) super.response.getExtra()); + imeta.removeSourceProperty(namespace, name); + } + + protected SourceMeta createMeta() { + return new InspectableSourceMeta(); + } + + protected void initMeta(SourceMeta meta, Source source) throws IOException { + super.initMeta(meta, source); + final InspectableSourceMeta imeta = ((InspectableSourceMeta) meta); + imeta.setSourceProperties(isource.getSourceProperties()); + } + + protected TraversableCachingSource newSource(String uri, Source wrapped) { + return new InspectableTraversableCachingSource(super.protocol, + uri, + (InspectableSource) wrapped, + super.expires, + super.cacheName, + super.async); + } + + protected static class InspectableSourceMeta extends TraversableSourceMeta { + + protected static final SourceProperty NULL_PROPERTY = new SourceProperty("cocoon", "isnull"); + + private Map properties; + + protected SourceProperty getSourceProperty(String namespace, String name) { + if (properties == null) return null; + final String key = namespace + "#" + name; + return (SourceProperty) properties.get(key); + } + + protected void setSourceProperty(SourceProperty property) { + if (this.properties == null) { + this.properties = Collections.synchronizedMap(new HashMap(11)); + } + final String key = property.getNamespace() + "#" + property.getName(); + properties.put(key, property); + } + + protected SourceProperty[] getSourceProperties() { + if (this.properties == null) return null; + final Collection values = this.properties.values(); + return (SourceProperty[]) values.toArray(new SourceProperty[values.size()]); + } + + protected void setSourceProperties(SourceProperty[] props) { + if (this.properties == null) { + this.properties = Collections.synchronizedMap(new HashMap(props.length)); + } + for (int i = 0; i < props.length; i++) { + setSourceProperty(props[i]); + } + } + + protected void removeSourceProperty(String namespace, String name) { + if (this.properties != null) { + final String key = namespace + "#" + name; + properties.remove(key); + } + } + } +} Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/TraversableCachingSource.java Thu Nov 11 08:24:43 2004 @@ -18,9 +18,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import org.apache.avalon.framework.container.ContainerUtil; -import org.apache.avalon.framework.parameters.Parameters; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.TraversableSource; @@ -33,12 +33,12 @@ private TraversableSource tsource; public TraversableCachingSource(String protocol, - String location, + String uri, TraversableSource source, - Parameters params, int expires, + String cacheName, boolean async) { - super(protocol, location, source, params, expires, async); + super(protocol, uri, source, expires, cacheName, async); this.tsource = source; } @@ -148,18 +148,26 @@ // ---------------------------------------------------- helper methods - private TraversableCachingSource createSource(String uri, Source wrapped) + + + protected final TraversableCachingSource createSource(String uri, Source wrapped) throws SourceException { - final TraversableCachingSource source = - new TraversableCachingSource(super.protocol, - uri, - (TraversableSource) wrapped, - new Parameters().merge(super.parameters), - super.expires, - super.async); + final TraversableCachingSource source = newSource(uri, wrapped); + initializeSource(source); + return source; + } + + protected TraversableCachingSource newSource(String uri, Source wrapped) { + return new TraversableCachingSource(super.protocol, + uri, + (TraversableSource) wrapped, + super.expires, + super.cacheName, + super.async); + } + + protected void initializeSource(TraversableCachingSource source) throws SourceException { source.cache = super.cache; - source.resolver = super.resolver; - source.refresher = super.refresher; ContainerUtil.enableLogging(source, getLogger()); try { ContainerUtil.service(source, super.manager); @@ -167,14 +175,64 @@ } catch (Exception e) { throw new SourceException("Unable to initialize source.", e); } - return source; } + protected SourceMeta createMeta() { + return new TraversableSourceMeta(); + } + + protected void initMeta(SourceMeta meta, Source source) throws IOException { + super.initMeta(meta, source); + + final TraversableSource tsource = (TraversableSource) source; + final TraversableSourceMeta tmeta = (TraversableSourceMeta) meta; + + tmeta.setName(tsource.getName()); + tmeta.setIsCollection(tsource.isCollection()); + + if (tmeta.isCollection()) { + final Collection children = tsource.getChildren(); + if (children != null) { + final String[] names = new String[children.size()]; + final Iterator iter = children.iterator(); + int count = 0; + while(iter.hasNext()) { + TraversableSource child = (TraversableSource) iter.next(); + names[count] = child.getName(); + count++; + } + tmeta.setChildren(names); + } + } + + } + + protected void remove() { + remove(true); + } + + /** + * The parent's cached response needs to be removed from cache + * as well because it's cached list of children is no longer valid. + */ + private void remove(boolean flag) { + super.remove(); + if (flag) { + try { + TraversableCachingSource parent = (TraversableCachingSource) getParent(); + parent.remove(false); + } + catch (SourceException e) { + getLogger().error("Error removing parent's cached response"); + } + } + } + /** * Calculate the cached child URI based on a parent URI * and a child name. */ - private String getChildURI(String parentURI, String childName) { + private static String getChildURI(String parentURI, String childName) { // separate query string from rest of parentURI String rest, qs; @@ -203,7 +261,7 @@ /** * Calculate the cached parent URI based on a child URI. */ - private String getParentURI(String childURI) { + private static String getParentURI(String childURI) { // separate query string from rest of uri String rest, qs; @@ -228,6 +286,36 @@ } return parentUri + qs; + } + + protected static class TraversableSourceMeta extends SourceMeta { + private String m_name; + private boolean m_isCollection; + private String[] m_children; + + protected String getName() { + return m_name; + } + + protected void setName(String name) { + m_name = name; + } + + protected boolean isCollection() { + return m_isCollection; + } + + protected void setIsCollection(boolean isCollection) { + m_isCollection = isCollection; + } + + protected String[] getChildren() { + return m_children; + } + + protected void setChildren(String[] children) { + m_children = children; + } } } Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl/UpdateTarget.java Thu Nov 11 08:24:43 2004 @@ -15,6 +15,7 @@ */ package org.apache.cocoon.components.source.impl; +import java.io.IOException; import java.util.Map; import org.apache.avalon.framework.logger.AbstractLogEnabled; @@ -23,12 +24,8 @@ import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.cocoon.caching.Cache; -import org.apache.cocoon.caching.IdentifierCacheKey; import org.apache.cocoon.components.cron.ConfigurableCronJob; -import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceResolver; -import org.apache.excalibur.source.SourceValidity; -import org.apache.excalibur.source.impl.validity.ExpiresValidity; /** * A target updating a cache entry. @@ -48,17 +45,13 @@ * The time in seconds the cached content is valid * </li> * <li> - * <code>fail-safe (boolean)</code> - * Whether to invalidate the cached response when updating it failed. - * </li> - * <li> - * <code>cache-key (SimpleCacheKey)</code>: + * <code>cache-name (String)</code>: * The key used to cache the content * </li> * </ul> * * @since 2.1.1 - * @version CVS $Id: UpdateTarget.java,v 1.6 2004/04/15 08:05:56 cziegeler Exp $ + * @version CVS $Id$ */ public class UpdateTarget extends AbstractLogEnabled implements Serviceable, ConfigurableCronJob { @@ -71,10 +64,7 @@ private String uri; private String cacheRole; private int expires; - private boolean failSafe; - - // the key under which to store the CachedResponse in the Cache - private IdentifierCacheKey cacheKey; + private String cacheName; // ---------------------------------------------------- Lifecycle @@ -96,14 +86,13 @@ public void setup(Parameters pars, Map objects) { this.uri = pars.getParameter("uri", null); this.cacheRole = pars.getParameter("cache-role", Cache.ROLE); - this.expires = pars.getParameterAsInteger("cache-expires", 60); - this.failSafe = pars.getParameterAsBoolean("fail-safe", true); - this.cacheKey = (IdentifierCacheKey) objects.get("cache-key"); + this.expires = pars.getParameterAsInteger("cache-expires", 0); + this.cacheName = pars.getParameter("cache-name", null); } // ---------------------------------------------------- CronJob implementation - + /* (non-Javadoc) * @see org.apache.avalon.cornerstone.services.scheduler.Target#targetTriggered(java.lang.String) */ @@ -112,78 +101,36 @@ if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Refreshing " + this.uri); } - - Source source = null; Cache cache = null; + CachingSource source = null; try { - - cache = (Cache) this.manager.lookup(this.cacheRole); - source = this.resolver.resolveURI(this.uri); - - // check if the source is really expired and invalid - CachedSourceResponse response = (CachedSourceResponse) cache.get(this.cacheKey); - if (response != null) { - final SourceValidity sourceValidity = response.getValidityObjects()[1]; - if (CachingSource.isValid(sourceValidity, source)) { - if (getLogger().isDebugEnabled()) { - getLogger().debug("Cached response is still valid " + "for source " + this.uri + "."); - } - response.getValidityObjects()[0] = new ExpiresValidity(this.expires * 1000); - return; - } - } - - if (source.exists()) { - - // what is in the cached response? - byte[] binary = null; - byte[] xml = null; - if (response != null) { - binary = response.getBinaryResponse(); - xml = response.getXMLResponse(); - } - - // create a new cached response - final ExpiresValidity cacheValidity = new ExpiresValidity(this.expires * 1000); - final SourceValidity sourceValidity = source.getValidity(); - response = new CachedSourceResponse(new SourceValidity[] {cacheValidity, sourceValidity}); - - // only create objects that have previously been used - if (binary != null) { - binary = CachingSource.readBinaryResponse(source); - response.setBinaryResponse(binary); - } - if (xml != null) { - xml = CachingSource.readXMLResponse(source, binary, this.manager); - response.setXMLResponse(xml); - } - // meta info is always set - response.setExtra(CachingSource.readMeta(source)); - cache.store(this.cacheKey, response); - } - else if (response != null) { - // FIXME: There is a potential problem when the parent - // source has not yet been updated thus listing this - // source still as one of its children. We'll have to remove - // the parent's cached response here too. - if (getLogger().isDebugEnabled()) { - getLogger().debug("Source " + this.uri + " no longer exists." + " Throwing out cached response."); - } - cache.remove(this.cacheKey); - } - } catch (Exception e) { - if (!failSafe) { - // the content expires, so remove it - cache.remove(cacheKey); - getLogger().warn("Exception during updating of source " + this.uri, e); + cache = (Cache) manager.lookup(cacheRole); + source = CachingSourceFactory.newCachingSource( + this.resolver.resolveURI(this.uri), + "cached", + "cached:" + uri, + expires, + cacheName, + true, + cache, + getLogger(), + this.manager); + + source.refresh(); + } + catch (IOException e) { + getLogger().error("Error refreshing source", e); + } + catch (ServiceException e) { + getLogger().error("Error refreshing source", e); + } + finally { + if (cache != null) { + manager.release(cache); } - else { - getLogger().warn("Updating of source " + this.uri + " failed. " + - "Cached response (if any) will be stale.", e); + if (source != null) { + this.resolver.release(source); } - } finally { - this.resolver.release(source); - this.manager.release(cache); } } } Modified: cocoon/branches/BRANCH_2_1_X/status.xml ============================================================================== --- cocoon/branches/BRANCH_2_1_X/status.xml (original) +++ cocoon/branches/BRANCH_2_1_X/status.xml Thu Nov 11 08:24:43 2004 @@ -465,6 +465,11 @@ AbstractMessageListener and AbstractMessagePublisher should be used as basis for custom publish/subscribe components. </action> + <action dev="UH" type="add"> + Still in the scratchpad area at the time of this writing, added a + CachedSource proxy subclass for Sources that implement TraversableSource and + InspectableSource (for instance WebDAVSource). + </action> <action dev="TC" type="add" fixes-bug="29935" due-to="Leszek Gawron" due-to-email="[EMAIL PROTECTED]"> Added support for stripping root elements in the CIncludeTransformer. </action>