001package org.apache.commons.digester3.binder; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import static org.apache.commons.digester3.binder.BinderClassLoader.createBinderClassLoader; 023 024import java.io.PrintWriter; 025import java.io.StringWriter; 026import java.net.MalformedURLException; 027import java.net.URL; 028import java.util.Arrays; 029import java.util.Collections; 030import java.util.Formatter; 031import java.util.HashMap; 032import java.util.Map; 033import java.util.concurrent.ExecutorService; 034 035import javax.xml.parsers.ParserConfigurationException; 036import javax.xml.parsers.SAXParser; 037import javax.xml.parsers.SAXParserFactory; 038import javax.xml.validation.Schema; 039 040import org.apache.commons.digester3.Digester; 041import org.apache.commons.digester3.RuleSet; 042import org.apache.commons.digester3.Rules; 043import org.apache.commons.digester3.RulesBase; 044import org.apache.commons.digester3.StackAction; 045import org.apache.commons.digester3.Substitutor; 046import org.xml.sax.EntityResolver; 047import org.xml.sax.ErrorHandler; 048import org.xml.sax.Locator; 049import org.xml.sax.SAXException; 050import org.xml.sax.SAXNotRecognizedException; 051import org.xml.sax.SAXNotSupportedException; 052import org.xml.sax.XMLReader; 053 054/** 055 * This class manages the creation of Digester instances from digester rules modules. 056 */ 057public final class DigesterLoader 058{ 059 060 /** 061 * The default head when reporting an errors list. 062 */ 063 private static final String HEADING = "Digester creation errors:%n%n"; 064 065 /** 066 * Creates a new {@link DigesterLoader} instance given one or more {@link RulesModule} instance. 067 * 068 * @param rulesModules The modules containing the {@code Rule} binding 069 * @return A new {@link DigesterLoader} instance 070 */ 071 public static DigesterLoader newLoader( RulesModule... rulesModules ) 072 { 073 if ( rulesModules == null || rulesModules.length == 0 ) 074 { 075 throw new DigesterLoadingException( "At least one RulesModule has to be specified" ); 076 } 077 return newLoader( Arrays.asList( rulesModules ) ); 078 } 079 080 /** 081 * Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance. 082 * 083 * @param rulesModules The modules containing the {@code Rule} binding 084 * @return A new {@link DigesterLoader} instance 085 */ 086 public static DigesterLoader newLoader( Iterable<RulesModule> rulesModules ) 087 { 088 if ( rulesModules == null ) 089 { 090 throw new DigesterLoadingException( "RulesModule has to be specified" ); 091 } 092 093 return new DigesterLoader( rulesModules ); 094 } 095 096 /** 097 * The concrete {@link RulesBinder} implementation. 098 */ 099 private final DefaultRulesBinder rulesBinder = new DefaultRulesBinder(); 100 101 /** 102 * The URLs of entityValidator that have been registered, keyed by the public 103 * identifier that corresponds. 104 */ 105 private final Map<String, URL> entityValidator = new HashMap<String, URL>(); 106 107 /** 108 * The SAXParserFactory to create new default {@link Digester} instances. 109 */ 110 private final SAXParserFactory factory = SAXParserFactory.newInstance(); 111 112 private final Iterable<RulesModule> rulesModules; 113 114 /** 115 * The class loader to use for instantiating application objects. 116 * If not specified, the context class loader, or the class loader 117 * used to load Digester itself, is used, based on the value of the 118 * <code>useContextClassLoader</code> variable. 119 */ 120 private BinderClassLoader classLoader; 121 122 /** 123 * An optional class that substitutes values in attributes and body text. This may be null and so a null check is 124 * always required before use. 125 */ 126 private Substitutor substitutor; 127 128 /** 129 * The EntityResolver used by the SAX parser. By default it use this class 130 */ 131 private EntityResolver entityResolver; 132 133 /** 134 * Object which will receive callbacks for every pop/push action on the default stack or named stacks. 135 */ 136 private StackAction stackAction; 137 138 /** 139 * The executor service to run asynchronous parse method. 140 * @since 3.1 141 */ 142 private ExecutorService executorService; 143 144 /** 145 * The application-supplied error handler that is notified when parsing warnings, errors, or fatal errors occur. 146 * @since 3.2 147 */ 148 private ErrorHandler errorHandler = null; 149 150 /** 151 * The Locator associated with our parser. 152 * @since 3.2 153 */ 154 private Locator locator = null; 155 156 /** 157 * Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance. 158 * 159 * @param rulesModules The modules containing the {@code Rule} binding 160 */ 161 private DigesterLoader( Iterable<RulesModule> rulesModules ) 162 { 163 this.rulesModules = rulesModules; 164 setUseContextClassLoader( true ); 165 } 166 167 /** 168 * Determine whether to use the Context ClassLoader (the one found by 169 * calling <code>Thread.currentThread().getContextClassLoader()</code>) 170 * to resolve/load classes that are defined in various rules. If not 171 * using Context ClassLoader, then the class-loading defaults to 172 * using the calling-class' ClassLoader. 173 * 174 * @param useContextClassLoader determines whether to use Context ClassLoader. 175 * @return This loader instance, useful to chain methods. 176 */ 177 public DigesterLoader setUseContextClassLoader( boolean useContextClassLoader ) 178 { 179 if ( useContextClassLoader ) 180 { 181 setClassLoader( Thread.currentThread().getContextClassLoader() ); 182 } 183 else 184 { 185 setClassLoader( getClass().getClassLoader() ); 186 } 187 return this; 188 } 189 190 /** 191 * Set the class loader to be used for instantiating application objects when required. 192 * 193 * @param classLoader the class loader to be used for instantiating application objects when required. 194 * @return This loader instance, useful to chain methods. 195 */ 196 public DigesterLoader setClassLoader( ClassLoader classLoader ) 197 { 198 if ( classLoader == null ) 199 { 200 throw new IllegalArgumentException( "Parameter 'classLoader' cannot be null" ); 201 } 202 203 this.classLoader = createBinderClassLoader( classLoader ); 204 205 rulesBinder.initialize( this.classLoader ); 206 for ( RulesModule rulesModule : rulesModules ) 207 { 208 rulesModule.configure( rulesBinder ); 209 } 210 211 return this; 212 } 213 214 /** 215 * Sets the <code>Substitutor</code> to be used to convert attributes and body text. 216 * 217 * @param substitutor the Substitutor to be used to convert attributes and body text 218 * or null if not substitution of these values is to be performed. 219 * @return This loader instance, useful to chain methods. 220 */ 221 public DigesterLoader setSubstitutor( Substitutor substitutor ) 222 { 223 this.substitutor = substitutor; 224 return this; 225 } 226 227 /** 228 * Set the "namespace aware" flag for parsers we create. 229 * 230 * @param namespaceAware The new "namespace aware" flag 231 * @return This loader instance, useful to chain methods. 232 */ 233 public DigesterLoader setNamespaceAware( boolean namespaceAware ) 234 { 235 factory.setNamespaceAware( namespaceAware ); 236 return this; 237 } 238 239 /** 240 * Return the "namespace aware" flag for parsers we create. 241 * 242 * @return true, if the "namespace aware" flag for parsers we create, false otherwise. 243 */ 244 public boolean isNamespaceAware() 245 { 246 return factory.isNamespaceAware(); 247 } 248 249 /** 250 * Set the XInclude-aware flag for parsers we create. This additionally 251 * requires namespace-awareness. 252 * 253 * @param xIncludeAware The new XInclude-aware flag 254 * @return This loader instance, useful to chain methods. 255 * @see #setNamespaceAware(boolean) 256 */ 257 public DigesterLoader setXIncludeAware( boolean xIncludeAware ) 258 { 259 factory.setXIncludeAware( xIncludeAware ); 260 return this; 261 } 262 263 /** 264 * Return the XInclude-aware flag for parsers we create; 265 * 266 * @return true, if the XInclude-aware flag for parsers we create is set, 267 * false otherwise 268 */ 269 public boolean isXIncludeAware() 270 { 271 return factory.isXIncludeAware(); 272 } 273 274 /** 275 * Set the {@code DOCTYPE} validation parser flag and should not be used when using schemas. 276 * 277 * @param validating The new validating parser flag. 278 * @return This loader instance, useful to chain methods. 279 * @see javax.xml.parsers.SAXParserFactory#setValidating(boolean) 280 */ 281 public DigesterLoader setValidating( boolean validating ) 282 { 283 factory.setValidating( validating ); 284 return this; 285 } 286 287 /** 288 * Return the {@code DOCTYPE} validation parser flag. 289 * 290 * @return true, if the validating parser flag is set, false otherwise 291 */ 292 public boolean isValidating() 293 { 294 return this.factory.isValidating(); 295 } 296 297 /** 298 * Set the XML Schema to be used when parsing. 299 * 300 * @param schema The {@link Schema} instance to use. 301 * @return This loader instance, useful to chain methods. 302 */ 303 public DigesterLoader setSchema( Schema schema ) 304 { 305 factory.setSchema( schema ); 306 return this; 307 } 308 309 /** 310 * Sets a flag indicating whether the requested feature is supported by the underlying implementation of 311 * <code>org.xml.sax.XMLReader</code>. 312 * 313 * @see org.apache.commons.digester3.Digester#setFeature(String, boolean) 314 * @param feature Name of the feature to set the status for 315 * @param value The new value for this feature 316 * @return This loader instance, useful to chain methods. 317 * @exception ParserConfigurationException if a parser configuration error occurs 318 * @exception SAXNotRecognizedException if the property name is not recognized 319 * @exception SAXNotSupportedException if the property name is recognized but not supported 320 * @since 3.3 321 */ 322 public DigesterLoader setFeature( String feature, boolean value ) 323 throws SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException 324 { 325 factory.setFeature(feature, value); 326 return this; 327 } 328 329 /** 330 * <p>Register the specified DTD URL for the specified public identifier. 331 * This must be called before the first call to <code>parse()</code>. 332 * </p><p> 333 * <code>Digester</code> contains an internal <code>EntityResolver</code> 334 * implementation. This maps <code>PUBLICID</code>'s to URLs 335 * (from which the resource will be loaded). A common use case for this 336 * method is to register local URLs (possibly computed at runtime by a 337 * classloader) for DTDs. This allows the performance advantage of using 338 * a local version without having to ensure every <code>SYSTEM</code> 339 * URI on every processed xml document is local. This implementation provides 340 * only basic functionality. If more sophisticated features are required, 341 * using {@link #setEntityResolver(EntityResolver)} to set a custom resolver is recommended. 342 * </p><p> 343 * <strong>Note:</strong> This method will have no effect when a custom 344 * <code>EntityResolver</code> has been set. (Setting a custom 345 * <code>EntityResolver</code> overrides the internal implementation.) 346 * </p> 347 * @param publicId Public identifier of the DTD to be resolved 348 * @param entityURL The URL to use for reading this DTD 349 * @return This loader instance, useful to chain methods. 350 */ 351 public DigesterLoader register( String publicId, URL entityURL ) 352 { 353 entityValidator.put( publicId, entityURL ); 354 return this; 355 } 356 357 /** 358 * <p>Convenience method that registers the string version of an entity URL 359 * instead of a URL version.</p> 360 * 361 * @param publicId Public identifier of the entity to be resolved 362 * @param entityURL The URL to use for reading this entity 363 * @return This loader instance, useful to chain methods. 364 */ 365 public DigesterLoader register( String publicId, String entityURL ) 366 { 367 try 368 { 369 return register( publicId, new URL( entityURL ) ); 370 } 371 catch ( MalformedURLException e ) 372 { 373 throw new IllegalArgumentException( "Malformed URL '" + entityURL + "' : " + e.getMessage() ); 374 } 375 } 376 377 /** 378 * Return the set of DTD URL registrations, keyed by public identifier. 379 * 380 * @return the set of DTD URL registrations. 381 */ 382 public Map<String, URL> getRegistrations() 383 { 384 return Collections.unmodifiableMap( this.entityValidator ); 385 } 386 387 /** 388 * Set the <code>EntityResolver</code> used by SAX when resolving public id and system id. This must be called 389 * before the first call to <code>parse()</code>. 390 * 391 * @param entityResolver a class that implement the <code>EntityResolver</code> interface. 392 * @return This loader instance, useful to chain methods. 393 */ 394 public DigesterLoader setEntityResolver( EntityResolver entityResolver ) 395 { 396 this.entityResolver = entityResolver; 397 return this; 398 } 399 400 /** 401 * Sets the Object which will receive callbacks for every pop/push action on the default stack or named stacks. 402 * 403 * @param stackAction the Object which will receive callbacks for every pop/push action on the default stack 404 * or named stacks. 405 * @return This loader instance, useful to chain methods. 406 */ 407 public DigesterLoader setStackAction( StackAction stackAction ) 408 { 409 this.stackAction = stackAction; 410 return this; 411 } 412 413 /** 414 * Returns the executor service used to run asynchronous parse method. 415 * 416 * @return the executor service used to run asynchronous parse method 417 * @since 3.1 418 */ 419 public ExecutorService getExecutorService() 420 { 421 return executorService; 422 } 423 424 /** 425 * Sets the executor service to run asynchronous parse method. 426 * 427 * @param executorService the executor service to run asynchronous parse method 428 * @return This loader instance, useful to chain methods. 429 * @since 3.1 430 */ 431 public DigesterLoader setExecutorService( ExecutorService executorService ) 432 { 433 this.executorService = executorService; 434 return this; 435 } 436 437 /** 438 * Return the error handler for this Digester. 439 * 440 * @return the error handler for this Digester. 441 * @since 3.2 442 */ 443 public ErrorHandler getErrorHandler() 444 { 445 return ( this.errorHandler ); 446 } 447 448 /** 449 * Set the error handler for this Digester. 450 * 451 * @param errorHandler The new error handler 452 * @return This loader instance, useful to chain methods. 453 * @since 3.2 454 */ 455 public DigesterLoader setErrorHandler( ErrorHandler errorHandler ) 456 { 457 this.errorHandler = errorHandler; 458 return this; 459 } 460 461 /** 462 * Gets the document locator associated with our parser. 463 * 464 * @return the Locator supplied by the document parser 465 * @since 3.2 466 */ 467 public Locator getDocumentLocator() 468 { 469 return locator; 470 } 471 472 /** 473 * Sets the document locator associated with our parser. 474 * 475 * @param locator the document locator associated with our parser. 476 * @return This loader instance, useful to chain methods. 477 * @since 3.2 478 */ 479 public DigesterLoader setDocumentLocator( Locator locator ) 480 { 481 this.locator = locator; 482 return this; 483 } 484 485 /** 486 * Creates a new {@link Digester} instance that relies on the default {@link Rules} implementation. 487 * 488 * @return a new {@link Digester} instance 489 */ 490 public Digester newDigester() 491 { 492 return this.newDigester( new RulesBase() ); 493 } 494 495 /** 496 * Creates a new {@link Digester} instance that relies on the custom user define {@link Rules} implementation 497 * 498 * @param rules The custom user define {@link Rules} implementation 499 * @return a new {@link Digester} instance 500 */ 501 public Digester newDigester( Rules rules ) 502 { 503 try 504 { 505 return this.newDigester( this.factory.newSAXParser(), rules ); 506 } 507 catch ( ParserConfigurationException e ) 508 { 509 throw new DigesterLoadingException( "SAX Parser misconfigured", e ); 510 } 511 catch ( SAXException e ) 512 { 513 throw new DigesterLoadingException( "An error occurred while initializing the SAX Parser", e ); 514 } 515 } 516 517 /** 518 * Creates a new {@link Digester} instance that relies on the given {@code SAXParser} 519 * and the default {@link Rules} implementation. 520 * 521 * @param parser the user defined {@code SAXParser} 522 * @return a new {@link Digester} instance 523 */ 524 public Digester newDigester( SAXParser parser ) 525 { 526 return newDigester( parser, new RulesBase() ); 527 } 528 529 /** 530 * Creates a new {@link Digester} instance that relies on the given {@code SAXParser} 531 * and custom user define {@link Rules} implementation. 532 * 533 * @param parser The user defined {@code SAXParser} 534 * @param rules The custom user define {@link Rules} implementation 535 * @return a new {@link Digester} instance 536 */ 537 public Digester newDigester( SAXParser parser, Rules rules ) 538 { 539 if ( parser == null ) 540 { 541 throw new DigesterLoadingException( "SAXParser must be not null" ); 542 } 543 544 try 545 { 546 return this.newDigester( parser.getXMLReader(), rules ); 547 } 548 catch ( SAXException e ) 549 { 550 throw new DigesterLoadingException( "An error occurred while creating the XML Reader", e ); 551 } 552 } 553 554 /** 555 * Creates a new {@link XMLReader} instance that relies on the given {@code XMLReader} 556 * and the default {@link Rules} implementation. 557 * 558 * <b>WARNING</b> Input {@link XMLReader} will be linked to built Digester instance so it is recommended 559 * to <b>NOT</b> share same {@link XMLReader} instance to produce the Digester. 560 * 561 * @param reader The user defined {@code XMLReader} 562 * @return a new {@link Digester} instance 563 */ 564 public Digester newDigester( XMLReader reader ) 565 { 566 return this.newDigester( reader, new RulesBase() ); 567 } 568 569 /** 570 * Creates a new {@link XMLReader} instance that relies on the given {@code XMLReader} 571 * and custom user define {@link Rules} implementation. 572 * 573 * <b>WARNING</b> Input {@link XMLReader} and {@link Rules} will be linked to built Digester instance, 574 * so it is recommended to <b>NOT</b> share same {@link XMLReader} and {@link Rules} instance to produce the Digester. 575 * 576 * @param reader The user defined {@code XMLReader} 577 * @param rules The custom user define {@link Rules} implementation 578 * @return a new {@link Digester} instance 579 */ 580 public Digester newDigester( XMLReader reader, Rules rules ) 581 { 582 if ( reader == null ) 583 { 584 throw new DigesterLoadingException( "XMLReader must be not null" ); 585 } 586 if ( rules == null ) 587 { 588 throw new DigesterLoadingException( "Impossible to create a new Digester with null Rules" ); 589 } 590 591 Digester digester = new Digester( reader ); 592 // the ClassLoader adapter is no needed anymore 593 digester.setClassLoader( classLoader.getAdaptedClassLoader() ); 594 digester.setRules( rules ); 595 digester.setSubstitutor( substitutor ); 596 digester.registerAll( entityValidator ); 597 digester.setEntityResolver( entityResolver ); 598 digester.setStackAction( stackAction ); 599 digester.setNamespaceAware( isNamespaceAware() ); 600 digester.setExecutorService( executorService ); 601 digester.setErrorHandler( errorHandler ); 602 digester.setDocumentLocator( locator ); 603 604 addRules( digester ); 605 606 return digester; 607 } 608 609 /** 610 * Add rules to an already created Digester instance, analyzing the digester annotations in the target class. 611 * 612 * @param digester the Digester instance reference. 613 */ 614 public void addRules( final Digester digester ) 615 { 616 RuleSet ruleSet = createRuleSet(); 617 ruleSet.addRuleInstances( digester ); 618 } 619 620 /** 621 * Creates a new {@link RuleSet} instance based on the current configuration. 622 * 623 * @return A new {@link RuleSet} instance based on the current configuration. 624 */ 625 public RuleSet createRuleSet() 626 { 627 if ( rulesBinder.hasError() ) 628 { 629 Formatter fmt = new Formatter().format( HEADING ); 630 int index = 1; 631 632 for ( ErrorMessage errorMessage : rulesBinder.getErrors() ) 633 { 634 fmt.format( "%s) %s%n", index++, errorMessage.getMessage() ); 635 636 Throwable cause = errorMessage.getCause(); 637 if ( cause != null ) 638 { 639 StringWriter writer = new StringWriter(); 640 cause.printStackTrace( new PrintWriter( writer ) ); 641 fmt.format( "Caused by: %s", writer.getBuffer() ); 642 } 643 644 fmt.format( "%n" ); 645 } 646 647 if ( rulesBinder.errorsSize() == 1 ) 648 { 649 fmt.format( "1 error" ); 650 } 651 else 652 { 653 fmt.format( "%s errors", rulesBinder.errorsSize() ); 654 } 655 656 throw new DigesterLoadingException( fmt.toString() ); 657 } 658 659 return rulesBinder.getFromBinderRuleSet(); 660 } 661 662}