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