KickJava   Java API By Example, From Geeks To Geeks.

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


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.components.source.impl.MultiSourceValidity;
24 import org.apache.cocoon.environment.SourceResolver;
25 import org.apache.excalibur.source.Source;
26 import org.apache.excalibur.source.SourceException;
27 import org.apache.excalibur.source.SourceValidity;
28 import org.apache.excalibur.source.TraversableSource;
29 import org.apache.regexp.RE;
30 import org.apache.regexp.RESyntaxException;
31 import org.xml.sax.SAXException JavaDoc;
32 import org.xml.sax.helpers.AttributesImpl JavaDoc;
33
34 import java.io.IOException JavaDoc;
35 import java.io.Serializable JavaDoc;
36 import java.text.SimpleDateFormat JavaDoc;
37 import java.util.Locale JavaDoc;
38 import java.util.ArrayList JavaDoc;
39 import java.util.Collection JavaDoc;
40 import java.util.Date JavaDoc;
41 import java.util.Enumeration JavaDoc;
42 import java.util.Iterator JavaDoc;
43 import java.util.List JavaDoc;
44 import java.util.Map JavaDoc;
45 import java.util.Stack JavaDoc;
46 import java.util.Arrays JavaDoc;
47 import java.util.Comparator JavaDoc;
48 import java.util.TimeZone JavaDoc;
49
50 /**
51  * Generates an XML source hierarchy listing from a Traversable Source.
52  * <p>
53  * The root node of the generated document will normally be a
54  * <code>collection</code> node and a collection node can contain zero or more
55  * <code>resource</code> or collection nodes. A resource node has no children.
56  * Each node will contain the following attributes:
57  * <blockquote>
58  * <dl>
59  * <dt> name
60  * <dd> the name of the source
61  * <dt> lastModified
62  * <dd> the time the source was last modified, measured as the number of
63  * milliseconds since the epoch (as in java.io.File.lastModified)
64  * <dt> size
65  * <dd> the source size, in bytes (as in java.io.File.length)
66  * <dt> date (optional)
67  * <dd> the time the source was last modified in human-readable form
68  * </dl>
69  * </blockquote>
70  * <p>
71  * <b>Configuration options:</b>
72  * <dl>
73  * <dt> <i>depth</i> (optional)
74  * <dd> Sets how deep TraversableGenerator should delve into the
75  * source hierarchy. If set to 1 (the default), only the starting
76  * collection's immediate contents will be returned.
77  * <dt> <i>sort</i> (optional)
78  * <dd> Sort order in which the nodes are returned. Possible values are
79  * name, size, time, collection. collection is the same as name,
80  * except that the collection entries are listed first. System order is
81  * default.
82  * <dt> <i>reverse</i> (optional)
83  * <dd> Reverse the order of the sort
84  * <dt> <i>dateFormat</i> (optional)
85  * <dd> Sets the format for the date attribute of each node, as
86  * described in java.text.SimpleDateFormat. If unset, the default
87  * format for the current locale will be used.
88  * <dt> <i>timeZone</i> (optional)
89  * <dd> Sets the time zone offset ID for the date attribute, as
90  * described in java.util.TimeZone. If unset, the default
91  * system time zone will be used.
92  * <dt> <i>refreshDelay</i> (optional)
93  * <dd> Sets the delay (in seconds) between checks on the source hierarchy
94  * for changed content. Defaults to 1 second.
95  * </dl>
96  * </p>
97  *
98  * @author <a HREF="mailto:pier@apache.org">Pierpaolo Fumagalli</a>
99  * (Apache Software Foundation)
100  * @author <a HREF="mailto:conny@smb-tec.com">Conny Krappatsch</a>
101  * (SMB GmbH) for Virbus AG
102  * @author <a HREF="d.madama@pro-netics.com">Daniele Madama</a>
103  * @author <a HREF="gianugo@apache.org">Gianugo Rabellino</a>
104  * @version CVS $Id: TraversableGenerator.java 240312 2005-08-26 19:20:33Z vgritsenko $
105  */

