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