001/* 002 * Copyright 2013 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.cocoon; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.InputStream; 021import java.lang.reflect.Method; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028 029import javax.xml.transform.Result; 030import javax.xml.transform.Templates; 031import javax.xml.transform.Transformer; 032import javax.xml.transform.TransformerConfigurationException; 033import javax.xml.transform.TransformerException; 034import javax.xml.transform.TransformerFactory; 035import javax.xml.transform.URIResolver; 036import javax.xml.transform.sax.SAXTransformerFactory; 037import javax.xml.transform.sax.TemplatesHandler; 038import javax.xml.transform.sax.TransformerHandler; 039import javax.xml.transform.stream.StreamSource; 040 041import org.apache.avalon.framework.activity.Disposable; 042import org.apache.avalon.framework.activity.Initializable; 043import org.apache.avalon.framework.context.Context; 044import org.apache.avalon.framework.context.ContextException; 045import org.apache.avalon.framework.context.Contextualizable; 046import org.apache.avalon.framework.logger.AbstractLogEnabled; 047import org.apache.avalon.framework.parameters.ParameterException; 048import org.apache.avalon.framework.parameters.Parameterizable; 049import org.apache.avalon.framework.parameters.Parameters; 050import org.apache.avalon.framework.service.ServiceException; 051import org.apache.avalon.framework.service.ServiceManager; 052import org.apache.avalon.framework.service.Serviceable; 053import org.apache.cocoon.components.ContextHelper; 054import org.apache.cocoon.components.xslt.TraxErrorListener; 055import org.apache.cocoon.environment.Request; 056import org.apache.commons.lang.BooleanUtils; 057import org.apache.commons.lang3.tuple.Pair; 058import org.apache.excalibur.source.Source; 059import org.apache.excalibur.source.SourceException; 060import org.apache.excalibur.source.SourceResolver; 061import org.apache.excalibur.source.SourceValidity; 062import org.apache.excalibur.xml.sax.XMLizable; 063import org.apache.excalibur.xml.xslt.XSLTProcessor; 064import org.apache.excalibur.xml.xslt.XSLTProcessorException; 065import org.apache.excalibur.xmlizer.XMLizer; 066import org.xml.sax.ContentHandler; 067import org.xml.sax.InputSource; 068import org.xml.sax.SAXException; 069import org.xml.sax.XMLFilter; 070 071/** 072 * Adaptation of Excalibur's XSLTProcessor implementation to allow for better error reporting. This implementation is also threadsafe.<br> 073 * It also handles a {@link Templates} cache, for performance purpose. 074 */ 075public class ThreadSafeTraxProcessor extends AbstractLogEnabled implements XSLTProcessor, Serviceable, Initializable, Disposable, Parameterizable, URIResolver, Contextualizable 076{ 077 private static final String __URI_CACHE_ATTR = "cache.xslt.resolvedURIs"; 078 079 /** The configured transformer factory to use */ 080 private String _transformerFactory; 081 082 /** The trax TransformerFactory this component uses */ 083 private SAXTransformerFactory _factory; 084 085 /** Is incremental processing turned on? (default for Xalan: no) */ 086 private boolean _incrementalProcessing; 087 088 /** Resolver used to resolve XSLT document() calls, imports and includes */ 089 private SourceResolver _resolver; 090 091 private XMLizer _xmlizer; 092 093 /** The ServiceManager */ 094 private ServiceManager _manager; 095 096 private Context _context; 097 098 // the XSLT cache 099 private Map<String, Collection<CachedTemplates>> _templatesCache = new HashMap<>(); 100 101 @Override 102 public void contextualize(Context context) throws ContextException 103 { 104 _context = context; 105 } 106 107 /** 108 * Compose. Try to get the store 109 */ 110 public void service(final ServiceManager manager) throws ServiceException 111 { 112 _manager = manager; 113 _xmlizer = (XMLizer) _manager.lookup(XMLizer.ROLE); 114 _resolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE); 115 } 116 117 /** 118 * Initialize 119 */ 120 public void initialize() throws Exception 121 { 122 _factory = _createTransformerFactory(_transformerFactory); 123 } 124 125 /** 126 * Disposable 127 */ 128 public void dispose() 129 { 130 if (null != _manager) 131 { 132 _manager.release(_resolver); 133 _manager.release(_xmlizer); 134 _manager = null; 135 } 136 137 _xmlizer = null; 138 _resolver = null; 139 _templatesCache.clear(); 140 } 141 142 /** 143 * Configure the component 144 */ 145 public void parameterize(final Parameters params) throws ParameterException 146 { 147 _incrementalProcessing = params.getParameterAsBoolean("incremental-processing", this._incrementalProcessing); 148 _transformerFactory = params.getParameter("transformer-factory", null); 149 } 150 151 public void setTransformerFactory(final String classname) 152 { 153 throw new UnsupportedOperationException("This implementation is threadsafe, so the TransformerFactory cannot be changed"); 154 } 155 156 public TransformerHandler getTransformerHandler(final Source stylesheet) throws XSLTProcessorException 157 { 158 return getTransformerHandler(stylesheet, null); 159 } 160 161 public TransformerHandler getTransformerHandler(final Source stylesheet, final XMLFilter filter) throws XSLTProcessorException 162 { 163 final XSLTProcessor.TransformerHandlerAndValidity validity = getTransformerHandlerAndValidity(stylesheet, filter); 164 return validity.getTransfomerHandler(); 165 } 166 167 public TransformerHandlerAndValidity getTransformerHandlerAndValidity(final Source stylesheet) throws XSLTProcessorException 168 { 169 return getTransformerHandlerAndValidity(stylesheet, null); 170 } 171 172 public TransformerHandlerAndValidity getTransformerHandlerAndValidity(Source stylesheet, XMLFilter filter) throws XSLTProcessorException 173 { 174 TraxErrorListener errorListener = new TraxErrorListener(getLogger(), stylesheet.getURI()); 175 try 176 { 177 // get Templates from cache or create it 178 Pair<Templates, CacheValidity> templates = _getTemplates(stylesheet, filter); 179 180 // Create transformer handler 181 TransformerHandler handler = _factory.newTransformerHandler(templates.getLeft()); 182 handler.getTransformer().setErrorListener(errorListener); 183 handler.getTransformer().setURIResolver(this); 184 185 // Create result 186 SourceValidity validity = stylesheet.getValidity(); 187 TransformerHandlerAndValidity handlerAndValidity = new ExtendedTransformerHandlerAndValidity(handler, validity, templates.getRight()); 188 189 return handlerAndValidity; 190 } 191 catch (IOException e) 192 { 193 throw new XSLTProcessorException("Exception when getting Templates for " + stylesheet.getURI(), e); 194 } 195 catch (TransformerConfigurationException e) 196 { 197 Throwable realEx = errorListener.getThrowable(); 198 if (realEx == null) 199 { 200 realEx = e; 201 } 202 203 if (realEx instanceof RuntimeException) 204 { 205 throw (RuntimeException) realEx; 206 } 207 208 if (realEx instanceof XSLTProcessorException) 209 { 210 throw (XSLTProcessorException) realEx; 211 } 212 213 throw new XSLTProcessorException("Exception when creating Transformer from " + stylesheet.getURI(), realEx); 214 } 215 } 216 217 private Pair<Templates, CacheValidity> _getTemplates(Source stylesheet, XMLFilter filter) throws XSLTProcessorException, IOException 218 { 219 String uri = stylesheet.getURI().intern(); 220 long lastModified = stylesheet.getLastModified(); 221 222 // synchronize on XSL name to avoid concurrent write access to the cache for a given stylesheet 223 synchronized (uri) 224 { 225 // cache not used, mainly in case of root "cocoon://" stylesheets 226 if (lastModified <= 0) 227 { 228 SAXTransformerFactory factory = _createTransformerFactory(_transformerFactory); 229 factory.setURIResolver(this); 230 231 return Pair.of(_createTemplates(factory, stylesheet, filter), CacheValidity.NON_CACHED); 232 } 233 234 Collection<CachedTemplates> cachedTemplatesForUri = _templatesCache.get(uri); 235 CacheValidity validity = CacheValidity.NON_CACHED; 236 237 if (cachedTemplatesForUri != null) 238 { 239 Pair<CachedTemplates, CacheValidity> result = _getCachedTemplates(cachedTemplatesForUri, stylesheet, _getResolutionCache()); 240 validity = result.getRight(); 241 242 if (validity == CacheValidity.CACHED && result.getLeft() != null) 243 { 244 if (getLogger().isDebugEnabled()) 245 { 246 getLogger().debug("Found Templates in cache for stylesheet : " + uri); 247 } 248 249 return Pair.of(result.getLeft().getTemplates(), validity); 250 } 251 } 252 else 253 { 254 cachedTemplatesForUri = new ArrayList<>(); 255 _templatesCache.put(uri, cachedTemplatesForUri); 256 } 257 258 CachedTemplates cachedTemplates = new CachedTemplates(lastModified); 259 260 SAXTransformerFactory factory = _createTransformerFactory(_transformerFactory); 261 factory.setURIResolver(cachedTemplates); 262 263 Templates templates = _createTemplates(factory, stylesheet, filter); 264 265 if (getLogger().isDebugEnabled()) 266 { 267 String[] rawURIs = cachedTemplates.getRawURIs(); 268 String[] resolvedURIs = cachedTemplates.getResolvedURIs(); 269 270 StringBuilder sb = new StringBuilder("Templates created for stylesheet : "); 271 sb.append(uri); 272 sb.append(" including the following stylesheets : "); 273 for (int i = 0; i < rawURIs.length; i++) 274 { 275 sb.append('\n'); 276 sb.append(rawURIs[i]); 277 sb.append(" => "); 278 sb.append(resolvedURIs[i]); 279 } 280 281 getLogger().debug(sb.toString()); 282 } 283 284 cachedTemplates.setTemplates(templates); 285 cachedTemplatesForUri.add(cachedTemplates); 286 287 return Pair.of(templates, validity); 288 } 289 } 290 291 private Pair<CachedTemplates, CacheValidity> _getCachedTemplates(Collection<CachedTemplates> cachedTemplates, Source stylesheet, Map<UnresolvedURI, ResolvedURI> resolutionCache) throws IOException 292 { 293 CachedTemplates outOfDateTemplates = null; 294 295 Iterator<CachedTemplates> it = cachedTemplates.iterator(); 296 297 while (outOfDateTemplates == null && it.hasNext()) 298 { 299 CachedTemplates templates = it.next(); 300 301 CacheValidity validity = _isValid(templates, stylesheet, resolutionCache); 302 if (validity == CacheValidity.CACHED) 303 { 304 return Pair.of(templates, CacheValidity.CACHED); 305 } 306 307 if (validity == CacheValidity.OUT_OF_DATE) 308 { 309 outOfDateTemplates = templates; 310 } 311 } 312 313 if (outOfDateTemplates != null) 314 { 315 cachedTemplates.remove(outOfDateTemplates); 316 return Pair.of(null, CacheValidity.OUT_OF_DATE); 317 } 318 319 return Pair.of(null, CacheValidity.NON_CACHED); 320 } 321 322 @SuppressWarnings("unchecked") 323 private Map<UnresolvedURI, ResolvedURI> _getResolutionCache() 324 { 325 Request request = null; 326 try 327 { 328 request = ContextHelper.getRequest(_context); 329 } 330 catch (Exception e) 331 { 332 // ignore, there's simply no current request 333 } 334 335 // very simple cache for storing raw/resolved URI pairs to avoid unnecessary calls to SourceResolver 336 Map<UnresolvedURI, ResolvedURI> resolutionCache = null; 337 if (request != null) 338 { 339 resolutionCache = (Map<UnresolvedURI, ResolvedURI>) request.getAttribute(__URI_CACHE_ATTR); 340 if (resolutionCache == null) 341 { 342 resolutionCache = new HashMap<>(); 343 request.setAttribute(__URI_CACHE_ATTR, resolutionCache); 344 } 345 } 346 else 347 { 348 resolutionCache = new HashMap<>(); 349 } 350 351 return resolutionCache; 352 } 353 354 private CacheValidity _isValid(CachedTemplates templates, Source stylesheet, Map<UnresolvedURI, ResolvedURI> resolutionCache) throws IOException 355 { 356 // the current Templates object is valid if and only if the resolution of raw URIs correspond to stored resolved URIs 357 String[] rawURIs = templates.getRawURIs(); 358 String[] baseURIs = templates.getBaseURIs(); 359 String[] resolvedURIs = templates.getResolvedURIs(); 360 Long[] timestamps = templates.getTimestamps(); 361 362 if (templates.getLastModified() != stylesheet.getLastModified()) 363 { 364 return CacheValidity.OUT_OF_DATE; 365 } 366 367 boolean outOfDate = false; 368 for (int i = 0; i < rawURIs.length; i++) 369 { 370 // small optimization in the case where the same resolution has already been requested in the current context 371 UnresolvedURI unresolved = new UnresolvedURI(rawURIs[i], baseURIs[i]); 372 ResolvedURI resolved = resolutionCache.get(new UnresolvedURI(rawURIs[i], baseURIs[i])); 373 374 String resolvedURI; 375 long lastModified; 376 if (resolved != null) 377 { 378 resolvedURI = resolved._resolvedURI; 379 lastModified = resolved._timestamp; 380 } 381 else 382 { 383 Source src = _resolve(rawURIs[i], baseURIs[i]); 384 resolvedURI = src.getURI(); 385 lastModified = src.getLastModified(); 386 resolutionCache.put(unresolved, new ResolvedURI(resolvedURI, lastModified)); 387 } 388 389 if (!resolvedURI.equals(resolvedURIs[i])) 390 { 391 return null; 392 } 393 394 if (lastModified == 0 || timestamps[i] == 0 || lastModified != timestamps[i]) 395 { 396 outOfDate = true; 397 } 398 } 399 400 return outOfDate ? CacheValidity.OUT_OF_DATE : CacheValidity.CACHED; 401 } 402 403 @SuppressWarnings("unchecked") 404 private Templates _createTemplates(SAXTransformerFactory factory, Source stylesheet, XMLFilter filter) throws XSLTProcessorException 405 { 406 String id = stylesheet.getURI(); 407 TraxErrorListener errorListener = new TraxErrorListener(getLogger(), id); 408 409 try 410 { 411 if (getLogger().isDebugEnabled()) 412 { 413 getLogger().debug("Creating new Templates for " + id); 414 } 415 416 factory.setErrorListener(errorListener); 417 418 // Create a Templates ContentHandler to handle parsing of the 419 // stylesheet. 420 TemplatesHandler templatesHandler = factory.newTemplatesHandler(); 421 422 // Set the system ID for the template handler since some 423 // TrAX implementations (XSLTC) rely on this in order to obtain 424 // a meaningful identifier for the Templates instances. 425 templatesHandler.setSystemId(id); 426 if (filter != null) 427 { 428 filter.setContentHandler(templatesHandler); 429 } 430 431 if (getLogger().isDebugEnabled()) 432 { 433 getLogger().debug("Source = " + stylesheet + ", templatesHandler = " + templatesHandler); 434 } 435 436 // Process the stylesheet. 437 _sourceToSAX(stylesheet, filter != null ? (ContentHandler) filter : (ContentHandler) templatesHandler); 438 439 // Get the Templates object (generated during the parsing of 440 // the stylesheet) from the TemplatesHandler. 441 final Templates templates = templatesHandler.getTemplates(); 442 443 if (null == templates) 444 { 445 throw new XSLTProcessorException("Unable to create templates for stylesheet: " + stylesheet.getURI()); 446 } 447 448 // Must set base for Xalan stylesheet. 449 // Otherwise document('') in logicsheet causes NPE. 450 Class clazz = templates.getClass(); 451 if (clazz.getName().equals("org.apache.xalan.templates.StylesheetRoot")) 452 { 453 Method method = clazz.getMethod("setHref", new Class[] {String.class}); 454 method.invoke(templates, new Object[] {id}); 455 } 456 457 return templates; 458 } 459 catch (Exception e) 460 { 461 Throwable realEx = errorListener.getThrowable(); 462 if (realEx == null) 463 { 464 realEx = e; 465 } 466 467 if (realEx instanceof RuntimeException) 468 { 469 throw (RuntimeException) realEx; 470 } 471 472 if (realEx instanceof XSLTProcessorException) 473 { 474 throw (XSLTProcessorException) realEx; 475 } 476 477 throw new XSLTProcessorException("Exception when creating Transformer from " + stylesheet.getURI(), realEx); 478 } 479 } 480 481 private void _sourceToSAX(Source source, ContentHandler handler) throws SAXException, IOException, SourceException 482 { 483 if (source instanceof XMLizable) 484 { 485 ((XMLizable) source).toSAX(handler); 486 } 487 else 488 { 489 final InputStream inputStream = source.getInputStream(); 490 final String mimeType = source.getMimeType(); 491 final String systemId = source.getURI(); 492 _xmlizer.toSAX(inputStream, mimeType, systemId, handler); 493 } 494 } 495 496 public void transform(final Source source, final Source stylesheet, final Parameters params, final Result result) throws XSLTProcessorException 497 { 498 try 499 { 500 if (getLogger().isDebugEnabled()) 501 { 502 getLogger().debug("Transform source = " + source + ", stylesheet = " + stylesheet + ", parameters = " + params + ", result = " + result); 503 } 504 final TransformerHandler handler = getTransformerHandler(stylesheet); 505 if (params != null) 506 { 507 final Transformer transformer = handler.getTransformer(); 508 transformer.clearParameters(); 509 String[] names = params.getNames(); 510 for (int i = names.length - 1; i >= 0; i--) 511 { 512 transformer.setParameter(names[i], params.getParameter(names[i])); 513 } 514 } 515 516 handler.setResult(result); 517 _sourceToSAX(source, handler); 518 if (getLogger().isDebugEnabled()) 519 { 520 getLogger().debug("Transform done"); 521 } 522 } 523 catch (SAXException e) 524 { 525 // Unwrapping the exception will "remove" the real cause with 526 // never Xalan versions and makes the exception message unusable 527 final String message = "Error in running Transformation"; 528 throw new XSLTProcessorException(message, e); 529 /* 530 * if( e.getException() == null ) { final String message = "Error in running Transformation"; throw new XSLTProcessorException( message, e ); } else { final String message = "Got SAXException. Rethrowing cause exception."; getLogger().debug( message, e ); throw new XSLTProcessorException( "Error in running Transformation", e.getException() ); } 531 */ 532 } 533 catch (Exception e) 534 { 535 final String message = "Error in running Transformation"; 536 throw new XSLTProcessorException(message, e); 537 } 538 } 539 540 /** 541 * Get the TransformerFactory associated with the given classname. If the class can't be found or the given class doesn't implement the required interface, the default factory is returned. 542 * @param factoryName The name of the factory class to create 543 * @return The instance created 544 */ 545 private SAXTransformerFactory _createTransformerFactory(String factoryName) 546 { 547 SAXTransformerFactory saxFactory; 548 549 if (null == factoryName) 550 { 551 saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 552 } 553 else 554 { 555 try 556 { 557 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 558 if (loader == null) 559 { 560 loader = getClass().getClassLoader(); 561 } 562 563 saxFactory = (SAXTransformerFactory) loader.loadClass(factoryName).newInstance(); 564 } 565 catch (ClassNotFoundException cnfe) 566 { 567 getLogger().error("Cannot find the requested TrAX factory '" + factoryName + "'. Using default TrAX Transformer Factory instead."); 568 if (_factory != null) 569 { 570 return _factory; 571 } 572 573 saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 574 } 575 catch (ClassCastException cce) 576 { 577 getLogger().error("The indicated class '" + factoryName + "' is not a TrAX Transformer Factory. Using default TrAX Transformer Factory instead."); 578 if (_factory != null) 579 { 580 return _factory; 581 } 582 583 saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 584 } 585 catch (Exception e) 586 { 587 getLogger().error("Error found loading the requested TrAX Transformer Factory '" + factoryName + "'. Using default TrAX Transformer Factory instead."); 588 if (_factory != null) 589 { 590 return _factory; 591 } 592 593 saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 594 } 595 } 596 597 saxFactory.setErrorListener(new TraxErrorListener(getLogger(), null)); 598 saxFactory.setURIResolver(this); 599 600 if (saxFactory.getClass().getName().equals("org.apache.xalan.processor.TransformerFactoryImpl")) 601 { 602 saxFactory.setAttribute("http://xml.apache.org/xalan/features/incremental", BooleanUtils.toBooleanObject(_incrementalProcessing)); 603 } 604 // SAXON 8 will not report errors unless version warning is set to false. 605 if (saxFactory.getClass().getName().equals("net.sf.saxon.TransformerFactoryImpl")) 606 { 607 saxFactory.setAttribute("http://saxon.sf.net/feature/version-warning", Boolean.FALSE); 608 } 609 610 return saxFactory; 611 } 612 613 /** 614 * Called by the processor when it encounters an xsl:include, xsl:import, or document() function. 615 * 616 * @param href An href attribute, which may be relative or absolute. 617 * @param base The base URI in effect when the href attribute was encountered. 618 * 619 * @return A Source object, or null if the href cannot be resolved, and the processor should try to resolve the URI itself. 620 * 621 * @throws TransformerException if an error occurs when trying to resolve the URI. 622 */ 623 public javax.xml.transform.Source resolve(String href, String base) throws TransformerException 624 { 625 return _resolve(href, base, null, null, null, null); 626 } 627 628 @SuppressWarnings("deprecation") 629 private Source _resolve(String href, String base) throws IOException 630 { 631 Source xslSource = null; 632 633 if (base == null || href.indexOf(":") > 1) 634 { 635 // Null base - href must be an absolute URL 636 xslSource = _resolver.resolveURI(href); 637 } 638 else if (href.length() == 0) 639 { 640 // Empty href resolves to base 641 xslSource = _resolver.resolveURI(base); 642 } 643 else 644 { 645 // is the base a file or a real m_url 646 if (!base.startsWith("file:")) 647 { 648 int lastPathElementPos = base.lastIndexOf('/'); 649 if (lastPathElementPos == -1) 650 { 651 // this should never occur as the base should 652 // always be protocol:/.... 653 return null; // we can't resolve this 654 } 655 else 656 { 657 xslSource = _resolver.resolveURI(base.substring(0, lastPathElementPos) + "/" + href); 658 } 659 } 660 else 661 { 662 File parent = new File(base.substring(5)); 663 File parent2 = new File(parent.getParentFile(), href); 664 xslSource = _resolver.resolveURI(parent2.toURL().toExternalForm()); 665 } 666 } 667 668 return xslSource; 669 } 670 671 javax.xml.transform.Source _resolve(String href, String base, Collection<String> rawURIs, Collection<String> baseURIs, Collection<Long> timestamps, Collection<String> resolvedURIs) 672 { 673 if (getLogger().isDebugEnabled()) 674 { 675 getLogger().debug("resolve(href = " + href + ", base = " + base + "); resolver = " + _resolver); 676 } 677 678 Source xslSource = null; 679 try 680 { 681 xslSource = _resolve(href, base); 682 683 if (rawURIs != null) 684 { 685 rawURIs.add(href); 686 } 687 688 if (baseURIs != null) 689 { 690 baseURIs.add(base); 691 } 692 693 if (timestamps != null) 694 { 695 timestamps.add(xslSource.getLastModified()); 696 } 697 698 if (resolvedURIs != null) 699 { 700 resolvedURIs.add(xslSource.getURI()); 701 } 702 703 InputSource is = _getInputSource(xslSource); 704 705 if (getLogger().isDebugEnabled()) 706 { 707 getLogger().debug("xslSource = " + xslSource + ", system id = " + xslSource.getURI()); 708 } 709 710 return new StreamSource(is.getByteStream(), is.getSystemId()); 711 } 712 catch (IOException ioe) 713 { 714 if (getLogger().isDebugEnabled()) 715 { 716 getLogger().debug("Failed to resolve " + href + "(base = " + base + "), return null", ioe); 717 } 718 719 return null; 720 } 721 finally 722 { 723 _resolver.release(xslSource); 724 } 725 } 726 727 /** 728 * Return a new <code>InputSource</code> object that uses the <code>InputStream</code> and the system ID of the <code>Source</code> object. 729 * @param source The source concerned 730 * @return The input source 731 * @throws IOException if I/O error occurred. 732 * @throws SourceException if an error occurred. 733 */ 734 private InputSource _getInputSource(final Source source) throws IOException, SourceException 735 { 736 final InputSource newObject = new InputSource(source.getInputStream()); 737 newObject.setSystemId(source.getURI()); 738 return newObject; 739 } 740 741 /** 742 * Special {@link org.apache.excalibur.xml.xslt.XSLTProcessor.TransformerHandlerAndValidity} with information about the cache state of the underlying {@link Templates}. 743 */ 744 public static class ExtendedTransformerHandlerAndValidity extends TransformerHandlerAndValidity 745 { 746 CacheValidity _cacheValidity; 747 748 /** 749 * Constructor. 750 * @param handler the {@link TransformerHandler}. 751 * @param validity the {@link SourceValidity}. 752 * @param cacheValidity the cache status. 753 */ 754 public ExtendedTransformerHandlerAndValidity(TransformerHandler handler, SourceValidity validity, CacheValidity cacheValidity) 755 { 756 super(handler, validity); 757 _cacheValidity = cacheValidity; 758 } 759 760 /** 761 * Returns true if the underlying {@link Templates} has been taken from cache. 762 * @return true if taken from cache. 763 */ 764 public boolean isFromCache() 765 { 766 return _cacheValidity == CacheValidity.CACHED; 767 } 768 769 /** 770 * Returns the {@link CacheValidity} associated with the current stylesheet. 771 * @return the {@link CacheValidity}. 772 */ 773 public CacheValidity getValidity() 774 { 775 return _cacheValidity; 776 } 777 } 778 779 // all known Templates for a single input stylesheet 780 private class CachedTemplates implements URIResolver 781 { 782 // root stylesheet timestamp 783 private long _lastModified; 784 785 // non-resolved included/imported URIs for the input stylesheet 786 private List<String> _rawURIs = new ArrayList<>(); 787 788 // base URIs for resolution 789 private List<String> _baseURIs = new ArrayList<>(); 790 791 // resolved URIs 792 private List<String> _resolvedURIs = new ArrayList<>(); 793 794 // last modified timestamps for resolved URIs 795 private List<Long> _timestamps = new ArrayList<>(); 796 797 // resulting templates 798 private Templates _templates; 799 800 CachedTemplates(long lastModified) 801 { 802 _lastModified = lastModified; 803 } 804 805 @Override 806 public javax.xml.transform.Source resolve(String href, String base) throws TransformerException 807 { 808 return _resolve(href, base, _rawURIs, _baseURIs, _timestamps, _resolvedURIs); 809 } 810 811 long getLastModified() 812 { 813 return _lastModified; 814 } 815 816 String[] getRawURIs() 817 { 818 return _rawURIs.toArray(new String[]{}); 819 } 820 821 String[] getBaseURIs() 822 { 823 return _baseURIs.toArray(new String[]{}); 824 } 825 826 Long[] getTimestamps() 827 { 828 return _timestamps.toArray(new Long[]{}); 829 } 830 831 String[] getResolvedURIs() 832 { 833 return _resolvedURIs.toArray(new String[]{}); 834 } 835 836 Templates getTemplates() 837 { 838 return _templates; 839 } 840 841 void setTemplates(Templates templates) 842 { 843 _templates = templates; 844 } 845 } 846 847 private static class UnresolvedURI 848 { 849 String _rawURI; 850 String _baseURI; 851 852 public UnresolvedURI(String rawURI, String baseURI) 853 { 854 _rawURI = rawURI; 855 _baseURI = baseURI; 856 } 857 858 @Override 859 public int hashCode() 860 { 861 if (_rawURI.indexOf(':') > 1) 862 { 863 // rawURI is absolute 864 return _rawURI.hashCode(); 865 } 866 867 int lastPathElementPos = _baseURI.lastIndexOf('/'); 868 if (lastPathElementPos == -1) 869 { 870 // this should never occur as the base should always be protocol:/.... 871 return _rawURI.hashCode(); 872 } 873 else 874 { 875 String uri = _baseURI.substring(0, lastPathElementPos) + "/" + _rawURI; 876 return uri.hashCode(); 877 } 878 } 879 880 @Override 881 public boolean equals(Object obj) 882 { 883 if (!(obj instanceof UnresolvedURI)) 884 { 885 return false; 886 } 887 888 UnresolvedURI unresolved = (UnresolvedURI) obj; 889 890 if (_rawURI.indexOf(':') > 1) 891 { 892 // rawURI is absolute 893 return _rawURI.equals(unresolved._rawURI); 894 } 895 896 int lastPathElementPos = _baseURI.lastIndexOf('/'); 897 if (lastPathElementPos == -1) 898 { 899 // this should never occur as the base should always be protocol:/.... 900 return false; 901 } 902 else if (unresolved._rawURI.length() <= lastPathElementPos) 903 { 904 return false; 905 } 906 else 907 { 908 String uri1 = _baseURI.substring(0, lastPathElementPos) + "/" + _rawURI; 909 String uri2 = unresolved._baseURI.substring(0, lastPathElementPos) + "/" + unresolved._rawURI; 910 911 return uri1.equals(uri2); 912 } 913 } 914 } 915 916 private static class ResolvedURI 917 { 918 String _resolvedURI; 919 long _timestamp; 920 921 public ResolvedURI(String resolvedURI, long timestamp) 922 { 923 _resolvedURI = resolvedURI; 924 _timestamp = timestamp; 925 } 926 } 927 928 /** 929 * The validity of the current stylesheet. 930 */ 931 public enum CacheValidity 932 { 933 /** The current stylesheet is in cache and valid*/ 934 CACHED, 935 /** The current styleseet is in cache but not valid anymore*/ 936 OUT_OF_DATE, 937 /** The current stylesheet is not cached*/ 938 NON_CACHED 939 } 940}