You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by rg...@apache.org on 2007/12/27 21:00:46 UTC

svn commit: r607115 - in /cocoon/branches/BRANCH_2_1_X: ./ src/java/org/apache/cocoon/ src/java/org/apache/cocoon/components/modules/input/ src/webapp/WEB-INF/ src/webapp/WEB-INF/properties/ src/webapp/samples/modules/ src/webapp/samples/modules/a/

Author: rgoers
Date: Thu Dec 27 12:00:45 2007
New Revision: 607115

URL: http://svn.apache.org/viewvc?rev=607115&view=rev
Log:
<action dev="RG" type="fix" fixes-bug="COCOON-1574">
      Created XPathXMLFileModule to address issus with XMLFileModule. XPathXMLFileModule supports variable replacement and caching of documents in ehcache and expressions as soft references. 
 </action>

Added:
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XPathXMLFileModule.java
    cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/a/
    cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/a/forrestconf.xml
Modified:
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/cocoon.roles
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathHelper.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java
    cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf
    cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/properties/core.properties
    cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/menu.xml
    cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/sitemap.xmap
    cocoon/branches/BRANCH_2_1_X/status.xml

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/cocoon.roles
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/cocoon.roles?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/cocoon.roles (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/cocoon.roles Thu Dec 27 12:00:45 2007
@@ -87,6 +87,10 @@
         shorthand="transient-store"
         default-class="org.apache.cocoon.components.store.impl.DefaultTransientStore"/>
 
+  <role name="org.apache.excalibur.store.Store/XPathXMLFileTransientStore"
+        shorthand="xmlfile-store"
+        default-class="org.apache.cocoon.components.store.impl.DefaultTransientStore"/>
+
 <!--
   The persistent store is only an auxiliary store that shouldn't be
   used by Cocoon users. It should only be used - if required - by

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java Thu Dec 27 12:00:45 2007
@@ -104,7 +104,7 @@
         if (modeConf != null) {
             name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name);
         }
-        return JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
+        return JXPathHelper.getAttributeValue(name, modeConf, this.configuration, contextObj);
     }
 
 

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathHelper.java
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathHelper.java?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathHelper.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathHelper.java Thu Dec 27 12:00:45 2007
@@ -81,6 +81,47 @@
         }
     }
 
