haul 2004/02/15 11:49:18
Modified:
src/blocks/scratchpad/java/org/apache/cocoon/components/source/impl
CachingSourceFactory.java
Log:
add docs
add configuration for refresher role
avoid race condition on lazy init possibly leading to memory lead
obtain refresher only when configured to be asynchronous
move async source handling to own method
Revision Changes Path
1.3 +120 -36
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.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- CachingSourceFactory.java 25 Oct 2003 18:06:19 -0000 1.2
+++ CachingSourceFactory.java 15 Feb 2004 19:49:18 -0000 1.3
@@ -78,9 +78,38 @@
* 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.
- *
- * <component-instance
class="org.apache.cocoon.components.source.impl.CachingSourceFactory"
name="cached"/>
+ *
+ * <h2>Example</h2>
+ * <pre>
+ * <component-instance name="cached"
+ *
class="org.apache.cocoon.components.source.impl.CachingSourceFactory"/>
+ * </pre>
*
+ * <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://[EMAIL
PROTECTED]://www.s-und-n.de</code>
+ * or <code>cached://[EMAIL PROTECTED]@http://www.s-und-n.de</code>
+ * </p>
+ * <p>
+ * The above examples show how the real source
<code>http://www.s-und-n.de</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.
+ * </p>
+ * <p>
+ * This factory creates either instances of [EMAIL PROTECTED]
org.apache.cocoon.components.source.impl.CachingSource}
+ * or [EMAIL PROTECTED]
org.apache.cocoon.components.source.impl.AsyncCachingSource}
+ * depending on the <code>async</code> parameter.
+ * </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>[EMAIL PROTECTED]
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>[EMAIL PROTECTED]
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>
+ * </tbody></table>
+ *
* @author <a href="mailto:[EMAIL PROTECTED]">Carsten Ziegeler</a>
* @version CVS $Id$
* @since 2.1.1
@@ -89,6 +118,12 @@
extends AbstractLogEnabled
implements SourceFactory, ThreadSafe, Serviceable, URIAbsolutizer,
Disposable, Parameterizable
{
+ /** The role of the refresher */
+ private String refresherRole;
+
+ /** Has the lazy initialization been done? */
+ private boolean isInitialized;
+
/** The <code>ServiceManager</code> */
protected ServiceManager manager;
@@ -126,48 +161,19 @@
}
// we must do a lazy lookup because of cyclic dependencies
- if (this.resolver == null) {
- try {
- this.resolver = (SourceResolver)this.manager.lookup(
SourceResolver.ROLE );
- } catch (ServiceException se) {
- throw new SourceException("SourceResolver is not
available.", se);
- }
- }
- if ( this.refresher == null ) {
- try {
- this.refresher =
(Refresher)this.manager.lookup(Refresher.ROLE);
- } catch (ServiceException se) {
- throw new SourceException("Refesher is not available.", se);
- }
+ if (!this.isInitialized) {
+ lazyInitialize();
}
CachingSource source;
if ( this.async ) {
- 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);
+ source = this.getAsyncSource(location, parameters);
} else {
source = new CachingSource( location, parameters);
}
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);
@@ -181,6 +187,74 @@
}
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;
+ }
+
+ /**
+ * Lazy initialization of resolver and refresher because of
+ * cyclic dependencies.
+ *
+ * @throws SourceException
+ */
+ private synchronized void lazyInitialize() throws SourceException {
+ if (this.isInitialized) {
+ // another thread finished initialization for us while
+ // 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);
+ }
+ }
+ if ( this.refresher == null && this.async) {
+ try {
+ 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);
+ }
+ }
+ this.isInitialized = true;
+ }
/**
* Release a [EMAIL PROTECTED] Source} object.
@@ -194,6 +268,10 @@
}
}
+ /*
+ * (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);
}
@@ -229,6 +307,12 @@
}
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);
+ }
+ }
}
}