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