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 }