+    /**
+     * Return the String value of the attribute or element identified in the XPath expression.
+     * @param name The XPath expression
+     * @param modeConf The Configuration.
+     * @param setup The JXPathHelperConfiguration.
+     * @param contextObj The root Element to search.
+     * @return The String value of the attribute or element identified.
+     * @throws ConfigurationException if an Exception occurs.
+     */
+    public static String getAttributeValue(String name,
+                                           Configuration modeConf,
+                                           JXPathHelperConfiguration setup,
+                                           Object contextObj)
+    throws ConfigurationException {
+
+        if (contextObj == null) {
+            return null;
+        }
+
+        try {
+            JXPathContext jxContext = JXPathContext.newContext(contextObj);
+            setup(setup, jxContext, modeConf);
+            Object obj = jxContext.getValue(name);
+            if (obj != null) {
+                return obj.toString();
+            }
+            return null;
+        } catch (Exception e) {
+            throw new ConfigurationException("Module does not support <" + name + ">" + "attribute.", e);
+        }
+    }
+
+    /**
+     * Return the String value of the attribute or the Node found using the XPath expression.
+     * @param name The XPath expression
+     * @param modeConf The Configuration.
+     * @param setup The JXPathHelperConfiguration.
+     * @param contextObj The root Element to search.
+     * @return The String value of the attribute or the Element located.
+     * @throws ConfigurationException if an Exception occurs.
+     */
     public static Object getAttribute(String name,
                                       Configuration modeConf,
                                       JXPathHelperConfiguration setup,

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java Thu Dec 27 12:00:45 2007
@@ -129,7 +129,7 @@
         if (modeConf != null) {
             name = modeConf.getChild("parameter").getValue(!this.parameter.equals("") ? this.parameter : name);
         }
-        return JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
+        return JXPathHelper.getAttributeValue(name, modeConf, this.configuration, contextObj);
     }
 
 

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java Thu Dec 27 12:00:45 2007
@@ -413,7 +413,7 @@
             if (getValues){
                 result = JXPathHelper.getAttributeValues(name, modeConf, this.configuration, contextObj);
             } else {
-                result = JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
+                result = JXPathHelper.getAttributeValue(name, modeConf, this.configuration, contextObj);
             }
             if (this.cacheExpressions) {
                 cache.put(name, result);

Added: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XPathXMLFileModule.java
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XPathXMLFileModule.java?rev=607115&view=auto
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XPathXMLFileModule.java (added)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/modules/input/XPathXMLFileModule.java Thu Dec 27 12:00:45 2007
@@ -0,0 +1,515 @@
+package org.apache.cocoon.components.modules.input;
+
+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.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.store.Store;
+import org.apache.cocoon.components.source.SourceUtil;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+
+import org.apache.cocoon.sitemap.PatternException;
+import org.apache.cocoon.ProcessingException;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.apache.commons.collections.map.AbstractReferenceMap;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.util.Map;
+import java.net.MalformedURLException;
+import java.io.IOException;
+
+/**
+ * <grammar>
+ *   <define name="input.module.config.contents" combine="choice">
+ *     <optional><element name="cacheable"><data type="boolean"/></element></optional>
+ *     <optional><element name="reloadable"><data type="boolean"/></element></optional>
+ *     <optional>
+ *       <ref name="org.apache.cocoon.components.modules.input.XPathXMLFileModule:file">
+ *     </optional>
+ *     <optional><element name="cache-role"><data type="String"/></element></optional>
+ *   </define>
+ * <p/>
+ *   <define name="input.module.runtime.contents" combine="choice">
+ *     <optional>
+ *       <ref name="org.apache.cocoon.components.modules.input.XPathXMLFileModule:file">
+ *     </optional>
+ *   </define>
+ * <p/>
+ *   <define name="org.apache.cocoon.components.modules.input.XPathXMLFileModule:file">
+ *     <element name="file">
+ *       <attribute name="src"><data type="anyURI"/></attribute>
+ *       <optional><attribute name="cacheable"><data type="boolean"/></attribute></optional>
+ *       <optional><attribute name="reloadable"><data type="boolean"/></attribute></optional>
+ *     </element>
+ *   </define>
+ * </grammar>
+ * <p/>
+ * This module provides an Input Module interface to any XML document, by using
+ * XPath expressions as attribute keys.
+ * The XML can be obtained from any Cocoon <code>Source</code> (e.g.,
+ * <code>cocoon:/...</code>, <code>context://..</code>, and regular URLs).
+ * Sources can be cached in memory for better performance and reloaded if
+ * changed. The source can also contain references to other input modules to allow the source
+ * file name to be determined dynamically.
+ * <p/>
+ * Caching and reloading can be turned on / off (default: caching on,
+ * reloading off) through <code>&lt;reloadable&gt;false&lt;/reloadable&gt;</code>
+ * and <code>&lt;cacheable&gt;false&lt;/cacheable&gt;</code>. The file
+ * (source) to use is specified through <code>&lt;file
+ * src="protocol:path/to/file.xml" reloadable="true" cacheable="true"/&gt;</code>
+ * optionally overriding the defaults for caching and/or reloading. When specfied as attributes
+ * to the file element the values for cacheable and reloadable may be input module references which
+ * will be resolved on every call. These must resolve to 'true' or 'false'.
+ * </>
+ * The XML documents will be cached using the Store configured via the cache-role configuration
+ * element. If not specified the default Store as specified in this classes ROLE attribute will
+ * be used.
+ * <p/>
+ * In addition, xpath expressions can be cached for higher performance.
+ * Thus, if an expression has been evaluated for a file, the result
+ * is cached and will be reused, the expression is not evaluated
+ * a second time. This can be turned off using the <code>cache-expressions</code>
+ * configuration option.
+ *
+ * @version $Id: $
+ */
+public class XPathXMLFileModule extends AbstractInputModule
+    implements Serviceable, ThreadSafe
+{
+    public static final String ROLE = Store.ROLE + "/XPathXMLFileTransientStore";
+    /**
+     * Contains all globally registered extension classes and
+     * packages. Thus the lookup and loading of globally registered
+     * extensions is done only once.
+     */
+    protected JXPathHelperConfiguration configuration;
+
+    /**
+     * Static (cocoon.xconf) configuration location, for error reporting
+     */
+    String staticConfLocation;
+
+    /**
+     * Cached documents
+     */
+    private Store cache;
+
+    /**
+     * Determines whether the configured source document should be cached.
+     */
+    private String  cacheParm;
+    private Boolean cacheSource;
+
+    /**
+     * Determines whether the configured source document should be reloaded.
+     */
+    private String  reloadParm;
+    private Boolean reloadSource;
+
+    /**
+     * Default value for reloadability of sources. Defaults to false.
+     */
+    boolean reloadAll;
+    /**
+     * Default value for cacheability of xpath expressions. Defaults to true.
+     */
+    private boolean cacheExpressions;
+
+    /**
+     *  Whether the source needs to be resolved.
+     */
+    private boolean needsResolve;
+
+    /**
+     * Overrides attribute name
+     */
+    protected String parameter;
+
+    /**
+     * Default src
+     */
+    private String src;
+
+    protected SourceResolver resolver;
+    protected ServiceManager manager;
+
+
+    /* (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.resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
+    }
+
+    /**
+     * Static (cocoon.xconf) configuration.
+     * Configuration is expected to be of the form:
+     * &lt;...&gt;
+     * &lt;reloadable&gt;<b>true</b>|false&lt;/reloadable&gt;
+     * &lt;cacheable&gt;<b>true</b>|false&lt;/cacheable&gt;
+     * &lt;cache-role&gt;org.apache.excalibur.store.Store/TransientStore&lt;/cache-role&gt;
+     * &lt;file src="<i>src</i>"/&gt;
+     * ...
+     * &lt;/...&gt;
+     * <p/>
+     * The &lt;file/&gt; element specifies a file pattern. Only one
+     * &lt;file&gt; can be specified, however it can contain references to input modules which will be resolved
+     * each time the module is used. The configured <i>src</i> is used if not
+     * overridden via a file parameter in the sitemap.
+     *
+     * @param config a <code>Configuration</code> value, as described above.
+     * @throws org.apache.avalon.framework.configuration.ConfigurationException
+     *          if an error occurs
+     */
+    public void configure(Configuration config) throws ConfigurationException {
+        this.configuration = JXPathHelper.setup(config);
+        this.staticConfLocation = config.getLocation();
+        Configuration roleConfig = config.getChild("cache-role", true);
+        boolean cacheAll = config.getChild("cacheable").getValueAsBoolean(true);
+        this.reloadAll = config.getChild("reloadable").getValueAsBoolean(true);
+        String cacheRole = roleConfig.getValue(ROLE);
+
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Using cache " + cacheRole);
+        }
+
+        try {
+            this.cache = (Store) this.manager.lookup(cacheRole);
+        } catch (ServiceException ce) {
+            throw new ConfigurationException("Unable to lookup cache: " + cacheRole, ce);
+        }
+
+        Configuration fileConfig = config.getChild("file");
+
+        this.src = fileConfig.getAttribute("src");
+        this.cacheParm = fileConfig.getAttribute("cacheable", null);
+        this.reloadParm = fileConfig.getAttribute("reloadable", null);
+        if (this.cacheParm == null) {
+            this.cacheSource = Boolean.valueOf(cacheAll);
+        } else if (VariableResolverFactory.needsResolve(this.cacheParm)) {
+            this.cacheSource = null;
+        } else {
+            this.cacheSource = Boolean.valueOf(this.cacheParm);
+        }
+        if (this.reloadParm == null) {
+            this.reloadSource = Boolean.valueOf(this.reloadAll);
+        } else if (VariableResolverFactory.needsResolve(this.reloadParm)) {
+            this.reloadSource = null;
+        } else {
+            this.reloadSource = Boolean.valueOf(this.reloadParm);
+        }
+
+        // init caches
+        this.cacheExpressions = config.getChild("cache-expressions").getValueAsBoolean(true);
+        this.needsResolve = VariableResolverFactory.needsResolve(this.src);
+    }
+
+    /**
+     * Dispose this component
+     */
+    public void dispose() {
+        super.dispose();
+        if (this.manager != null) {
+            this.manager.release(this.resolver);
+            this.manager.release(this.cache);
+            this.resolver = null;
+            this.cache = null;
+            this.manager = null;
+        }
+    }
+
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+        throws ConfigurationException {
+        return getAttribute(name, modeConf, objectModel, false);
+    }
+
+    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
+        throws ConfigurationException {
+        Object result = getAttribute(name, modeConf, objectModel, true);
+        return (result != null ? (Object[]) result : null);
+    }
+    /**
+     * Get the DocumentInfo for the DOM object that JXPath will operate on when evaluating
+     * attributes.  This DOM is loaded from a Source, specified in the
+     * modeConf, or (if modeConf is null) from the
+     * {@link #configure(org.apache.avalon.framework.configuration.Configuration)}.
+     *
+     * @param name The JXPath to retrieve
+     * @param modeConf    The dynamic configuration for the current operation. May
+     *                    be <code>null</code>, in which case static (cocoon.xconf) configuration
+     *                    is used.  Configuration is expected to have a &lt;file> child node, and
+     *                    be of the form:
+     *                    &lt;...&gt;
+     *                    &lt;file src="..." reloadable="true|false"/&gt;
+     *                    &lt;/...&gt;
+     * @param objectModel Object Model for the current module operation.
+     * @param getValues true if multiple values should be retrieve, false otherwise
+     * @return the result of the XPath query into the XML document
+     * @throws ConfigurationException if an error occurs.
+     */
+    private Object getAttribute(String name, Configuration modeConf, Map objectModel, boolean getValues)
+        throws ConfigurationException {
+
+        if (modeConf != null) {
+            name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name);
+        }
+
+        boolean hasDynamicConf = false; // whether we have a <file src="..."> dynamic configuration
+        Configuration fileConf = null;  // the nested <file>, if any
+
+        if (modeConf != null && modeConf.getChildren().length > 0) {
+            fileConf = modeConf.getChild("file", false);
+            if (fileConf == null) {
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("Missing 'file' child element at " + modeConf.getLocation());
+                }
+            } else {
+                hasDynamicConf = true;
+            }
+        }
+
+        String src = this.src;
+        Boolean cacheSource = this.cacheSource;
+        Boolean reloadSource = this.cacheSource;
+        boolean needsResolve = this.needsResolve;
+        String cacheParm = this.cacheParm;
+        String reloadParm = this.reloadParm;
+
+        if (hasDynamicConf) {
+            src = fileConf.getAttribute("src");
+            cacheParm = fileConf.getAttribute("cacheable", this.cacheParm);
+            reloadParm = fileConf.getAttribute("reloadable", this.reloadParm);
+            if (cacheParm == null) {
+                cacheSource = this.cacheSource;
+            } else if (VariableResolverFactory.needsResolve(cacheParm)) {
+                cacheSource = null;
+                if (cacheSource == null) {
+                    try {
+                        VariableResolver varResolver = VariableResolverFactory.getResolver(cacheParm, this.manager);
+                        cacheSource = Boolean.valueOf(varResolver.resolve(objectModel));
+                    } catch (PatternException pe) {
+                        throw new ConfigurationException("Error resolving " + cacheParm, pe);
+                    }
+                }
+            } else {
+                cacheSource = Boolean.valueOf(cacheParm);
+            }
+            if (reloadParm == null) {
+                reloadSource = this.reloadSource;
+            } else if (VariableResolverFactory.needsResolve(reloadParm)) {
+                reloadSource = null;
+            } else {
+                reloadSource = Boolean.valueOf(reloadParm);
+            }
+            needsResolve = true;
+        }
+        if (cacheSource == null) {
+            try {
+                VariableResolver varResolver = VariableResolverFactory.getResolver(cacheParm, this.manager);
+                cacheSource = Boolean.valueOf(varResolver.resolve(objectModel));
+            } catch (PatternException pe) {
+                throw new ConfigurationException("Error resolving " + cacheParm, pe);
+            }
+        }
+        if (reloadSource == null) {
+            try {
+                VariableResolver varResolver =
+                    VariableResolverFactory.getResolver(reloadParm, this.manager);
+                reloadSource = Boolean.valueOf(varResolver.resolve(objectModel));
+            } catch (PatternException pe) {
+                throw new ConfigurationException("Error resolving " + reloadParm, pe);
+            }
+        }
+
+        if (src == null) {
+            throw new ConfigurationException(
+                "No source specified"
+                    + (modeConf != null ? ", either dynamically in " + modeConf.getLocation() + ", or " : "")
+                    + " statically in "
+                    + staticConfLocation);
+        }
+
+        if (needsResolve) {
+            try {
+                VariableResolver varResolver = VariableResolverFactory.getResolver(src, this.manager);
+                src = varResolver.resolve(objectModel);
+            } catch (PatternException pe) {
+                throw new ConfigurationException("Error resolving variables for " + src, pe);
+            }
+        }
+
+        Object result;
+
+        if (cacheSource.booleanValue()) {
+            DocumentInfo info = (DocumentInfo) this.cache.get(src);
+            if (info == null || (reloadSource.booleanValue() && !info.isValid())) {
+                Source docSource = null;
+                try {
+                    docSource = resolver.resolveURI(src);
+                    DocumentInfo newInfo =  new DocumentInfo(src, SourceUtil.toDOM(docSource),
+                        docSource.getValidity(), this.cacheExpressions, this.resolver);
+                    synchronized(this.cache) {
+                        DocumentInfo cachedInfo = (DocumentInfo)this.cache.get(src);
+                        if (cachedInfo == null || cachedInfo == info) {
+                            this.cache.store(src, newInfo);
+                            info = newInfo;
+                        } else {
+                            info = cachedInfo;
+                        }
+                    }
+                } catch (MalformedURLException mue) {
+                    throw new ConfigurationException("Unable to resolve " + src, mue);
+                } catch (IOException ioe) {
+                    throw new ConfigurationException("Unable to access" + src, ioe);
+                } catch (ProcessingException pe) {
+                    throw new ConfigurationException("Unable to process " + src, pe);
+                } catch (SAXException se) {
+                    throw new ConfigurationException("Error processing XML document " + src, se);
+                } finally {
+                    if (docSource != null) {
+                        resolver.release(docSource);
+                    }
+                }
+            }
+            if (info.cacheExpressions) {
+                Map cache = getValues ? info.expressionValuesCache : info.expressionCache;
+                synchronized (cache) {
+                    if (cache.containsKey(name)) {
+                        result = cache.get(name);
+                        if (getLogger().isDebugEnabled()) {
+                            getLogger().debug("for " + name + " using cached result " + result);
+                        }
+                    } else {
+                        result = getResult(name, info.document, modeConf, getValues);
+                        if (result != null) {
+                            cache.put(name, result);
+                            if (getLogger().isDebugEnabled()) {
+                                getLogger().debug("for " + name + " newly caching result " + result);
+                            }
+                        } else {
+                            if (getLogger().isDebugEnabled()) {
+                                getLogger().debug("for " + name + " result is null");
+                            }
+                        }
+                    }
+                }
+            } else {
+                result = getResult(name, info.document, modeConf, getValues);
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("for " + name + " result is " + result);
+                }
+            }
+        } else {
+            Source docSource = null;
+            try {
+                docSource = resolver.resolveURI(src);
+                result = getResult(name, SourceUtil.toDOM(docSource), modeConf, getValues);
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("for " + name + " result is " + result);
+                }
+            } catch (MalformedURLException mue) {
+                throw new ConfigurationException("Unable to resolve " + src, mue);
+            } catch (IOException ioe) {
+                throw new ConfigurationException("Unable to access" + src, ioe);
+            } catch (ProcessingException pe) {
+                throw new ConfigurationException("Unable to process " + src, pe);
+            } catch (SAXException se) {
+                throw new ConfigurationException("Error processing XML document " + src, se);
+            } finally {
+                if (docSource != null) {
+                    resolver.release(docSource);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private Object getResult(String name, Document document, Configuration modeConf, boolean getValues)
+        throws ConfigurationException {
+        Object result;
+
+        if (getValues) {
+            result = JXPathHelper.getAttributeValues(name, modeConf, this.configuration, document);
+        } else {
+            result = JXPathHelper.getAttributeValue(name, modeConf, this.configuration, document);
+        }
+        return result;
+    }
+
+    /**
+     * Used to keep track of the Document, its validity and any cached expressions.
+     */
+    private static class DocumentInfo
+    {
+        public DocumentInfo(String uri, Document doc, SourceValidity validity, boolean cacheExpressions,
+                            SourceResolver resolver) {
+            this.cacheExpressions = cacheExpressions;
+            if (cacheExpressions) {
+                expressionCache = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT);
+                expressionValuesCache = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT);
+            }
+            this.resolver = resolver;
+            this.uri = uri;
+            this.document = doc;
+            this.validity = validity;
+        }
+
+        private boolean cacheExpressions;
+
+        private final String uri;
+
+        private final SourceValidity validity;
+
+        private final SourceResolver resolver;
+
+        /**
+         * Source content cached as DOM Document
+         */
+        private final Document document;
+
+        private Map expressionCache;
+        private Map expressionValuesCache;
+
+        /**
+         * Returns true if the document is valid, false otherwise.
+         * <p/>
+         *
+         * @return returns true if the document is valid, false otherwise.
+         */
+        private boolean isValid() {
+            Source src = null;
+            boolean result = true;
+
+            try {
+                int valid = validity == null ? SourceValidity.INVALID : validity.isValid();
+                if (valid == SourceValidity.UNKNOWN) {
+                    // Get new source and validity
+                    src = resolver.resolveURI(this.uri);
+                    SourceValidity newValidity = src.getValidity();
+                    valid = validity.isValid(newValidity);
+                }
+                if (valid != SourceValidity.VALID) {
+                    result = false;
+                }
+            }
+            catch (Exception ex) {
+                result = false;
+            }
+            finally {
+                if (src != null) {
+                    resolver.release(src);
+                }
+            }
+            return result;
+        }
+    }
+}
\ No newline at end of file

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf Thu Dec 27 12:00:45 2007
@@ -228,6 +228,12 @@
     <component-instance logger="core.modules.input" name="slashdot"     class="org.apache.cocoon.components.modules.input.XMLFileModule">
       <file src="http://slashdot.org/slashdot.rss"/>
     </component-instance>