106 public class TraversableGenerator extends ServiceableGenerator
107                                   implements CacheableProcessingComponent {
108
109     /** The URI of the namespace of this generator. */
110     protected static final String JavaDoc URI = "http://apache.org/cocoon/collection/1.0";
111
112     /** The namespace prefix for this namespace. */
113     protected static final String JavaDoc PREFIX = "collection";
114
115     /* Node and attribute names */
116     protected static final String JavaDoc COL_NODE_NAME = "collection";
117     protected static final String JavaDoc RESOURCE_NODE_NAME = "resource";
118
119     protected static final String JavaDoc RES_NAME_ATTR_NAME = "name";
120     protected static final String JavaDoc URI_ATTR_NAME = "uri";
121     protected static final String JavaDoc LASTMOD_ATTR_NAME = "lastModified";
122     protected static final String JavaDoc DATE_ATTR_NAME = "date";
123     protected static final String JavaDoc SIZE_ATTR_NAME = "size";
124
125     /** The validity that is being built */
126     protected MultiSourceValidity validity;
127
128     /**
129      * Convenience object, so we don't need to create an AttributesImpl for every element.
130      */

131     protected AttributesImpl JavaDoc attributes;
132
133     /**
134      * The cache key needs to be generated for the configuration of this
135      * generator, so storing the parameters for generateKey().
136      * Using the member variables after setup() would not work I guess. I don't
137      * know a way from the regular expressions back to the pattern or at least
138      * a useful string.
139      */

140     protected List JavaDoc cacheKeyParList;
141
142     /**
143      * The depth parameter determines how deep the TraversableGenerator should delve.
144      */

145     protected int depth;
146
147     /**
148      * The dateFormatter determines into which date format the lastModified
149      * time should be converted.
150      * FIXME: SimpleDateFormat is not supported by all locales!
151      */

152     protected SimpleDateFormat JavaDoc dateFormatter;
153
154     /** The delay between checks on updates to the source hierarchy. */
155     protected long refreshDelay;
156
157     /**
158      * The sort parameter determines by which attribute the content of one
159      * collection should be sorted. Possible values are "name", "size", "time"
160      * and "collection", where "collection" is the same as "name", except that
161      * collection entries are listed first.
162      */

163     protected String JavaDoc sort;
164
165     /** The reverse parameter reverses the sort order. <code>false</code> is default. */
166     protected boolean reverse;
167
168     /** The regular expression for the root pattern. */
169     protected RE rootRE;
170
171     /** The regular expression for the include pattern. */
172     protected RE includeRE;
173
174     /** The regular expression for the exclude pattern. */
175     protected RE excludeRE;
176
177     /**
178      * This is only set to true for the requested source specified by the
179      * <code>src</code> attribute on the generator's configuration.
180      */

181     protected boolean isRequestedSource;
182
183     /**
184      * Set the request parameters. Must be called before the generate method.
185      *
186      * @param resolver the SourceResolver object
187      * @param objectModel a <code>Map</code> containing model object
188      * @param src the Traversable Source to be XMLized specified as
189      * <code>src</code> attribute on &lt;map:generate/>
190      * @param par configuration parameters
191      */

192     public void setup(SourceResolver resolver, Map JavaDoc objectModel, String JavaDoc src, Parameters par)
193     throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
194         if (src == null) {
195             throw new ProcessingException("No src attribute pointing to a traversable source to be XMLized specified.");
196         }
197         super.setup(resolver, objectModel, src, par);
198
199         this.cacheKeyParList = new ArrayList JavaDoc();
200         this.cacheKeyParList.add(src);
201
202         this.depth = par.getParameterAsInteger("depth", 1);
203         this.cacheKeyParList.add(String.valueOf(this.depth));
204
205         String JavaDoc dateFormatString = par.getParameter("dateFormat", null);
206         this.cacheKeyParList.add(dateFormatString);
207         if (dateFormatString != null) {
208             String JavaDoc locale = par.getParameter("locale", null);
209             if (locale != null) {
210                 this.dateFormatter = new SimpleDateFormat JavaDoc(dateFormatString, new Locale JavaDoc(locale, ""));
211             } else {
212                 this.dateFormatter = new SimpleDateFormat JavaDoc(dateFormatString);
213             }
214         } else {
215             this.dateFormatter = new SimpleDateFormat JavaDoc();
216         }
217
218         String JavaDoc timeZone = par.getParameter("timeZone", null);
219         if (timeZone != null) {
220             this.dateFormatter.setTimeZone(TimeZone.getTimeZone(timeZone));
221         }
222
223         this.sort = par.getParameter("sort", "name");
224         this.cacheKeyParList.add(this.sort);
225
226         this.reverse = par.getParameterAsBoolean("reverse", false);
227         this.cacheKeyParList.add(String.valueOf(this.reverse));
228
229         this.refreshDelay = par.getParameterAsLong("refreshDelay", 1L) * 1000L;
230         this.cacheKeyParList.add(String.valueOf(this.refreshDelay));
231
232         if (this.getLogger().isDebugEnabled()) {
233             this.getLogger().debug("depth: " + this.depth);
234             this.getLogger().debug("dateFormat: " + this.dateFormatter.toPattern());
235             this.getLogger().debug("timeZone: " + timeZone);
236             this.getLogger().debug("sort: " + this.sort);
237             this.getLogger().debug("reverse: " + this.reverse);
238             this.getLogger().debug("refreshDelay: " + this.refreshDelay);
239         }
240
241         String JavaDoc rePattern = null;
242         try {
243             rePattern = par.getParameter("root", null);
244             if (this.getLogger().isDebugEnabled()) {
245                 this.getLogger().debug("root pattern: " + rePattern);
246             }
247             this.cacheKeyParList.add(rePattern);
248             this.rootRE = (rePattern == null) ? null : new RE(rePattern);
249
250             rePattern = par.getParameter("include", null);
251             if (this.getLogger().isDebugEnabled()) {
252                 this.getLogger().debug("include pattern: " + rePattern);
253             }
254             this.cacheKeyParList.add(rePattern);
255             this.includeRE = (rePattern == null) ? null : new RE(rePattern);
256
257             rePattern = par.getParameter("exclude", null);
258             if (this.getLogger().isDebugEnabled()) {
259                 this.getLogger().debug("exclude pattern: " + rePattern);
260             }
261             this.cacheKeyParList.add(rePattern);
262             this.excludeRE = (rePattern == null) ? null : new RE(rePattern);
263
264         } catch (RESyntaxException rese) {
265             throw new ProcessingException("Syntax error in regexp pattern '"
266                                           + rePattern + "'", rese);
267         }
268
269         this.isRequestedSource = false;
270         this.attributes = new AttributesImpl JavaDoc();
271     }
272
273     /* (non-Javadoc)
274      * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey()
275      */

