KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > generation > DirectoryGenerator


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.generation;
17
18 import org.apache.avalon.framework.parameters.Parameters;
19 import org.apache.cocoon.ProcessingException;
20 import org.apache.cocoon.ResourceNotFoundException;
21 import org.apache.cocoon.caching.CacheableProcessingComponent;
22 import org.apache.cocoon.components.source.SourceUtil;
23 import org.apache.cocoon.environment.SourceResolver;
24 import org.apache.excalibur.source.Source;
25 import org.apache.excalibur.source.SourceException;
26 import org.apache.excalibur.source.SourceValidity;
27 import org.apache.regexp.RE;
28 import org.apache.regexp.RESyntaxException;
29 import org.xml.sax.SAXException JavaDoc;
30 import org.xml.sax.helpers.AttributesImpl JavaDoc;
31
32 import java.io.File JavaDoc;
33 import java.io.IOException JavaDoc;
34 import java.io.Serializable JavaDoc;
35 import java.net.URL JavaDoc;
36 import java.text.SimpleDateFormat JavaDoc;
37 import java.util.ArrayList JavaDoc;
38 import java.util.Date JavaDoc;
39 import java.util.List JavaDoc;
40 import java.util.Map JavaDoc;
41 import java.util.Stack JavaDoc;
42 import java.util.Arrays JavaDoc;
43 import java.util.Comparator JavaDoc;
44
45 /**
46  * @cocoon.sitemap.component.documentation
47  * Generates an XML directory listing.
48  * A more general approach is implemented by the TraversableGenerator (src/blocks/repository/java/org/apache/cocoon/generation/TraversableGenerator.java)
49  *
50  * @cocoon.sitemap.component.name directory
51  * @cocoon.sitemap.component.label content
52  * @cocoon.sitemap.component.logger sitemap.generator.directory
53  * @cocoon.sitemap.component.documentation.caching
54  * Uses the last modification date of the directory and the contained files
55  *
56  * @cocoon.sitemap.component.pooling.max 16
57  *
58  * @author <a HREF="mailto:pier@apache.org">Pierpaolo Fumagalli</a>
59  * (Apache Software Foundation)
60  * @author <a HREF="mailto:conny@smb-tec.com">Conny Krappatsch</a>
61  * (SMB GmbH) for Virbus AG
62  * @version CVS $Id: DirectoryGenerator.java 225247 2005-07-26 07:37:04Z cziegeler $
63  */