+    <component-instance logger="core.modules.input" name="xmyxml"        class="org.apache.cocoon.components.modules.input.XPathXMLFileModule">
+      <file src="context://samples/modules/forrestconf.xml"/>
+    </component-instance>
+    <component-instance logger="core.modules.input" name="varxml"        class="org.apache.cocoon.components.modules.input.XPathXMLFileModule">
+      <file src="context://samples/modules/{request-param:dir}/forrestconf.xml"/>
+    </component-instance>
     <component-instance logger="core.modules.input" name="random-100-to-500" class="org.apache.cocoon.components.modules.input.RandomNumberModule">
       <min>100</min>
       <max>500</max>
@@ -447,6 +453,19 @@
   <transient-store logger="core.store.transient">
     <parameter name="maxobjects" value="${transient-store.maxobjects}"/>
   </transient-store>
+
+
+  <!--+
+      | Transient Store: holds objects that don't have to survive shutdown
+      |
+      | Common configuration parameters:
+      | maxobjects: Indicates how many objects will be held in the cache.
+      |    When the number of maxobjects has been reached. The last object
+      |    in the cache will be thrown out.
+      +-->
+  <xmlfile-store logger="core.store.transient">
+    <parameter name="maxobjects" value="${xmlfile-store.maxobjects}"/>
+  </xmlfile-store>
 
   <!--+
       | Store: generic store. The default implementation is an in-memory store

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/properties/core.properties
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/properties/core.properties?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/properties/core.properties (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/properties/core.properties Thu Dec 27 12:00:45 2007
@@ -20,6 +20,7 @@
 continuations-manager.expirations-check.period = 180000
 xml-parser.pool-max = 32
 transient-store.maxobjects = 1000
+xmlfile-store.maxobjects = 100
 store.maxobjects = 1000
 store-janitor.freememory = 2048000
 store-janitor.heapsize = 66600000

Added: cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/a/forrestconf.xml
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/a/forrestconf.xml?rev=607115&view=auto
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/a/forrestconf.xml (added)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/a/forrestconf.xml Thu Dec 27 12:00:45 2007
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<!--+
+    | Demo file for XMLFileModule
+    |
+    | SVN $Id: forrestconf.xml 433543 2006-08-22 06:22:54Z crossley $
+    +-->
+<forrestconf version="1.0">
+  <skin>test-site</skin>
+  <base-url>http://localhost:8786/cocoon</base-url>
+</forrestconf>

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/menu.xml
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/menu.xml?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/menu.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/menu.xml Thu Dec 27 12:00:45 2007
@@ -48,6 +48,7 @@
     <menu-item label="URLEncodeModule" href="url-encode.html" desc="URLEncode passed parameter"/>
     <menu-item label="URLDecodeModule" href="url-decode.html" desc="URLDecode passed parameter"/>
     <menu-item label="XMLFileModule" href="xml.html" desc="XML node values"/>
+    <menu-item label="XPathXMLFileModule" href="xxml.html?dir=a" desc="XML node values"/>
   </menu>
 
   <menu label="Documentation">

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/sitemap.xmap
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/sitemap.xmap?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/sitemap.xmap (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/samples/modules/sitemap.xmap Thu Dec 27 12:00:45 2007
@@ -220,6 +220,28 @@
         <map:serialize/>
       </map:match>
 
+       <map:match pattern="content/xxml.xml">
+        <map:generate type="jx" src="properties.xml">
+          <map:parameter name="/forrestconf/@version" value="{xmyxml:/forrestconf/@version}"/>
+          <map:parameter name="/forrestconf/skin" value="{xmyxml:/forrestconf/skin}"/>
+          <map:parameter name="/*/base-url" value="{xmyxml:/*/base-url}"/>
+          <map:parameter name="a/forestconf/@version" value="{varxml:/forrestconf/@version}"/>
+          <map:parameter name="a/forrestconf/skin" value="{varxml:/forrestconf/skin}"/>
+          <map:parameter name="a/*/base-url" value="{varxml:/*/base-url}"/>
+            <!--
+            <map:parameter name="slashdot-headline" value="{slashdot:/*:RDF/item[1]/title}"/>
+            -->
+        </map:generate>
+        <map:transform src="properties2html.xsl">
+          <map:parameter name="title" value="Xpath XML File Input Module (XPathXMLFileModule)"/>
+          <map:parameter name="description" value="XMLFileModule uses an XML
+            file as a data source, with XPath expressions as the key values. In
+            this example, the keys identify nodes in
+            context://samples/modules/forrestconf.xml"/>
+        </map:transform>
+        <map:serialize/>
+      </map:match>
+
       <map:match pattern="content/xml.xml">
         <map:generate type="jx" src="properties.xml">
           <map:parameter name="/forrestconf/@version" value="{myxml:/forrestconf/@version}"/>

Modified: cocoon/branches/BRANCH_2_1_X/status.xml
URL: http://svn.apache.org/viewvc/cocoon/branches/BRANCH_2_1_X/status.xml?rev=607115&r1=607114&r2=607115&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/status.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/status.xml Thu Dec 27 12:00:45 2007
@@ -182,6 +182,10 @@
 
   <changes>
   <release version="2.1.11" date="TBD">
+    <action dev="RG" type="fix" fixes-bug="COCOON-1574">
+      Created XPathXMLFileModule to address issus with XMLFileModule. XPathXMLFileModule supports variable
+      replacement and caching of documents in ehcache and expressions as soft references. 
+    </action>
     <action dev="AG" type="fix" fixes-bug="COCOON-2052" due-to="Robin Wyles" due-to-email="rob@robinwyles.com">
       Forms: Allow Ajax submission of forms with empty upload field.
     </action>