001    package org.apache.commons.digester3.binder;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import static org.apache.commons.digester3.binder.BinderClassLoader.createBinderClassLoader;
023    
024    import java.io.PrintWriter;
025    import java.io.StringWriter;
026    import java.net.MalformedURLException;
027    import java.net.URL;
028    import java.util.Arrays;
029    import java.util.Collections;
030    import java.util.Formatter;
031    import java.util.HashMap;
032    import java.util.Map;
033    import java.util.concurrent.ExecutorService;
034    
035    import javax.xml.parsers.ParserConfigurationException;
036    import javax.xml.parsers.SAXParser;
037    import javax.xml.parsers.SAXParserFactory;
038    import javax.xml.validation.Schema;
039    
040    import org.apache.commons.digester3.Digester;
041    import org.apache.commons.digester3.RuleSet;
042    import org.apache.commons.digester3.Rules;
043    import org.apache.commons.digester3.RulesBase;
044    import org.apache.commons.digester3.StackAction;
045    import org.apache.commons.digester3.Substitutor;
046    import org.xml.sax.EntityResolver;
047    import org.xml.sax.ErrorHandler;
048    import org.xml.sax.Locator;
049    import org.xml.sax.SAXException;
050    import org.xml.sax.XMLReader;
051    
052    /**
053     * This class manages the creation of Digester instances from digester rules modules.
054     */
055    public final class DigesterLoader
056    {
057    
058        /**
059         * The default head when reporting an errors list.
060         */
061        private static final String HEADING = "Digester creation errors:%n%n";
062    
063        /**
064         * Creates a new {@link DigesterLoader} instance given one or more {@link RulesModule} instance.
065         *
066         * @param rulesModules The modules containing the {@code Rule} binding
067         * @return A new {@link DigesterLoader} instance
068         */
069        public static DigesterLoader newLoader( RulesModule... rulesModules )
070        {
071            if ( rulesModules == null || rulesModules.length == 0 )
072            {
073                throw new DigesterLoadingException( "At least one RulesModule has to be specified" );
074            }
075            return newLoader( Arrays.asList( rulesModules ) );
076        }
077    
078        /**
079         * Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance.
080         *
081         * @param rulesModules The modules containing the {@code Rule} binding
082         * @return A new {@link DigesterLoader} instance
083         */
084        public static DigesterLoader newLoader( Iterable<RulesModule> rulesModules )
085        {
086            if ( rulesModules == null )
087            {
088                throw new DigesterLoadingException( "RulesModule has to be specified" );
089            }
090    
091            return new DigesterLoader( rulesModules );
092        }
093    
094        /**
095         * The concrete {@link RulesBinder} implementation.
096         */
097        private final DefaultRulesBinder rulesBinder = new DefaultRulesBinder();
098    
099        /**
100         * The URLs of entityValidator that have been registered, keyed by the public
101         * identifier that corresponds.
102         */
103        private final Map<String, URL> entityValidator = new HashMap<String, URL>();
104    
105        /**
106         * The SAXParserFactory to create new default {@link Digester} instances.
107         */
108        private final SAXParserFactory factory = SAXParserFactory.newInstance();
109    
110        private final Iterable<RulesModule> rulesModules;
111    
112        /**
113         * The class loader to use for instantiating application objects.
114         * If not specified, the context class loader, or the class loader
115         * used to load Digester itself, is used, based on the value of the
116         * <code>useContextClassLoader</code> variable.
117         */
118        private BinderClassLoader classLoader;
119    
120        /**
121         * An optional class that substitutes values in attributes and body text. This may be null and so a null check is
122         * always required before use.
123         */
124        private Substitutor substitutor;
125    
126        /**
127         * The EntityResolver used by the SAX parser. By default it use this class
128         */
129        private EntityResolver entityResolver;
130    
131        /**
132         * Object which will receive callbacks for every pop/push action on the default stack or named stacks.
133         */
134        private StackAction stackAction;
135    
136        /**
137         * The executor service to run asynchronous parse method.
138         * @since 3.1
139         */
140        private ExecutorService executorService;
141    
142        /**
143         * The application-supplied error handler that is notified when parsing warnings, errors, or fatal errors occur.
144         * @since 3.2
145         */
146        private ErrorHandler errorHandler = null;
147    
148        /**
149         * The Locator associated with our parser.
150         * @since 3.2
151         */
152        private Locator locator = null;
153    
154        /**
155         * Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance.
156         *
157         * @param rulesModules The modules containing the {@code Rule} binding
158         */
159        private DigesterLoader( Iterable<RulesModule> rulesModules )
160        {
161            this.rulesModules = rulesModules;
162            setUseContextClassLoader( true );
163        }
164    
165        /**
166         * Determine whether to use the Context ClassLoader (the one found by
167         * calling <code>Thread.currentThread().getContextClassLoader()</code>)
168         * to resolve/load classes that are defined in various rules.  If not
169         * using Context ClassLoader, then the class-loading defaults to
170         * using the calling-class' ClassLoader.
171         *
172         * @param useContextClassLoader determines whether to use Context ClassLoader.
173         * @return This loader instance, useful to chain methods.
174         */
175        public DigesterLoader setUseContextClassLoader( boolean useContextClassLoader )
176        {
177            if ( useContextClassLoader )
178            {
179                setClassLoader( Thread.currentThread().getContextClassLoader() );
180            }
181            else
182            {
183                setClassLoader( getClass().getClassLoader() );
184            }
185            return this;
186        }
187    
188        /**
189         * Set the class loader to be used for instantiating application objects when required.
190         *
191         * @param classLoader the class loader to be used for instantiating application objects when required.
192         * @return This loader instance, useful to chain methods.
193         */
194        public DigesterLoader setClassLoader( ClassLoader classLoader )
195        {
196            if ( classLoader == null )
197            {
198                throw new IllegalArgumentException( "Parameter 'classLoader' cannot be null" );
199            }
200    
201            this.classLoader = createBinderClassLoader( classLoader );
202            return this;
203        }
204    
205        /**
206         * Sets the <code>Substitutor</code> to be used to convert attributes and body text.
207         *
208         * @param substitutor the Substitutor to be used to convert attributes and body text
209         *        or null if not substitution of these values is to be performed.
210         * @return This loader instance, useful to chain methods.
211         */
212        public DigesterLoader setSubstitutor( Substitutor substitutor )
213        {
214            this.substitutor = substitutor;
215            return this;
216        }
217    
218        /**
219         * Set the "namespace aware" flag for parsers we create.
220         *
221         * @param namespaceAware The new "namespace aware" flag
222         * @return This loader instance, useful to chain methods.
223         */
224        public DigesterLoader setNamespaceAware( boolean namespaceAware )
225        {
226            factory.setNamespaceAware( namespaceAware );
227            return this;
228        }
229    
230        /**
231         * Return the "namespace aware" flag for parsers we create.
232         *
233         * @return true, if the "namespace aware" flag for parsers we create, false otherwise.
234         */
235        public boolean isNamespaceAware()
236        {
237            return factory.isNamespaceAware();
238        }
239    
240        /**
241         * Set the XInclude-aware flag for parsers we create. This additionally
242         * requires namespace-awareness.
243         *
244         * @param xIncludeAware The new XInclude-aware flag
245         * @return This loader instance, useful to chain methods.
246         * @see #setNamespaceAware(boolean)
247         */
248        public DigesterLoader setXIncludeAware( boolean xIncludeAware )
249        {
250            factory.setXIncludeAware( xIncludeAware );
251            return this;
252        }
253    
254        /**
255         * Return the XInclude-aware flag for parsers we create;
256         *
257         * @return true, if the XInclude-aware flag for parsers we create is set,
258         *         false otherwise
259         */
260        public boolean isXIncludeAware()
261        {
262            return factory.isXIncludeAware();
263        }
264    
265        /**
266         * Set the validating parser flag.
267         *
268         * @param validating The new validating parser flag.
269         * @return This loader instance, useful to chain methods.
270         */
271        public DigesterLoader setValidating( boolean validating )
272        {
273            factory.setValidating( validating );
274            return this;
275        }
276    
277        /**
278         * Return the validating parser flag.
279         *
280         * @return true, if the validating parser flag is set, false otherwise
281         */
282        public boolean isValidating()
283        {
284            return this.factory.isValidating();
285        }
286    
287        /**
288         * Set the XML Schema to be used when parsing.
289         *
290         * @param schema The {@link Schema} instance to use.
291         * @return This loader instance, useful to chain methods.
292         */
293        public DigesterLoader setSchema( Schema schema )
294        {
295            factory.setSchema( schema );
296            return this;
297        }
298    
299        /**
300         * <p>Register the specified DTD URL for the specified public identifier.
301         * This must be called before the first call to <code>parse()</code>.
302         * </p><p>
303         * <code>Digester</code> contains an internal <code>EntityResolver</code>
304         * implementation. This maps <code>PUBLICID</code>'s to URLs
305         * (from which the resource will be loaded). A common use case for this
306         * method is to register local URLs (possibly computed at runtime by a
307         * classloader) for DTDs. This allows the performance advantage of using
308         * a local version without having to ensure every <code>SYSTEM</code>
309         * URI on every processed xml document is local. This implementation provides
310         * only basic functionality. If more sophisticated features are required,
311         * using {@link #setEntityResolver(EntityResolver)} to set a custom resolver is recommended.
312         * </p><p>
313         * <strong>Note:</strong> This method will have no effect when a custom
314         * <code>EntityResolver</code> has been set. (Setting a custom
315         * <code>EntityResolver</code> overrides the internal implementation.)
316         * </p>
317         * @param publicId Public identifier of the DTD to be resolved
318         * @param entityURL The URL to use for reading this DTD
319         * @return This loader instance, useful to chain methods.
320         */
321        public DigesterLoader register( String publicId, URL entityURL )
322        {
323            entityValidator.put( publicId, entityURL );
324            return this;
325        }
326    
327        /**
328         * <p>Convenience method that registers the string version of an entity URL
329         * instead of a URL version.</p>
330         *
331         * @param publicId Public identifier of the entity to be resolved
332         * @param entityURL The URL to use for reading this entity
333         * @return This loader instance, useful to chain methods.
334         */
335        public DigesterLoader register( String publicId, String entityURL )
336        {
337            try
338            {
339                return register( publicId, new URL( entityURL ) );
340            }
341            catch ( MalformedURLException e )
342            {
343                throw new IllegalArgumentException( "Malformed URL '" + entityURL + "' : " + e.getMessage() );
344            }
345        }
346    
347        /**
348         * Return the set of DTD URL registrations, keyed by public identifier.
349         *
350         * @return the set of DTD URL registrations.
351         */
352        public Map<String, URL> getRegistrations()
353        {
354            return Collections.unmodifiableMap( this.entityValidator );
355        }
356    
357        /**
358         * Set the <code>EntityResolver</code> used by SAX when resolving public id and system id. This must be called
359         * before the first call to <code>parse()</code>.
360         *
361         * @param entityResolver a class that implement the <code>EntityResolver</code> interface.
362         * @return This loader instance, useful to chain methods.
363         */
364        public DigesterLoader setEntityResolver( EntityResolver entityResolver )
365        {
366            this.entityResolver = entityResolver;
367            return this;
368        }
369    
370        /**
371         * Sets the Object which will receive callbacks for every pop/push action on the default stack or named stacks.
372         *
373         * @param stackAction the Object which will receive callbacks for every pop/push action on the default stack
374         *        or named stacks.
375         * @return This loader instance, useful to chain methods.
376         */
377        public DigesterLoader setStackAction( StackAction stackAction )
378        {
379            this.stackAction = stackAction;
380            return this;
381        }
382    
383        /**
384         * Returns the executor service used to run asynchronous parse method.
385         *
386         * @return the executor service used to run asynchronous parse method
387         * @since 3.1
388         */
389        public ExecutorService getExecutorService()
390        {
391            return executorService;
392        }
393    
394        /**
395         * Sets the executor service to run asynchronous parse method.
396         *
397         * @param executorService the executor service to run asynchronous parse method
398         * @return This loader instance, useful to chain methods.
399         * @since 3.1
400         */
401        public DigesterLoader setExecutorService( ExecutorService executorService )
402        {
403            this.executorService = executorService;
404            return this;
405        }
406    
407        /**
408         * Return the error handler for this Digester.
409         *
410         * @return the error handler for this Digester.
411         * @since 3.2
412         */
413        public ErrorHandler getErrorHandler()
414        {
415            return ( this.errorHandler );
416        }
417    
418        /**
419         * Set the error handler for this Digester.
420         *
421         * @param errorHandler The new error handler
422         * @return This loader instance, useful to chain methods.
423         * @since 3.2
424         */
425        public DigesterLoader setErrorHandler( ErrorHandler errorHandler )
426        {
427            this.errorHandler = errorHandler;
428            return this;
429        }
430    
431        /**
432         * Gets the document locator associated with our parser.
433         *
434         * @return the Locator supplied by the document parser
435         * @since 3.2
436         */
437        public Locator getDocumentLocator()
438        {
439            return locator;
440        }
441    
442        /**
443         * Sets the document locator associated with our parser.
444         *
445         * @param locator the document locator associated with our parser.
446         * @return This loader instance, useful to chain methods.
447         * @since 3.2
448         */
449        public DigesterLoader setDocumentLocator( Locator locator )
450        {
451            this.locator = locator;
452            return this;
453        }
454    
455        /**
456         * Creates a new {@link Digester} instance that relies on the default {@link Rules} implementation.
457         *
458         * @return a new {@link Digester} instance
459         */
460        public Digester newDigester()
461        {
462            return this.newDigester( new RulesBase() );
463        }
464    
465        /**
466         * Creates a new {@link Digester} instance that relies on the custom user define {@link Rules} implementation
467         *
468         * @param rules The custom user define {@link Rules} implementation
469         * @return a new {@link Digester} instance
470         */
471        public Digester newDigester( Rules rules )
472        {
473            try
474            {
475                return this.newDigester( this.factory.newSAXParser(), rules );
476            }
477            catch ( ParserConfigurationException e )
478            {
479                throw new DigesterLoadingException( "SAX Parser misconfigured", e );
480            }
481            catch ( SAXException e )
482            {
483                throw new DigesterLoadingException( "An error occurred while initializing the SAX Parser", e );
484            }
485        }
486    
487        /**
488         * Creates a new {@link Digester} instance that relies on the given {@code SAXParser}
489         * and the default {@link Rules} implementation.
490         *
491         * @param parser the user defined {@code SAXParser}
492         * @return a new {@link Digester} instance
493         */
494        public Digester newDigester( SAXParser parser )
495        {
496            return newDigester( parser, new RulesBase() );
497        }
498    
499        /**
500         * Creates a new {@link Digester} instance that relies on the given {@code SAXParser}
501         * and custom user define {@link Rules} implementation.
502         *
503         * @param parser The user defined {@code SAXParser}
504         * @param rules The custom user define {@link Rules} implementation
505         * @return a new {@link Digester} instance
506         */
507        public Digester newDigester( SAXParser parser, Rules rules )
508        {
509            if ( parser == null )
510            {
511                throw new DigesterLoadingException( "SAXParser must be not null" );
512            }
513    
514            try
515            {
516                return this.newDigester( parser.getXMLReader(), rules );
517            }
518            catch ( SAXException e )
519            {
520                throw new DigesterLoadingException( "An error occurred while creating the XML Reader", e );
521            }
522        }
523    
524        /**
525         * Creates a new {@link XMLReader} instance that relies on the given {@code XMLReader}
526         * and the default {@link Rules} implementation.
527         *
528         * @param reader The user defined {@code XMLReader}
529         * @return a new {@link Digester} instance
530         */
531        public Digester newDigester( XMLReader reader )
532        {
533            return this.newDigester( reader, new RulesBase() );
534        }
535    
536        /**
537         * Creates a new {@link XMLReader} instance that relies on the given {@code XMLReader}
538         * and custom user define {@link Rules} implementation.
539         *
540         * @param reader The user defined {@code XMLReader}
541         * @param rules The custom user define {@link Rules} implementation
542         * @return a new {@link Digester} instance
543         */
544        public Digester newDigester( XMLReader reader, Rules rules )
545        {
546            if ( reader == null )
547            {
548                throw new DigesterLoadingException( "XMLReader must be not null" );
549            }
550            if ( rules == null )
551            {
552                throw new DigesterLoadingException( "Impossible to create a new Digester with null Rules" );
553            }
554    
555            Digester digester = new Digester( reader );
556            // the ClassLoader adapter is no needed anymore
557            digester.setClassLoader( classLoader.getAdaptedClassLoader() );
558            digester.setRules( rules );
559            digester.setSubstitutor( substitutor );
560            digester.registerAll( entityValidator );
561            digester.setEntityResolver( entityResolver );
562            digester.setStackAction( stackAction );
563            digester.setNamespaceAware( isNamespaceAware() );
564            digester.setExecutorService( executorService );
565            digester.setErrorHandler( errorHandler );
566            digester.setDocumentLocator( locator );
567    
568            addRules( digester );
569    
570            return digester;
571        }
572    
573        /**
574         * Add rules to an already created Digester instance, analyzing the digester annotations in the target class.
575         *
576         * @param digester the Digester instance reference.
577         */
578        public void addRules( final Digester digester )
579        {
580            RuleSet ruleSet = createRuleSet();
581            ruleSet.addRuleInstances( digester );
582        }
583    
584        /**
585         * Creates a new {@link RuleSet} instance based on the current configuration.
586         *
587         * @return A new {@link RuleSet} instance based on the current configuration.
588         */
589        public RuleSet createRuleSet()
590        {
591            if ( classLoader != rulesBinder.getContextClassLoader() )
592            {
593                rulesBinder.initialize( classLoader );
594                for ( RulesModule rulesModule : rulesModules )
595                {
596                    rulesModule.configure( rulesBinder );
597                }
598            }
599    
600            if ( rulesBinder.hasError() )
601            {
602                Formatter fmt = new Formatter().format( HEADING );
603                int index = 1;
604    
605                for ( ErrorMessage errorMessage : rulesBinder.getErrors() )
606                {
607                    fmt.format( "%s) %s%n", index++, errorMessage.getMessage() );
608    
609                    Throwable cause = errorMessage.getCause();
610                    if ( cause != null )
611                    {
612                        StringWriter writer = new StringWriter();
613                        cause.printStackTrace( new PrintWriter( writer ) );
614                        fmt.format( "Caused by: %s", writer.getBuffer() );
615                    }
616    
617                    fmt.format( "%n" );
618                }
619    
620                if ( rulesBinder.errorsSize() == 1 )
621                {
622                    fmt.format( "1 error" );
623                }
624                else
625                {
626                    fmt.format( "%s errors", rulesBinder.errorsSize() );
627                }
628    
629                throw new DigesterLoadingException( fmt.toString() );
630            }
631    
632            return rulesBinder.getFromBinderRuleSet();
633        }
634    
635    }