/** * 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.sax; import java.io.IOException; import java.net.URI; import java.util.Map; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.sax.TemplatesHandler; import javax.xml.namespace.QName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xchain.framework.lifecycle.XmlFactoryLifecycle; import org.xchain.framework.factory.TemplatesFactory; import org.xchain.framework.strategy.TemplatesConsumerStrategy; import org.xchain.framework.strategy.SourceSourceStrategy; import org.xchain.framework.util.ParserUtil; import org.xchain.framework.util.ParseException; import org.xchain.framework.util.ParsedTransformerFactory; import org.xchain.framework.net.DependencyTracker; import org.xchain.framework.net.UrlFactoryUriResolver; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.EntityResolver; import org.xml.sax.DTDHandler; import org.xml.sax.SAXException; import org.xml.sax.Locator; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.helpers.XMLFilterImpl; /** * This filter handles <?xchain-transformer-factory?> processing instructions. These can be used to style the contents * of the xchain before it is loaded. * <?xchain-stylesheet system-id="" parameters="qname='value', qname='value', qname='value'"?> * * @author Christian Trimble * @author Josh Kennedy */ public class XChainTemplatesHandler extends HandlerWrapper implements SaxTemplatesHandler { private static Logger log = LoggerFactory.getLogger(XChainTemplatesHandler.class); public static String XCHAIN_TRANSFORMER_FACTORY_TARGET = "xchain-transformer-factory"; /** True if we are in the prolog, false otherwise. */ private boolean inProlog = false; /** The list of sax events that we are caching until the prolog is finished. */ private List<SaxEvent> eventList = new ArrayList<SaxEvent>(); /** The locator object for this sax stream. */ private Locator locator; /** The system id for the templates object created by this handler. */ private String systemId; /** The name of the transformer factory that will load the templates object for this handler. */ private QName transformerFactoryName = XmlFactoryLifecycle.DEFAULT_TRANSFORMER_FACTORY_NAME; /** The sax transformer handler that was used to load the templates object for the stream of sax events passed to this handler. */ private SAXTransformerFactory transformerFactory; /** The templates handler that was created to handle this stream, based on the processing instructions found in the sax stream. */ private TemplatesHandler templatesHandler; /** * Sets the system id for the templates classes created by this handler. * * @param systemId the system id of this templates object. */ public void setSystemId( String systemId ) { this.systemId = systemId; } /** * Returns the system id for the templates class created by this handler. * * @return system id for the templates object created by this handler. */ public String getSystemId() { return this.systemId; } /** * Returns a non-reloading SaxTemplates object for the stream of sax events that were passed to this handler. */ public SaxTemplates getTemplates() { Templates templates = templatesHandler.getTemplates(); // this is where we need a reloading reference. return new SaxTemplatesImpl(templates, transformerFactory); } /** * Sets the locator object for this handler. */ public void setDocumentLocator( Locator locator ) { super.setDocumentLocator(locator); this.locator = locator; } /** * Return sthe locator object for this handler. */ public Locator getDocumentLocator() { return this.locator; } /** * Prepares the filter to start tracking prolog events and caches the start document event. */ public void startDocument() throws SAXException { // we are now in the prolog. inProlog = true; eventList.clear(); // track the start document event. eventList.add(new StartDocumentEvent()); } public void endDocument() throws SAXException { if( inProlog ) { throw new SAXException("The template document does not contain a root element."); } super.endDocument(); } /** * Handles <?xchain-stylesheet?> processing instructions and caches all others. */ public void processingInstruction(String target, String data) throws SAXException { if( XCHAIN_TRANSFORMER_FACTORY_TARGET.equals(target) && inProlog ) { if( templatesHandler != null ) { if( log.isWarnEnabled() ) { log.warn("More than one xchain-transformer-factory processing instructions were found in the prolog of '"+systemId+"'."); } } else { loadTemplatesHandler(data); } } else if( inProlog ) { // cache the processing instruction. eventList.add(new ProcessingInstructionEvent(target, data)); } else { super.processingInstruction(target, data); } } /** * Caches notation decl events until the prolog is finished. */ public void notationDecl(String name, String publicId, String systemId) throws SAXException { if( inProlog ) { eventList.add(new NotationDeclEvent(name, publicId, systemId)); } else { super.notationDecl(name, publicId, systemId); } } /** * Caches unparsed entity decl events until the prolog is finished. */ public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { if( inProlog ) { eventList.add(new UnparsedEntityDeclEvent(name, publicId, systemId, notationName)); } else { super.unparsedEntityDecl(name, publicId, systemId, notationName); } } /** * If this start element is the end of the prolog, then endProlog() is called. Then start * elements are passed along. */ public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException { if( inProlog ) { endProlog( uri, localName, qName, attributes ); } super.startElement( uri, localName, qName, attributes ); } /** * If this start prefix mapping is the end of the prolog, then endProlog() is called. Then * the start prefix mapping event is passed along. */ public void startPrefixMapping( String prefix, String uri ) throws SAXException { if( inProlog ) { // TODO: map the prefix. eventList.add(new StartPrefixMappingEvent(prefix, uri)); } else { super.startPrefixMapping( prefix, uri ); } } private String resolveEntities( String data ) { return data; } /** * Starts passing events to the filter. */ private void endProlog( String uri, String localName, String qName, Attributes attributes ) throws SAXException { inProlog = false; //if( transformerFactoryName == null ) { // then we need to use the uri, localName, qName, and attributes to select the default using the uri, localName, and attributes. //throw new SAXException("The transformer factory name is null."); //} // load the actual templates handler. if( transformerFactoryName != null ) { try { transformerFactory = XmlFactoryLifecycle.newTransformerFactory(transformerFactoryName); } catch( Exception e ) { throw new SAXException("Could not create transformer factory for name '"+transformerFactoryName+"'.", e); } } else { try { transformerFactory = XmlFactoryLifecycle.newTransformerFactory(uri, localName, attributes); } catch( Exception e ) { throw new SAXException("Could not load the default factory for the template starting with {"+uri+"}"+localName+".", e); } } try { templatesHandler = transformerFactory.newTemplatesHandler(); } catch( Exception e ) { throw new SAXException("Could not load templates handler from transformer factory '"+transformerFactoryName+"'.", e); } // check all of the interfaces implemented by this templates factory. contentHandler = templatesHandler; contentHandler.setDocumentLocator(locator); if( templatesHandler instanceof LexicalHandler ) { lexicalHandler = (LexicalHandler)templatesHandler; } if( templatesHandler instanceof DTDHandler ) { dtdHandler = (DTDHandler)templatesHandler; } // pass all of the cached events to the handlers. for( SaxEvent event : eventList ) { event.flushEvent(); } eventList.clear(); } private void loadTemplatesHandler( String data ) throws SAXException { try { // NOTE: this needs to be connected to the parsing utility method added to ParserUtil. ParsedTransformerFactory parsed = ParserUtil.parseTransformerFactory(data); String nameValue = parsed.getFields().get("name"); if( nameValue != null ) { transformerFactoryName = QName.valueOf(nameValue); } else { transformerFactoryName = XmlFactoryLifecycle.DEFAULT_TRANSFORMER_FACTORY_NAME; } } catch( Exception e ) { throw new SAXException("Could not parse xchain-transformer-factory processing instrution.", e); } } /** * An implementation of the SaxTemplates interface that provides access to transformer handlers from a templates object. */ private static class SaxTemplatesImpl implements SaxTemplates { /** The templates object that was loaded from the sax stream. */ private Templates templates; /** The transformer factory that was specified in the sax stream. */ private SAXTransformerFactory factory; /** * Creates a new SaxTemplatesImpl that is used to provide access to the creation of transformer handlers from * the templates object. */ public SaxTemplatesImpl( Templates templates, SAXTransformerFactory factory ) { this.templates = templates; this.factory = factory; } /** * Returns the result of newTransformer() from the contained templates object. */ public Transformer newTransformer() throws TransformerConfigurationException { return templates.newTransformer(); } /** * Returns the result of getOutputProperties() from the contained templates object. */ public Properties getOutputProperties() { return templates.getOutputProperties(); } /** * Returns the transformer handler created by the contained factory and templates objects. */ public TransformerHandler newTransformerHandler() throws TransformerConfigurationException { synchronized(factory) { return factory.newTransformerHandler(templates); } } } /** * The interface for cached sax events. */ private interface SaxEvent { /** * Flushes this event through to the parent implementation. */ public void flushEvent() throws SAXException; } private class ProcessingInstructionEvent implements SaxEvent { private String target; private String data; public ProcessingInstructionEvent( String target, String data ) { this.target = target; this.data = data; } public void flushEvent() throws SAXException { XChainTemplatesHandler.super.processingInstruction( target, data ); } } private class StartDocumentEvent implements SaxEvent { public void flushEvent() throws SAXException { XChainTemplatesHandler.super.startDocument(); } } private class NotationDeclEvent implements SaxEvent { private String name; private String publicId; private String systemId; public NotationDeclEvent( String name, String publicId, String systemId ) { this.name = name; this.publicId = publicId; this.systemId = systemId; } public void flushEvent() throws SAXException { XChainTemplatesHandler.super.notationDecl(name, publicId, systemId); } } private class UnparsedEntityDeclEvent implements SaxEvent { private String name; private String publicId; private String systemId; private String notationName; public UnparsedEntityDeclEvent( String name, String publicId, String systemId, String notationName ) { this.name = name; this.publicId = publicId; this.systemId = systemId; this.notationName = notationName; } public void flushEvent() throws SAXException { XChainTemplatesHandler.super.unparsedEntityDecl(name, publicId, systemId, notationName); } } private class StartPrefixMappingEvent implements SaxEvent { private String prefix; private String uri; public StartPrefixMappingEvent( String prefix, String uri ) { this.prefix = prefix; this.uri = uri; } public void flushEvent() throws SAXException { XChainTemplatesHandler.super.startPrefixMapping(prefix, uri); } } }