/**
* 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 javax.xml.transform.sax.SAXResult;
import javax.xml.transform.Transformer;
import javax.xml.transform.Templates;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import java.net.URL;
import java.util.Iterator;
import java.io.OutputStream;
import java.io.File;
import java.io.FileOutputStream;
import org.xchain.framework.lifecycle.XmlFactoryLifecycle;
import org.xchain.framework.sax.SaxTemplates;
import org.xchain.framework.net.UrlFactory;
import org.xchain.framework.net.UrlFactoryUriResolver;
import org.xchain.framework.util.AttributesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Mike Moulton
* @author Christian Trimble
* @author Jason Rose
* @author Josh Kennedy
*/
public class MultiDocumentResult
extends SAXResult
{
public static Logger log = LoggerFactory.getLogger( MultiDocumentResult.class );
public static String MULTI_DOCUMENT_NAMESPACE_URI = "http://www.xchain.org/sax/multi-document/1.0";
public static String MULTI_DOCUMENT_ELEMENT_LOCAL_NAME = "multi-document";
public static String DOCUMENT_ELEMENT_LOCAL_NAME = "document";
public static String SYSTEM_ID_ATTRIBUTE = "system-id";
public static String PATH_ATTRIBUTE = "path";
public static String METHOD_ATTRIBUTE = "method";
public static String DOCTYPE_SYSTEM_ATTRIBUTE = "doctype-system";
public static String DOCTYPE_PUBLIC_ATTRIBUTE = "doctype-public";
public static String INDENT_ATTRIBUTE = "indent";
public static String INDENT_AMOUNT_ATTRIBUTE = "indent-amount";
public static String OVERWRITE_ATTRIBUTE = "overwrite";
public static String TEXT_METHOD = "text";
public static String XML_METHOD = "xml";
public static String HTML_METHOD = "html";
public static String DEFAULT_OVERWRITE_VALUE = "false";
MultiDocumentContentHandler multiDocumentContentHandler;
Templates identityTemplates;
public MultiDocumentResult()
{
multiDocumentContentHandler = new MultiDocumentContentHandler();
setHandler(multiDocumentContentHandler);
setLexicalHandler(multiDocumentContentHandler);
}
public static class MultiDocumentContentHandler
extends HandlerWrapper
{
public static Logger log = LoggerFactory.getLogger( MultiDocumentContentHandler.class );
// the number of file elements that we have encountered.
protected int documentElementCount = 0;
protected PrefixMappingContext prefixMappingContext = null;
protected void decrementDocumentElementCount()
{
documentElementCount--;
if( log.isDebugEnabled() ) {
log.debug("The document element count is now "+documentElementCount+".");
}
}
protected void incrementDocumentElementCount()
{
documentElementCount++;
if( log.isDebugEnabled() ) {
log.debug("The document element count is now "+documentElementCount+".");
}
}
public void startDocument()
throws SAXException
{
if( log.isDebugEnabled() ) {
log.debug("Start Document Called.");
}
// always intercept the start document event, we will create our own on the target content handler.
documentElementCount = 0;
prefixMappingContext = new PrefixMappingContext();
}
public void endDocument()
throws SAXException
{
if( log.isDebugEnabled() ) {
log.debug("End Document Called.");
}
// always intercept the end document event, we will create our own on each target content handler.
documentElementCount = 0;
prefixMappingContext = null;
}
public void startElement( String namespaceUri, String localName, String qName, Attributes attributes )
throws SAXException
{
if( MULTI_DOCUMENT_NAMESPACE_URI.equals(namespaceUri) && DOCUMENT_ELEMENT_LOCAL_NAME.equals(localName) && documentElementCount == 0 ) {
try {
if( log.isDebugEnabled() ) {
log.debug("Starting creation of new multi-document document.");
}
// initialize the wrapped handler.
initializeWrappedHandler(attributes);
if( log.isDebugEnabled() ) {
log.debug("Done initializing the wrapped handler.");
}
// send the start document event to the handler.
super.startDocument();
// send all of the namespace declarations that were defined on the muti-document tag.
Iterator<String> prefixIterator = prefixMappingContext.prefixSet().iterator();
while( prefixIterator.hasNext() ) {
String prefix = prefixIterator.next();
super.startPrefixMapping(prefix, prefixMappingContext.lookUpNamespaceUri(prefix));
}
// send the file element.
super.startElement( namespaceUri, localName, qName, attributes );
}
finally {
incrementDocumentElementCount();
}
}
else if( MULTI_DOCUMENT_NAMESPACE_URI.equals(namespaceUri) && DOCUMENT_ELEMENT_LOCAL_NAME.equals(localName) ) {
try {
// send the file element.
super.startElement( namespaceUri, localName, qName, attributes );
}
finally {
incrementDocumentElementCount();
}
}
// if we are in the document, then send output to the wrapped class.
else if( documentElementCount > 0 ) {
super.startElement( namespaceUri, localName, qName, attributes );
}
}
public void endElement( String namespaceUri, String localName, String qName )
throws SAXException
{
if( MULTI_DOCUMENT_NAMESPACE_URI.equals(namespaceUri) && DOCUMENT_ELEMENT_LOCAL_NAME.equals(localName) && documentElementCount == 1 ) {
if( log.isDebugEnabled() ) {
log.debug("Ending multi-document document.");
}
decrementDocumentElementCount();
// end the file element.
super.endElement(namespaceUri, localName, qName);
// remove all of the mappings added to the wrapped document.
Iterator<String> prefixIterator = prefixMappingContext.prefixSet().iterator();
while( prefixIterator.hasNext() ) {
String prefix = prefixIterator.next();
super.endPrefixMapping(prefix);
}
// end the document being sent to the wrapped handler.
super.endDocument();
// remove the wrapped handler.
setWrappedHandler(null);
}
else if( MULTI_DOCUMENT_NAMESPACE_URI.equals(namespaceUri) && DOCUMENT_ELEMENT_LOCAL_NAME.equals(localName) ) {
decrementDocumentElementCount();
super.endElement( namespaceUri, localName, qName );
}
else if( documentElementCount > 0 ) {
super.endElement( namespaceUri, localName, qName );
}
}
public void startPrefixMapping( String prefix, String namespaceUri )
throws SAXException
{
if( documentElementCount == 0 ) {
prefixMappingContext.pushPrefixMapping( prefix, namespaceUri );
}
else {
super.startPrefixMapping( prefix, namespaceUri );
}
}
public void endPrefixMapping( String prefix )
throws SAXException
{
if( documentElementCount == 0 ) {
prefixMappingContext.popPrefixMapping( prefix );
}
else {
super.endPrefixMapping(prefix);
}
}
public void initializeWrappedHandler( Attributes attributes )
throws SAXException
{
if( log.isDebugEnabled() ) {
log.debug("Initializing the wrapped handler.");
}
try {
String systemId = AttributesUtil.getAttribute( attributes, "", SYSTEM_ID_ATTRIBUTE );
String path = AttributesUtil.getAttribute( attributes, "", PATH_ATTRIBUTE );
String method = AttributesUtil.getAttribute( attributes, "", METHOD_ATTRIBUTE, XML_METHOD );
String doctypeSystem = AttributesUtil.getAttribute( attributes, "", DOCTYPE_SYSTEM_ATTRIBUTE );
String doctypePublic = AttributesUtil.getAttribute( attributes, "", DOCTYPE_PUBLIC_ATTRIBUTE );
String indent = AttributesUtil.getAttribute( attributes, "", INDENT_ATTRIBUTE );
String indentAmount = AttributesUtil.getAttribute( attributes, "", INDENT_AMOUNT_ATTRIBUTE );
if( shouldOverwrite( attributes ) ) {
StreamResult streamResult = new StreamResult();
if( systemId != null ) {
if( log.isDebugEnabled() ) {
log.debug("Creating result for system-id '"+systemId+"'.");
}
URL url = UrlFactory.getInstance().newUrl( systemId );
// create an output stream for this url.
OutputStream out = url.openConnection().getOutputStream();
// create a stream result for the output stream.
streamResult.setSystemId(systemId);
streamResult.setOutputStream(out);
}
else if( path != null ) {
if( log.isDebugEnabled() ) {
log.debug("Creating result for path '"+path+"'.");
}
File file = new File( path );
File parentFile = file.getParentFile();
if( !parentFile.exists() ) {
parentFile.mkdirs();
}
file.createNewFile();
streamResult.setSystemId(file.toURL().toExternalForm());
streamResult.setOutputStream(new FileOutputStream(file));
}
else {
throw new SAXException("The document element of a multi-document must have the system-id attribute or the path attribute.");
}
// create a transformer handler that removes the document element, and sets all of the document information.
TransformerHandler transformerHandler = createTransformerHandler( method, doctypePublic, doctypeSystem, indent, indentAmount );
// attach the stream handler to the result.
transformerHandler.setResult( streamResult );
if( log.isDebugEnabled() ) {
log.debug("Setting the transformerHandler as the wrapped handler.");
}
// set the fileContentHandler as the current handler strategy.
setWrappedHandler(transformerHandler);
}
}
catch( Exception e ) {
if( log.isWarnEnabled() ) {
log.warn("Could not initialize the wrapped handler", e);
}
throw new SAXException("Could not initialize wrapped handler.", e);
}
}
}
public static boolean shouldOverwrite( Attributes attributes )
throws Exception
{
String systemId = AttributesUtil.getAttribute( attributes, "", SYSTEM_ID_ATTRIBUTE );
String path = AttributesUtil.getAttribute( attributes, "", PATH_ATTRIBUTE );
String overwrite = AttributesUtil.getAttribute( attributes, "", OVERWRITE_ATTRIBUTE, DEFAULT_OVERWRITE_VALUE );
boolean overwriteFlag = Boolean.valueOf(overwrite).booleanValue();
if( overwriteFlag ) {
return true;
}
if( systemId != null ) {
if( log.isWarnEnabled() ) {
log.warn("Did not write document to system id '"+systemId+"'. System id docurments are currently not overwritten when overwrite is false.");
}
return false;
}
if( path != null ) {
File file = new File(path);
if( !file.exists() ) {
return true;
}
else {
if( log.isDebugEnabled() ) {
log.debug("Did not write document to path '"+path+"', it already exists and the overwrite flag is set to '"+overwriteFlag+"'.");
}
return false;
}
}
if( log.isWarnEnabled() ) {
log.warn("The overwrite flag is '"+overwriteFlag+"' and no path or system-id could be found, so no output is being generated.");
}
return false;
}
public static TransformerHandler createTransformerHandler( String method, String doctypePublicId, String doctypeSystemId, String indent, String indentAmount )
throws Exception
{
if( log.isDebugEnabled() ) {
log.debug("Creating transformer handlers.");
}
String systemId = "resource://context-class-loader/org/xchain/namespaces/sax/mutil-document-format.xsl";
SaxTemplates templates = XmlFactoryLifecycle.newTemplates(systemId);
if( log.isDebugEnabled() ) {
log.debug("Loaded the templates object.");
}
// create the transformer handler for the templates object.
TransformerHandler transformerHandler = templates.newTransformerHandler();
// get the transformer from the transformer handler object.
Transformer transformer = transformerHandler.getTransformer();
// set the output method.
transformer.setOutputProperty("method", method);
// set the output properties for xml and html documents.
if( XML_METHOD.equals(method) || HTML_METHOD.equals(method) ) {
if( doctypePublicId != null ) {
transformer.setOutputProperty("doctype-public", doctypePublicId);
}
if( doctypeSystemId != null ) {
transformer.setOutputProperty("doctype-system", doctypeSystemId);
}
if( indent != null ) {
transformer.setOutputProperty("indent", indent);
}
if( indentAmount != null ) {
transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", indentAmount);
}
}
transformer.setURIResolver(new UrlFactoryUriResolver());
if( log.isDebugEnabled() ) {
log.debug("Transformer created.");
}
return transformerHandler;
}
}