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