1 16 package org.apache.cocoon.transformation; 17 18 import java.io.BufferedReader ; 19 import java.io.IOException ; 20 import java.io.InputStream ; 21 import java.io.InputStreamReader ; 22 import java.io.Reader ; 23 import java.io.Serializable ; 24 import java.net.MalformedURLException ; 25 import java.util.Map ; 26 27 import org.apache.avalon.framework.CascadingException; 28 import org.apache.avalon.framework.CascadingRuntimeException; 29 import org.apache.avalon.framework.parameters.Parameters; 30 import org.apache.avalon.framework.service.ServiceManager; 31 import org.apache.avalon.framework.service.Serviceable; 32 import org.apache.cocoon.ProcessingException; 33 import org.apache.cocoon.ResourceNotFoundException; 34 import org.apache.cocoon.caching.CacheableProcessingComponent; 35 import org.apache.cocoon.components.source.SourceUtil; 36 import org.apache.cocoon.components.source.impl.MultiSourceValidity; 37 import org.apache.cocoon.components.xpointer.XPointer; 38 import org.apache.cocoon.components.xpointer.XPointerContext; 39 import org.apache.cocoon.components.xpointer.parser.ParseException; 40 import org.apache.cocoon.components.xpointer.parser.XPointerFrameworkParser; 41 import org.apache.cocoon.environment.SourceResolver; 42 import org.apache.cocoon.util.NetUtils; 43 import org.apache.cocoon.xml.AbstractXMLPipe; 44 import org.apache.cocoon.xml.IncludeXMLConsumer; 45 import org.apache.cocoon.xml.XMLBaseSupport; 46 import org.apache.cocoon.xml.XMLConsumer; 47 import org.apache.excalibur.source.Source; 48 import org.apache.excalibur.source.SourceException; 49 import org.apache.excalibur.source.SourceValidity; 50 import org.xml.sax.Attributes ; 51 import org.xml.sax.ContentHandler ; 52 import org.xml.sax.Locator ; 53 import org.xml.sax.SAXException ; 54 import org.xml.sax.ext.LexicalHandler ; 55 56 73 public class XIncludeTransformer extends AbstractTransformer implements Serviceable, CacheableProcessingComponent { 74 protected SourceResolver resolver; 75 protected ServiceManager manager; 76 private XIncludePipe xIncludePipe; 77 78 81 public static final String XMLBASE_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace"; 82 public static final String XMLBASE_ATTRIBUTE = "base"; 83 84 public static final String XINCLUDE_NAMESPACE_URI = "http://www.w3.org/2001/XInclude"; 85 public static final String XINCLUDE_INCLUDE_ELEMENT = "include"; 86 public static final String XINCLUDE_FALLBACK_ELEMENT = "fallback"; 87 public static final String XINCLUDE_INCLUDE_ELEMENT_HREF_ATTRIBUTE = "href"; 88 public static final String XINCLUDE_INCLUDE_ELEMENT_PARSE_ATTRIBUTE = "parse"; 89 90 private static final String XINCLUDE_CACHE_KEY = "XInclude"; 91 92 93 private MultiSourceValidity validity; 94 95 public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters) 96 throws ProcessingException, SAXException , IOException { 97 this.resolver = resolver; 98 this.validity = new MultiSourceValidity(resolver, MultiSourceValidity.CHECK_ALWAYS); 99 this.xIncludePipe = new XIncludePipe(); 100 this.xIncludePipe.enableLogging(getLogger()); 101 this.xIncludePipe.init(null); 102 super.setContentHandler(xIncludePipe); 103 super.setLexicalHandler(xIncludePipe); 104 } 105 106 public void setConsumer(XMLConsumer consumer) { 107 xIncludePipe.setConsumer(consumer); 108 } 109 110 public void setContentHandler(ContentHandler handler) { 111 xIncludePipe.setContentHandler(handler); 112 } 113 114 public void setLexicalHandler(LexicalHandler handler) { 115 xIncludePipe.setLexicalHandler(handler); 116 } 117 118 public void service(ServiceManager manager) { 119 this.manager = manager; 120 } 121 122 123 public Serializable getKey() { 124 return XINCLUDE_CACHE_KEY; 125 } 126 127 128 public SourceValidity getValidity() { 129 return this.validity; 130 } 131 132 public void recycle() 133 { 134 this.resolver = null; 136 this.validity = null; 137 this.xIncludePipe = null; 138 super.recycle(); 139 } 140 141 145 private class XIncludePipe extends AbstractXMLPipe { 146 147 private XMLBaseSupport xmlBaseSupport; 148 149 private int xIncludeLevel = 0; 150 151 private boolean useFallback = false; 152 153 private int fallbackLevel; 154 155 private Exception fallBackException; 156 160 private Locator locator; 161 165 private String href; 166 private XIncludePipe parent; 167 168 public void init(String uri) { 169 this.href = uri; 170 this.xmlBaseSupport = new XMLBaseSupport(resolver, getLogger()); 171 } 172 173 public void setParent(XIncludePipe parent) { 174 this.parent = parent; 175 } 176 177 public XIncludePipe getParent() { 178 return parent; 179 } 180 181 public String getHref() { 182 return href; 183 } 184 185 public void endDocument() throws SAXException { 186 validity.close(); 188 super.endDocument(); 189 } 190 191 public void startElement(String uri, String name, String raw, Attributes attr) throws SAXException { 192 if (xIncludeLevel == 1 && useFallback && XINCLUDE_NAMESPACE_URI.equals(uri) && XINCLUDE_FALLBACK_ELEMENT.equals(name)) { 193 fallbackLevel++; 194 195 useFallback = false; 197 fallBackException = null; 198 199 return; 200 } else if (xIncludeLevel > 0 && fallbackLevel < 1) { 201 xIncludeLevel++; 202 return; 203 } else if (xIncludeLevel > 0 && fallbackLevel > 0) { 204 fallbackLevel++; 205 } 206 207 xmlBaseSupport.startElement(uri, name, raw, attr); 208 if (XINCLUDE_NAMESPACE_URI.equals(uri)) { 209 if (XINCLUDE_INCLUDE_ELEMENT.equals(name)) { 210 String href = attr.getValue("",XINCLUDE_INCLUDE_ELEMENT_HREF_ATTRIBUTE); 211 if (href == null) { 212 throw new SAXException (raw + " must have a 'href' attribute at " + getLocation()); 213 } 214 215 String parse = attr.getValue("",XINCLUDE_INCLUDE_ELEMENT_PARSE_ATTRIBUTE); 216 217 if (null == parse) parse="xml"; 218 xIncludeLevel++; 219 220 try { 221 processXIncludeElement(href, parse); 222 } catch (ProcessingException e) { 223 getLogger().debug("Rethrowing exception", e); 224 throw new SAXException (e); 225 } catch (IOException e) { 226 getLogger().debug("Rethrowing exception", e); 227 throw new SAXException (e); 228 } 229 return; 230 } 231 232 throw new SAXException ("Unknown XInclude element " + raw + " at " + getLocation()); 233 234 } else { 235 super.startElement(uri,name,raw,attr); 236 } 237 } 238 239 public void endElement(String uri, String name, String raw) throws SAXException { 240 if (xIncludeLevel > 0 && fallbackLevel < 1) { 241 xIncludeLevel--; 242 if (xIncludeLevel == 0) 243 xmlBaseSupport.endElement(uri, name, raw); 244 if (xIncludeLevel == 0 && useFallback) { 245 useFallback = false; 247 Exception localFallBackException = fallBackException; 248 fallBackException = null; 249 fallbackLevel = 0; 250 getLogger().error("Exception occured during xinclude processing, and did not find a fallback element.", localFallBackException); 251 throw new SAXException ("Exception occured during xinclude processing, and did not find a fallback element.", localFallBackException); 252 } 253 return; 254 } 255 256 if (fallbackLevel > 0) { 257 fallbackLevel--; 258 if (fallbackLevel == 0) 259 return; 260 } 261 262 xmlBaseSupport.endElement(uri, name, raw); 263 super.endElement(uri,name,raw); 264 } 265 266 public void startPrefixMapping(String prefix, String uri) 267 throws SAXException { 268 if (xIncludeLevel > 0 && fallbackLevel < 1) 269 return; 270 super.startPrefixMapping(prefix, uri); 271 } 272 273 public void endPrefixMapping(String prefix) 274 throws SAXException { 275 if (xIncludeLevel > 0 && fallbackLevel < 1) 276 return; 277 super.endPrefixMapping(prefix); 278 } 279 280 public void characters(char c[], int start, int len) 281 throws SAXException { 282 if (xIncludeLevel > 0 && fallbackLevel < 1) 283 return; 284 super.characters(c, start, len); 285 } 286 287 public void ignorableWhitespace(char c[], int start, int len) 288 throws SAXException { 289 if (xIncludeLevel > 0 && fallbackLevel < 1) 290 return; 291 super.ignorableWhitespace(c, start, len); 292 } 293 294 public void processingInstruction(String target, String data) 295 throws SAXException { 296 if (xIncludeLevel > 0 && fallbackLevel < 1) 297 return; 298 super.processingInstruction(target, data); 299 } 300 301 public void skippedEntity(String name) 302 throws SAXException { 303 if (xIncludeLevel > 0 && fallbackLevel < 1) 304 return; 305 super.skippedEntity(name); 306 } 307 308 public void startEntity(String name) 309 throws SAXException { 310 if (xIncludeLevel > 0 && fallbackLevel < 1) 311 return; 312 super.startEntity(name); 313 } 314 315 public void endEntity(String name) 316 throws SAXException { 317 if (xIncludeLevel > 0 && fallbackLevel < 1) 318 return; 319 super.endEntity(name); 320 } 321 322 public void startCDATA() 323 throws SAXException { 324 if (xIncludeLevel > 0 && fallbackLevel < 1) 325 return; 326 super.startCDATA(); 327 } 328 329 public void endCDATA() 330 throws SAXException { 331 if (xIncludeLevel > 0 && fallbackLevel < 1) 332 return; 333 super.endCDATA(); 334 } 335 336 public void comment(char ch[], int start, int len) 337 throws SAXException { 338 if (xIncludeLevel > 0 && fallbackLevel < 1) 339 return; 340 super.comment(ch, start, len); 341 } 342 343 public void setDocumentLocator(Locator locator) { 344 try { 345 if (getLogger().isDebugEnabled()) { 346 getLogger().debug("setDocumentLocator called " + locator.getSystemId()); 347 } 348 349 if (locator.getSystemId() != null) { 351 Source source = resolver.resolveURI(locator.getSystemId()); 352 try { 353 xmlBaseSupport.setDocumentLocation(source.getURI()); 354 if (href == null) 357 href = source.getURI(); 358 } finally { 359 resolver.release(source); 360 } 361 } 362 } catch (Exception e) { 363 throw new CascadingRuntimeException("Error in XIncludeTransformer while trying to resolve base URL for document", e); 364 } 365 this.locator = locator; 366 super.setDocumentLocator(locator); 367 } 368 369 protected void processXIncludeElement(String href, String parse) 370 throws SAXException ,ProcessingException,IOException { 371 if (getLogger().isDebugEnabled()) { 372 getLogger().debug("Processing XInclude element: HREF="+href+", parse="+parse); 373 } 374 375 Source url = null; 376 String suffix = ""; 377 try { 378 int fragmentIdentifierPos = href.indexOf('#'); 379 if (fragmentIdentifierPos != -1) { 380 suffix = href.substring(fragmentIdentifierPos + 1); 381 href = href.substring(0, fragmentIdentifierPos); 382 } 383 384 if (href.equals("")) { 386 if (this.href == null) 387 throw new SAXException ("XIncludeTransformer: encountered empty href (= href pointing to the current document) but the location of the current document is unknown."); 388 int fragmentIdentifierPos2 = this.href.indexOf('#'); 389 if (fragmentIdentifierPos2 != -1) 390 href = this.href.substring(0, fragmentIdentifierPos2); 391 else 392 href = this.href; 393 } 394 395 url = xmlBaseSupport.makeAbsolute(href); 396 if (getLogger().isDebugEnabled()) { 397 getLogger().debug("URL: " + url.getURI() + "\nSuffix: " + suffix); 398 } 399 400 validity.addSource(url); 402 403 String canonicURI = url.getURI() + (suffix.length() > 0 ? "#" + suffix: ""); 405 if (isLoopInclusion(canonicURI)) 406 throw new ProcessingException("Detected loop inclusion of " + canonicURI); 407 408 if (parse.equals("text")) { 409 getLogger().debug("Parse type is text"); 410 InputStream input = url.getInputStream(); 411 Reader reader = new BufferedReader (new InputStreamReader (input)); 412 int read; 413 char ary[] = new char[1024]; 414 if (reader != null) { 415 while ((read = reader.read(ary)) != -1) { 416 super.characters(ary,0,read); 417 } 418 reader.close(); 419 } 420 } else if (parse.equals("xml")) { 421 XIncludePipe subPipe = new XIncludePipe(); 422 subPipe.enableLogging(getLogger()); 423 subPipe.init(canonicURI); 424 subPipe.setConsumer(xmlConsumer); 425 subPipe.setParent(this); 426 427 getLogger().debug("Parse type is XML"); 428 try { 429 if (suffix.length() > 0) { 430 XPointer xpointer; 431 xpointer = XPointerFrameworkParser.parse(NetUtils.decodePath(suffix)); 432 XPointerContext context = new XPointerContext(suffix, url, subPipe, getLogger(), manager); 433 xpointer.process(context); 434 } else { 435 SourceUtil.toSAX(url, new IncludeXMLConsumer(subPipe)); 436 } 437 if (locator != null) 439 xmlConsumer.setDocumentLocator(locator); 440 } catch (ResourceNotFoundException e) { 441 useFallback = true; 442 fallBackException = new CascadingException("Resource not found: " + url.getURI()); 443 getLogger().error("xIncluded resource not found: " + url.getURI(), e); 444 } catch (ParseException e) { 445 useFallback = true; 447 fallBackException = new CascadingException("Error parsing xPointer expression", e); 448 fallBackException.fillInStackTrace(); 449 getLogger().error("Error parsing XPointer expression, will try to use fallback.", e); 450 } catch(SAXException e) { 451 getLogger().error("Error in processXIncludeElement", e); 452 throw e; 453 } catch(ProcessingException e) { 454 getLogger().error("Error in processXIncludeElement", e); 455 throw e; 456 } catch(MalformedURLException e) { 457 useFallback = true; 458 fallBackException = e; 459 getLogger().error("Error processing an xInclude, will try to use fallback.", e); 460 } catch(IOException e) { 461 useFallback = true; 462 fallBackException = e; 463 getLogger().error("Error processing an xInclude, will try to use fallback.", e); 464 } 465 } 466 } catch (SourceException se) { 467 throw SourceUtil.handle(se); 468 } finally { 469 if (url != null) { 470 resolver.release(url); 471 } 472 } 473 } 474 475 public boolean isLoopInclusion(String uri) { 476 if (uri.equals(this.href)) { 477 return true; 478 } 479 480 XIncludePipe parent = getParent(); 481 while (parent != null) { 482 if (uri.equals(parent.getHref())) { 483 return true; 484 } 485 parent = parent.getParent(); 486 } 487 return false; 488 } 489 490 private String getLocation() { 491 if (this.locator == null) { 492 return "unknown location"; 493 } else { 494 return this.locator.getSystemId() + ":" + this.locator.getColumnNumber() + ":" + this.locator.getLineNumber(); 495 } 496 } 497 } 498 } 499 | Popular Tags |