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 }