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