View Javadoc

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 }