64 public class DirectoryGenerator
65     extends ServiceableGenerator
66     implements CacheableProcessingComponent {
67
68     /** Constant for the file protocol. */
69     private static final String JavaDoc FILE = "file:";
70
71     /** The URI of the namespace of this generator. */
72     protected static final String JavaDoc URI = "http://apache.org/cocoon/directory/2.0";
73
74     /** The namespace prefix for this namespace. */
75     protected static final String JavaDoc PREFIX = "dir";
76
77     /* Node and attribute names */
78     protected static final String JavaDoc DIR_NODE_NAME = "directory";
79     protected static final String JavaDoc FILE_NODE_NAME = "file";
80
81     protected static final String JavaDoc FILENAME_ATTR_NAME = "name";
82     protected static final String JavaDoc LASTMOD_ATTR_NAME = "lastModified";
83     protected static final String JavaDoc DATE_ATTR_NAME = "date";
84     protected static final String JavaDoc SIZE_ATTR_NAME = "size";
85
86     /** The validity that is being built */
87     protected DirValidity validity;
88     /** Convenience object, so we don't need to create an AttributesImpl for every element. */
89     protected AttributesImpl JavaDoc attributes;
90
91     /**
92      * The cache key needs to be generated for the configuration of this
93      * generator, so storing the parameters for generateKey().
94      * Using the member variables after setup() would not work I guess. I don't
95      * know a way from the regular expressions back to the pattern or at least
96      * a useful string.
97      */

98     protected List JavaDoc cacheKeyParList;
99
100     /** The depth parameter determines how deep the DirectoryGenerator should delve. */
101     protected int depth;
102     /**
103      * The dateFormatter determines into which date format the lastModified
104      * time should be converted.
105      * FIXME: SimpleDateFormat is not supported by all locales!
106      */

107     protected SimpleDateFormat JavaDoc dateFormatter;
108     /** The delay between checks on updates to the filesystem. */
109     protected long refreshDelay;
110     /**
111      * The sort parameter determines by which attribute the content of one
112      * directory should be sorted. Possible values are "name", "size", "lastmodified"
113      * and "directory", where "directory" is the same as "name", except that
114      * directory entries are listed first.
115      */

116     protected String JavaDoc sort;
117     /** The reverse parameter reverses the sort order. <code>false</code> is default. */
118     protected boolean reverse;
119     /** The regular expression for the root pattern. */
120     protected RE rootRE;
121     /** The regular expression for the include pattern. */
122     protected RE includeRE;
123     /** The regular expression for the exclude pattern. */
124     protected RE excludeRE;
125     /**
126      * This is only set to true for the requested directory specified by the
127      * <code>src</code> attribute on the generator's configuration.
128      */

129     protected boolean isRequestedDirectory;
130
131     /** The source object for the directory. */
132     protected Source directorySource;
133
134     /**
135      * Set the request parameters. Must be called before the generate method.
136      *
137      * @param resolver the SourceResolver object
138      * @param objectModel a <code>Map</code> containing model object
139      * @param src the directory to be XMLized specified as src attribute on &lt;map:generate/>
140      * @param par configuration parameters
141      */

142     public void setup(SourceResolver resolver, Map JavaDoc objectModel, String JavaDoc src, Parameters par)
143             throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
144         if (src == null) {
145             throw new ProcessingException("No src attribute pointing to a directory to be XMLized specified.");
146         }
147         super.setup(resolver, objectModel, src, par);
148
149         try {
150             this.directorySource = this.resolver.resolveURI(src);
151         } catch (SourceException se) {
152             throw SourceUtil.handle(se);
153         }
154
155         this.cacheKeyParList = new ArrayList JavaDoc();
156         this.cacheKeyParList.add(this.directorySource.getURI());
157
158         this.depth = par.getParameterAsInteger("depth", 1);
159         this.cacheKeyParList.add(String.valueOf(this.depth));
160
161         String JavaDoc dateFormatString = par.getParameter("dateFormat", null);
162         this.cacheKeyParList.add(dateFormatString);
163         if (dateFormatString != null) {
164             this.dateFormatter = new SimpleDateFormat JavaDoc(dateFormatString);
165         } else {
166             this.dateFormatter = new SimpleDateFormat JavaDoc();
167         }
168
169         this.sort = par.getParameter("sort", "name");
170         this.cacheKeyParList.add(this.sort);
171
172         this.reverse = par.getParameterAsBoolean("reverse", false);
173         this.cacheKeyParList.add(String.valueOf(this.reverse));
174
175         this.refreshDelay = par.getParameterAsLong("refreshDelay", 1L) * 1000L;
176         this.cacheKeyParList.add(String.valueOf(this.refreshDelay));
177
178         if (this.getLogger().isDebugEnabled()) {
179             this.getLogger().debug("depth: " + this.depth);
180             this.getLogger().debug("dateFormat: " + this.dateFormatter.toPattern());
181             this.getLogger().debug("sort: " + this.sort);
182             this.getLogger().debug("reverse: " + this.reverse);
183             this.getLogger().debug("refreshDelay: " + this.refreshDelay);
184         }
185
186         String JavaDoc rePattern = null;
187         try {
188             rePattern = par.getParameter("root", null);
189             this.cacheKeyParList.add(rePattern);
190             this.rootRE = (rePattern == null) ? null : new RE(rePattern);
191             if (this.getLogger().isDebugEnabled()) {
192                 this.getLogger().debug("root pattern: " + rePattern);
193             }
194
195             rePattern = par.getParameter("include", null);
196             this.cacheKeyParList.add(rePattern);
197             this.includeRE = (rePattern == null) ? null : new RE(rePattern);
198             if (this.getLogger().isDebugEnabled()) {
199                 this.getLogger().debug("include pattern: " + rePattern);
200             }
201
202             rePattern = par.getParameter("exclude", null);
203             this.cacheKeyParList.add(rePattern);
204             this.excludeRE = (rePattern == null) ? null : new RE(rePattern);
205             if (this.getLogger().isDebugEnabled()) {
206                 this.getLogger().debug("exclude pattern: " + rePattern);
207             }
208         } catch (RESyntaxException rese) {
209             throw new ProcessingException("Syntax error in regexp pattern '"
210                                           + rePattern + "'", rese);
211         }
212
213         this.isRequestedDirectory = false;
214         this.attributes = new AttributesImpl JavaDoc();
215     }
216
217     /* (non-Javadoc)
218      * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey()
219      */

