/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2010-2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.datasource.deployer.reader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.math.BigInteger;
import java.util.Properties;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.sax.SAXSource;

import org.ow2.jonas.datasource.deployer.DatasourceDeployable;
import org.ow2.jonas.datasource.deployer.binding.ConnectionManagerConfigurationType;
import org.ow2.jonas.datasource.deployer.binding.DatasourceConfigurationType;
import org.ow2.jonas.datasource.deployer.binding.DatasourceType;
import org.ow2.jonas.datasource.deployer.binding.Datasources;
import org.ow2.jonas.datasource.deployer.binding.ObjectFactory;
import org.ow2.jonas.dbm.internal.DBMConstants;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * This class is used to transform an XML file to java objects and then to
 * generate Properties objects from java objects.
 * @author Rodrigue SOUBEIGA
 * @author Mickael LEDUQUE
 */
public class DatasourceXmlReader {

    /**
     * Cache policy parameter name.
     */
    private static final String PSTMT_CACHE_POLICY = "jdbc.pstmtcachepolicy";

    /**
     * Cache policy default value.
     */
    private static final String DEF_PSTMT_CACHE_POLICY = "List";


    /**
     * {@inheritDoc}
     */
    public boolean isXmlValid(final File xmlFile) {
        try {
            new DatasourceValidator(xmlFile);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     * @throws java.io.FileNotFoundException
     */
    @SuppressWarnings("unchecked")
    public Datasources extractDataSources(final File xmlFile) throws JAXBException {

        if (!isXmlValid(xmlFile)) {
            throw new JAXBException("XML file is not valid.");
        }

        JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();

        // Create an XMLReader to use with our filter
        XMLReader reader;
        try {
            reader = XMLReaderFactory.createXMLReader();
        } catch (SAXException e) {
            throw new JAXBException("Unable to create XML Reader", e);
        }

        // Create the filter (to add namespace) and set the xmlReader as its parent.
        NamespaceFilter inFilter = new NamespaceFilter(DatasourceDeployable.NAMESPACE);
        inFilter.setParent(reader);

        // Prepare the input
        InputSource is;
        try {
            is = new InputSource(new FileInputStream(xmlFile));
        } catch (FileNotFoundException e) {
            throw new JAXBException("Unable to analyze file '" + xmlFile + "'.", e);
        }

        // Create a SAXSource specifying the filter
        SAXSource source = new SAXSource(inFilter, is);

        // Do unmarshalling
        Datasources theDatasources = (Datasources) unmarshaller.unmarshal(source);

        return theDatasources;
    }

    /**
     * {@inheritDoc}
     */
    public DatasourceType initDataSource(final DatasourceType dataSource) {

        this.initConnectionManagerConfiguration(dataSource.getConnectionManagerConfiguration());
        this.initDatasourceConfiguration(dataSource.getDatasourceConfiguration());

        return dataSource;
    }

    /**
     * {@inheritDoc}
     */
    public Properties getProperties(final DatasourceType dataSource) {
        // TODO use constants? (Mickael)
        // Yes, they are even defined in DBMConstants AFAIK
        Properties props = new Properties();
        DatasourceConfigurationType dsConfig = dataSource.getDatasourceConfiguration();
        ConnectionManagerConfigurationType cmConfig = dataSource.getConnectionManagerConfiguration();

        props.setProperty(DBMConstants.NAME, dsConfig.getName());
        props.setProperty(DBMConstants.URL, dsConfig.getUrl());
        props.setProperty(DBMConstants.CLASSNAME, dsConfig.getClassname());
        props.setProperty(DBMConstants.USERNAME, dsConfig.getUsername());
        props.setProperty(DBMConstants.PASSWORD, dsConfig.getPassword());
        props.setProperty(DBMConstants.MAPPERNAME, dsConfig.getMapper());
        props.setProperty(DBMConstants.DESCRIPTION, dsConfig.getDescription());
        props.setProperty(DBMConstants.ISOLATIONLEVEL, dsConfig.getIsolationlevel());

        props.setProperty(DBMConstants.CONNCHECKLEVEL, String.valueOf(cmConfig.getConnchecklevel()));
        props.setProperty(DBMConstants.CONNMAXAGE, String.valueOf(cmConfig.getConnmaxage()));
        props.setProperty(DBMConstants.MAXOPENTIME, String.valueOf(cmConfig.getMaxopentime()));
        props.setProperty(DBMConstants.CONNTESTSTMT, cmConfig.getConnteststmt());
        props.setProperty(DBMConstants.INITCONPOOL, String.valueOf(cmConfig.getInitconpool()));
        props.setProperty(DBMConstants.MINCONPOOL, String.valueOf(cmConfig.getMinconpool()));
        props.setProperty(DBMConstants.MAXCONPOOL, String.valueOf(cmConfig.getMaxconpool()));
        props.setProperty(DBMConstants.SAMPLINGPERIOD, String.valueOf(cmConfig.getSamplingperiod()));
        props.setProperty(DBMConstants.MAXWAITTIME, String.valueOf(cmConfig.getMaxwaittime()));
        props.setProperty(DBMConstants.MAXWAITERS, String.valueOf(cmConfig.getMaxwaiters()));
        props.setProperty(DBMConstants.PSTMTMAX, String.valueOf(cmConfig.getPstmtmax()));
        props.setProperty(PSTMT_CACHE_POLICY, cmConfig.getPstmtcachepolicy());

        return props;
    }

    /**
     * This method is used to initialize the fields of a
     * ConnectionmanagerConfigurationType.
     * @param cmConfig the object which fields must be initialized.
     */
    private void initConnectionManagerConfiguration(final ConnectionManagerConfigurationType cmConfig) {
        String buffer;

        cmConfig.setConnchecklevel(Integer.valueOf(initParam(DBMConstants.DEF_CONNCHECKLEVEL, cmConfig.getConnchecklevel())));

        buffer = initParam(DBMConstants.DEF_CONNMAXAGE, cmConfig.getConnmaxage());
        cmConfig.setConnmaxage(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_MAXOPENTIME, cmConfig.getMaxopentime());
        cmConfig.setMaxopentime(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_CONNTESTSTMT, cmConfig.getConnteststmt());
        cmConfig.setConnteststmt(buffer);

        buffer = initParam(DBMConstants.DEF_PSTMTMAX, cmConfig.getPstmtmax());
        cmConfig.setPstmtmax(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DEF_PSTMT_CACHE_POLICY, cmConfig.getPstmtcachepolicy());
        cmConfig.setPstmtcachepolicy(buffer);

        buffer = initParam(DBMConstants.DEF_INITCONPOOL, cmConfig.getInitconpool());
        cmConfig.setInitconpool(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_MINCONPOOL, cmConfig.getMinconpool());
        cmConfig.setMinconpool(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_MAXCONPOOL, cmConfig.getMaxconpool());
        cmConfig.setMaxconpool(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_MAXWAITTIME, cmConfig.getMaxwaittime());
        cmConfig.setMaxwaittime(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_MAXWAITERS, cmConfig.getMaxwaiters());
        cmConfig.setMaxwaiters(BigInteger.valueOf(Long.valueOf(buffer)));

        buffer = initParam(DBMConstants.DEF_SAMPLINGPERIOD, cmConfig.getSamplingperiod());
        cmConfig.setSamplingperiod(BigInteger.valueOf(Long.valueOf(buffer)));
    }

    /**
     * This method is used to initialize the fields of a
     * DatasourceConfigurationType.
     * @param dsConfig the object which fields must be initialized.
     */
    private void initDatasourceConfiguration(final DatasourceConfigurationType dsConfig) {
        String buffer;

        buffer = initParam(DBMConstants.NAME, dsConfig.getName());
        dsConfig.setName(buffer);

        buffer = initParam(DBMConstants.DEF_CLASSNAME, dsConfig.getClassname());
        dsConfig.setClassname(buffer);

        buffer = initParam(DBMConstants.DEF_URL, dsConfig.getUrl());
        dsConfig.setUrl(buffer);

        buffer = initParam(DBMConstants.DEF_DESCRIPTION, dsConfig.getDescription());
        dsConfig.setDescription(buffer);

        buffer = initParam(DBMConstants.DEF_USERNAME, dsConfig.getUsername());
        dsConfig.setUsername(buffer);

        buffer = initParam(DBMConstants.DEF_PASSWORD, dsConfig.getPassword());
        dsConfig.setPassword(buffer);

        buffer = initParam(DBMConstants.DEF_ISOLATIONLEVEL, dsConfig.getIsolationlevel());
        dsConfig.setIsolationlevel(buffer);

        buffer = initParam(DBMConstants.DEF_MAPPERNAME, dsConfig.getMapper());
        dsConfig.setMapper(buffer);

    }

    /**
     * Returns the value of the parameter if it is not null or a default
     * constant if not.
     * @param paramDefaut represents the default constant value of the
     *        parameter.
     * @param paramUser represents the old value of the parameter.
     * @return a string which is the value of the parameter.
     */
    private String initParam(final String paramDefaut, final Object paramUser) {

        if (paramUser == null) {
            return paramDefaut;
        } else {
            return String.valueOf(paramUser);
        }

    }
}

