/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
 */
package fr.gouv.culture.sdx.pipeline;

import java.io.File;
import java.io.IOException;

import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.TransformerHandler;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.LogEnabled;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.excalibur.source.ModifiableSource;
import org.apache.excalibur.xml.xslt.XSLTProcessor;
import org.apache.excalibur.xml.xslt.XSLTProcessorException;
import org.xml.sax.SAXException;

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.configuration.ConfigurationUtils;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;

/**
 * An XSLTTransformation used in a SDX pipeline.
 *
 * This abstract class handles most of the work. It will only let subclasses
 * handle the choice of a XSLT processor factory, in order to choose a specific
 * XSLT processor if needed.
 */
public abstract class AbstractXSLTTransformation extends AbstractTransformation {

    /** Trax processor to use in the transformation. */
    private XSLTProcessor xsltProcessor;

    /** InputSource for the transformation stylesheet. */
    private ModifiableSource inputSource;

    /** TransformerHandler built from the TraxProcessor and the stylesheet. */
    private TransformerHandler transformerHandler;

    /**Builds this object
     *
     * In addition to the parameters needed in the base configuration handled
     * by the parent class, the "src" parameter is required and can be absolute
     * or relative to the directory containing the application.xconf file
     * or relative to the SDX installation.
     *
     * @param configuration     An Configuration object from the Pipeline
     *
     *<p> Sample configuration entry:
     * <p>&lt;sdx:transformation src = "path to stylesheet, can be absolute or relative to the directory containing this file" sdx:id = "step3" sdx:type = "xslt" keep = "true"/>
     */
    public void configure(Configuration configuration) throws ConfigurationException {

        try {

            // Basic configuration from super class
            super.configure(configuration);

            // The src attribute contains the URI of the XSLT stylesheet
            String src = configuration.getAttribute(Transformation.ATTRIBUTE_SRC);
            ConfigurationUtils.checkConfAttributeValue(Transformation.ATTRIBUTE_SRC, src, configuration.getLocation());
            File srcFile = null;
            try {
                srcFile = Utilities.resolveFile(null, configuration.getLocation(), super.getContext(), src, false);
            } catch (SDXException e) {
                throw new ConfigurationException(e.getMessage(), e);
            }
            src = srcFile.toURI().toURL().toExternalForm();

            // We get an XSLT processor to use
            XSLTProcessor xsltProcessor = (XSLTProcessor) this._manager.lookup(XSLTProcessor.ROLE);
            this.xsltProcessor = xsltProcessor;

            // We set the appropriate factory. If null, the first one in the classpath will be used.
            // This is handled by subclasses of this abstratc class
            this.xsltProcessor.setTransformerFactory(getTransformerFactory());

            // We keep the XSLT stylesheet source
            this.inputSource = (ModifiableSource) this._resolver.resolveURI(src);

        } catch (IOException e) {
            LoggingUtils.logException(super.getLog(), e);
            throw new ConfigurationException(e.getMessage(), e.fillInStackTrace());
        } catch (ServiceException e) {
            LoggingUtils.logException(super.getLog(), e);
            throw new ConfigurationException(e.getMessage(), e.fillInStackTrace());
        }
    }

    /**Sets the XMLConsumer for this transformation.
     *
     * @param consumer  The consumer for the SAX events of this transformation
     */
    public void setConsumer(XMLConsumer consumer) {
        //verifying the consumer
        try {
            Utilities.checkXmlConsumer(super.getLog(), consumer);
        } catch (SDXException e) {
            /*doing nothing here as the exception should have been logged,
            as we are governed by superClasses for the exceceptions signatures*/
        }

        //refreshing the input source so that any changes in the stylesheet will be reflected
        this.inputSource.refresh();


        try {
            //getting a transform handler based from the processor and stylesheet input source
            //is this necessary every time we set consumer, as the inputSource is cached?-rbp
            transformerHandler = xsltProcessor.getTransformerHandler(inputSource);
            //applying parameters
            if (this.getParameters() != null) {
                //getting the transformer
                javax.xml.transform.Transformer trans = transformerHandler.getTransformer();
                //clearing any existing parameters
                //TODO?:should this be done here, or do we risk destroying something we may need?-rbp
                trans.clearParameters();
                //getting the params
                Parameters params = this.getParameters();
                //getting the param names
                String[] paramNames = params.getNames();
                //setting the params
                for (int i = 0; i < paramNames.length; i++) {
                    try {
                        trans.setParameter(paramNames[i], (String) params.getParameter(paramNames[i]));
                    } catch (ParameterException e) {
                        LoggingUtils.logException(super.getLog(), e);
                    }
                }
            }
        } catch (XSLTProcessorException e) {
            LoggingUtils.logException(super.getLog(), e);
        }

        //setting the content handler
        super.setContentHandler(transformerHandler);
        //setting the lexical handler
        super.setLexicalHandler(transformerHandler);

        if (transformerHandler instanceof LogEnabled) {
            //TODO?:does transformhandler implement logEnabled?-rbp
            ((LogEnabled) transformerHandler).enableLogging(super.getLog());
        }

        // According to TrAX specs, all TransformerHandlers are LexicalHandlers
        //creating a result object for this tansformation
        SAXResult result = new SAXResult(consumer);
        //setting consumer of the result's lexical handler
        result.setLexicalHandler(consumer);
        //passing the  result object to the transformHandler
        if (this.transformerHandler != null)//TODOException: send a message here as we could not get an input source
            transformerHandler.setResult(result);

        //setting the consumer of the transformation
        super.xmlConsumer = consumer;
    }

    public void startDocument()
            throws SAXException {
        this.setConsumer(super.xmlConsumer);
        super.startDocument();
    }

    /* (non-Javadoc)
     * @see fr.gouv.culture.sdx.utils.xml.AbstractSdxXMLPipe#initToSax()
     */
    protected boolean initToSax() {
        if(!super.initToSax())
            return false;
        else{
            this._xmlizable_objects.put("Name",this.getClass().getName());
            return true;
        }
    }

    /**
     * Returns the XSLT processor factory to use (Xalan, Saxon, ...)
     *
     * @return  The factory class name or null to use the first one in the classpath
     */
    public abstract String getTransformerFactory();
}

