1 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 ; 32 import org.xml.sax.helpers.AttributesImpl ; 33 34 import java.io.IOException ; 35 import java.io.Serializable ; 36 import java.text.SimpleDateFormat ; 37 import java.util.Locale ; 38 import java.util.ArrayList ; 39 import java.util.Collection ; 40 import java.util.Date ; 41 import java.util.Enumeration ; 42 import java.util.Iterator ; 43 import java.util.List ; 44 import java.util.Map ; 45 import java.util.Stack ; 46 import java.util.Arrays ; 47 import java.util.Comparator ; 48 import java.util.TimeZone ; 49 50 106 public class TraversableGenerator extends ServiceableGenerator 107 implements CacheableProcessingComponent { 108 109 110 protected static final String URI = "http://apache.org/cocoon/collection/1.0"; 111 112 113 protected static final String PREFIX = "collection"; 114 115 116 protected static final String COL_NODE_NAME = "collection"; 117 protected static final String RESOURCE_NODE_NAME = "resource"; 118 119 protected static final String RES_NAME_ATTR_NAME = "name"; 120 protected static final String URI_ATTR_NAME = "uri"; 121 protected static final String LASTMOD_ATTR_NAME = "lastModified"; 122 protected static final String DATE_ATTR_NAME = "date"; 123 protected static final String SIZE_ATTR_NAME = "size"; 124 125 126 protected MultiSourceValidity validity; 127 128 131 protected AttributesImpl attributes; 132 133 140 protected List cacheKeyParList; 141 142 145 protected int depth; 146 147 152 protected SimpleDateFormat dateFormatter; 153 154 155 protected long refreshDelay; 156 157 163 protected String sort; 164 165 166 protected boolean reverse; 167 168 169 protected RE rootRE; 170 171 172 protected RE includeRE; 173 174 175 protected RE excludeRE; 176 177 181 protected boolean isRequestedSource; 182 183 192 public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) 193 throws ProcessingException, SAXException , IOException { 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 (); 200 this.cacheKeyParList.add(src); 201 202 this.depth = par.getParameterAsInteger("depth", 1); 203 this.cacheKeyParList.add(String.valueOf(this.depth)); 204 205 String dateFormatString = par.getParameter("dateFormat", null); 206 this.cacheKeyParList.add(dateFormatString); 207 if (dateFormatString != null) { 208 String locale = par.getParameter("locale", null); 209 if (locale != null) { 210 this.dateFormatter = new SimpleDateFormat (dateFormatString, new Locale (locale, "")); 211 } else { 212 this.dateFormatter = new SimpleDateFormat (dateFormatString); 213 } 214 } else { 215 this.dateFormatter = new SimpleDateFormat (); 216 } 217 218 String 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 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 (); 271 } 272 273 276 public Serializable getKey() { 277 StringBuffer buffer = new StringBuffer (); 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 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 308 public void generate() throws SAXException , ProcessingException { 309 Source src = null; 310 Stack 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 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 enumeration = ancestors.elements(); 345 while (enumeration.hasMoreElements()) { 346 resolver.release((Source) enumeration.nextElement()); 347 } 348 } 349 } 350 } 351 352 359 protected Stack getAncestors(TraversableSource source) throws IOException { 360 TraversableSource parent = source; 361 Stack ancestors = new Stack (); 362 363 while ((parent != null) && !isRoot(parent)) { 364 parent = (TraversableSource) parent.getParent(); 365 if (parent != null) { 366 ancestors.push(parent); 367 } else { 368 ancestors.clear(); 370 } 371 } 372 373 return ancestors; 374 } 375 376 385 protected void addAncestorPath(TraversableSource source, Stack ancestors) 386 throws SAXException , 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 408 protected void addPath(TraversableSource source, int depth) 409 throws SAXException , ProcessingException { 410 if (source.isCollection()) { 411 startNode(COL_NODE_NAME, source); 412 addContent(source); 413 if (depth > 0) { 414 415 Collection contents = null; 416 417 try { 418 contents = source.getChildren(); 419 if (sort.equals("name")) { 420 Arrays.sort(contents.toArray(), new Comparator () { 421 public int compare(Object o1, Object 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 () { 430 public int compare(Object o1, Object o2) { 431 if (reverse) { 432 return new Long (((TraversableSource) o2).getContentLength()).compareTo(new Long (((TraversableSource) o1).getContentLength())); 433 } 434 return new Long (((TraversableSource) o1).getContentLength()).compareTo(new Long (((TraversableSource) o2).getContentLength())); 435 } 436 }); 437 } else if (sort.equals("lastmodified")) { 438 Arrays.sort(contents.toArray(), new Comparator () { 439 public int compare(Object o1, Object o2) { 440 if (reverse) { 441 return new Long (((TraversableSource) o2).getLastModified()).compareTo(new Long (((TraversableSource) o1).getLastModified())); 442 } 443 return new Long (((TraversableSource) o1).getLastModified()).compareTo(new Long (((TraversableSource) o2).getLastModified())); 444 } 445 }); 446 } else if (sort.equals("collection")) { 447 Arrays.sort(contents.toArray(), new Comparator () { 448 public int compare(Object o1, Object 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 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 500 protected void addContent(TraversableSource source) throws SAXException , ProcessingException { 501 } 502 503 511 protected void startNode(String nodeName, TraversableSource source) 512 throws SAXException , 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 526 protected void setNodeAttributes(TraversableSource source) 527 throws SAXException , 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 (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 556 protected void endNode(String nodeName) throws SAXException { 557 super.contentHandler.endElement(URI, nodeName, PREFIX + ':' + nodeName); 558 } 559 560 568 protected boolean isRoot(TraversableSource source) { 569 return this.rootRE == null ? true : this.rootRE.match(source.getName()); 570 } 571 572 580 protected boolean isIncluded(TraversableSource source) { 581 return this.includeRE == null ? true : this.includeRE.match(source.getName()); 582 } 583 584 592 protected boolean isExcluded(TraversableSource source) { 593 return this.excludeRE == null ? false : this.excludeRE.match(source.getName()); 594 } 595 596 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 |