gcasper     2003/10/23 00:55:49

  Added:       src/blocks/repository/java/org/apache/cocoon/generation
                        XPathTraversableGenerator.java
                        TraversableGenerator.java
  Removed:     src/blocks/scratchpad/java/org/apache/cocoon/generation
                        XPathTraversableGenerator.java
                        TraversableGenerator.java
  Log:
  Moved the (XPath)TraversableGenerator to the repository block to remove
  the (hidden :-) dependency of the repository and webdav blocks on the
  scratchpad block.
  These should probably go into the core once we had a final vote about
  the names.
  
  Revision  Changes    Path
  1.1                  
cocoon-2.1/src/blocks/repository/java/org/apache/cocoon/generation/XPathTraversableGenerator.java
  
  Index: XPathTraversableGenerator.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
      [EMAIL PROTECTED]
  
   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  <[EMAIL PROTECTED]>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.generation;
  
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.Map;
  
  import org.apache.avalon.framework.parameters.Parameters;
  import org.apache.avalon.framework.service.ServiceException;
  import org.apache.avalon.framework.service.ServiceManager;
  import org.apache.cocoon.ProcessingException;
  import org.apache.cocoon.environment.Context;
  import org.apache.cocoon.environment.ObjectModelHelper;
  import org.apache.cocoon.environment.SourceResolver;
  import org.apache.cocoon.xml.dom.DOMStreamer;
  import org.apache.excalibur.source.TraversableSource;
  import org.apache.excalibur.xml.dom.DOMParser;
  import org.apache.excalibur.xml.xpath.PrefixResolver;
  import org.apache.excalibur.xml.xpath.XPathProcessor;
  import org.apache.regexp.RE;
  import org.apache.regexp.RESyntaxException;
  
  import org.w3c.dom.Document;
  import org.w3c.dom.NodeList;
  
  import org.xml.sax.InputSource;
  import org.xml.sax.SAXException;
  import org.xml.sax.helpers.AttributesImpl;
  
  /**
   * Generates an XML collection listing performing XPath queries on XML 
sources.
   * It can be used both as a plain TraversableGenerator or, if an XPath is
   * specified, it will perform an XPath query on every XML resource, where "xml
   * resource" is, by default, any resource ending with ".xml", which can be
   * overriden by setting the (regexp) pattern "xmlFiles as a sitemap 
parameter, 
   * or where the name of the resource has a container-wide mime-type mapping 
to 
   * 'text/xml' such as specified by mime-mapping elements in a web.xml 
   * descriptor file.
   * 
   * The XPath can be specified in two ways:
   * <ol>
   *    <li>By using an XPointerish syntax in the URL: everything following the
   *         pound                 sign                 (possiby preceding  
query
   * string arguments)  will be treated as the XPath;
   *     </li>
   *     <li>Specifying it as a sitemap parameter named "xpath"
   *  </ol>
   *
   * Sample usage:
   *
   * Sitemap:
   * &lt;map:match pattern="documents/**"&gt;
   *   &lt;map:generate type="xpathdirectory"
   *     src="    docs/{1}#/article/title|/article/abstract" &gt;
   *     &lt;          map:parameter name="xmlFiles" value="\.xml$"/&gt;   
   * &lt;/map:generate&gt;
   * &lt;map: serialize type="xml" /&gt; &lt;/map:match&gt;
   *
   * Request:
   *   http://www.some.host/documents/test
   * Result:
   * &lt;dir:directory
   *   name="test" lastModified="1010400942000"
   *   date="1/7/02 11:55 AM" requested="true"
   *   xmlns:dir="http://apache.org/cocoon/directory/2.0"&gt;
   *   &lt;dir:directory name="subdirectory" lastModified="1010400942000" 
date="1/7/02 11:55 AM" /&gt;
   *   &lt;dir:file name="test.xml" lastModified="1011011579000" date="1/14/02 
1:32 PM"&gt;
   *     &lt;dir:xpath docid="test.xml" query="/article/title"&gt;
   *       &lt;title&gt;This is a test document&lt;/title&gt;
   *       &lt;abstract&gt;
   *         &lt;para&gt;Abstract of my test article&lt;/para&gt;
   *       &lt;/abstract&gt;
   *     &lt;/dir:xpath&gt;
   *   &lt;/dir:file&gt;
   *   &lt;dir:file name="test.gif" lastModified="1011011579000" date="1/14/02 
1:32 PM"&gt;
   * &lt;/dir:directory&gt;
   * 
   * If you need to use namespaces, you can set them as sitemap parameters in
   * the form:
   * lt;map:parameter name="xmlns:<i>your prefix</i>" value="nsURI"/**"&gt; 
   *
   * @author <a href="mailto:[EMAIL PROTECTED]">Gianugo Rabellino</a>
   * @author <a href="mailto:[EMAIL PROTECTED]">Daniele Madama</a>
   * @version CVS $Id: XPathTraversableGenerator.java,v 1.1 2003/10/23 07:55:49 
gcasper Exp $
   */
  public class XPathTraversableGenerator extends TraversableGenerator {
  
        /** Local name for the element that contains the included XML snippet. 
*/
        protected static final String XPATH_NODE_NAME = "xpath";
        /** Attribute for the XPath query. */
        protected static final String QUERY_ATTR_NAME = "query";
        /** The document containing a successful XPath query */
        protected static final String RESULT_DOCID_ATTR = "docid";
  
        /** The regular expression for the XML files pattern. */
        protected RE xmlRE = null;
        /** The document that should be parsed and (partly) included. */
        protected Document doc = null;
        /** The XPath. */
        protected String xpath = null;
        /** The XPath processor. */
        protected XPathProcessor processor = null;
        /** The parser for the XML snippets to be included. */
        protected DOMParser parser = null;
      /** The prefix resolver for namespaced queries */
        protected XPathPrefixResolver prefixResolver;
      /** The cocoon context used for mime-type mappings */
      protected Context context;
                
      public void setup(SourceResolver resolver, Map objectModel, String src, 
Parameters par)
          throws ProcessingException, SAXException, IOException {
          super.setup(resolver, objectModel, src, par);
          // See if an XPath was specified
          int pointer;
          if ((pointer = this.source.indexOf("#")) != -1) {
            int endpointer = this.source.indexOf('?');
            if (endpointer != -1) {
                this.xpath = source.substring(pointer + 1, endpointer); 
            } else {
                        this.xpath = source.substring(pointer + 1);
            }
            this.source = src.substring(0, pointer);
            if (endpointer != -1) {
                this.source += src.substring(endpointer);
            }
                  
                  this.cacheKeyParList.add(this.xpath);         
            if (this.getLogger().isDebugEnabled())
              this.getLogger().debug("Applying XPath: " + xpath
                + " to collection " + source);
          } else {
                        this.xpath = par.getParameter("xpath", null);
                        this.cacheKeyParList.add(this.xpath);
                        this.getLogger().debug("Applying XPath: " + xpath
                           + " to collection " + source);
          }
          
                String xmlFilesPattern = null;
                try {
                        xmlFilesPattern = par.getParameter("xmlFiles", 
"\\.xml$");
                        this.cacheKeyParList.add(xmlFilesPattern);
                        this.xmlRE = new RE(xmlFilesPattern);
                        if (this.getLogger().isDebugEnabled()) {
                                this.getLogger().debug("pattern for XML files: 
" + xmlFilesPattern);
                        }
                } catch (RESyntaxException rese) {
                        throw new ProcessingException("Syntax error in regexp 
pattern '"
                                                                                
  + xmlFilesPattern + "'", rese);
                }
          
          String[] params = par.getNames();
          this.prefixResolver = new XPathPrefixResolver();
          for (int i = 0; i < params.length; i++) {
              if (params[i].startsWith("xmlns:")) {
                String paramValue = par.getParameter(params[i], "");
                String paramName = params[i].substring(6);
                if (getLogger().isDebugEnabled()) {
                        getLogger().debug("add param to prefixResolver: " + 
paramName);
                }
                this.prefixResolver.addPrefix(paramName, paramValue);
              }
          }
          
          this.context = ObjectModelHelper.getContext(objectModel);
      }
      
      public void service(ServiceManager manager) throws ServiceException {
          super.service(manager);
          processor = (XPathProcessor)manager.lookup(XPathProcessor.ROLE);
          parser = (DOMParser)manager.lookup(DOMParser.ROLE);
      }
  
      public void dispose() {
          if ( this.manager != null ) {
              this.manager.release( processor );
              this.manager.release( parser );
              this.processor = null;
              this.parser = null;
          }
          super.dispose();
      }
      
      protected void addContent(TraversableSource source) throws SAXException, 
ProcessingException {
          super.addContent(source);
          if (!source.isCollection() && isXML(source) && xpath != null) {
              performXPathQuery(source);
          }
      }
      
        /**
         * Determines if a given TraversableSource shall be handled as XML.
         *
         * @param path  the TraversableSource to check
         * @return true  if the given TraversableSource shall handled as XML, 
false
         * otherwise.
         */
        protected boolean isXML(TraversableSource path) {
          String mimeType = this.context.getMimeType(path.getName());
                return this.xmlRE.match(path.getName()) || 
"text/xml".equalsIgnoreCase(mimeType);
        }
        
        /**
         * Performs an XPath query on the source.
         * @param xmlFile  the Source the XPath is performed on.
         * @throws SAXException  if something goes wrong while adding the XML 
snippet.
         */
  
      protected void performXPathQuery(TraversableSource in)
        throws SAXException {
        doc = null;
        try {
          doc = parser.parseDocument(new InputSource(in.getInputStream()));
        } catch (SAXException se) {
           this.getLogger().error("Warning:" + in.getName()
            + " is not a valid XML document. Ignoring");
        } catch (Exception e) {
           this.getLogger().error("Unable to resolve and parse document" + e);
         }
         if (doc != null) {
           NodeList nl = processor.selectNodeList(doc.getDocumentElement(), 
xpath, this.prefixResolver);
           final String id = in.getName();
           AttributesImpl attributes = new AttributesImpl();
           attributes.addAttribute("", RESULT_DOCID_ATTR, RESULT_DOCID_ATTR,
            " CDATA", id);
           attributes.addAttribute("", QUERY_ATTR_NAME, QUERY_ATTR_NAME, 
"CDATA",
             xpath);
           super.contentHandler.startElement(URI, XPATH_NODE_NAME, PREFIX + ":" 
+ XPATH_NODE_NAME, attributes);
           DOMStreamer ds = new DOMStreamer(super.xmlConsumer);
           for (int i = 0; i < nl.getLength(); i++)
             ds.stream(nl.item(i));
           super.contentHandler.endElement(URI, XPATH_NODE_NAME, PREFIX + ":" + 
XPATH_NODE_NAME);
        }
      }
  
      /**
       * Recycle resources
       *
       */
     public void recycle() {
        super.recycle();
        this.xpath = null;
        this.attributes = null;
        this.doc = null;
        this.xmlRE = null;
        this.prefixResolver = null;
        this.context = null;
      }
  
      /**
       * A brain-dead PrefixResolver implementation
       * 
       */
      
      class XPathPrefixResolver implements PrefixResolver {
        
        private Map params;
  
          public XPathPrefixResolver() {
                this.params = new HashMap();
          }
  
          /**
           * Get a namespace URI given a prefix.
           * 
           * @see 
org.apache.excalibur.xml.xpath.PrefixResolver#prefixToNamespace(java.lang.String)
           */
          public String prefixToNamespace(String prefix) {
                if (getLogger().isDebugEnabled()) {
                        getLogger().debug("prefix: " + prefix);
                }
                if (this.params.containsKey(prefix)) {
                        if(getLogger().isDebugEnabled()) {
                                getLogger().debug("prefix; " + prefix + " - 
namespace: " + this.params.get(prefix));
                        }
                        return (String) this.params.get(prefix);
                }
              return null;
          }
        
        public void addPrefix(String prefix, String uri) {              
                this.params.put(prefix, uri);
        }
  
      }
      
  }
  
  
  
  1.1                  
cocoon-2.1/src/blocks/repository/java/org/apache/cocoon/generation/TraversableGenerator.java
  
  Index: TraversableGenerator.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
      [EMAIL PROTECTED]
  
   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  <[EMAIL PROTECTED]>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.generation;
  
  import org.apache.avalon.framework.parameters.Parameters;
  import org.apache.cocoon.ProcessingException;
  import org.apache.cocoon.ResourceNotFoundException;
  import org.apache.cocoon.caching.CacheableProcessingComponent;
  import org.apache.cocoon.components.source.SourceUtil;
  import org.apache.cocoon.components.source.impl.MultiSourceValidity;
  import org.apache.cocoon.environment.SourceResolver;
  import org.apache.excalibur.source.SourceException;
  import org.apache.excalibur.source.SourceValidity;
  import org.apache.excalibur.source.TraversableSource;
  import org.apache.regexp.RE;
  import org.apache.regexp.RESyntaxException;
  import org.xml.sax.SAXException;
  import org.xml.sax.helpers.AttributesImpl;
  
  import java.io.IOException;
  import java.io.Serializable;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Date;
  import java.util.List;
  import java.util.Map;
  import java.util.Stack;
  import java.util.Arrays;
  import java.util.Comparator;
  
  /**
   * Generates an XML source hierarchy listing from a Traversable Source.
   * <p>
   * The root node of the generated document will normally be a
   * <code>collection</code> node and a collection node can contain zero or more
   * <code>resource</code> or collection nodes. A resource node has no children.
   * Each node will contain the following attributes:
   * <blockquote>
   *  <dl>
   *   <dt> name
   *   <dd> the name of the source
   *   <dt> lastModified
   *   <dd> the time the source was last modified, measured as the number of
   *        milliseconds since the epoch (as in java.io.File.lastModified)
   *   <dt> size
   *   <dd> the source size, in bytes (as in java.io.File.length)
   *   <dt> date (optional)
   *   <dd> the time the source was last modified in human-readable form
   *  </dl>
   * </blockquote>
   * <p>
   *  <b>Configuration options:</b>
   *  <dl>
   *   <dt> <i>depth</i> (optional)
   *   <dd> Sets how deep TraversableGenerator should delve into the
   *        source hierarchy. If set to 1 (the default), only the starting
   *        collection's immediate contents will be returned.
   *   <dt> <i>sort</i> (optional)
   *   <dd> Sort order in which the nodes are returned. Possible values are
   *        name, size, time, collection. collection is the same as name,
   *        except that the collection entries are listed first. System order is
   *        default.
   *   <dt> <i>reverse</i> (optional)
   *   <dd> Reverse the order of the sort
   *   <dt> <i>dateFormat</i> (optional)
   *   <dd> Sets the format for the date attribute of each node, as
   *        described in java.text.SimpleDateFormat. If unset, the default
   *        format for the current locale will be used.
   *   <dt> <i>refreshDelay</i> (optional)
   *   <dd> Sets the delay (in seconds) between checks on the source hierarchy
   *        for changed content. Defaults to 1 second.
   *  </dl>
   * </p>
   *
   * @author <a href="mailto:[EMAIL PROTECTED]">Pierpaolo Fumagalli</a>
   *         (Apache Software Foundation)
   * @author <a href="mailto:[EMAIL PROTECTED]">Conny Krappatsch</a>
   *         (SMB GmbH) for Virbus AG
   * @author <a href="[EMAIL PROTECTED]">Daniele Madama</a>
   * @author <a href="[EMAIL PROTECTED]">Gianugo Rabellino</a>
   * @version CVS $Id: TraversableGenerator.java,v 1.1 2003/10/23 07:55:49 
gcasper Exp $
   */
  public class TraversableGenerator extends ServiceableGenerator implements 
CacheableProcessingComponent {
  
      /** The URI of the namespace of this generator. */
      protected static final String URI = 
"http://apache.org/cocoon/collection/1.0";;
  
      /** The namespace prefix for this namespace. */
      protected static final String PREFIX = "collection";
  
      /* Node and attribute names */
      protected static final String COL_NODE_NAME = "collection";
      protected static final String RESOURCE_NODE_NAME = "resource";
  
      protected static final String RES_NAME_ATTR_NAME = "name";
      protected static final String LASTMOD_ATTR_NAME = "lastModified";
      protected static final String DATE_ATTR_NAME = "date";
      protected static final String SIZE_ATTR_NAME = "size";
  
      /** The validity that is being built */
      protected MultiSourceValidity validity;
      /** Convenience object, so we don't need to create an AttributesImpl for 
every element. */
      protected AttributesImpl attributes;
  
      /**
       * The cache key needs to be generated for the configuration of this
       * generator, so storing the parameters for generateKey().
       * Using the member variables after setup() would not work I guess. I 
don't
       * know a way from the regular expressions back to the pattern or at least
       * a useful string.
       */
      protected List cacheKeyParList;
  
      /** The depth parameter determines how deep the TraversableGenerator 
should delve. */
      protected int depth;
      /**
       * The dateFormatter determines into which date format the lastModified
       * time should be converted.
       * FIXME: SimpleDateFormat is not supported by all locales!
       */
      protected SimpleDateFormat dateFormatter;
      /** The delay between checks on updates to the source hierarchy. */
      protected long refreshDelay;
      /**
       * The sort parameter determines by which attribute the content of one
       * collection should be sorted. Possible values are "name", "size", "time"
       * and "collection", where "collection" is the same as "name", except that
       * collection entries are listed first.
       */
      protected String sort;
      /** The reverse parameter reverses the sort order. <code>false</code> is 
default. */
      protected boolean reverse;
      /** The regular expression for the root pattern. */
      protected RE rootRE;
      /** The regular expression for the include pattern. */
      protected RE includeRE;
      /** The regular expression for the exclude pattern. */
      protected RE excludeRE;
      /**
       * This is only set to true for the requested source specified by the
       * <code>src</code> attribute on the generator's configuration.
       */
      protected boolean isRequestedSource;
  
      /**
       * Set the request parameters. Must be called before the generate method.
       *
       * @param resolver     the SourceResolver object
       * @param objectModel  a <code>Map</code> containing model object
       * @param src          the Traversable Source to be XMLized specified as
       *                     <code>src</code> attribute on &lt;map:generate/>
       * @param par          configuration parameters
       */
      public void setup(SourceResolver resolver, Map objectModel, String src, 
Parameters par)
              throws ProcessingException, SAXException, IOException {
          if (src == null) {
              throw new ProcessingException("No src attribute pointing to a 
traversable source to be XMLized specified.");
          }
          super.setup(resolver, objectModel, src, par);
  
          this.cacheKeyParList = new ArrayList();
          this.cacheKeyParList.add(src);
  
          this.depth = par.getParameterAsInteger("depth", 1);
          this.cacheKeyParList.add(String.valueOf(this.depth));
  
          String dateFormatString = par.getParameter("dateFormat", null);
          this.cacheKeyParList.add(dateFormatString);
          if (dateFormatString != null) {
              this.dateFormatter = new SimpleDateFormat(dateFormatString);
          } else {
              this.dateFormatter = new SimpleDateFormat();
          }
  
          this.sort = par.getParameter("sort", "name");
          this.cacheKeyParList.add(this.sort);
  
          this.reverse = par.getParameterAsBoolean("reverse", false);
          this.cacheKeyParList.add(String.valueOf(this.reverse));
  
          this.refreshDelay = par.getParameterAsLong("refreshDelay", 1L) * 
1000L;
          this.cacheKeyParList.add(String.valueOf(this.refreshDelay));
  
          if (this.getLogger().isDebugEnabled()) {
              this.getLogger().debug("depth: " + this.depth);
              this.getLogger().debug("dateFormat: " + 
this.dateFormatter.toPattern());
              this.getLogger().debug("sort: " + this.sort);
              this.getLogger().debug("reverse: " + this.reverse);
              this.getLogger().debug("refreshDelay: " + this.refreshDelay);
          }
  
          String rePattern = null;
          try {
              rePattern = par.getParameter("root", null);
              this.cacheKeyParList.add(rePattern);
              this.rootRE = (rePattern == null) ? null : new RE(rePattern);
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("root pattern: " + rePattern);
              }
  
              rePattern = par.getParameter("include", null);
              this.cacheKeyParList.add(rePattern);
              this.includeRE = (rePattern == null) ? null : new RE(rePattern);
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("include pattern: " + rePattern);
              }
  
              rePattern = par.getParameter("exclude", null);
              this.cacheKeyParList.add(rePattern);
              this.excludeRE = (rePattern == null) ? null : new RE(rePattern);
              if (this.getLogger().isDebugEnabled()) {
                  this.getLogger().debug("exclude pattern: " + rePattern);
              }
          } catch (RESyntaxException rese) {
              throw new ProcessingException("Syntax error in regexp pattern '" 
                                                  + rePattern + "'", rese);
          }
  
          this.isRequestedSource = false;
          this.attributes = new AttributesImpl();
      }
  
      /* (non-Javadoc)
       * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey()
       */
      public Serializable getKey() {
          StringBuffer buffer = new StringBuffer();
          int len = this.cacheKeyParList.size();
          for (int i = 0; i < len; i++) {
              buffer.append((String)this.cacheKeyParList.get(i) + ":");
          }
          return buffer.toString();
      }
  
      /**
       * Gets the source validity, using a deferred validity object. The 
validity
       * is initially empty since the resources that define it are not known
       * before generation has occured. So the returned object is kept by the
       * generator and filled with each of the resources that is traversed.
       * 
       * @see TraversableGenerator.CollectionValidity
       */
      public SourceValidity getValidity() {
          if (this.validity == null) {
              this.validity = new MultiSourceValidity(this.resolver, 
this.refreshDelay);
          }
          return this.validity;
      }
  
      /**
       * Generate XML data.
       *
       * @throws  SAXException if an error occurs while outputting the document
       * @throws  ProcessingException if something went wrong while traversing
       *                              the source hierarchy 
       */
      public void generate() throws SAXException, ProcessingException {
          TraversableSource inputSource = null;
          try {
              inputSource = (TraversableSource) 
this.resolver.resolveURI(this.source);
  
              if (!inputSource.exists()) {
                  throw new ResourceNotFoundException(this.source + " does not 
exist.");
              }
  
              this.contentHandler.startDocument();
              this.contentHandler.startPrefixMapping(PREFIX, URI);
  
              Stack ancestors = getAncestors(inputSource);
              addAncestorPath(inputSource, ancestors);
  
              this.contentHandler.endPrefixMapping(PREFIX);
              this.contentHandler.endDocument();
              if (this.validity != null) {
                  this.validity.close();
              }
          } catch (SourceException se) {
              throw SourceUtil.handle(se);
          } catch (IOException ioe) {
              throw new ResourceNotFoundException("Could not read collection "
                                                  + this.source, ioe);
          } catch (ClassCastException ce) {
              throw new ResourceNotFoundException(this.source
                                                  + " is not a traversable 
source");
          } finally {
              this.resolver.release(inputSource);
          }
      }
  
      /**
       * Creates a stack containing the ancestors of a traversable source up to
       * specific parent matching the root pattern.
       * 
       * @param source the traversable source whose ancestors shall be retrieved
       * @return a Stack containing the ancestors.
       */
      protected Stack getAncestors(TraversableSource source) throws IOException 
{
          TraversableSource parent = source;
          Stack ancestors = new Stack();
  
          while ((parent != null) && !isRoot(parent)) {
              parent = (TraversableSource) parent.getParent();
              if (parent != null) {
                  ancestors.push(parent);
              } else {
                  // no ancestor matched the root pattern
                  ancestors.clear();
              }
          }
  
          return ancestors;
      }
  
      /**
       * Adds recursively the path from the source matched by the root pattern
       * down to the requested source.
       * 
       * @param source       the requested source.
       * @param ancestors  the stack of the ancestors.
       * @throws SAXException
       * @throws ProcessingException
       */
      protected void addAncestorPath(TraversableSource source, Stack ancestors)
              throws SAXException, ProcessingException {
          if (ancestors.empty()) {
              this.isRequestedSource = true;
              addPath(source, depth);
          } else {
              startNode(COL_NODE_NAME, (TraversableSource) ancestors.pop());
              addAncestorPath(source, ancestors);
              endNode(COL_NODE_NAME);
          }
      }
  
      /**
       * Adds a single node to the generated document. If the path is a
       * collection and depth is greater than zero, then recursive calls
       * are made to add nodes for the collection's children.
       *
       * @param source  the resource/collection to process
       * @param depth   how deep to scan the collection hierarchy
       *
       * @throws SAXException  if an error occurs while constructing nodes
       * @throws ProcessingException  if a problem occurs with the source
       */
      protected void addPath(TraversableSource source, int depth)
              throws SAXException, ProcessingException {
          if (source.isCollection()) {
              startNode(COL_NODE_NAME, source);
              if (depth > 0) {
  
                  Collection contents = null;
                  try {
                      contents = source.getChildren();
                  } catch (SourceException e) {
                      throw new ProcessingException("Error adding paths", e);
                  }
  
                  if (sort.equals("name")) {
                      Arrays.sort(contents.toArray(), new Comparator() {
                          public int compare(Object o1, Object o2) {
                              if (reverse) {
                                  return ((TraversableSource) 
o2).getName().compareTo(((TraversableSource) o1).getName());
                              }
                              return ((TraversableSource) 
o1).getName().compareTo(((TraversableSource) o2).getName());
                          }
                      });
                  } else if (sort.equals("size")) {
                      Arrays.sort(contents.toArray(), new Comparator() {
                          public int compare(Object o1, Object o2) {
                              if (reverse) {
                                  return new Long(((TraversableSource) 
o2).getContentLength()).compareTo(new Long(((TraversableSource) 
o1).getContentLength()));
                              }
                              return new Long(((TraversableSource) 
o1).getContentLength()).compareTo(new Long(((TraversableSource) 
o2).getContentLength()));
                          }
                      });
                  } else if (sort.equals("lastmodified")) {
                      Arrays.sort(contents.toArray(), new Comparator() {
                          public int compare(Object o1, Object o2) {
                              if (reverse) {
                                  return new Long(((TraversableSource) 
o2).getLastModified()).compareTo(new Long(((TraversableSource) 
o1).getLastModified()));
                              }
                              return new Long(((TraversableSource) 
o1).getLastModified()).compareTo(new Long(((TraversableSource) 
o2).getLastModified()));
                          }
                      });
                  } else if (sort.equals("collection")) {
                      Arrays.sort(contents.toArray(), new Comparator() {
                          public int compare(Object o1, Object o2) {
                              TraversableSource ts1 = (TraversableSource) o1;
                              TraversableSource ts2 = (TraversableSource) o2;
  
                              if (reverse) {
                                  if (ts2.isCollection() && !ts1.isCollection())
                                      return -1;
                                  if (!ts2.isCollection() && ts1.isCollection())
                                      return 1;
                                  return ts2.getName().compareTo(ts1.getName());
                              }
                              if (ts2.isCollection() && !ts1.isCollection())
                                  return 1;
                              if (!ts2.isCollection() && ts1.isCollection())
                                  return -1;
                              return ts1.getName().compareTo(ts2.getName());
                          }
                      });
                  }
                  
                  addContent(source);
                  
                  for (int i = 0; i < contents.size(); i++) {
                      if (isIncluded((TraversableSource) contents.toArray()[i]) 
&& !isExcluded((TraversableSource) contents.toArray()[i])) {
                          addPath((TraversableSource) contents.toArray()[i], 
depth - 1);
                      }
                  }
              }
              endNode(COL_NODE_NAME);
          } else {
              if (isIncluded(source) && !isExcluded(source)) {
                  startNode(RESOURCE_NODE_NAME, source);
                  addContent(source);
                  endNode(RESOURCE_NODE_NAME);
              }
          }
      }
      
      /**
       * Allow subclasses a chance to generate additional elements within 
collection and resource
       * elements.
       * 
       * @param source  the source to generate additional data for.
       */
      protected void addContent(TraversableSource source) throws SAXException, 
ProcessingException {
      }
      
      /**
       * Begins a named node and calls setNodeAttributes to set its attributes.
       *
       * @param nodeName  the name of the new node
       * @param source    the source a node with its attributes is added for
       * 
       * @throws SAXException  if an error occurs while creating the node
       */
      protected void startNode(String nodeName, TraversableSource source)
              throws SAXException, ProcessingException {
          if (this.validity != null) {
              this.validity.addSource(source);
          }
          setNodeAttributes(source);
          super.contentHandler.startElement(URI, nodeName, PREFIX + ':' + 
nodeName, attributes);
      }
  
      /**
       * Sets the attributes for a given source. For example attributes for the
       * name, the size and the last modification date of the source are added.
       *
       * @param source  the source attributes are added for
       */
      protected void setNodeAttributes(TraversableSource source) throws 
SAXException, ProcessingException {
          long lastModified = source.getLastModified();
          attributes.clear();
          attributes.addAttribute("", RES_NAME_ATTR_NAME,RES_NAME_ATTR_NAME,
                                  "CDATA", source.getName());
          attributes.addAttribute("", LASTMOD_ATTR_NAME, LASTMOD_ATTR_NAME,
                                  "CDATA", 
Long.toString(source.getLastModified()));
          attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME,
                                  "CDATA", dateFormatter.format(new 
Date(lastModified)));
          attributes.addAttribute("", SIZE_ATTR_NAME, SIZE_ATTR_NAME,
                                  "CDATA", 
Long.toString(source.getContentLength()));
          if (this.isRequestedSource) {
              attributes.addAttribute("", "sort", "sort", "CDATA", this.sort);
              attributes.addAttribute("", "reverse", "reverse", "CDATA",
                                      String.valueOf(this.reverse));
              attributes.addAttribute("", "requested", "requested", "CDATA", 
"true");
              this.isRequestedSource = false;
          }
      }
  
      /**
       * Ends the named node.
       *
       * @param nodeName  the name of the new node
       *
       * @throws SAXException  if an error occurs while closing the node
       */
      protected void endNode(String nodeName) throws SAXException {
          super.contentHandler.endElement(URI, nodeName, PREFIX + ':' + 
nodeName);
      }
  
      /**
       * Determines if a given source is the defined root.
       *
       * @param source  the source to check
       *
       * @return true if the source is the root or the root pattern is not set,
       *         false otherwise.
       */
      protected boolean isRoot(TraversableSource source) {
          return (this.rootRE == null) ? true : 
this.rootRE.match(source.getName());
      }
  
      /**
       * Determines if a given source shall be visible.
       *
       * @param source  the source to check
       *
       * @return true if the source shall be visible or the include Pattern is 
not set,
       *         false otherwise.
       */
      protected boolean isIncluded(TraversableSource source) {
          return (this.includeRE == null) ? true : 
this.includeRE.match(source.getName());
      }
  
      /**
       * Determines if a given source shall be excluded from viewing.
       *
       * @param source  the source to check
       *
       * @return false if the given source shall not be excluded or the exclude 
Pattern is not set,
       *         true otherwise.
       */
      protected boolean isExcluded(TraversableSource source) {
          return (this.excludeRE == null) ? false : 
this.excludeRE.match(source.getName());
      }
  
      /**
       * Recycle resources
       */
      public void recycle() {
          this.cacheKeyParList = null;
          this.attributes = null;
          this.dateFormatter = null;
          this.rootRE = null;
          this.includeRE = null;
          this.excludeRE = null;
          this.validity = null;
          super.recycle();
      }
  }
  
  
  

Reply via email to