220     public Serializable JavaDoc getKey() {
221         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
222         int len = this.cacheKeyParList.size();
223         for (int i = 0; i < len; i++) {
224             buffer.append((String JavaDoc)this.cacheKeyParList.get(i) + ":");
225         }
226         return buffer.toString();
227     }
228
229     /**
230      * Gets the source validity, using a deferred validity object. The validity
231      * is initially empty since the files that define it are not known before
232      * generation has occured. So the returned object is kept by the generator
233      * and filled with each of the files that are traversed.
234      *
235      * @see DirectoryGenerator.DirValidity
236      */

237     public SourceValidity getValidity() {
238         if (this.validity == null) {
239             this.validity = new DirValidity(this.refreshDelay);
240         }
241         return this.validity;
242     }
243
244     /**
245      * Generate XML data.
246      *
247      * @throws SAXException if an error occurs while outputting the document
248      * @throws ProcessingException if the requsted URI isn't a directory on the local filesystem
249      */

250     public void generate() throws SAXException JavaDoc, ProcessingException {
251         try {
252             String JavaDoc systemId = this.directorySource.getURI();
253             if (!systemId.startsWith(FILE)) {
254                 throw new ResourceNotFoundException(systemId + " does not denote a directory");
255             }
256             // This relies on systemId being of the form "file://..."
257
File JavaDoc directoryFile = new File JavaDoc(new URL JavaDoc(systemId).getFile());
258             if (!directoryFile.isDirectory()) {
259                 throw new ResourceNotFoundException(super.source + " is not a directory.");
260             }
261
262             this.contentHandler.startDocument();
263             this.contentHandler.startPrefixMapping(PREFIX, URI);
264
265             Stack JavaDoc ancestors = getAncestors(directoryFile);
266             addAncestorPath(directoryFile, ancestors);
267
268             this.contentHandler.endPrefixMapping(PREFIX);
269             this.contentHandler.endDocument();
270         } catch (IOException JavaDoc ioe) {
271             throw new ResourceNotFoundException("Could not read directory " + super.source, ioe);
272         }
273     }
274
275     /**
276      * Creates a stack containing the ancestors of File up to specified directory.
277      *
278      * @param path the File whose ancestors shall be retrieved
279      * @return a Stack containing the ancestors.
280      */

281     protected Stack JavaDoc getAncestors(File JavaDoc path) {
282         File JavaDoc parent = path;
283         Stack JavaDoc ancestors = new Stack JavaDoc();
284
285         while ((parent != null) && !isRoot(parent)) {
286             parent = parent.getParentFile();
287             if (parent != null) {
288                 ancestors.push(parent);
289             } else {
290                 // no ancestor matched the root pattern
291
ancestors.clear();
292             }
293         }
294
295         return ancestors;
296     }
297
298     /**
299      * Adds recursively the path from the directory matched by the root pattern
300      * down to the requested directory.
301      *
302      * @param path the requested directory.
303      * @param ancestors the stack of the ancestors.
304      * @throws SAXException
305      */

306     protected void addAncestorPath(File JavaDoc path, Stack JavaDoc ancestors) throws SAXException JavaDoc {
307         if (ancestors.empty()) {
308             this.isRequestedDirectory = true;
309             addPath(path, depth);
310         } else {
311             startNode(DIR_NODE_NAME, (File JavaDoc)ancestors.pop());
312             addAncestorPath(path, ancestors);
313             endNode(DIR_NODE_NAME);
314         }
315     }
316
317     /**
318      * Adds a single node to the generated document. If the path is a
319      * directory, and depth is greater than zero, then recursive calls
320      * are made to add nodes for the directory's children.
321      *
322      * @param path the file/directory to process
323      * @param depth how deep to scan the directory
324      * @throws SAXException if an error occurs while constructing nodes
325      */