276     public Serializable JavaDoc getKey() {
277         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
278         int len = this.cacheKeyParList.size();
279         for (int i = 0; i < len; i++) {
280             buffer.append(this.cacheKeyParList.get(i));
281             buffer.append(':');
282         }
283         return buffer.toString();
284     }
285
286     /**
287      * Gets the source validity, using a deferred validity object. The validity
288      * is initially empty since the resources that define it are not known
289      * before generation has occured. So the returned object is kept by the
290      * generator and filled with each of the resources that is traversed.
291      *
292      * @see org.apache.cocoon.components.source.impl.MultiSourceValidity
293      */

294     public SourceValidity getValidity() {
295         if (this.validity == null) {
296             this.validity = new MultiSourceValidity(this.resolver, this.refreshDelay);
297         }
298         return this.validity;
299     }
300
301     /**
302      * Generate XML data.
303      *
304      * @throws SAXException if an error occurs while outputting the document
305      * @throws ProcessingException if something went wrong while traversing
306      * the source hierarchy
307      */

308     public void generate() throws SAXException JavaDoc, ProcessingException {
309         Source src = null;
310         Stack JavaDoc ancestors = null;
311         try {
312             src = this.resolver.resolveURI(this.source);
313             if (!(src instanceof TraversableSource)) {
314                 throw new SourceException(this.source + " is not a traversable source");
315             }
316             final TraversableSource inputSource =
317                 (TraversableSource) this.resolver.resolveURI(this.source);
318
319             if (!inputSource.exists()) {
320                 throw new ResourceNotFoundException(this.source + " does not exist.");
321             }
322
323             this.contentHandler.startDocument();
324             this.contentHandler.startPrefixMapping(PREFIX, URI);
325
326             ancestors = getAncestors(inputSource);
327             addAncestorPath(inputSource, ancestors);
328
329             this.contentHandler.endPrefixMapping(PREFIX);
330             this.contentHandler.endDocument();
331             if (this.validity != null) {
332                 this.validity.close();
333             }
334         } catch (SourceException se) {
335             throw SourceUtil.handle(se);
336         } catch (IOException JavaDoc ioe) {
337             throw new ResourceNotFoundException("Could not read collection "
338                                                 + this.source, ioe);
339         } finally {
340             if (src != null) {
341                 this.resolver.release(src);
342             }
343             if (ancestors != null) {
344                 Enumeration JavaDoc enumeration = ancestors.elements();
345                 while (enumeration.hasMoreElements()) {
346                     resolver.release((Source) enumeration.nextElement());
347                 }
348             }
349         }
350     }
351
352     /**
353      * Creates a stack containing the ancestors of a traversable source up to
354      * specific parent matching the root pattern.
355      *
356      * @param source the traversable source whose ancestors shall be retrieved
357      * @return a Stack containing the ancestors.
358      */

