joerg 2003/06/21 06:34:04
Modified: src/java/org/apache/cocoon/generation DirectoryGenerator.java Log: fixed cache key generation (missing rootRE + dateFormatter), fixed the root pattern handling (if not matching directory found leave out the ancestors), fixed empty src attribute handling, fixed logging (rePattern in setup(), javadoc updates Revision Changes Path 1.4 +249 -276 cocoon-2.1/src/java/org/apache/cocoon/generation/DirectoryGenerator.java Index: DirectoryGenerator.java =================================================================== RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/generation/DirectoryGenerator.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- DirectoryGenerator.java 13 May 2003 22:14:51 -0000 1.3 +++ DirectoryGenerator.java 21 Jun 2003 13:34:03 -0000 1.4 @@ -125,66 +125,79 @@ * (SMB GmbH) for Virbus AG * @version CVS $Id$ */ -public class DirectoryGenerator extends ComposerGenerator implements CacheableProcessingComponent { - private static final String FILE = "file:"; +public class DirectoryGenerator extends ComposerGenerator implements CacheableProcessingComponent { + + /** Constant for the file protocol. */ + private static final String FILE = "file:"; /** The URI of the namespace of this generator. */ - protected static final String URI = - "http://apache.org/cocoon/directory/2.0"; + protected static final String URI = "http://apache.org/cocoon/directory/2.0"; /** The namespace prefix for this namespace. */ protected static final String PREFIX = "dir"; /* Node and attribute names */ - protected static final String DIR_NODE_NAME = "directory"; - protected static final String FILE_NODE_NAME = "file"; + protected static final String DIR_NODE_NAME = "directory"; + protected static final String FILE_NODE_NAME = "file"; - protected static final String FILENAME_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"; + protected static final String FILENAME_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"; - /* - * Variables set per-request - * + /** The validity that is being built */ + protected DirValidity validity; + /** Convenience object, so we don't need to create an AttributesImpl for every element. */ + protected AttributesImpl attributes; + + /** The depth parameter determines how deep the DirectoryGenerator 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 int depth; - protected AttributesImpl attributes = new AttributesImpl(); protected SimpleDateFormat dateFormatter; + /** The delay between checks on updates to the filesystem. */ + protected long refreshDelay; + /** + * The sort parameter determines by which attribute the content of one + * directory should be sorted. Possible values are "name", "size", "time" + * and "directory", where "directory" is the same as "name", except that + * directory 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 directory specified by the + * <code>src</code> attribute on the generator's configuration. + */ protected boolean isRequestedDirectory; - - /** The validity that is being built */ - protected DirValidity validity; - - /** The delay between checks to the filesystem */ - protected long refreshDelay; - /** - * Set the request parameters. Must be called before the generate - * method. + * 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 URI for this request (?) - * @param par - * configuration parameters + * @param resolver the SourceResolver object + * @param objectModel a <code>Map</code> containing model object + * @param src the directory to be XMLized specified as src attribute on <map:generate/> + * @param par configuration parameters */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) - throws ProcessingException, SAXException, IOException { + throws ProcessingException, SAXException, IOException { + if (src == null) { + throw new ProcessingException("No src attribute pointing to a directory to be XMLized specified."); + } super.setup(resolver, objectModel, src, par); + this.depth = par.getParameterAsInteger("depth", 1); + String dateFormatString = par.getParameter("dateFormat", null); if (dateFormatString != null) { this.dateFormatter = new SimpleDateFormat(dateFormatString); @@ -192,67 +205,70 @@ this.dateFormatter = new SimpleDateFormat(); } - this.depth = par.getParameterAsInteger("depth", 1); - this.sort = par.getParameter("sort", "name"); this.reverse = par.getParameterAsBoolean("reverse", false); - String rePattern = par.getParameter("root", null); - this.refreshDelay = par.getParameterAsLong("refreshDelay", 1L) * 1000L; - + 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 { - this.rootRE = (rePattern == null)?null:new RE(rePattern); + rePattern = par.getParameter("root", null); + this.rootRE = (rePattern == null) ? null : new RE(rePattern); + if (this.getLogger().isDebugEnabled()) { + this.getLogger().debug("root pattern: " + rePattern); + } rePattern = par.getParameter("include", null); - this.includeRE = (rePattern == null)?null:new RE(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.excludeRE = (rePattern == null)?null:new RE(rePattern); + this.excludeRE = (rePattern == null) ? null : new RE(rePattern); if (this.getLogger().isDebugEnabled()) { - this.getLogger().debug("root pattern: " + rePattern); - this.getLogger().debug("include pattern: " + rePattern); this.getLogger().debug("exclude pattern: " + rePattern); } } catch (RESyntaxException rese) { throw new ProcessingException("Syntax error in regexp pattern '" - + rePattern + "'", rese); + + rePattern + "'", rese); } this.isRequestedDirectory = false; - - /* Create a reusable attributes for creating nodes */ this.attributes = new AttributesImpl(); } - /* (non-Javadoc) - * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey() - */ - public Serializable getKey() - { - return super.source + this.sort + this.depth + this.excludeRE + this.includeRE + this.reverse; - } - - - /** - * Gets the source validity, using a deferred validity object. The validity is initially empty since - * the files 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 files that are traversed. - * @see DirectoryGenerator.DirValidity - */ - public SourceValidity getValidity() - { - this.validity = new DirValidity(this.refreshDelay); - return this.validity; - } + /* (non-Javadoc) + * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey() + * FIXME: SimpleDateFormat and RE don't have a toString() implemented, so + * the key generation is buggy!! + */ + public Serializable getKey() { + return super.source + this.depth + this.dateFormatter + this.sort + + this.reverse + this.rootRE + this.excludeRE + this.includeRE; + } + + /** + * Gets the source validity, using a deferred validity object. The validity + * is initially empty since the files 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 files that are traversed. + * @see DirectoryGenerator.DirValidity + */ + public SourceValidity getValidity() { + this.validity = new DirValidity(this.refreshDelay); + return this.validity; + } /** * Generate XML data. @@ -260,18 +276,16 @@ * @throws SAXException * if an error occurs while outputting the document * @throws ProcessingException - * if the requsted URI isn't a directory on the local - * filesystem + * if the requsted URI isn't a directory on the local filesystem */ - public void generate() - throws SAXException, ProcessingException { + public void generate() throws SAXException, ProcessingException { String directory = super.source; Source inputSource = null; try { inputSource = this.resolver.resolveURI(directory); String systemId = inputSource.getURI(); if (!systemId.startsWith(FILE)) { - throw new ResourceNotFoundException(systemId + " does not denote a directory"); + throw new ResourceNotFoundException(systemId + " does not denote a directory"); } // This relies on systemId being of the form "file://..." File directoryFile = new File(new URL(systemId).getFile()); @@ -280,28 +294,23 @@ } this.contentHandler.startDocument(); - this.contentHandler.startPrefixMapping(PREFIX,URI); Stack ancestors = getAncestors(directoryFile); - addPathWithAncestors(directoryFile, ancestors); + addAncestorPath(directoryFile, ancestors); - this.contentHandler.endPrefixMapping(PREFIX); this.contentHandler.endDocument(); } catch (SourceException se) { throw SourceUtil.handle(se); } catch (IOException ioe) { - throw new ResourceNotFoundException("Could not read directory " - + directory, ioe); + throw new ResourceNotFoundException("Could not read directory " + directory, ioe); } finally { - this.resolver.release( inputSource ); + this.resolver.release(inputSource); } } /** - * Creates a stack containing the ancestors of File up to specified - * directory. + * Creates a stack containing the ancestors of File up to specified directory. * @param path the File whose ancestors shall be retrieved - * * @return a Stack containing the ancestors. */ protected Stack getAncestors(File path) { @@ -312,109 +321,105 @@ parent = parent.getParentFile(); if (parent != null) { ancestors.push(parent); + } else { + // no ancestor matched the root pattern + ancestors.clear(); } } return ancestors; } - - protected void addPathWithAncestors(File path, Stack ancestors) - throws SAXException { - + /** + * Adds recursively the path from the directory matched by the root pattern + * down to the requested directory. + * @param path the requested directory. + * @param ancestors the stack of the ancestors. + * @throws SAXException + */ + protected void addAncestorPath(File path, Stack ancestors) throws SAXException { if (ancestors.empty()) { this.isRequestedDirectory = true; addPath(path, depth); } else { startNode(DIR_NODE_NAME, (File)ancestors.pop()); - addPathWithAncestors(path, ancestors); + addAncestorPath(path, ancestors); endNode(DIR_NODE_NAME); } } - /** * Adds a single node to the generated document. If the path is a * directory, and depth is greater than zero, then recursive calls * are made to add nodes for the directory's children. * - * @param path - * the file/directory to process - * @param depth - * how deep to scan the directory + * @param path the file/directory to process + * @param depth how deep to scan the directory * - * @throws SAXException - * if an error occurs while constructing nodes + * @throws SAXException if an error occurs while constructing nodes */ - protected void addPath(File path, int depth) - throws SAXException { + protected void addPath(File path, int depth) throws SAXException { if (path.isDirectory()) { startNode(DIR_NODE_NAME, path); - if (depth>0) { + if (depth > 0) { File contents[] = path.listFiles(); - if(sort.equals("name")) { - Arrays.sort(contents, - new Comparator() { - public int compare(Object o1, Object o2) { - if(reverse) { - return ((File) o2).getName() - .compareTo(((File) o1).getName()); - } - return ((File) o1).getName() - .compareTo(((File) o2).getName()); + if (sort.equals("name")) { + Arrays.sort(contents, new Comparator() { + public int compare(Object o1, Object o2) { + if (reverse) { + return ((File)o2).getName().compareTo(((File)o1).getName()); } - }); - } else if(sort.equals("size")) { - Arrays.sort(contents, - new Comparator() { - public int compare(Object o1, Object o2) { - if(reverse) { - return new Long(((File) o2).length()) - .compareTo(new Long(((File) o1).length())); - } - return new Long(((File) o1).length()) - .compareTo(new Long(((File) o2).length())); + return ((File)o1).getName().compareTo(((File)o2).getName()); + } + }); + } else if (sort.equals("size")) { + Arrays.sort(contents, new Comparator() { + public int compare(Object o1, Object o2) { + if (reverse) { + return new Long(((File)o2).length()).compareTo( + new Long(((File)o1).length())); } - }); - } else if(sort.equals("lastmodified")) { - Arrays.sort(contents, - new Comparator() { - public int compare(Object o1, Object o2) { - if(reverse) { - return new Long(((File) o2).lastModified()) - .compareTo(new Long(((File) o1).lastModified())); - } - return new Long(((File) o1).lastModified()) - .compareTo(new Long(((File) o2).lastModified())); + return new Long(((File)o1).length()).compareTo( + new Long(((File)o2).length())); + } + }); + } else if (sort.equals("lastmodified")) { + Arrays.sort(contents, new Comparator() { + public int compare(Object o1, Object o2) { + if (reverse) { + return new Long(((File)o2).lastModified()).compareTo( + new Long(((File)o1).lastModified())); } - }); - } else if(sort.equals("directory")) { - Arrays.sort(contents, - new Comparator() { - public int compare(Object o1, Object o2) { - File f1 = (File) o1; - File f2 = (File) o2; - - if(reverse) { - if(f2.isDirectory() && f1.isFile()) - return -1; - if(f2.isFile() && f1.isDirectory()) - return 1; - return f2.getName().compareTo(f1.getName()); - } - if(f2.isDirectory() && f1.isFile()) - return 1; - if(f2.isFile() && f1.isDirectory()) + return new Long(((File)o1).lastModified()).compareTo( + new Long(((File)o2).lastModified())); + } + }); + } else if (sort.equals("directory")) { + Arrays.sort(contents, new Comparator() { + public int compare(Object o1, Object o2) { + File f1 = (File)o1; + File f2 = (File)o2; + + if (reverse) { + if (f2.isDirectory() && f1.isFile()) return -1; - return f1.getName().compareTo(f2.getName()); + if (f2.isFile() && f1.isDirectory()) + return 1; + return f2.getName().compareTo(f1.getName()); } - }); + if (f2.isDirectory() && f1.isFile()) + return 1; + if (f2.isFile() && f1.isDirectory()) + return -1; + return f1.getName().compareTo(f2.getName()); + } + }); } - for (int i=0; i<contents.length; i++) { + for (int i = 0; i < contents.length; i++) { if (isIncluded(contents[i]) && !isExcluded(contents[i])) { - addPath(contents[i], depth-1); + addPath(contents[i], depth - 1); } } } @@ -427,189 +432,157 @@ } } - /** - * Begins a named node, and calls setNodeAttributes to set its - * attributes. - * - * @param nodeName - * the name of the new node - * @param path - * the file/directory to use when setting attributes + * Begins a named node and calls setNodeAttributes to set its attributes. * - * @throws SAXException - * if an error occurs while creating the node - */ - protected void startNode(String nodeName, File path) - throws SAXException { - if (this.validity != null) { - this.validity.addFile(path); - } - + * @param nodeName the name of the new node + * @param path the file/directory to use when setting attributes + * + * @throws SAXException if an error occurs while creating the node + */ + protected void startNode(String nodeName, File path) throws SAXException { + if (this.validity != null) { + this.validity.addFile(path); + } setNodeAttributes(path); - super.contentHandler.startElement(URI, nodeName, PREFIX+':'+nodeName, attributes); + super.contentHandler.startElement(URI, nodeName, PREFIX + ':' + nodeName, attributes); } - /** * Sets the attributes for a given path. The default method sets attributes * for the name of thefile/directory and for the last modification time * of the path. * - * @param path - * the file/directory to use when setting attributes + * @param path the file/directory to use when setting attributes * - * @throws SAXException - * if an error occurs while setting the attributes + * @throws SAXException if an error occurs while setting the attributes */ protected void setNodeAttributes(File path) throws SAXException { long lastModified = path.lastModified(); attributes.clear(); - attributes.addAttribute("", FILENAME_ATTR_NAME, - FILENAME_ATTR_NAME, "CDATA", - path.getName()); - attributes.addAttribute("", LASTMOD_ATTR_NAME, - LASTMOD_ATTR_NAME, "CDATA", - Long.toString(path.lastModified())); - 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(path.length())); - + attributes.addAttribute("", FILENAME_ATTR_NAME, FILENAME_ATTR_NAME, + "CDATA", path.getName()); + attributes.addAttribute("", LASTMOD_ATTR_NAME, LASTMOD_ATTR_NAME, + "CDATA", Long.toString(path.lastModified())); + 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(path.length())); if (this.isRequestedDirectory) { attributes.addAttribute("", "sort", "sort", "CDATA", this.sort); attributes.addAttribute("", "reverse", "reverse", "CDATA", - String.valueOf(this.reverse)); - attributes.addAttribute("", "requested", "requested", "CDATA", - "true"); + String.valueOf(this.reverse)); + attributes.addAttribute("", "requested", "requested", "CDATA", "true"); this.isRequestedDirectory = false; } } - /** * Ends the named node. * - * @param nodeName - * the name of the new node + * @param nodeName the name of the new node * - * @throws SAXException - * if an error occurs while closing the 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); + protected void endNode(String nodeName) throws SAXException { + super.contentHandler.endElement(URI, nodeName, PREFIX + ':' + nodeName); } - /** * Determines if a given File is the defined root. * - * @param path the File to check + * @param path the File to check * * @return true if the File is the root or the root pattern is not set, - * false otherwise. + * false otherwise. */ protected boolean isRoot(File path) { - - return (this.rootRE == null) - ? true - : this.rootRE.match(path.getName()); + return (this.rootRE == null) ? true : this.rootRE.match(path.getName()); } - /** * Determines if a given File shall be visible. * - * @param path the File to check + * @param path the File to check * - * @return true if the File shall be visible or the include Pattern is - <code>null</code>, false otherwise. + * @return true if the File shall be visible or the include Pattern is <code>null</code>, + * false otherwise. */ protected boolean isIncluded(File path) { - - return (this.includeRE == null) - ? true - : this.includeRE.match(path.getName()); + return (this.includeRE == null) ? true : this.includeRE.match(path.getName()); } - /** * Determines if a given File shall be excluded from viewing. * - * @param path the File to check + * @param path the File to check * - * @return false if the given File shall not be excluded or the - * exclude Pattern is <code>null</code>, true otherwise. + * @return false if the given File shall not be excluded or the exclude Pattern is <code>null</code>, + * true otherwise. */ protected boolean isExcluded(File path) { - - return (this.excludeRE == null) - ? false - : this.excludeRE.match(path.getName()); + return (this.excludeRE == null) ? false : this.excludeRE.match(path.getName()); } /** * Recycle resources - * */ - public void recycle() { - super.recycle(); - this.attributes = null; - this.dateFormatter = null; - this.rootRE = null; - this.includeRE = null; - this.excludeRE = null; - this.validity = null; - + super.recycle(); + this.attributes = null; + this.dateFormatter = null; + this.rootRE = null; + this.includeRE = null; + this.excludeRE = null; + this.validity = null; } /** Specific validity class, that holds all files that have been generated */ - public static class DirValidity implements SourceValidity { - private long expiry; - private long delay; - List files = new ArrayList(); - List fileDates = new ArrayList(); - - public DirValidity(long delay) { - expiry = System.currentTimeMillis() + delay; - this.delay = delay; - } - - public int isValid() { - if (System.currentTimeMillis() <= expiry) - return 1; - - // System.out.println("Regenerating cache validity"); - expiry = System.currentTimeMillis() + delay; - int len = files.size(); - for (int i = 0; i < len; i++) { - File f = (File) files.get(i); - if (!f.exists()) - return -1; // File was removed - - long oldDate = ((Long) fileDates.get(i)).longValue(); - long newDate = f.lastModified(); - - if (oldDate != newDate) - return -1; - } - - // All content is up to date : update the expiry date - expiry = System.currentTimeMillis() + delay; - return 1; - } - - public int isValid(SourceValidity newValidity) { - return isValid(); - } - - public void addFile(File f) { - files.add(f); - fileDates.add(new Long(f.lastModified())); - } - } + public static class DirValidity implements SourceValidity { + + private long expiry; + private long delay; + List files = new ArrayList(); + List fileDates = new ArrayList(); + + public DirValidity(long delay) { + expiry = System.currentTimeMillis() + delay; + this.delay = delay; + } + + public int isValid() { + if (System.currentTimeMillis() <= expiry) { + return 1; + } + + expiry = System.currentTimeMillis() + delay; + int len = files.size(); + for (int i = 0; i < len; i++) { + File f = (File)files.get(i); + if (!f.exists()) { + return -1; // File was removed + } + + long oldDate = ((Long)fileDates.get(i)).longValue(); + long newDate = f.lastModified(); + + if (oldDate != newDate) { + return -1; + } + } + + // all content is up to date: update the expiry date + expiry = System.currentTimeMillis() + delay; + return 1; + } + + public int isValid(SourceValidity newValidity) { + return isValid(); + } + + public void addFile(File f) { + files.add(f); + fileDates.add(new Long(f.lastModified())); + } + } }