1 package org.apache.commons.digester3.binder;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import static org.apache.commons.digester3.binder.BinderClassLoader.createBinderClassLoader;
23
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.net.MalformedURLException;
27 import java.net.URL;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Formatter;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.concurrent.ExecutorService;
34
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.parsers.SAXParser;
37 import javax.xml.parsers.SAXParserFactory;
38 import javax.xml.validation.Schema;
39
40 import org.apache.commons.digester3.Digester;
41 import org.apache.commons.digester3.RuleSet;
42 import org.apache.commons.digester3.Rules;
43 import org.apache.commons.digester3.RulesBase;
44 import org.apache.commons.digester3.StackAction;
45 import org.apache.commons.digester3.Substitutor;
46 import org.xml.sax.EntityResolver;
47 import org.xml.sax.ErrorHandler;
48 import org.xml.sax.Locator;
49 import org.xml.sax.SAXException;
50 import org.xml.sax.SAXNotRecognizedException;
51 import org.xml.sax.SAXNotSupportedException;
52 import org.xml.sax.XMLReader;
53
54 /**
55 * This class manages the creation of Digester instances from digester rules modules.
56 */
57 public final class DigesterLoader
58 {
59
60 /**
61 * The default head when reporting an errors list.
62 */
63 private static final String HEADING = "Digester creation errors:%n%n";
64
65 /**
66 * Creates a new {@link DigesterLoader} instance given one or more {@link RulesModule} instance.
67 *
68 * @param rulesModules The modules containing the {@code Rule} binding
69 * @return A new {@link DigesterLoader} instance
70 */
71 public static DigesterLoader newLoader( RulesModule... rulesModules )
72 {
73 if ( rulesModules == null || rulesModules.length == 0 )
74 {
75 throw new DigesterLoadingException( "At least one RulesModule has to be specified" );
76 }
77 return newLoader( Arrays.asList( rulesModules ) );
78 }
79
80 /**
81 * Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance.
82 *
83 * @param rulesModules The modules containing the {@code Rule} binding
84 * @return A new {@link DigesterLoader} instance
85 */
86 public static DigesterLoader newLoader( Iterable<RulesModule> rulesModules )
87 {
88 if ( rulesModules == null )
89 {
90 throw new DigesterLoadingException( "RulesModule has to be specified" );
91 }
92
93 return new DigesterLoader( rulesModules );
94 }
95
96 /**
97 * The concrete {@link RulesBinder} implementation.
98 */
99 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 }