359     protected Stack JavaDoc getAncestors(TraversableSource source) throws IOException JavaDoc {
360         TraversableSource parent = source;
361         Stack JavaDoc ancestors = new Stack JavaDoc();
362
363         while ((parent != null) && !isRoot(parent)) {
364             parent = (TraversableSource) parent.getParent();
365             if (parent != null) {
366                 ancestors.push(parent);
367             } else {
368                 // no ancestor matched the root pattern
369
ancestors.clear();
370             }
371         }
372
373         return ancestors;
374     }
375
376     /**
377      * Adds recursively the path from the source matched by the root pattern
378      * down to the requested source.
379      *
380      * @param source the requested source.
381      * @param ancestors the stack of the ancestors.
382      * @throws SAXException
383      * @throws ProcessingException
384      */

385     protected void addAncestorPath(TraversableSource source, Stack JavaDoc ancestors)
386     throws SAXException JavaDoc, ProcessingException {
387         if (ancestors.empty()) {
388             this.isRequestedSource = true;
389             addPath(source, depth);
390         } else {
391             startNode(COL_NODE_NAME, (TraversableSource) ancestors.pop());
392             addAncestorPath(source, ancestors);
393             endNode(COL_NODE_NAME);
394         }
395     }
396
397     /**
398      * Adds a single node to the generated document. If the path is a
399      * collection and depth is greater than zero, then recursive calls
400      * are made to add nodes for the collection's children.
401      *
402      * @param source the resource/collection to process
403      * @param depth how deep to scan the collection hierarchy
404      *
405      * @throws SAXException if an error occurs while constructing nodes
406      * @throws ProcessingException if a problem occurs with the source
407      */

