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