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