408     protected void addPath(TraversableSource source, int depth)
409     throws SAXException JavaDoc, ProcessingException {
410         if (source.isCollection()) {
411             startNode(COL_NODE_NAME, source);
412             addContent(source);
413             if (depth > 0) {
414
415                 Collection JavaDoc contents = null;
416
417                 try {
418                     contents = source.getChildren();
419                     if (sort.equals("name")) {
420                         Arrays.sort(contents.toArray(), new Comparator JavaDoc() {
421                             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
422                                 if (reverse) {
423                                     return ((TraversableSource) o2).getName().compareTo(((TraversableSource) o1).getName());
424                                 }
425                                 return ((TraversableSource) o1).getName().compareTo(((TraversableSource) o2).getName());
426                             }
427                         });
428                     } else if (sort.equals("size")) {
429                         Arrays.sort(contents.toArray(), new Comparator JavaDoc() {
430                             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
431                                 if (reverse) {
432                                     return new Long JavaDoc(((TraversableSource) o2).getContentLength()).compareTo(new Long JavaDoc(((TraversableSource) o1).getContentLength()));
433                                 }
434                                 return new Long JavaDoc(((TraversableSource) o1).getContentLength()).compareTo(new Long JavaDoc(((TraversableSource) o2).getContentLength()));
435                             }
436                         });
437                     } else if (sort.equals("lastmodified")) {
438                         Arrays.sort(contents.toArray(), new Comparator JavaDoc() {
439                             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
440                                 if (reverse) {
441                                     return new Long JavaDoc(((TraversableSource) o2).getLastModified()).compareTo(new Long JavaDoc(((TraversableSource) o1).getLastModified()));
442                                 }
443                                 return new Long JavaDoc(((TraversableSource) o1).getLastModified()).compareTo(new Long JavaDoc(((TraversableSource) o2).getLastModified()));
444                             }
445                         });
446                     } else if (sort.equals("collection")) {
447                         Arrays.sort(contents.toArray(), new Comparator JavaDoc() {
448                             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
449                                 TraversableSource ts1 = (TraversableSource) o1;
450                                 TraversableSource ts2 = (TraversableSource) o2;
451
452                                 if (reverse) {
453                                     if (ts2.isCollection() && !ts1.isCollection())
454                                         return -1;
455                                     if (!ts2.isCollection() && ts1.isCollection())
456                                         return 1;
457                                     return ts2.getName().compareTo(ts1.getName());
458                                 }
459                                 if (ts2.isCollection() && !ts1.isCollection())
460                                     return 1;
461                                 if (!ts2.isCollection() && ts1.isCollection())
462                                     return -1;
463                                 return ts1.getName().compareTo(ts2.getName());
464                             }
465                         });
466                     }
467
468                     for (int i = 0; i < contents.size(); i++) {
469                         if (isIncluded((TraversableSource) contents.toArray()[i]) && !isExcluded((TraversableSource) contents.toArray()[i])) {
470                             addPath((TraversableSource) contents.toArray()[i], depth - 1);
471                         }
472                     }
473                 } catch (SourceException e) {
474                     throw new ProcessingException("Error adding paths", e);
475                 } finally {
476                     if (contents != null) {
477                         Iterator JavaDoc iter = contents.iterator();
478                         while (iter.hasNext()) {
479                             resolver.release((Source) iter.next());
480                         }
481                     }
482                 }
483             }
484             endNode(COL_NODE_NAME);
485         } else {
486             if (isIncluded(source) && !isExcluded(source)) {
487                 startNode(RESOURCE_NODE_NAME, source);
488                 addContent(source);
489                 endNode(RESOURCE_NODE_NAME);
490             }
491         }
492     }
493
494     /**
495      * Allow subclasses a chance to generate additional elements within collection and resource
496      * elements.
497      *
498      * @param source the source to generate additional data for.
499      */

