org.apache.commons.digester3.binder.DigesterLoader Maven / Gradle / Ivy
Show all versions of commons-digester3 Show documentation
package org.apache.commons.digester3.binder;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.validation.Schema;
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.RuleSet;
import org.apache.commons.digester3.Rules;
import org.apache.commons.digester3.RulesBase;
import org.apache.commons.digester3.StackAction;
import org.apache.commons.digester3.Substitutor;
import org.xml.sax.EntityResolver;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* This class manages the creation of Digester instances from digester rules modules.
*/
public final class DigesterLoader
{
/**
* The default head when reporting an errors list.
*/
private static final String HEADING = "Digester creation errors:%n%n";
/**
* Creates a new {@link DigesterLoader} instance given one or more {@link RulesModule} instance.
*
* @param rulesModules The modules containing the {@code Rule} binding
* @return A new {@link DigesterLoader} instance
*/
public static DigesterLoader newLoader( RulesModule... rulesModules )
{
if ( rulesModules == null || rulesModules.length == 0 )
{
throw new DigesterLoadingException( "At least one RulesModule has to be specified" );
}
return newLoader( Arrays.asList( rulesModules ) );
}
/**
* Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance.
*
* @param rulesModules The modules containing the {@code Rule} binding
* @return A new {@link DigesterLoader} instance
*/
public static DigesterLoader newLoader( Iterable rulesModules )
{
if ( rulesModules == null )
{
throw new DigesterLoadingException( "RulesModule has to be specified" );
}
return new DigesterLoader( rulesModules );
}
/**
* The concrete {@link RulesBinder} implementation.
*/
private final DefaultRulesBinder rulesBinder = new DefaultRulesBinder();
/**
* The URLs of entityValidator that have been registered, keyed by the public
* identifier that corresponds.
*/
private final Map entityValidator = new HashMap();
/**
* The SAXParserFactory to create new default {@link Digester} instances.
*/
private final SAXParserFactory factory = SAXParserFactory.newInstance();
private final Iterable rulesModules;
private boolean useContextClassLoader = true;
/**
* The class loader to use for instantiating application objects.
* If not specified, the context class loader, or the class loader
* used to load Digester itself, is used, based on the value of the
* useContextClassLoader
variable.
*/
private ClassLoader classLoader;
private Substitutor substitutor;
/**
* The EntityResolver used by the SAX parser. By default it use this class
*/
private EntityResolver entityResolver;
/**
* Object which will receive callbacks for every pop/push action on the default stack or named stacks.
*/
private StackAction stackAction;
/**
* The executor service to run asynchronous parse method.
* @since 3.1
*/
private ExecutorService executorService;
/**
* Creates a new {@link DigesterLoader} instance given a collection of {@link RulesModule} instance.
*
* @param rulesModules The modules containing the {@code Rule} binding
*/
private DigesterLoader( Iterable rulesModules )
{
this.rulesModules = rulesModules;
}
/**
* Determine whether to use the Context ClassLoader (the one found by
* calling Thread.currentThread().getContextClassLoader()
)
* to resolve/load classes that are defined in various rules. If not
* using Context ClassLoader, then the class-loading defaults to
* using the calling-class' ClassLoader.
*
* @param useContextClassLoader determines whether to use Context ClassLoader.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setUseContextClassLoader( boolean useContextClassLoader )
{
this.useContextClassLoader = useContextClassLoader;
return this;
}
/**
* Set the class loader to be used for instantiating application objects when required.
*
* @param classLoader the class loader to be used for instantiating application objects when required.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setClassLoader( ClassLoader classLoader )
{
this.classLoader = classLoader;
return this;
}
/**
* Sets the Substitutor
to be used to convert attributes and body text.
*
* @param substitutor the Substitutor to be used to convert attributes and body text
* or null if not substitution of these values is to be performed.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setSubstitutor( Substitutor substitutor )
{
this.substitutor = substitutor;
return this;
}
/**
* Set the "namespace aware" flag for parsers we create.
*
* @param namespaceAware The new "namespace aware" flag
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setNamespaceAware( boolean namespaceAware )
{
factory.setNamespaceAware( namespaceAware );
return this;
}
/**
* Return the "namespace aware" flag for parsers we create.
*
* @return true, if the "namespace aware" flag for parsers we create, false otherwise.
*/
public boolean isNamespaceAware()
{
return factory.isNamespaceAware();
}
/**
* Set the XInclude-aware flag for parsers we create. This additionally
* requires namespace-awareness.
*
* @param xIncludeAware The new XInclude-aware flag
* @return This loader instance, useful to chain methods.
* @see #setNamespaceAware(boolean)
*/
public DigesterLoader setXIncludeAware( boolean xIncludeAware )
{
factory.setXIncludeAware( xIncludeAware );
return this;
}
/**
* Return the XInclude-aware flag for parsers we create;
*
* @return true, if the XInclude-aware flag for parsers we create is set,
* false otherwise
*/
public boolean isXIncludeAware()
{
return factory.isXIncludeAware();
}
/**
* Set the validating parser flag.
*
* @param validating The new validating parser flag.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setValidating( boolean validating )
{
factory.setValidating( validating );
return this;
}
/**
* Return the validating parser flag.
*
* @return true, if the validating parser flag is set, false otherwise
*/
public boolean isValidating()
{
return this.factory.isValidating();
}
/**
* Set the XML Schema to be used when parsing.
*
* @param schema The {@link Schema} instance to use.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setSchema( Schema schema )
{
factory.setSchema( schema );
return this;
}
/**
* Register the specified DTD URL for the specified public identifier.
* This must be called before the first call to parse()
.
*
* Digester
contains an internal EntityResolver
* implementation. This maps PUBLICID
's to URLs
* (from which the resource will be loaded). A common use case for this
* method is to register local URLs (possibly computed at runtime by a
* classloader) for DTDs. This allows the performance advantage of using
* a local version without having to ensure every SYSTEM
* URI on every processed xml document is local. This implementation provides
* only basic functionality. If more sophisticated features are required,
* using {@link #setEntityResolver(EntityResolver)} to set a custom resolver is recommended.
*
* Note: This method will have no effect when a custom
* EntityResolver
has been set. (Setting a custom
* EntityResolver
overrides the internal implementation.)
*
* @param publicId Public identifier of the DTD to be resolved
* @param entityURL The URL to use for reading this DTD
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader register( String publicId, URL entityURL )
{
entityValidator.put( publicId, entityURL );
return this;
}
/**
* Convenience method that registers the string version of an entity URL
* instead of a URL version.
*
* @param publicId Public identifier of the entity to be resolved
* @param entityURL The URL to use for reading this entity
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader register( String publicId, String entityURL )
{
try
{
return register( publicId, new URL( entityURL ) );
}
catch ( MalformedURLException e )
{
throw new IllegalArgumentException( "Malformed URL '" + entityURL + "' : " + e.getMessage() );
}
}
/**
* Return the set of DTD URL registrations, keyed by public identifier.
*
* @return the set of DTD URL registrations.
*/
public Map getRegistrations()
{
return Collections.unmodifiableMap( this.entityValidator );
}
/**
* Set the EntityResolver
used by SAX when resolving public id and system id. This must be called
* before the first call to parse()
.
*
* @param entityResolver a class that implement the EntityResolver
interface.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setEntityResolver( EntityResolver entityResolver )
{
this.entityResolver = entityResolver;
return this;
}
/**
* Sets the Object which will receive callbacks for every pop/push action on the default stack or named stacks.
*
* @param stackAction the Object which will receive callbacks for every pop/push action on the default stack
* or named stacks.
* @return This loader instance, useful to chain methods.
*/
public DigesterLoader setStackAction( StackAction stackAction )
{
this.stackAction = stackAction;
return this;
}
/**
* Returns the executor service used to run asynchronous parse method.
*
* @return the executor service used to run asynchronous parse method
* @since 3.1
*/
public ExecutorService getExecutorService()
{
return executorService;
}
/**
* Sets the executor service to run asynchronous parse method.
*
* @param executorService the executor service to run asynchronous parse method
* @return This loader instance, useful to chain methods.
* @since 3.1
*/
public DigesterLoader setExecutorService( ExecutorService executorService )
{
this.executorService = executorService;
return this;
}
/**
* Creates a new {@link Digester} instance that relies on the default {@link Rules} implementation.
*
* @return a new {@link Digester} instance
*/
public Digester newDigester()
{
return this.newDigester( new RulesBase() );
}
/**
* Creates a new {@link Digester} instance that relies on the custom user define {@link Rules} implementation
*
* @param rules The custom user define {@link Rules} implementation
* @return a new {@link Digester} instance
*/
public Digester newDigester( Rules rules )
{
try
{
return this.newDigester( this.factory.newSAXParser(), rules );
}
catch ( ParserConfigurationException e )
{
throw new DigesterLoadingException( "SAX Parser misconfigured", e );
}
catch ( SAXException e )
{
throw new DigesterLoadingException( "An error occurred while initializing the SAX Parser", e );
}
}
/**
* Creates a new {@link Digester} instance that relies on the given {@code SAXParser}
* and the default {@link Rules} implementation.
*
* @param parser the user defined {@code SAXParser}
* @return a new {@link Digester} instance
*/
public Digester newDigester( SAXParser parser )
{
return newDigester( parser, new RulesBase() );
}
/**
* Creates a new {@link Digester} instance that relies on the given {@code SAXParser}
* and custom user define {@link Rules} implementation.
*
* @param parser The user defined {@code SAXParser}
* @param rules The custom user define {@link Rules} implementation
* @return a new {@link Digester} instance
*/
public Digester newDigester( SAXParser parser, Rules rules )
{
if ( parser == null )
{
throw new DigesterLoadingException( "SAXParser must be not null" );
}
try
{
return this.newDigester( parser.getXMLReader(), rules );
}
catch ( SAXException e )
{
throw new DigesterLoadingException( "An error occurred while creating the XML Reader", e );
}
}
/**
* Creates a new {@link XMLReader} instance that relies on the given {@code XMLReader}
* and the default {@link Rules} implementation.
*
* @param reader The user defined {@code XMLReader}
* @return a new {@link Digester} instance
*/
public Digester newDigester( XMLReader reader )
{
return this.newDigester( reader, new RulesBase() );
}
/**
* Creates a new {@link XMLReader} instance that relies on the given {@code XMLReader}
* and custom user define {@link Rules} implementation.
*
* @param reader The user defined {@code XMLReader}
* @param rules The custom user define {@link Rules} implementation
* @return a new {@link Digester} instance
*/
public Digester newDigester( XMLReader reader, Rules rules )
{
if ( reader == null )
{
throw new DigesterLoadingException( "XMLReader must be not null" );
}
if ( rules == null )
{
throw new DigesterLoadingException( "Impossible to create a new Digester with null Rules" );
}
Digester digester = new Digester( reader );
digester.setRules( rules );
digester.setSubstitutor( substitutor );
digester.registerAll( entityValidator );
digester.setEntityResolver( entityResolver );
digester.setStackAction( stackAction );
digester.setNamespaceAware( isNamespaceAware() );
digester.setExecutorService( executorService );
addRules( digester );
return digester;
}
/**
* Add rules to an already created Digester instance, analyzing the digester annotations in the target class.
*
* @param digester the Digester instance reference.
*/
public void addRules( final Digester digester )
{
RuleSet ruleSet = createRuleSet();
ruleSet.addRuleInstances( digester );
}
/**
* Creates a new {@link RuleSet} instance based on the current configuration.
*
* @return A new {@link RuleSet} instance based on the current configuration.
*/
public RuleSet createRuleSet()
{
ClassLoader contextClassLoader =
classLoader != null ? classLoader
: ( useContextClassLoader ? Thread.currentThread().getContextClassLoader()
: getClass().getClassLoader() );
if ( !contextClassLoader.equals( rulesBinder.getContextClassLoader() ) )
{
rulesBinder.initialize( contextClassLoader );
for ( RulesModule rulesModule : rulesModules )
{
rulesModule.configure( rulesBinder );
}
}
if ( rulesBinder.hasError() )
{
Formatter fmt = new Formatter().format( HEADING );
int index = 1;
for ( ErrorMessage errorMessage : rulesBinder.getErrors() )
{
fmt.format( "%s) %s%n", index++, errorMessage.getMessage() );
Throwable cause = errorMessage.getCause();
if ( cause != null )
{
StringWriter writer = new StringWriter();
cause.printStackTrace( new PrintWriter( writer ) );
fmt.format( "Caused by: %s", writer.getBuffer() );
}
fmt.format( "%n" );
}
if ( rulesBinder.errorsSize() == 1 )
{
fmt.format( "1 error" );
}
else
{
fmt.format( "%s errors", rulesBinder.errorsSize() );
}
throw new DigesterLoadingException( fmt.toString() );
}
return rulesBinder.getFromBinderRuleSet();
}
}