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