326     protected void addPath(File JavaDoc path, int depth) throws SAXException JavaDoc {
327         if (path.isDirectory()) {
328             startNode(DIR_NODE_NAME, path);
329             if (depth > 0) {
330                 File JavaDoc contents[] = path.listFiles();
331
332                 if (sort.equals("name")) {
333                     Arrays.sort(contents, new Comparator JavaDoc() {
334                         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
335                             if (reverse) {
336                                 return ((File JavaDoc)o2).getName().compareTo(((File JavaDoc)o1).getName());
337                             }
338                             return ((File JavaDoc)o1).getName().compareTo(((File JavaDoc)o2).getName());
339                         }
340                     });
341                 } else if (sort.equals("size")) {
342                     Arrays.sort(contents, new Comparator JavaDoc() {
343                         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
344                             if (reverse) {
345                                 return new Long JavaDoc(((File JavaDoc)o2).length()).compareTo(
346                                     new Long JavaDoc(((File JavaDoc)o1).length()));
347                             }
348                             return new Long JavaDoc(((File JavaDoc)o1).length()).compareTo(
349                                 new Long JavaDoc(((File JavaDoc)o2).length()));
350                         }
351                     });
352                 } else if (sort.equals("lastmodified")) {
353                     Arrays.sort(contents, new Comparator JavaDoc() {
354                         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
355                             if (reverse) {
356                                 return new Long JavaDoc(((File JavaDoc)o2).lastModified()).compareTo(
357                                     new Long JavaDoc(((File JavaDoc)o1).lastModified()));
358                             }
359                             return new Long JavaDoc(((File JavaDoc)o1).lastModified()).compareTo(
360                                 new Long JavaDoc(((File JavaDoc)o2).lastModified()));
361                         }
362                     });
363                 } else if (sort.equals("directory")) {
364                     Arrays.sort(contents, new Comparator JavaDoc() {
365                         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
366                             File JavaDoc f1 = (File JavaDoc)o1;
367                             File JavaDoc f2 = (File JavaDoc)o2;
368
369                             if (reverse) {
370                                 if (f2.isDirectory() && f1.isFile())
371                                     return -1;
372                                 if (f2.isFile() && f1.isDirectory())
373                                     return 1;
374                                 return f2.getName().compareTo(f1.getName());
375                             }
376                             if (f2.isDirectory() && f1.isFile())
377                                 return 1;
378                             if (f2.isFile() && f1.isDirectory())
379                                 return -1;
380                             return f1.getName().compareTo(f2.getName());
381                         }
382                     });
383                 }
384
385                 for (int i = 0; i < contents.length; i++) {
386                     if (isIncluded(contents[i]) && !isExcluded(contents[i])) {
387                         addPath(contents[i], depth - 1);
388                     }
389                 }
390             }
391             endNode(DIR_NODE_NAME);
392         } else {
393             if (isIncluded(path) && !isExcluded(path)) {
394                 startNode(FILE_NODE_NAME, path);
395                 endNode(FILE_NODE_NAME);
396             }
397         }
398     }
399
400     /**
401      * Begins a named node and calls setNodeAttributes to set its attributes.
402      *
403      * @param nodeName the name of the new node
404      * @param path the file/directory to use when setting attributes
405      * @throws SAXException if an error occurs while creating the node
406      */

407     protected void startNode(String JavaDoc nodeName, File JavaDoc path) throws SAXException JavaDoc {
408         if (this.validity != null) {
409             this.validity.addFile(path);
410         }
411         setNodeAttributes(path);
412         super.contentHandler.startElement(URI, nodeName, PREFIX + ':' + nodeName, attributes);
413     }
414
415     /**
416      * Sets the attributes for a given path. The default method sets attributes
417      * for the name of thefile/directory and for the last modification time
418      * of the path.
419      *
420      * @param path the file/directory to use when setting attributes
421      * @throws SAXException if an error occurs while setting the attributes
422      */

