/**
* Copyright 2011 meltmedia
*
* Licensed 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.
*/
package org.xchain.framework.lifecycle;
import java.util.Map;
import java.util.HashMap;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xchain.framework.factory.TemplatesFactory;
import org.xchain.framework.net.UrlFactory;
import org.xchain.framework.net.UrlSourceUtil;
import org.xchain.framework.sax.XChainTemplatesHandler;
import org.xchain.framework.sax.SaxTemplatesHandler;
import org.xchain.framework.sax.SaxTemplates;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* This class provides a common place to manage factories for the TrAX and SAX APIs, by allowing the creation
* of several named factories.
*
* @author Christian Trimble
* @author John Trimble
* @author Josh Kennedy
*/
public class XmlFactoryLifecycle
{
private static Logger log = LoggerFactory.getLogger(XmlFactoryLifecycle.class);
/** The qname for the default sax parser factory - {http://www.xchain.org/core}default-sax-parser-factory */
public static final QName DEFAULT_SAX_PARSER_FACTORY_NAME = new QName("http://www.xchain.org/core", "default-sax-parser-factory");
/** The qname for the default transformer factory - {http://www.xchain.org/core}default-transformer-factory */
public static final QName DEFAULT_TRANSFORMER_FACTORY_NAME = new QName("http://www.xchain.org/core", "default-transformer-factory");
/** The qname for the default document build factory - {http://www.xchain.org/core}default-document-builder-factory */
public static final QName DEFAULT_DOCUMENT_BUILDER_FACTORY_NAME = new QName("http://www.xchain.org/core", "default-document-builder-factory");
/** The qname for the default xml reader factory - {http://www.xchain.org/core}default-xml-reader-factory */
public static final QName DEFAULT_XML_READER_FACTORY_NAME = new QName("http://www.xchain.org/core", "default-xml-reader-factory");
/** The qname for xalan's transformer factory, if it is on the class path - {http://www.xchain.org/core}xalan */
public static final QName XALAN_FACTORY_NAME = new QName("http://www.xchain.org/core", "xalan");
/** The qname for xsltc's transformer factory, if it is on the class path - {http://www.xchain.org/core}xsltc */
public static final QName XSLTC_FACTORY_NAME = new QName("http://www.xchain.org/core", "xsltc");
/** The qname for saxon's transformer factory, if it is on the class path - {http://www.xchain.org/core}saxon */
public static final QName SAXON_FACTORY_NAME = new QName("http://www.xchain.org/core", "saxon");
/** The qname for joost's transformer factory, if it is on the class path - {http://www.xchain.org/core}joost */
public static final QName JOOST_FACTORY_NAME = new QName("http://www.xchain.org/core", "joost");
/** The class name of xalan's transformer factory - {@value} */
public static final String XALAN_FACTORY_CLASS_NAME = "org.apache.xalan.processor.TransformerFactoryImpl";
/** The class name of xsltc's transformer factory - {@value} */
public static final String XSLTC_FACTORY_CLASS_NAME = "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";
/** The class name of saxon's transformer factory - {@value} */
public static final String SAXON_FACTORY_CLASS_NAME = "net.sf.saxon.TransformerFactoryImpl";
/** The class name of joost's transformer factory - {@value} */
public static final String JOOST_FACTORY_CLASS_NAME = "net.sf.joost.trax.TransformerFactoryImpl";
public static final String XSLT_NAMESPACE = "http://www.w3.org/1999/XSL/Transform";
public static final String STX_NAMESPACE= "http://stx.sourceforge.net/2002/ns";
private static boolean started = false;
private static Map<QName, Factory<SAXParserFactory>> saxParserFactoryMap = new HashMap<QName, Factory<SAXParserFactory>>();
private static Map<QName, Factory<DocumentBuilderFactory>> documentBuilderFactoryMap = new HashMap<QName, Factory<DocumentBuilderFactory>>();
private static Map<QName, Factory<SAXTransformerFactory>> transformerFactoryMap = new HashMap<QName, Factory<SAXTransformerFactory>>();
/** We should provide a factory for validators. */
public static boolean isStarted()
{
return started;
}
public static void startLifecycle( LifecycleContext context )
throws LifecycleException
{
synchronized( XmlFactoryLifecycle.class ) {
started = true;
// log all of the factories that have been added to this lifecycle.
if( log.isInfoEnabled() ) {
StringBuilder status = new StringBuilder();
status.append("XML Lifecycle State\n");
if( saxParserFactoryMap.isEmpty() ) {
status.append(" There are no Factory<SAXParserFactory> objects defined.\n");
}
else {
status.append(" SAXParserFactories:\n");
for( Map.Entry<QName, Factory<SAXParserFactory>> entry : saxParserFactoryMap.entrySet() ) {
status.append(" ").append(entry.getKey()).append(" => ").append(entry.getValue().getClass().getName()).append("\n");
}
}
if( transformerFactoryMap.isEmpty() ) {
status.append(" There are no Factory<TransfromerFactory> objects defined.\n");
}
else {
status.append(" SAXTransformerFactories:\n");
for( Map.Entry<QName, Factory<SAXTransformerFactory>> entry : transformerFactoryMap.entrySet() ) {
status.append(" ").append(entry.getKey()).append(" => ").append(entry.getValue().getClass().getName()).append("\n");
}
}
log.info(status.toString());
}
}
}
public static void stopLifecycle( LifecycleContext context )
{
started = false;
}
/**
* Puts a new SAXParserFactory factory into the XmlFactoryLifecycle with the specified name. If a factory is already bound to the
* specified name, it is removed.
*/
public static void putSaxParserFactoryFactory( QName name, Factory<SAXParserFactory> factory )
{
synchronized( XmlFactoryLifecycle.class ) {
if( started ) {
throw new IllegalStateException("You may not add factories to the sax parser factory while the xml lifecycle is on.");
}
synchronized( saxParserFactoryMap ) {
saxParserFactoryMap.put( name, factory );
}
}
}
/**
* Returns the SAXParserFactory factory for the specified name.
*/
public static Factory<SAXParserFactory> getSaxParserFactoryFactory( QName name )
{
synchronized( saxParserFactoryMap ) {
return saxParserFactoryMap.get( name );
}
}
/**
* Removes the SAXParserFactory factory for the specified name.
*/
public static Factory<SAXParserFactory> removeSaxParserFactoryFactory( QName name )
{
synchronized( XmlFactoryLifecycle.class ) {
if( started ) {
throw new IllegalStateException("You may not add factories to the sax parser factory while the xml lifecycle is on.");
}
synchronized( saxParserFactoryMap ) {
return saxParserFactoryMap.remove( name );
}
}
}
/**
* Returns a new SAXParserFactory from the SAXParserFactory factory bound to the specified name.
*/
public static SAXParserFactory newSaxParserFactory( QName name )
{
return getSaxParserFactoryFactory( name ).newInstance();
}
public static SAXParser newSaxParser()
throws ParserConfigurationException, SAXException
{
return newSaxParser(DEFAULT_SAX_PARSER_FACTORY_NAME);
}
/**
* Returns a new SAXParser for the specified SAXParserFactory factory name.
*/
public static SAXParser newSaxParser( QName name )
throws ParserConfigurationException, SAXException
{
return newSaxParserFactory( name ).newSAXParser();
}
public static XMLReader newXmlReader()
throws ParserConfigurationException, SAXException
{
return newXmlReader(DEFAULT_SAX_PARSER_FACTORY_NAME);
}
/**
* Returns a new XMLReader for the specified SAXParserFactory factory name.
*/
public static XMLReader newXmlReader( QName name )
throws ParserConfigurationException, SAXException
{
return newSaxParser( name ).getXMLReader();
}
public static void putTransformerFactoryFactory( QName name, Factory<SAXTransformerFactory> factory )
{
synchronized( XmlFactoryLifecycle.class ) {
if( started ) {
throw new IllegalStateException("You may not add factories to the sax parser factory while the xml lifecycle is on.");
}
synchronized( transformerFactoryMap ) {
transformerFactoryMap.put( name, factory );
}
}
}
/**
* Returns the Factory<SAXTransformerFactory> that is mapped to the specified name, or null if there is no such
* factory.
* @param name the name of the factory to return.
* @return the Factory<SAXTransformerFactory> that is mapped to the specified name, or null if there is no such factory.
*/
public static Factory<SAXTransformerFactory> getTransformerFactoryFactory( QName name )
{
synchronized( transformerFactoryMap ) {
return transformerFactoryMap.get( name );
}
}
/**
* Removes the Factory<SAXTransformerFactory> with the specifed name. The factory that was removed is returned.
* @param name the name of the Factory<SAXTransformerFactory> that will be removed.
* @return the factory that was removed, or null if there was not a factory with that specified name.
* @throws IllegalStateException if this lifecycle is running when this method is called.
*/
public static Factory<SAXTransformerFactory> removeTransformerFactoryFactory( QName name )
{
synchronized( XmlFactoryLifecycle.class ) {
if( started ) {
throw new IllegalStateException("You may not add factories to the sax parser factory while the xml lifecycle is on.");
}
synchronized( transformerFactoryMap ) {
return transformerFactoryMap.remove( name );
}
}
}
/**
* Creates a new SAXTransformerFactory based on the Factory registered with the specified name.
* @param name the name of the Factory<SAXTransformerFactory> to use.
* @return the new SAXTransformerFactory that was created.
* @throws RuntimeException if there is not a factory with the specified name.
*/
public static SAXTransformerFactory newTransformerFactory( QName name )
{
Factory<SAXTransformerFactory> factory = getTransformerFactoryFactory( name );
if( factory == null ) {
throw new RuntimeException("There is no Factory<SAXTransformerFactory> named '"+name+".");
}
return factory.newInstance();
}
public static SAXTransformerFactory newTransformerFactory( String uri, String localName, Attributes attributes )
{
// TODO: replace this with some kind of configurable strategy for looking up the local name.
// if the uri is equal to the xslt uri, then use the default xslt factory.
if( XSLT_NAMESPACE.equals(uri) ) {
return newTransformerFactory(SAXON_FACTORY_NAME);
}
else if( STX_NAMESPACE.equals(uri) ) {
return newTransformerFactory(JOOST_FACTORY_NAME);
}
else {
throw new RuntimeException("There is no Factory<SAXTransformerFactory> for '"+uri+"', '"+localName+"'.");
}
}
/**
* Returns a new Templates object for the specified systemId.
*/
public static SaxTemplates newTemplates( String systemId )
throws TransformerConfigurationException
{
try {
return TemplatesFactory.getInstance().getTemplates(systemId);
}
catch( Exception e ) {
throw new TransformerConfigurationException("An exception was thrown while loading the templates object for system id '"+systemId+"'.", e);
}
}
public static TransformerHandler newTransformerHandler( Templates templates )
throws TransformerConfigurationException
{
if( !( templates instanceof SaxTemplates ) ) {
throw new TransformerConfigurationException("Wrong type of templates object.");
}
return ((SaxTemplates)templates).newTransformerHandler();
}
public static TransformerHandler newTransformerHandler( String systemId )
throws TransformerConfigurationException
{
return newTransformerHandler(newTemplates( systemId ));
}
/**
* Creates a templates handler that does not cache the templates object that is returned. The templates objects from this
* method cannot be cached, as there is no reliable way to create the sax stream that is associated with the use of this templates
* handler. If you want to create a cacheable and reloadable templates object, please ask for the templates object by system id from
* the newTemplates( String ) method. This will create a sax stream for the templates object, cache the object, and watch for changes to
* the
*/
public static SaxTemplatesHandler newTemplatesHandler()
{
return new XChainTemplatesHandler();
}
/**
* Puts a new DocumentBuilderFactory factory into the XmlFactoryLifecycle with the specified name. If a factory is already bound to the
* specified name, it is removed.
*/
public static void putDocumentBuilderFactoryFactory( QName name, Factory<DocumentBuilderFactory> factory)
{
synchronized( XmlFactoryLifecycle.class ) {
if( started ) {
throw new IllegalStateException("You may not add factories to the document builder factory while the xml lifecycle is on.");
}
synchronized( documentBuilderFactoryMap ) {
documentBuilderFactoryMap.put( name, factory );
}
}
}
/**
* Returns the DocumentBuilderFactory factory for the specified name.
*/
public static Factory<DocumentBuilderFactory> getDocumentBuilderFactory( QName name )
{
synchronized( documentBuilderFactoryMap ) {
return documentBuilderFactoryMap.get( name );
}
}
/**
* Removes the DocumentBuilderFactory factory for the specified name.
*/
public static Factory<DocumentBuilderFactory> removeDocumentBuilderFactoryFactory( QName name )
{
synchronized( XmlFactoryLifecycle.class ) {
if( started ) {
throw new IllegalStateException("You may not add factories to the document builder factory while the xml lifecycle is on.");
}
synchronized( documentBuilderFactoryMap ) {
return documentBuilderFactoryMap.remove( name );
}
}
}
/**
* Returns a new DocumentBuilderFactory from the DocumentBuilderFactory factory bound to the specified name.
*/
public static DocumentBuilderFactory newDocumentBuilderFactory( QName name )
{
return getDocumentBuilderFactory( name ).newInstance();
}
public static DocumentBuilder newDocumentBuilder()
throws ParserConfigurationException
{
return newDocumentBuilder( DEFAULT_DOCUMENT_BUILDER_FACTORY_NAME );
}
/**
* Returns a new DocumentBuilder for the specified DocumentBuilderFactory factory name.
* @throws ParserConfigurationException
*/
public static DocumentBuilder newDocumentBuilder( QName name )
throws ParserConfigurationException
{
return newDocumentBuilderFactory( name ).newDocumentBuilder();
}
}