500     protected void addContent(TraversableSource source) throws SAXException JavaDoc, ProcessingException {
501     }
502
503     /**
504      * Begins a named node and calls setNodeAttributes to set its attributes.
505      *
506      * @param nodeName the name of the new node
507      * @param source the source a node with its attributes is added for
508      *
509      * @throws SAXException if an error occurs while creating the node
510      */

511     protected void startNode(String JavaDoc nodeName, TraversableSource source)
512     throws SAXException JavaDoc, ProcessingException {
513         if (this.validity != null) {
514             this.validity.addSource(source);
515         }
516         setNodeAttributes(source);
517         super.contentHandler.startElement(URI, nodeName, PREFIX + ':' + nodeName, attributes);
518     }
519
520     /**
521      * Sets the attributes for a given source. For example attributes for the
522      * name, the size and the last modification date of the source are added.
523      *
524      * @param source the source attributes are added for
525      */

526     protected void setNodeAttributes(TraversableSource source)
527     throws SAXException JavaDoc, ProcessingException {
528         long lastModified = source.getLastModified();
529         attributes.clear();
530         attributes.addAttribute("", RES_NAME_ATTR_NAME,RES_NAME_ATTR_NAME,
531                                 "CDATA", source.getName());
532         attributes.addAttribute("", URI_ATTR_NAME,URI_ATTR_NAME,
533                                 "CDATA", source.getURI());
534         attributes.addAttribute("", LASTMOD_ATTR_NAME, LASTMOD_ATTR_NAME,
535                                 "CDATA", Long.toString(source.getLastModified()));
536         attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME,
537                                 "CDATA", dateFormatter.format(new Date JavaDoc(lastModified)));
538         attributes.addAttribute("", SIZE_ATTR_NAME, SIZE_ATTR_NAME,
539                                 "CDATA", Long.toString(source.getContentLength()));
540         if (this.isRequestedSource) {
541             attributes.addAttribute("", "sort", "sort", "CDATA", this.sort);
542             attributes.addAttribute("", "reverse", "reverse", "CDATA",
543                                     String.valueOf(this.reverse));
544             attributes.addAttribute("", "requested", "requested", "CDATA", "true");
545             this.isRequestedSource = false;
546         }
547     }
548
549     /**
550      * Ends the named node.
551      *
552      * @param nodeName the name of the new node
553      *
554      * @throws SAXException if an error occurs while closing the node
555      */

556     protected void endNode(String JavaDoc nodeName) throws SAXException JavaDoc {
557         super.contentHandler.endElement(URI, nodeName, PREFIX + ':' + nodeName);
558     }
559
560     /**
561      * Determines if a given source is the defined root.
562      *
563      * @param source the source to check
564      *
565      * @return true if the source is the root or the root pattern is not set,
566      * false otherwise.
567      */

568     protected boolean isRoot(TraversableSource source) {
569         return this.rootRE == null ? true : this.rootRE.match(source.getName());
570     }
571
572     /**
573      * Determines if a given source shall be visible.
574      *
575      * @param source the source to check
576      *
577      * @return true if the source shall be visible or the include Pattern is not set,
578      * false otherwise.
579      */

580     protected boolean isIncluded(TraversableSource source) {
581         return this.includeRE == null ? true : this.includeRE.match(source.getName());
582     }
583
584     /**
585      * Determines if a given source shall be excluded from viewing.
586      *
587      * @param source the source to check
588      *
589      * @return false if the given source shall not be excluded or the exclude Pattern is not set,
590      * true otherwise.
591      */

592     protected boolean isExcluded(TraversableSource source) {
593         return this.excludeRE == null ? false : this.excludeRE.match(source.getName());
594     }
595
596     /**
597      * Recycle resources
598      */

599     public void recycle() {
600         this.cacheKeyParList = null;
601         this.attributes = null;
602         this.dateFormatter = null;
603         this.rootRE = null;
604         this.includeRE = null;
605         this.excludeRE = null;
606         this.validity = null;
607         super.recycle();
608     }
609 }
610
Popular Tags