423     protected void setNodeAttributes(File JavaDoc path) throws SAXException JavaDoc {
424         long lastModified = path.lastModified();
425         attributes.clear();
426         attributes.addAttribute("", FILENAME_ATTR_NAME, FILENAME_ATTR_NAME,
427                                 "CDATA", path.getName());
428         attributes.addAttribute("", LASTMOD_ATTR_NAME, LASTMOD_ATTR_NAME,
429                                 "CDATA", Long.toString(path.lastModified()));
430         attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME,
431                                 "CDATA", dateFormatter.format(new Date JavaDoc(lastModified)));
432         attributes.addAttribute("", SIZE_ATTR_NAME, SIZE_ATTR_NAME,
433                                 "CDATA", Long.toString(path.length()));
434         if (this.isRequestedDirectory) {
435             attributes.addAttribute("", "sort", "sort", "CDATA", this.sort);
436             attributes.addAttribute("", "reverse", "reverse", "CDATA",
437                                     String.valueOf(this.reverse));
438             attributes.addAttribute("", "requested", "requested", "CDATA", "true");
439             this.isRequestedDirectory = false;
440         }
441     }
442
443     /**
444      * Ends the named node.
445      *
446      * @param nodeName the name of the new node
447      * @throws SAXException if an error occurs while closing the node
448      */

449     protected void endNode(String JavaDoc nodeName) throws SAXException JavaDoc {
450         super.contentHandler.endElement(URI, nodeName, PREFIX + ':' + nodeName);
451     }
452
453     /**
454      * Determines if a given File is the defined root.
455      *
456      * @param path the File to check
457      * @return true if the File is the root or the root pattern is not set,
458      * false otherwise.
459      */

460     protected boolean isRoot(File JavaDoc path) {
461         return (this.rootRE == null) ? true : this.rootRE.match(path.getName());
462     }
463
464     /**
465      * Determines if a given File shall be visible.
466      *
467      * @param path the File to check
468      * @return true if the File shall be visible or the include Pattern is <code>null</code>,
469      * false otherwise.
470      */

471     protected boolean isIncluded(File JavaDoc path) {
472         return (this.includeRE == null) ? true : this.includeRE.match(path.getName());
473     }
474
475     /**
476      * Determines if a given File shall be excluded from viewing.
477      *
478      * @param path the File to check
479      * @return false if the given File shall not be excluded or the exclude Pattern is <code>null</code>,
480      * true otherwise.
481      */

482     protected boolean isExcluded(File JavaDoc path) {
483         return (this.excludeRE == null) ? false : this.excludeRE.match(path.getName());
484     }
485
486     /**
487      * Recycle resources
488      */

489     public void recycle() {
490         if ( this.resolver != null ) {
491             this.resolver.release(this.directorySource);
492             this.directorySource = null;
493         }
494         this.cacheKeyParList = null;
495         this.attributes = null;
496         this.dateFormatter = null;
497         this.rootRE = null;
498         this.includeRE = null;
499         this.excludeRE = null;
500         this.validity = null;
501         super.recycle();
502     }
503
504     /** Specific validity class, that holds all files that have been generated */
505     public static class DirValidity implements SourceValidity {
506
507         private long expiry;
508         private long delay;
509         List JavaDoc files = new ArrayList JavaDoc();
510         List JavaDoc fileDates = new ArrayList JavaDoc();
511
512         public DirValidity(long delay) {
513             expiry = System.currentTimeMillis() + delay;
514             this.delay = delay;
515         }
516
517         public int isValid() {
518             if (System.currentTimeMillis() <= expiry) {
519                 return 1;
520             }
521
522             expiry = System.currentTimeMillis() + delay;
523             int len = files.size();
524             for (int i = 0; i < len; i++) {
525                 File JavaDoc f = (File JavaDoc)files.get(i);
526                 if (!f.exists()) {
527                     return -1; // File was removed
528
}
529
530                 long oldDate = ((Long JavaDoc)fileDates.get(i)).longValue();
531                 long newDate = f.lastModified();
532
533                 if (oldDate != newDate) {
534                     return -1;
535                 }
536             }
537
538             // all content is up to date: update the expiry date
539
expiry = System.currentTimeMillis() + delay;
540             return 1;
541         }
542
543         public int isValid(SourceValidity newValidity) {
544             return isValid();
545         }
546
547         public void addFile(File JavaDoc f) {
548             files.add(f);
549             fileDates.add(new Long JavaDoc(f.lastModified()));
550         }
551     }
552 }
553
Popular Tags