

package gov.va.med.cds.xml.transform;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.transform.Result;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.io.DocumentResult;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.xml.sax.InputSource;

import gov.va.med.cds.exception.ErrorCodeEnum;
import net.sf.saxon.Configuration;
import net.sf.saxon.TransformerFactoryImpl;
import net.sf.saxon.lib.ExtensionFunctionDefinition;


/***********************************************************************************************************************
 * 
 * XsltHelper loader class compiles XSLTs and holds the Transformer objects in a
 * non-persistent data structure in
 * 
 * physical memory
 * 
 * 
 * 
 * XsltHelper can also be instantiated using a constructor that takes a list of
 * xlst template locations and ExtensionFunctionDefinition objects
 * 
 * if Saxon integrated extensions are needed.
 * 
 * 
 * 
 * @author DNS   BHAGAV
 * 
 * 
 * 
 */

public final class XsltHelper
    implements
        TransformerInterface
{

    public XsltHelper( )
    {
        super();
    }


    public XsltHelper( List<String> xsltLocations, List<ExtensionFunctionDefinition> extFunctions )
        throws TransformerConfigurationException,
            IOException
    {
        super();

        loadXsltTransformsIntoMemory( xsltLocations, extFunctions );
    }

    private static Log logger = LogFactory.getLog( XsltHelper.class );

    // this is the container for compiled XSLTs

    private Map<String, Templates> xsltTemplate = new HashMap<String, Templates>();


    /*******************************************************************************************************************
     * 
     * Compiles XSLT files and stores the compiled XSLT in a non persistent
     * store - does not register ExtensionFunctionDefinitions.
     * 
     * 
     * 
     * @throws IOException
     * 
     * @throws TransformerConfigurationException
     * 
     */

    private void loadXsltTransformsIntoMemory( List<String> xsltLocations )
        throws IOException,
            TransformerConfigurationException
    {
        // empty ExtensionFunctionDefinitions - there aren't any to register

        loadXsltTransformsIntoMemory( xsltLocations, new ArrayList<ExtensionFunctionDefinition>() );
    }


    /*******************************************************************************************************************
     * 
     * Compiles XSLT files and stores the compiled XSLT in a non persistent
     * store and registers any ExtensionFunctionDefinitions present.
     * 
     * 
     * 
     * @throws IOException
     * 
     * @throws TransformerConfigurationException
     * 
     */

    private void loadXsltTransformsIntoMemory( List<String> xsltLocations, List<ExtensionFunctionDefinition> extFunctions )
        throws IOException,
            TransformerConfigurationException
    {
        StreamSource templateStreamSource = null;
        InputStream is = null;
        TransformerFactory transformerFactory = new TransformerFactoryImpl();

        // register saxonExtensionFunctions
        Configuration saxonConfig = ( ( TransformerFactoryImpl )transformerFactory ).getConfiguration();
        registerSaxonExtensionFunctions( saxonConfig, extFunctions );

        Resource[] resources = null;
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

        for ( String xsltPath : xsltLocations )
        {
            resources = resolver.getResources( xsltPath.trim() );

            for ( Resource resource : resources )
            {
                try
                {
                    logger.debug( "XSLT resource: " + resource.getFilename() + " is being loaded." );
                    is = resource.getInputStream();
                    templateStreamSource = new StreamSource( is );
                    xsltTemplate.put( resource.getFilename(), transformerFactory.newTemplates( templateStreamSource ) );
                }
                finally
                {
                    if ( is != null )
                    {
                        is.close();
                    }
                }
            }
        }
    }


    /**
     * 
     * Method registers Saxon Extension Functions with the Saxon
     * framework/transform factory
     * 
     * 
     * 
     * @param saxonConfig
     *            - Transform factory configuration
     * 
     * @param extFunctions
     *            - List of ExtensionFunctionDefintion objects
     * 
     */

    private void registerSaxonExtensionFunctions( Configuration saxonConfig, List<ExtensionFunctionDefinition> extFunctions )
    {
        for ( ExtensionFunctionDefinition extFunction : extFunctions )
        {
            saxonConfig.registerExtensionFunction( extFunction );
        }
    }


    /**
     * 
     * Utility method intended to reduced duplicated logic in clients needing to
     * transfrom an xml file with an xslt
     * 
     * template - for ease this method returns a usable String value
     * 
     * 
     * 
     * @param source
     *            The XML data as a string.
     * 
     * @param xsltKey
     *            The key to look up the transformer by.
     * 
     * @return The transformed results.
     * 
     */

    public String transformMessageAsString( String source, String xsltKey )
    {
        return transformMessageAsString( source, xsltKey, ( Map<String, Object> )null );
    }


    /**
     * 
     * Utility method intended to reduced duplicated logic in clients needing to
     * transfrom an xml file with an xslt
     * 
     * template - for ease this method returns a usable String value
     * 
     * 
     * 
     * @param source
     *            The XML data as a string.
     * 
     * @param xsltKey
     *            The key to look up the transformer by.
     * 
     * @param parameters
     *            The parameters that will be passed to the Transformer.
     * 
     * @return The transformed results.
     * 
     */

    public String transformMessageAsString( String source, String xsltKey, Map<String, Object> parameters )

    {
        try
        {
            Document result = transformMessage( source, xsltKey, parameters );
            String tempStr = result.asXML();

            return tempStr;
        }
        catch ( Exception e )
        {
            throw new XsltTransformationException( ErrorCodeEnum.XSLT_HELPER_TRANSLATION_EXCEPTION, e, xsltKey );
        }
    }


    public String transformMessageAsPlainText( String source, String xsltKey )
    {
        return transformMessageAsPlainText( source, xsltKey, ( Map<String, Object> )null );
    }


    public String transformMessageAsPlainText( String source, String xsltKey, Map<String, Object> parameters )
    {
        try
        {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            Result result = new StreamResult( bos );
            Transformer transformer = getXsltTransformer( xsltKey );
            setTransformerParameters( transformer, parameters );
            transformer.transform( new SAXSource( new InputSource( new StringReader( source ) ) ), result );
            String tempStr = bos.toString();
            bos.flush();
            bos.close();

            return tempStr;
        }

        catch ( Exception e )
        {
            throw new XsltTransformationException( ErrorCodeEnum.XSLT_HELPER_TRANSLATION_EXCEPTION, e, xsltKey );
        }
    }


    /**
     * 
     * 
     * 
     * @param source
     * 
     * @param xsltKey
     * 
     * @param result
     * 
     */

    public Document transformMessage( String source, String xsltKey )

    {
        return transformMessage( source, xsltKey, null );
    }


    public Document transformMessage( String source, String xsltKey, Map<String, Object> parameters )
    {
        try
        {
            Transformer transformer = getXsltTransformer( xsltKey );
            setTransformerParameters( transformer, parameters );
            DocumentResult result = new DocumentResult();
            transformer.transform( new SAXSource( new InputSource( new StringReader( source ) ) ), result );

            return result.getDocument();
        }
        catch ( Exception e )
        {
            throw new XsltTransformationException( ErrorCodeEnum.XSLT_HELPER_TRANSLATION_EXCEPTION, e, xsltKey );
        }
    }


    private void setTransformerParameters( Transformer transformer, Map<String, Object> parameters )

    {
        if ( parameters != null )
        {
            for ( Entry<String, Object> entry : parameters.entrySet() )
            {
                transformer.setParameter( entry.getKey(), entry.getValue() );
            }
        }
    }


    /*******************************************************************************************************************
     * 
     * Look for a compiled XSLT Transformer in the store
     * 
     * 
     * 
     * @param xsltTransformerName
     *            is the name of XSLT file for which a compiled Transformer is
     *            sought
     * 
     * @return Transformer for a compiled XSLT
     * 
     * @throws NullPointerException
     *             if no transformer template exists for transform name.
     * 
     */

    public Transformer getXsltTransformer( String xsltTransformerName )
        throws TransformerConfigurationException
    {
        return xsltTemplate.get( xsltTransformerName ).newTransformer();
    }


    public void setXsltLocations( List<String> xsltLocations )
        throws Exception
    {
        loadXsltTransformsIntoMemory( xsltLocations );
    }

}
