You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by ha...@apache.org on 2004/02/15 20:12:44 UTC

cvs commit: cocoon-2.1/src/java/org/apache/cocoon/components/modules/input SelectMetaInputModule.java JXPathHelperConfiguration.java JXPathHelper.java AbstractJXPathModule.java JXPathMetaModule.java XMLFileModule.java

haul        2004/02/15 11:12:44

  Modified:    src/java/org/apache/cocoon/components/modules/input
                        AbstractJXPathModule.java JXPathMetaModule.java
                        XMLFileModule.java
  Added:       src/java/org/apache/cocoon/components/modules/input
                        SelectMetaInputModule.java
                        JXPathHelperConfiguration.java JXPathHelper.java
  Log:
  move common code of AbstractJXPathModule and JXPathMetaModule to helper class
  handle empty configuration similar to no configuration
  cache JXPath expressions in XMLFileModule (will move to TransientStore later)
  add new module for switch like cases
  
  Revision  Changes    Path
  1.3       +13 -165   cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java
  
  Index: AbstractJXPathModule.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/AbstractJXPathModule.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- AbstractJXPathModule.java	16 Mar 2003 17:49:12 -0000	1.2
  +++ AbstractJXPathModule.java	15 Feb 2004 19:12:44 -0000	1.3
  @@ -51,15 +51,12 @@
   
   package org.apache.cocoon.components.modules.input;
   
  -import org.apache.avalon.framework.configuration.Configuration;
  -import org.apache.avalon.framework.configuration.ConfigurationException;
  -import org.apache.commons.jxpath.*;
  -
   import java.util.Iterator;
  -import java.util.LinkedList;
  -import java.util.List;
   import java.util.Map;
   
  +import org.apache.avalon.framework.configuration.Configuration;
  +import org.apache.avalon.framework.configuration.ConfigurationException;
  +
   /**
    * JXPathModule allows to access properties of any object in generic
    * way.  JXPath provides APIs for the traversal of graphs of
  @@ -95,18 +92,9 @@
    */
   public abstract class AbstractJXPathModule extends AbstractInputModule {
   
  -    /**
  -     * Contains all globally registered extension classes and
  -     * packages. Thus the lookup and loading of globally registered
  -     * extensions is done only once.
  -     *
  -     */
  -    protected FunctionLibrary library = null;
  +    protected JXPathHelperConfiguration configuration = null;
   
  -    /** set lenient mode for jxpath (i.e. throw an exception on
  -     * unsupported attributes) ? 
  -     */
  -    protected boolean lenient = true;
  +    private static final boolean lenient = true; 
   
       /** override attribute name */
       protected String parameter = null;
  @@ -120,103 +108,7 @@
        */
       public void configure(Configuration config) throws ConfigurationException {
   
  -        // JXPathMetaModule starts copying here
  -        // please keep both in sync.
  -
  -        this.lenient = config.getChild("lenient").getValueAsBoolean(this.lenient);
  -        this.library = new FunctionLibrary();
  -        getFunctions(this.library, config);
  -        getPackages(this.library, config);
  -    }
  -
  -
  -
  -    /**
  -     * Register all extension functions listed in the configuration
  -     * through <code>&lt;function name="fully.qualified.Class"
  -     * prefix="prefix"/&gt;</code> in the given FunctionLibrary.
  -     *
  -     * @param lib a <code>FunctionLibrary</code> value
  -     * @param conf a <code>Configuration</code> value
  -     */
  -    protected void getFunctions(FunctionLibrary lib, Configuration conf) {
  -
  -        Configuration[] children = conf.getChildren("function");
  -        int i = children.length;
  -        while (i-- >0) {
  -            String clazzName = children[i].getAttribute("name",null);
  -            String prefix = children[i].getAttribute("prefix",null);
  -            if (clazzName != null && prefix != null) {
  -                try {
  -                    Class clazz = Class.forName(clazzName);
  -                    if (getLogger().isDebugEnabled())
  -                        getLogger().debug("adding Class "+clazzName+" to functions");
  -                    lib.addFunctions(new ClassFunctions(clazz, prefix));
  -                } catch (ClassNotFoundException cnf) {
  -                    if (getLogger().isWarnEnabled())
  -                        getLogger().warn("Class not found: "+clazzName);
  -                }
  -            } else {
  -                if (getLogger().isWarnEnabled())
  -                    getLogger().warn("Class name or prefix null: "+clazzName+" / "+prefix);
  -            }
  -        }
  -    }
  -
  -
  -    /**
  -     * Register all extension packages listed in the configuration
  -     * through <code>&lt;package name="fully.qualified.package"
  -     * prefix="prefix"/&gt;</code> in the given FunctionLibrary.
  -     *
  -     * @param lib a <code>FunctionLibrary</code> value
  -     * @param conf a <code>Configuration</code> value
  -     */
  -    protected void getPackages(FunctionLibrary lib, Configuration conf)  {
  -
  -        Configuration[] children = conf.getChildren("package");
  -        int i = children.length;
  -        while (i-- >0) {
  -            String packageName = children[i].getAttribute("name",null);
  -            String prefix = children[i].getAttribute("prefix",null);
  -            if (packageName != null && prefix != null) {
  -                if (getLogger().isDebugEnabled())
  -                    getLogger().debug("adding Package "+packageName+" to functions");
  -                lib.addFunctions(new PackageFunctions(packageName, prefix));
  -            } else {
  -                if (getLogger().isWarnEnabled())
  -                    getLogger().warn("Package name or prefix null: "+packageName+" / "+prefix);
  -            }
  -        }
  -    }
  -
  -
  -    /**
  -     * Actually add global functions and packages as well as those
  -     * listed in the configuration object.
  -     *
  -     * @param context a <code>JXPathContext</code> value
  -     * @param conf a <code>Configuration</code> value holding local
  -     * packages and functions.
  -     */
  -    protected void setupExtensions(JXPathContext context, Configuration conf) {
  -        
  -        FunctionLibrary localLib = null;
  -
  -        if (conf != null) {
  -            if (getLogger().isDebugEnabled())
  -                getLogger().debug("adding local Classes and Packages to functions");
  -            localLib = new FunctionLibrary();
  -            localLib.addFunctions(this.library);
  -            getPackages(localLib, conf);
  -            getFunctions(localLib, conf);
  -        } else {
  -            if (getLogger().isDebugEnabled())
  -                getLogger().debug("no local Classes or Packages");
  -            localLib = this.library;
  -        }
  -        
  -        context.setFunctions(localLib);
  +        this.configuration = JXPathHelper.setup(config, lenient); 
       }
   
   
  @@ -224,75 +116,31 @@
           throws ConfigurationException {
   
           Object contextObj = getContextObject(modeConf, objectModel);
  -        if (contextObj == null) return null;
           if (modeConf != null) { 
               name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name); 
           }
  -        try {
  -            JXPathContext jxContext = JXPathContext.newContext(contextObj);
  -            setupExtensions(jxContext, modeConf);
  -            if (this.lenient) jxContext.setLenient(true); // return null insted of exception on non existing property
  -            Object obj = jxContext.getValue(name);
  -            return obj;
  -        } catch (Exception e) {
  -            throw new ConfigurationException(
  -                "Module does not support <" + name + ">" + "attribute.",
  -                e
  -            );
  -        }
  +        return JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
       }
   
  +
       public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
           throws ConfigurationException {
   
           Object contextObj = getContextObject(modeConf, objectModel);
  -        if (contextObj == null) return null;
  -        try {
  -            JXPathBeanInfo info = JXPathIntrospector.getBeanInfo(
  -                contextObj.getClass());
  -            java.beans.PropertyDescriptor[] properties = info.getPropertyDescriptors();
  -            java.util.List names = new java.util.LinkedList();
  -            for (int i = 0; i < properties.length; i++) {
  -                names.add(properties[i].getName());
  -            }
  -            return names.listIterator();
  -        } catch (Exception e) {
  -            throw new ConfigurationException(
  -                "Error retrieving attribute names for class: "
  -                + contextObj.getClass(),
  -                e
  -            );
  -        }
  -
  +        return JXPathHelper.getAttributeNames(this.configuration, contextObj);
       }
   
  +
       public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
           throws ConfigurationException {
   
           Object contextObj = getContextObject(modeConf, objectModel);
  -        if (contextObj == null) return null;
           if (modeConf != null) { 
               name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name); 
           }
  -        try {
  -            JXPathContext jxContext = JXPathContext.newContext(contextObj);
  -            List values = new LinkedList();
  -            setupExtensions(jxContext, modeConf);
  -            if (this.lenient) jxContext.setLenient(true); // return null insted of exception on non existing property
  -            Iterator i = jxContext.iterate(name);
  -            while (i.hasNext()) {
  -                values.add(i.next());
  -            }
  -            Object[] obj = values.toArray();
  -            if (obj.length == 0) obj = null;
  -            return obj;
  -        } catch (Exception e) {
  -            throw new ConfigurationException(
  -                "Module does not support <" + name + ">" + "attribute.",
  -                e
  -            );
  -        }
  +        return JXPathHelper.getAttributeValues(name, modeConf, this.configuration, contextObj);
       }
  +
   
       /**
        * Returns the object which should be used as JXPath context.
  
  
  
  1.5       +15 -163   cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java
  
  Index: JXPathMetaModule.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/JXPathMetaModule.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- JXPathMetaModule.java	21 May 2003 14:05:15 -0000	1.4
  +++ JXPathMetaModule.java	15 Feb 2004 19:12:44 -0000	1.5
  @@ -4,7 +4,7 @@
                      The Apache Software License, Version 1.1
    ============================================================================
   
  - Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  + Copyright (C) 1999-2004 The Apache Software Foundation. All rights reserved.
   
    Redistribution and use in source and binary forms, with or without modifica-
    tion, are permitted provided that the following conditions are met:
  @@ -114,12 +114,12 @@
        * extensions is done only once.
        *
        */
  -    protected FunctionLibrary library = null;
  +    protected JXPathHelperConfiguration configuration = null;
   
       /** set lenient mode for jxpath (i.e. throw an exception on
        * unsupported attributes) ? 
        */
  -    protected boolean lenient = true;
  +    private static final boolean lenient = true;
   
       protected String parameter = "";
   
  @@ -143,187 +143,39 @@
           this.defaultInput = this.inputConf.getAttribute("name",this.defaultInput);
           this.parameter = config.getChild("parameter").getValue(this.parameter);
   
  -        // start verbatim copy of AbstractJXPathModule
  -        // please keep both in sync.
  -
  -        this.lenient = config.getChild("lenient").getValueAsBoolean(this.lenient);
  -        this.library = new FunctionLibrary();
  -        getFunctions(this.library, config);
  -        getPackages(this.library, config);
  -    }
  -
  -
  -
  -    /**
  -     * Register all extension functions listed in the configuration
  -     * through <code>&lt;function name="fully.qualified.Class"
  -     * prefix="prefix"/&gt;</code> in the given FunctionLibrary.
  -     *
  -     * @param lib a <code>FunctionLibrary</code> value
  -     * @param conf a <code>Configuration</code> value
  -     */
  -    protected void getFunctions(FunctionLibrary lib, Configuration conf) {
  -
  -        Configuration[] children = conf.getChildren("function");
  -        int i = children.length;
  -        while (i-- >0) {
  -            String clazzName = children[i].getAttribute("name",null);
  -            String prefix = children[i].getAttribute("prefix",null);
  -            if (clazzName != null && prefix != null) {
  -                try {
  -                    Class clazz = Class.forName(clazzName);
  -                    if (getLogger().isDebugEnabled())
  -                        getLogger().debug("adding Class "+clazzName+" to functions");
  -                    lib.addFunctions(new ClassFunctions(clazz, prefix));
  -                } catch (ClassNotFoundException cnf) {
  -                    if (getLogger().isWarnEnabled())
  -                        getLogger().warn("Class not found: "+clazzName);
  -                }
  -            } else {
  -                if (getLogger().isWarnEnabled())
  -                    getLogger().warn("Class name or prefix null: "+clazzName+" / "+prefix);
  -            }
  -        }
  +        this.configuration = JXPathHelper.setup(config, lenient);
       }
   
   
  -    /**
  -     * Register all extension packages listed in the configuration
  -     * through <code>&lt;package name="fully.qualified.package"
  -     * prefix="prefix"/&gt;</code> in the given FunctionLibrary.
  -     *
  -     * @param lib a <code>FunctionLibrary</code> value
  -     * @param conf a <code>Configuration</code> value
  -     */
  -    protected void getPackages(FunctionLibrary lib, Configuration conf)  {
  -
  -        Configuration[] children = conf.getChildren("package");
  -        int i = children.length;
  -        while (i-- >0) {
  -            String packageName = children[i].getAttribute("name",null);
  -            String prefix = children[i].getAttribute("prefix",null);
  -            if (packageName != null && prefix != null) {
  -                if (getLogger().isDebugEnabled())
  -                    getLogger().debug("adding Package "+packageName+" to functions");
  -                lib.addFunctions(new PackageFunctions(packageName, prefix));
  -            } else {
  -                if (getLogger().isWarnEnabled())
  -                    getLogger().warn("Package name or prefix null: "+packageName+" / "+prefix);
  -            }
  -        }
  -    }
  -
  -
  -    /**
  -     * Actually add global functions and packages as well as those
  -     * listed in the configuration object.
  -     *
  -     * @param context a <code>JXPathContext</code> value
  -     * @param conf a <code>Configuration</code> value holding local
  -     * packages and functions.
  -     */
  -    protected void setupExtensions(JXPathContext context, Configuration conf) {
  -        
  -        FunctionLibrary localLib = null;
  -
  -        if (conf != null) {
  -            if (getLogger().isDebugEnabled())
  -                getLogger().debug("adding local Classes and Packages to functions");
  -            localLib = new FunctionLibrary();
  -            localLib.addFunctions(this.library);
  -            getPackages(localLib, conf);
  -            getFunctions(localLib, conf);
  -        } else {
  -            if (getLogger().isDebugEnabled())
  -                getLogger().debug("no local Classes or Packages");
  -            localLib = this.library;
  -        }
  -        
  -        context.setFunctions(localLib);
  -    }
  -
  -
  -    public Object getAttribute(String name, Configuration modeConf,
  -                               Map objectModel)
  +    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
           throws ConfigurationException {
   
           Object contextObj = getContextObject(modeConf, objectModel);
  -        if (contextObj == null) return null;
  -        if (modeConf != null) {
  -            name = modeConf.getChild("parameter").getValue(name);
  -        }
  -        try {
  -            JXPathContext jxContext = JXPathContext.newContext(contextObj);
  -            setupExtensions(jxContext, modeConf);
  -            if (this.lenient) jxContext.setLenient(true); // return null insted of exception on non existing property
  -            Object obj = jxContext.getValue(name);
  -            if (getLogger().isDebugEnabled())
  -                getLogger().debug("for "+name+" returning an "+(obj == null ? "null" : obj.getClass().getName())+" as "+obj);
  -            return obj;
  -        } catch (Exception e) {
  -            throw new ConfigurationException(
  -                "Module does not support <" + name + ">" + "attribute.",
  -                e
  -            );
  +        if (modeConf != null) { 
  +            name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name); 
           }
  +        return JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
       }
   
  +
       public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
           throws ConfigurationException {
   
           Object contextObj = getContextObject(modeConf, objectModel);
  -        if (contextObj == null) return null;
  -        try {
  -            JXPathBeanInfo info = JXPathIntrospector.getBeanInfo(
  -                contextObj.getClass());
  -            java.beans.PropertyDescriptor[] properties = info.getPropertyDescriptors();
  -            java.util.List names = new java.util.LinkedList();
  -            for (int i = 0; i < properties.length; i++) {
  -                names.add(properties[i].getName());
  -            }
  -            return names.listIterator();
  -        } catch (Exception e) {
  -            throw new ConfigurationException(
  -                "Error retrieving attribute names for class: "
  -                + contextObj.getClass(),
  -                e
  -            );
  -        }
  -
  +        return JXPathHelper.getAttributeNames(this.configuration, contextObj);
       }
   
  +
       public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
           throws ConfigurationException {
   
           Object contextObj = getContextObject(modeConf, objectModel);
  -        if (contextObj == null) return null;
  -        if (modeConf != null) {
  -            name = modeConf.getChild("parameter").getValue(name);
  -        }
  -        try {
  -            JXPathContext jxContext = JXPathContext.newContext(contextObj);
  -            List values = null;
  -            setupExtensions(jxContext, modeConf);
  -            if (this.lenient) jxContext.setLenient(true); // return null insted of exception on non existing property
  -            Iterator i = jxContext.iterate(name);
  -            if (i.hasNext()) { values = new LinkedList(); } 
  -            while (i.hasNext()) {
  -                values.add(i.next());
  -            }
  -            Object[] obj = values.toArray();
  -            if (obj.length == 0) obj = null;
  -            if (getLogger().isDebugEnabled())
  -                getLogger().debug("for "+name+" returning an "+(obj == null ? "null" : obj.getClass().getName())+" as "+obj);
  -            return obj;
  -        } catch (Exception e) {
  -            throw new ConfigurationException(
  -                "Module does not support <" + name + ">" + "attribute.",
  -                e
  -            );
  +        if (modeConf != null) { 
  +            name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name); 
           }
  +        return JXPathHelper.getAttributeValues(name, modeConf, this.configuration, contextObj);
       }
   
  -    // end verbatim copy of AbstractJXPathModule
   
       /**
        * Looks up object from configured InputModule. 
  
  
  
  1.12      +163 -29   cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java
  
  Index: XMLFileModule.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/XMLFileModule.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- XMLFileModule.java	21 Jan 2004 16:15:19 -0000	1.11
  +++ XMLFileModule.java	15 Feb 2004 19:12:44 -0000	1.12
  @@ -4,7 +4,7 @@
                      The Apache Software License, Version 1.1
    ============================================================================
   
  - Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  + Copyright (C) 1999-2004 The Apache Software Foundation. All rights reserved.
   
    Redistribution and use in source and binary forms, with or without modifica-
    tion, are permitted provided that the following conditions are met:
  @@ -59,6 +59,7 @@
   import org.apache.avalon.framework.logger.Logger;
   import org.apache.avalon.framework.thread.ThreadSafe;
   import org.apache.cocoon.components.source.SourceUtil;
  +import org.apache.commons.collections.ReferenceMap;
   import org.apache.excalibur.source.Source;
   import org.apache.excalibur.source.SourceResolver;
   import org.apache.excalibur.source.SourceValidity;
  @@ -69,6 +70,32 @@
   import java.util.Map;
   
   /**
  +
  + <grammar>
  +    <define name="input.module.config.contents" combine="choice">
  +       <optional><element name="reloadable"><data type="boolean"/></element></optional>
  +       <optional><element name="cachable"><data type="boolean"/></element></optional>
  +       <optional>
  +          <ref name="org.apache.cocoon.components.modules.input.XMLFileModule:file">
  +       </optional>
  +    </define>
  +
  +    <define name="input.module.runtime.contents" combine="choice">
  +       <optional>
  +          <ref name="org.apache.cocoon.components.modules.input.XMLFileModule:file">
  +       </optional>
  +    </define>
  +
  +    <define name="org.apache.cocoon.components.modules.input.XMLFileModule:file">
  +       <element name="file">
  +          <attribute name="src"><data type="anyURI"/></attribute>
  +          <optional><attribute name="reloadable"><data type="boolean"/></attribute></optional>
  +          <optional><attribute name="cachable"><data type="boolean"/></attribute></optional>
  +       </element>
  +    </define>
  +
  + </grammar>
  + 
    * 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.,
  @@ -83,13 +110,18 @@
    * src="protocol:path/to/file.xml" reloadable="true"
    * cacheable="true"/&gt;</code> optionally overriding defaults for
    * caching and or reloading.</p>
  + * 
  + * <p>In addition, xpath expressions are 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-xpaths</code>
  + * configuration option.</p>
    *
    * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
    * @author <a href="mailto:haul@apache.org">Christian Haul</a>
    * @version CVS $Id$
    */
  -public class XMLFileModule extends AbstractJXPathModule
  -    implements Composable, ThreadSafe {
  +public class XMLFileModule extends AbstractJXPathModule implements Composable, ThreadSafe {
   
       /** Static (cocoon.xconf) configuration location, for error reporting */
       String staticConfLocation;
  @@ -99,12 +131,21 @@
       boolean reloadAll = false;
       /** Default value for cachability of sources */
       boolean cacheAll = true;
  +    /** Default value for cachability of xpath expressions. */
  +    boolean cacheExpressions = true;
       /** Default src */
       String src = null;
   
       SourceResolver resolver = null;
       ComponentManager manager = null;
  -    
  +
  +    //
  +    // need two caches for Object and Object[]
  +    //
  +    /** XPath expression cache for single attribute values. */
  +    private Map expressionCache;
  +    /** XPath expression cache for multiple attribute values. */
  +    private Map expressionValuesCache;
   
       /**
        * Takes care of (re-)loading and caching of sources.
  @@ -134,7 +175,6 @@
               // deferr loading document
           }
   
  -        
           /**
            * Returns the Document belonging to the configured
            * source. Transparently handles reloading and caching.
  @@ -144,9 +184,8 @@
            * @return a <code>Document</code> value
            * @exception Exception if an error occurs
            */
  -        public synchronized Document getDocument(ComponentManager manager, 
  -                                                 SourceResolver resolver, 
  -                                                 Logger logger) throws Exception {
  +        public synchronized Document getDocument(ComponentManager manager, SourceResolver resolver, Logger logger)
  +            throws Exception {
               Source src = null;
               Document dom = null;
               try {
  @@ -167,7 +206,7 @@
                           SourceValidity valid = src.getValidity();
                           if (srcVal != null && this.srcVal.isValid(valid) != 1) {
                               if (logger.isDebugEnabled())
  -                                logger.debug("reloading document... uri "+this.uri);
  +                                logger.debug("reloading document... uri " + this.uri);
                               this.srcVal = valid;
                               this.document = SourceUtil.toDOM(src);
                           }
  @@ -201,10 +240,11 @@
        */
       public void compose(ComponentManager manager) throws ComponentException {
           this.manager = manager;
  -        this.resolver = (SourceResolver)manager.lookup(SourceResolver.ROLE);
  +        this.resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
       }
   
   
  +
       /**
        * Static (cocoon.xconf) configuration.
        * Configuration is expected to be of the form:
  @@ -229,7 +269,9 @@
           this.reloadAll = config.getChild("reloadable").getValueAsBoolean(this.reloadAll);
           if (config.getChild("cachable", false) != null) {
               throw new ConfigurationException(
  -                    "Bzzt! Wrong spelling at "+config.getChild("cachable").getLocation()+": please use 'cacheable', not 'cachable'");
  +                "Bzzt! Wrong spelling at "
  +                    + config.getChild("cachable").getLocation()
  +                    + ": please use 'cacheable', not 'cachable'");
           }
           this.cacheAll = config.getChild("cacheable").getValueAsBoolean(this.cacheAll);
   
  @@ -239,16 +281,22 @@
   
           for (int i = 0; i < files.length; i++) {
               boolean reload = files[i].getAttributeAsBoolean("reloadable", this.reloadAll);
  -            boolean cache  = files[i].getAttributeAsBoolean("cacheable", this.cacheAll);
  +            boolean cache = files[i].getAttributeAsBoolean("cacheable", this.cacheAll);
               this.src = files[i].getAttribute("src");
               // by assigning the source uri to this.src the last one will be the default
               // OTOH caching / reload parameters can be specified in one central place
               // if multiple file tags are used.
               this.documents.put(files[i], new DocumentHelper(reload, cache, this.src));
           }
  +
  +        // init caches
  +        this.expressionCache = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.SOFT));
  +        this.expressionValuesCache =
  +            Collections.synchronizedMap(new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.SOFT));
       }
   
   
  +
       /**
        * Get the DOM object that JXPath will operate on when evaluating
        * attributes.  This DOM is loaded from a Source, specified in the
  @@ -263,14 +311,13 @@
        * &lt;/...>
        * @param objectModel Object Model for the current module operation.
        */
  -    protected Object getContextObject(Configuration modeConf,
  -                                      Map objectModel) throws ConfigurationException {
  +    protected Object getContextObject(Configuration modeConf, Map objectModel) throws ConfigurationException {
   
           String src = this.src;
           boolean reload = this.reloadAll;
           boolean cache = this.cacheAll;
           boolean hasDynamicConf = false; // whether we have a <file src="..."> dynamic configuration
  -        Configuration fileConf = null;  // the nested <file>, if any
  +        Configuration fileConf = null; // the nested <file>, if any
           if (modeConf != null) {
               fileConf = modeConf.getChild("file", false);
               if (fileConf == null) {
  @@ -278,7 +325,7 @@
                       getLogger().debug("Missing 'file' child element at "+modeConf.getLocation());
                   }
               } else {
  -              hasDynamicConf = true;
  +                hasDynamicConf = true;
               }
           }
   
  @@ -286,23 +333,26 @@
               src = fileConf.getAttribute("src");
           }
   
  -        if (this.documents == null) 
  +        if (this.documents == null)
               this.documents = Collections.synchronizedMap(new HashMap());
   
  -        if (src==null) {
  -            throw new ConfigurationException("No source specified"+
  -                    (modeConf!=null?", either dynamically in "+modeConf.getLocation()+", or ":"")+
  -                    " statically in "+staticConfLocation
  -                    );
  +        if (src == null) {
  +            throw new ConfigurationException(
  +                "No source specified"
  +                    + (modeConf != null ? ", either dynamically in " + modeConf.getLocation() + ", or " : "")
  +                    + " statically in "
  +                    + staticConfLocation);
           }
   
           if (!this.documents.containsKey(src)) {
               if (hasDynamicConf) {
  -                reload = fileConf.getAttributeAsBoolean("reloadable",reload);
  -                cache = fileConf.getAttributeAsBoolean("cacheable",cache);
  +                reload = fileConf.getAttributeAsBoolean("reloadable", reload);
  +                cache = fileConf.getAttributeAsBoolean("cacheable", cache);
                   if (fileConf.getAttribute("cachable", null) != null) {
                       throw new ConfigurationException(
  -                            "Bzzt! Wrong spelling at "+fileConf.getLocation()+": please use 'cacheable', not 'cachable'");
  +                        "Bzzt! Wrong spelling at "
  +                            + fileConf.getLocation()
  +                            + ": please use 'cacheable', not 'cachable'");
                   }
   
               }
  @@ -311,15 +361,99 @@
   
           Document dom = null;
   
  -        try{            
  +        try {
               dom = ((DocumentHelper) this.documents.get(src)).getDocument(this.manager, this.resolver, getLogger());
           } catch (Exception e) {
               if (getLogger().isDebugEnabled())
  -                getLogger().debug("Error using source "+src+"\n"+ e.getMessage(), e);
  -            throw new ConfigurationException("Error using source "+src, e);
  +                getLogger().debug("Error using source " + src + "\n" + e.getMessage(), e);
  +            throw new ConfigurationException("Error using source " + src, e);
           }
           return dom;
   
  +    }
  +
  +    public Object getAttribute(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
  +        return this.getAttribute(name, modeConf, objectModel, false);
  +    }
  +
  +    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
  +        Object result = this.getAttribute(name, modeConf, objectModel, true);
  +        return (result != null ? (Object[]) result : null);
  +    }
  +    
  +    
  +    public Object getAttribute(String name, Configuration modeConf, Map objectModel, boolean getValues) throws ConfigurationException {
  +
  +        Object contextObj = getContextObject(modeConf, objectModel);
  +        if (modeConf != null) {
  +            name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name);
  +        }
  +        Object result = null;
  +        Map cache = null;
  +        boolean hasBeenCached = false;
  +        if (this.cacheExpressions) {
  +            if (getValues){
  +                cache = this.getValuesCache(contextObj);
  +            } else {
  +                cache = this.getCache(contextObj);
  +            }
  +            hasBeenCached = cache.containsKey(name);
  +            if (hasBeenCached) {
  +                result = cache.get(name);
  +            }
  +        }
  +        if (!hasBeenCached) {
  +            if (getValues){
  +                result = JXPathHelper.getAttributeValues(name, modeConf, this.configuration, contextObj);
  +            } else {
  +                result = JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
  +            }
  +            if (this.cacheExpressions) {
  +                cache.put(name, result);
  +                if (this.getLogger().isDebugEnabled()) {
  +                    this.getLogger().debug("for " + name + " newly caching result " + result);
  +                }
  +            } else {
  +                if (this.getLogger().isDebugEnabled()) {
  +                    this.getLogger().debug("for " + name + " result is " + result);
  +                }
  +            }
  +        } else {
  +            if (this.getLogger().isDebugEnabled()) {
  +                this.getLogger().debug("for " + name + " using cached result " + result);
  +            }
  +        }
  +
  +        return result;
  +    }
  +
  +
  +    private Map getCache(Object key) {
  +        Map cache = (Map) this.expressionCache.get(key);
  +        if (cache == null) {
  +            cache = this.initSubExpressionCache(key);
  +        }
  +        return cache;
  +    }
  +
  +    private Map getValuesCache(Object key) {
  +        Map cache = (Map) this.expressionValuesCache.get(key);
  +        if (cache == null) {
  +            cache = this.initSubExpressionCache(key);
  +        }
  +        return cache;
  +    }
  +
  +    private synchronized Map initSubExpressionCache(Object key) {
  +        // check whether some other thread did all the work while we were waiting
  +        Map cache = (Map) this.expressionCache.get(key);
  +        if (cache == null) {
  +            // need to use HashMap here b/c we need to be able to store nulls
  +            // which are prohibited for the ReferenceMap
  +            cache = Collections.synchronizedMap(new HashMap());
  +            this.expressionCache.put(key, cache);
  +        }
  +        return cache;
       }
   
   }
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/SelectMetaInputModule.java
  
  Index: SelectMetaInputModule.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2004 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.components.modules.input;
  
  import java.util.Iterator;
  import java.util.Map;
  import java.util.TreeMap;
  
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.thread.ThreadSafe;
  
  /**
   * 
   * <h2>Configuration</h2>
   * <table><tbody>
   * <tr><th>input-module</th>
   *  <td>Configuration and name of input module used for the selection.</td>
   *  <td>req</td>
   *  <td>String</td><td><code>null</code></td>
   * </tr>
   * <tr><th>when</th>
   *  <td>Selection case, condition in test attribute, input module name
   *      in name attribute. Optional configuration as nested content.</td>
   *  <td>req</td><td>String</td><td><code>null</code></td>
   * </tr>
   * <tr><th>otherwise</th>
   *  <td>Default selection case. If not present and no case matches, <code>null</code>
   *      is returned.</td>
   *  <td></td><td>String</td><td><code>null</code></td>
   * </tr>
   * </tbody></table>
   * 
   * @author <a href="mailto:haul@apache.org">Christian Haul</a>
   * @version CVS $Id: SelectMetaInputModule.java,v 1.1 2004/02/15 19:12:44 haul Exp $
   */
  public class SelectMetaInputModule extends AbstractMetaModule implements ThreadSafe {
  
      private Map whenTest = null;
      private ModuleHolder expression = null;
      private ModuleHolder otherwise = null;
      private String parameter = null;
  
      /* (non-Javadoc)
       * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
       */
      public void configure(Configuration config) throws ConfigurationException {
  
          Configuration[] expr = config.getChildren("input-module");
          if (expr == null || expr.length != 1) {
              throw new ConfigurationException("Need to have exactly one input-module element.");
          }
          this.parameter = config.getChild("parameter").getValue();
          Configuration[] whens = config.getChildren("when");
          Configuration[] others = config.getChildren("otherwise");
          if ((whens == null && others == null)
              || ((whens == null || whens.length == 0) && (others == null || others.length == 0))) {
              throw new ConfigurationException("Need to have at least one when or otherwise element.");
          }
          if (others != null && others.length > 1) {
              throw new ConfigurationException("Need to have at most one otherwise element.");
          }
          this.whenTest = new TreeMap();
          for (int i = 0; i < expr.length; i++) {
              String name = expr[i].getAttribute("name");
              this.expression = new ModuleHolder(name, expr[i], null);
          }
  
          if (others != null) {
              for (int i = 0; i < others.length; i++) {
                  String name = others[i].getAttribute("name");
                  this.otherwise = new ModuleHolder(name, others[i], null);
              }
          }
          if (whens != null) {
              for (int i = 0; i < whens.length; i++) {
                  String name = whens[i].getAttribute("name");
                  this.whenTest.put(
                      whens[i].getAttribute("test"),
                      new ModuleHolder(name, whens[i], null));
              }
          }
  
      }
  
      /* (non-Javadoc)
       * @see org.apache.cocoon.components.modules.input.InputModule#getAttribute(java.lang.String, org.apache.avalon.framework.configuration.Configuration, java.util.Map)
       */
      public Object getAttribute(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
          Object result = this.getAttribute(name, modeConf, objectModel, false);
          return result;
      }
  
      public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
          Object result = this.getAttribute(name, modeConf, objectModel, true);
          return (result != null ? (Object[]) result : null );
      }
      
      private Object getAttribute(String name, Configuration modeConf, Map objectModel, boolean getValues) throws ConfigurationException {
          if (!this.initialized) {
              this.lazy_initialize();
          }
          ModuleHolder expression = this.expression;
          ModuleHolder otherwise = this.otherwise;
          ModuleHolder module = null;
          String parameter = this.parameter;
          boolean needRelease = false;
          boolean dynamicConfig = (modeConf != null && modeConf.getChildren().length > 0);
  
          if (dynamicConfig) {
              // clear all configured values so that they
              // don't get mixed up
              expression = null;
              otherwise = null;
              needRelease = true;
  
              Configuration[] expr = modeConf.getChildren("input-module");
              Configuration[] other = modeConf.getChildren("otherwise");
              if (expr != null && expr.length == 1) {
                  expression = new ModuleHolder(expr[0].getAttribute("name"), expr[0]);
              }
              if (other != null && other.length == 1) {
                  otherwise = new ModuleHolder(other[0].getAttribute("name"), other[0]);
              }
              parameter = modeConf.getChild("parameter").getValue();
          }
  
          String value =
              (String) this.getValue(parameter, objectModel, expression.input, expression.name, expression.config);
          if (needRelease) {
              this.releaseModule(expression.input);
          }
          if (this.getLogger().isDebugEnabled()) {
              this.getLogger().debug(
                  (dynamicConfig ? "(dyn)" : "(static)")
                      + " select ("
                      + value
                      + ") from "
                      + expression.name
                      + ":"
                      + parameter);
          }
  
          if (dynamicConfig && value != null) {
              Configuration[] whens = modeConf.getChildren("when");
              if (whens != null && whens.length > 0) {
                  int i = 0;
                  boolean found = false;
                  while (!found && i < whens.length) {
                      if (whens[i].getAttribute("test").equals(value)) {
                          found = true;
                          break;
                      }
                      i++;
                  }
                  if (found) {
                      module = new ModuleHolder(whens[i].getAttribute("name"), whens[i]);
                  }
              }
          } else if (value != null) {
              module = (ModuleHolder) this.whenTest.get(value);
          }
          if (module != null) {
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("found matching when : "+module.name);
              }
          } else {
              module = otherwise;
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("using otherwise : "+module.name);
              }
          }
  
          Object result;
          if (getValues){
              result = (module == null ? null : this.getValues(name, objectModel, module));
          } else {
              result = (module == null ? null : this.getValue(name, objectModel, module));
          }
          
          if (needRelease && module != null) {
              this.releaseModule(module.input);
          }
          if (this.getLogger().isDebugEnabled()) {
              this.getLogger().debug("Obtained value : "+result);
          }
          return result;
      }
  
      /* (non-Javadoc)
       * @see org.apache.avalon.framework.component.Composable#compose(org.apache.avalon.framework.component.ComponentManager)
       */
      public void compose(ComponentManager manager) throws ComponentException {
          super.compose(manager);
      }
  
      /* (non-Javadoc)
       * @see org.apache.avalon.framework.activity.Disposable#dispose()
       */
      public void dispose() {
          this.releaseModule(this.expression.input);
          this.expression = null;
  
          if (this.otherwise != null) {
              this.releaseModule(this.otherwise.input);
              this.otherwise = null;
          }
  
          for (Iterator i = this.whenTest.values().iterator(); i.hasNext();) {
              ModuleHolder holder = (ModuleHolder) i.next();
              this.releaseModule(holder.input);
          }
          this.whenTest = null;
  
          super.dispose();
      }
  
      /* (non-Javadoc)
       * @see org.apache.cocoon.components.modules.input.AbstractMetaModule#lazy_initialize()
       */
      public synchronized void lazy_initialize() {
  
          if (this.initialized) return;
  
          if (this.expression != null) {
              this.expression.input = this.obtainModule(this.expression.name);
          }
          if (this.otherwise != null){
              this.otherwise.input = this.obtainModule(this.otherwise.name);
          }
          if (this.whenTest != null){
              for (Iterator i = this.whenTest.values().iterator(); i.hasNext(); ){
                  ModuleHolder moduleHolder = (ModuleHolder) i.next();
                  moduleHolder.input = this.obtainModule(moduleHolder.name);
              }
          }
          this.initialized = true;
      }
  
  }
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/JXPathHelperConfiguration.java
  
  Index: JXPathHelperConfiguration.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.components.modules.input;
  
  import org.apache.commons.jxpath.FunctionLibrary;
  
  /**
   * @author <a href="mailto:haul@apache.org">Christian Haul</a>
   * @version CVS $Id: JXPathHelperConfiguration.java,v 1.1 2004/02/15 19:12:44 haul Exp $
   */
  public class JXPathHelperConfiguration {
  
      /**
       * Contains all globally registered extension classes and
       * packages. Thus the lookup and loading of globally registered
       * extensions is done only once.
       *
       */
      private FunctionLibrary library = null;
  
      /** set lenient mode for jxpath (i.e. throw an exception on
       * unsupported attributes) ? 
       */
      private boolean lenient = true;
  
  
      public JXPathHelperConfiguration() {
      }
  
      public JXPathHelperConfiguration(FunctionLibrary library, boolean lenient) {
          this.library = library;
          this.lenient = lenient;
      }
  
      /**
       * @return
       */
      public boolean isLenient() {
          return lenient;
      }
  
      /**
       * @return
       */
      public FunctionLibrary getLibrary() {
          return library;
      }
  
  }
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/modules/input/JXPathHelper.java
  
  Index: JXPathHelper.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.components.modules.input;
  
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.commons.jxpath.ClassFunctions;
  import org.apache.commons.jxpath.FunctionLibrary;
  import org.apache.commons.jxpath.JXPathBeanInfo;
  import org.apache.commons.jxpath.JXPathContext;
  import org.apache.commons.jxpath.JXPathIntrospector;
  import org.apache.commons.jxpath.PackageFunctions;
  
  /**
   * @author <a href="mailto:haul@apache.org">Christian Haul</a>
   * @version CVS $Id: JXPathHelper.java,v 1.1 2004/02/15 19:12:44 haul Exp $
   */
  public class JXPathHelper {
  
      private JXPathHelper() {
          // no instances allowed
      }
  
      /**
       * Configure component. Preprocess list of packages and functions
       * to add to JXPath context later.
       *
       * @param config a <code>Configuration</code> value
       * @exception ConfigurationException if an error occurs
       */
      public static JXPathHelperConfiguration setup(Configuration config, boolean lenient) throws ConfigurationException {
  
          // JXPathMetaModule starts copying here
          // please keep both in sync.
  
          lenient = config.getChild("lenient").getValueAsBoolean(lenient);
          FunctionLibrary library = new FunctionLibrary();
          getFunctions(library, config);
          getPackages(library, config);
          return new JXPathHelperConfiguration(library, lenient);
      }
  
      /**
       * Register all extension functions listed in the configuration
       * through <code>&lt;function name="fully.qualified.Class"
       * prefix="prefix"/&gt;</code> in the given FunctionLibrary.
       *
       * @param lib a <code>FunctionLibrary</code> value
       * @param conf a <code>Configuration</code> value
       */
      private static void getFunctions(FunctionLibrary lib, Configuration conf) {
  
          Configuration[] children = conf.getChildren("function");
          int i = children.length;
          while (i-- > 0) {
              String clazzName = children[i].getAttribute("name", null);
              String prefix = children[i].getAttribute("prefix", null);
              if (clazzName != null && prefix != null) {
                  try {
                      Class clazz = Class.forName(clazzName);
                      lib.addFunctions(new ClassFunctions(clazz, prefix));
                  } catch (ClassNotFoundException cnf) {
                      // ignore
                  }
              }
          }
      }
  
      /**
       * Register all extension packages listed in the configuration
       * through <code>&lt;package name="fully.qualified.package"
       * prefix="prefix"/&gt;</code> in the given FunctionLibrary.
       *
       * @param lib a <code>FunctionLibrary</code> value
       * @param conf a <code>Configuration</code> value
       */
      private static void getPackages(FunctionLibrary lib, Configuration conf) {
  
          Configuration[] children = conf.getChildren("package");
          int i = children.length;
          while (i-- > 0) {
              String packageName = children[i].getAttribute("name", null);
              String prefix = children[i].getAttribute("prefix", null);
              if (packageName != null && prefix != null) {
                  lib.addFunctions(new PackageFunctions(packageName, prefix));
              }
          }
      }
  
      /**
       * Actually add global functions and packages as well as those
       * listed in the configuration object.
       *
       * @param context a <code>JXPathContext</code> value
       * @param conf a <code>Configuration</code> value holding local
       * packages and functions.
       */
      private static void setupExtensions(JXPathHelperConfiguration setup, JXPathContext context, Configuration conf) {
  
          FunctionLibrary localLib = null;
  
          if (conf != null) {
              localLib = new FunctionLibrary();
              localLib.addFunctions(setup.getLibrary());
              getPackages(localLib, conf);
              getFunctions(localLib, conf);
          } else {
              localLib = setup.getLibrary();
          }
  
          context.setFunctions(localLib);
      }
  
      public static Object getAttribute(
          String name,
          Configuration modeConf,
          JXPathHelperConfiguration setup,
          Object contextObj)
          throws ConfigurationException {
  
          if (contextObj == null)
              return null;
          try {
              JXPathContext jxContext = JXPathContext.newContext(contextObj);
              setupExtensions(setup, jxContext, modeConf);
              if (setup.isLenient())
                  jxContext.setLenient(true); // return null insted of exception on non existing property
              Object obj = jxContext.getValue(name);
              return obj;
          } catch (Exception e) {
              throw new ConfigurationException("Module does not support <" + name + ">" + "attribute.", e);
          }
      }
  
      public static Object[] getAttributeValues(
          String name,
          Configuration modeConf,
          JXPathHelperConfiguration setup,
          Object contextObj)
          throws ConfigurationException {
              
          if (contextObj == null)
              return null;
          try {
              JXPathContext jxContext = JXPathContext.newContext(contextObj);
              List values = null;
              setupExtensions(setup, jxContext, modeConf);
              if (setup.isLenient())
                  jxContext.setLenient(true); // return null insted of exception on non existing property
              Iterator i = jxContext.iterate(name);
              if (i.hasNext()) { 
                  values = new LinkedList(); 
              } 
              while (i.hasNext()) {
                  values.add(i.next());
              }
              Object[] obj = null;
              if (values != null) {
                  obj = values.toArray();
                  if (obj.length == 0)
                      obj = null;
              }
              return obj;
          } catch (Exception e) {
              throw new ConfigurationException("Module does not support <" + name + ">" + "attribute.", e);
          }
      }
  
  
      public static Iterator getAttributeNames(JXPathHelperConfiguration setup, Object contextObj) throws ConfigurationException {
  
          if (contextObj == null)
              return null;
          try {
              JXPathBeanInfo info = JXPathIntrospector.getBeanInfo(contextObj.getClass());
              java.beans.PropertyDescriptor[] properties = info.getPropertyDescriptors();
              java.util.List names = new java.util.LinkedList();
              for (int i = 0; i < properties.length; i++) {
                  names.add(properties[i].getName());
              }
              return names.listIterator();
          } catch (Exception e) {
              throw new ConfigurationException("Error retrieving attribute names for class: " + contextObj.getClass(), e);
          }
  
      }
  }