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