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 }