001package 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
022import static org.apache.commons.digester3.binder.BinderClassLoader.createBinderClassLoader;
023
024import java.io.PrintWriter;
025import java.io.StringWriter;
026import java.net.MalformedURLException;
027import java.net.URL;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.Formatter;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.concurrent.ExecutorService;
034
035import javax.xml.parsers.ParserConfigurationException;
036import javax.xml.parsers.SAXParser;
037import javax.xml.parsers.SAXParserFactory;
038import javax.xml.validation.Schema;
039
040import org.apache.commons.digester3.Digester;
041import org.apache.commons.digester3.RuleSet;
042import org.apache.commons.digester3.Rules;
043import org.apache.commons.digester3.RulesBase;
044import org.apache.commons.digester3.StackAction;
045import org.apache.commons.digester3.Substitutor;
046import org.xml.sax.EntityResolver;
047import org.xml.sax.ErrorHandler;
048import org.xml.sax.Locator;
049import org.xml.sax.SAXException;
050import org.xml.sax.XMLReader;
051
052/**
053 * This class manages the creation of Digester instances from digester rules modules.
054 */
055public 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}