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.SAXNotRecognizedException;
051import org.xml.sax.SAXNotSupportedException;
052import org.xml.sax.XMLReader;
053
054/**
055 * This class manages the creation of Digester instances from digester rules modules.
056 */
057public final class DigesterLoader
058{
059
060    /**
061     * The default head when reporting an errors list.
062     */
063    private static final String HEADING = "Digester creation errors:%n%n";
064
065    /**
066     * Creates a new {@link DigesterLoader} instance given one or more {@link RulesModule} instance.
067     *
068     * @param rulesModules The modules containing the {@code Rule} binding
069     * @return A new {@link DigesterLoader} instance
070     */
071    public static DigesterLoader newLoader( RulesModule... rulesModules )
072    {
073        if ( rulesModules == null || rulesModules.length == 0 )
074        {
075            throw new DigesterLoadingException( "At least one RulesModule has to be specified" );
076        }
077        return newLoader( Arrays.asList( rulesModules ) );
078    }
079
080    /**
081     * Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance.
082     *
083     * @param rulesModules The modules containing the {@code Rule} binding
084     * @return A new {@link DigesterLoader} instance
085     */
086    public static DigesterLoader newLoader( Iterable<RulesModule> rulesModules )
087    {
088        if ( rulesModules == null )
089        {
090            throw new DigesterLoadingException( "RulesModule has to be specified" );
091        }
092
093        return new DigesterLoader( rulesModules );
094    }
095
096    /**
097     * The concrete {@link RulesBinder} implementation.
098     */
099    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}