package org.apache.torque.generator.configuration.outlet;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.torque.generator.configuration.ConfigurationException;
import org.apache.torque.generator.configuration.ConfigurationHandlers;
import org.apache.torque.generator.configuration.ConfigurationProvider;
import org.apache.torque.generator.configuration.UnitDescriptor;
import org.apache.torque.generator.configuration.mergepoint.MergepointMapping;
import org.apache.torque.generator.configuration.paths.ProjectPaths;
import org.apache.torque.generator.outlet.Outlet;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;

/**
 * Parses outlet configuration files and creates a OutletConfiguration.
 */
public class OutletConfigurationXmlParser
{
    /**
     * The SaxParserFactory to create the sax parsers for reading in the
     * outlet configuration files.
     */
    private static SAXParserFactory saxFactory;

    /** The logger. */
    private static Log log
            = LogFactory.getLog(OutletConfigurationXmlParser.class);

    static
    {
        saxFactory = SAXParserFactory.newInstance();
        saxFactory.setNamespaceAware(true);
        try
        {
            saxFactory.setFeature(
                    "http://xml.org/sax/features/validation",
                    true);
            saxFactory.setFeature(
                    "http://apache.org/xml/features/validation/schema", true);
        }
        catch (SAXNotSupportedException e)
        {
            throw new RuntimeException(e);
        }
        catch (SAXNotRecognizedException e)
        {
            throw new RuntimeException(e);
        }
        catch (ParserConfigurationException e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * Reads all outlet configuration files and creates the outlet
     * configuration from them.
     * All the outlet configuration files known to the provide are parsed.
     * These are typically all XML files in the outletDefintiton configuration
     * directory and its subdirectories.
     *
     * @param configurationProvider The access object for the configuration
     *        files, not null.
     * @param configurationHandlers the handlers for reading the configuration,
     *        not null.
     * @param unitDescriptor the unit descriptor, not null.
     *
     * @return the outlet configuration.
     *
     * @throws ConfigurationException if the Configuration cannot be read
     *         or errors exists in the outlet configuration files.
     */
    public OutletConfiguration readOutletConfiguration(
            ConfigurationProvider configurationProvider,
            ConfigurationHandlers configurationHandlers,
            UnitDescriptor unitDescriptor)
        throws ConfigurationException
    {
        if (configurationHandlers == null)
        {
            log.error("OutletConfiguration: "
                    + " configurationHandlers is null");
            throw new NullPointerException("configurationHandlers is null");
        }
        if (configurationProvider == null)
        {
            log.error("OutletConfiguration: "
                    + " configurationProvider is null");
            throw new NullPointerException("configurationProvider is null");
        }

        List<Outlet> allOutlets = new ArrayList<Outlet>();
        List<MergepointMapping> allMergepointMappings
                = new ArrayList<MergepointMapping>();

        // Outlets from all files
        Collection<String> outletConfigNames
                = configurationProvider.getOutletConfigurationNames();

        for (String outletConfigName : outletConfigNames)
        {
            InputStream inputStream = null;
            try
            {
                inputStream
                        = configurationProvider.getOutletConfigurationInputStream(
                            outletConfigName);
                OutletConfigFileContent fileContent
                        = readOutletConfig(
                                inputStream,
                                configurationProvider,
                                unitDescriptor.getProjectPaths(),
                                configurationHandlers);
                allOutlets.addAll(fileContent.getOutlets());
                allMergepointMappings.addAll(
                        fileContent.getMergepointMappings());
            }
            catch (SAXParseException e)
            {
                throw new ConfigurationException(
                        "Error parsing outlet configuration "
                            + outletConfigName
                            + " at line "
                            + e.getLineNumber()
                            + " column "
                            + e.getColumnNumber()
                            + " : "
                            + e.getMessage(),
                        e);

            }
            catch (Exception e)
            {
                throw new ConfigurationException(
                        "Error parsing outlet configuration "
                            + outletConfigName,
                        e);
            }
            finally
            {
                if (inputStream != null)
                {
                    try
                    {
                        inputStream.close();
                    }
                    catch (IOException e)
                    {
                        log.warn("Could not close "
                                    + "outletConfigurationInputStream "
                                    + outletConfigName,
                                e);
                    }
                }
            }
        }
        return new OutletConfiguration(
                allOutlets,
                allMergepointMappings,
                unitDescriptor);
    }


    /**
     * Reads a outlet configuration file and returns the outlets
     * and isolated mergepoint mappings which are configured in this file.
     *
     * @param outletConfigurationInputStream the stream containing the
     *        outlet configuration.
     * @param configurationProvider The access object for the configuration
     *        files, not null.
     * @param projectPaths The paths of the surrounding project, not null.
     * @param configurationHandlers the handlers for reading the configuration,
     *        not null.
     *
     * @return All the outlets and isolated mergepoint mappings
     *         configured in the file.
     * @throws SAXException if an error occurs while parsing the configuration
     *         file.
     * @throws IOException if the file cannot be read.
     * @throws ParserConfigurationException if a serious parser configuration
     *         error occurs..
     */
    private OutletConfigFileContent readOutletConfig(
            InputStream outletConfigurationInputStream,
            ConfigurationProvider configurationProvider,
            ProjectPaths projectPaths,
            ConfigurationHandlers configurationHandlers)
        throws SAXException, IOException, ParserConfigurationException
    {
        SAXParser parser = saxFactory.newSAXParser();
        OutletConfigurationSaxHandler saxHandler
                = new OutletConfigurationSaxHandler(
                        configurationProvider,
                        projectPaths,
                        configurationHandlers);
        InputSource is = new InputSource(outletConfigurationInputStream);
        parser.parse(is, saxHandler);

        return new OutletConfigFileContent(
                saxHandler.getOutlets(),
                saxHandler.getMergepointMappings());
    }

    /**
     * The parsed content of a outlet definition file.
     * Contains the parsed outlets and the isolated
     * (i.e. outside outlet context) mergepoints.
     */
    private static final class OutletConfigFileContent
    {
        /** The parsed outlets. */
        private final List<Outlet> outlets;

        /** The parsed isolated mergepoint mappings. */
        private final List<MergepointMapping> mergepointMappings;

        /**
         * Constructor.
         *
         * @param outlets the parsed outlets.
         * @param mergepointMappings the isolated mergepoint mappings.
         */
        public OutletConfigFileContent(
                List<Outlet> outlets,
                List<MergepointMapping> mergepointMappings)
        {
            this.outlets = outlets;
            this.mergepointMappings = mergepointMappings;
        }

        /**
         * Returns the parsed outlets.
         *
         * @return the parsed outlets.
         */
        public List<Outlet> getOutlets()
        {
            return outlets;
        }

        /**
         * Returns the parsed isolated mergepoint mappings.
         *
         * @return the isolated mergepoint mappings.
         */
        public List<MergepointMapping> getMergepointMappings()
        {
            return mergepointMappings;
        }
    }
}
