package org.apache.torque;

/*
 * 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.sql.Connection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import junit.framework.TestCase;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.torque.criteria.Criteria;
import org.apache.torque.dsfactory.AbstractDataSourceFactory;
import org.apache.torque.dsfactory.DataSourceFactory;
import org.apache.torque.dsfactory.JndiDataSourceFactory;
import org.apache.torque.test.peer.BookPeer;

/**
 * Test for configuration using JNDI. The test assumes that we have a
 * non-jndi configuration, and creates the data source configuration from it.
 * It is tested whether we can bind a data source manually into jndi and
 * retrieve it in Torque, and also it is tested whether we can bind a
 * data source using Torque.
 * Note: This test does not extend BaseRuntimeTestCase as we do not want Torque
 * to be initialized.
 *
 * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
 * @version $Id $
 */
public class JndiConfigurationTest extends TestCase
{
    /**
     * The subcontext where the data source is bound.
     */
    protected static final String JNDI_SUBCONTEXT = "jdbc";

    /**
     * the full jndi path to the data source.
     */
    protected static final String JNDI_PATH
            = JNDI_SUBCONTEXT + "/" + "jndiTestDataSource";

    public void setUp()
    {
        // super.setUp() initializes torque, but here we want to
        // do that ourselves
    }

    /**
     * Tests whether our home-made Data Source works.
     * @throws Exception if the test fails.
     */
    public void testDataSource() throws Exception
    {
        BasicDataSource dataSource = null;
        try
        {
            dataSource = getDataSource();
            dataSourceConnect(dataSource);
        }
        finally
        {
            if (dataSource != null)
            {
                dataSource.close();
            }
        }
    }

    /**
     * Binds a DataSource to the jndi and checks that we have successfully
     * bound it. Then Torque is configured to lookup the DataSource in jndi,
     * and it is checked if Torque can read from the database. Finally,
     * the DataSource is closed and unbound.
     * @throws Exception if the test fails
     */
    public void testExternalBindTorqueLookup() throws Exception
    {
        // compose the correct configuration
        Configuration torqueConfiguration = getTorqueConfiguraton();
        String defaultDatabase = getDefaultDatabase(torqueConfiguration);

        // remove the dsfactory configuration from the configuration
        {
            Configuration dsfactoryConfiguration = torqueConfiguration.subset(
                    Torque.TORQUE_KEY + "."
                    + DataSourceFactory.DSFACTORY_KEY + "."
                    + defaultDatabase);
            dsfactoryConfiguration.clear();
        }

        // add the jndi configuration to the configuration
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + DataSourceFactory.FACTORY_KEY,
                JndiDataSourceFactory.class.getName());
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + JndiDataSourceFactory.JNDI_KEY + "."
                + JndiDataSourceFactory.PATH_KEY,
                JNDI_PATH);
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + JndiDataSourceFactory.JNDI_KEY + "."
                + Context.INITIAL_CONTEXT_FACTORY,
                org.apache.naming.java.javaURLContextFactory.class.getName());

        //System.out.println("Configuration for testExternalBindTorqueLookup:");
        //debugConfiguration(torqueConfiguration);
        //System.out.println();

        try
        {
            // bind datasource and check bind.
            bindDataSource();
            BasicDataSource dataSource = retrieveDataSource();
            dataSourceConnect(dataSource);


            if (Torque.isInit())
            {
                Torque.shutdown();
            }
            // initialize torque with the created configuration
            // and check that we can connect to the database.
            try
            {
                Torque.init(torqueConfiguration);
                torqueConnect();
            }
            finally
            {
                Torque.shutdown();
            }
        }
        finally
        {
            unbindDataSource();
        }
    }

    /**
     * Binds a DataSource to the jndi and checks that we have successfully
     * bound it. Then Torque is configured to lookup the DataSource in jndi,
     * and it is checked if Torque can read from the database. Finally,
     * the DataSource is closed and unbound.
     * @throws Exception if the test fails
     */
    public void testTorqueBindTorqueLookup() throws Exception
    {
        // compose the correct configuration
        Configuration torqueConfiguration = getTorqueConfiguraton();
        String defaultDatabase = getDefaultDatabase(torqueConfiguration);

        // add the jndi configuration to the configuration
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + DataSourceFactory.FACTORY_KEY,
                JndiDataSourceFactory.class.getName());
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + JndiDataSourceFactory.JNDI_KEY + "."
                + JndiDataSourceFactory.PATH_KEY,
                JNDI_PATH);
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + JndiDataSourceFactory.JNDI_KEY + "."
                + Context.INITIAL_CONTEXT_FACTORY,
                org.apache.naming.java.javaURLContextFactory.class.getName());

        // add the datasource configuration
        torqueConfiguration.setProperty(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + JndiDataSourceFactory.DATASOURCE_KEY + "."
                + JndiDataSourceFactory.CLASSNAME_KEY,
                BasicDataSource.class.getName());
        {
            Map tempStore = new HashMap();
            Configuration connectionConfiguration
                    = torqueConfiguration.subset(
                        Torque.TORQUE_KEY + "."
                        + DataSourceFactory.DSFACTORY_KEY + "."
                        + defaultDatabase + "."
                        + AbstractDataSourceFactory.CONNECTION_KEY);
            for (Iterator keyIt = connectionConfiguration.getKeys();
               keyIt.hasNext();)
            {
                String key = (String) keyIt.next();
                String value = connectionConfiguration.getString(key);

                if ("user".equals(key))
                {
                    // setUser() in SharedPoolDataSouce corresponds to
                    // setUsername() in BasicDataSourceFactory
                    key = "username";
                }
                else if ("driver".equals(key))
                {
                    // setDriver() in SharedPoolDataSouce corresponds to
                    // setDriverClassName() in BasicDataSourceFactory
                    key = "driverClassName";
                }
                tempStore.put(
                        Torque.TORQUE_KEY + "."
                        + DataSourceFactory.DSFACTORY_KEY + "."
                        + defaultDatabase + "."
                        + JndiDataSourceFactory.DATASOURCE_KEY + "."
                        + key,
                        value);
            }
            // add the new keys
            for (Iterator keyIt = tempStore.keySet().iterator();
                keyIt.hasNext(); )
            {
                String key = (String) keyIt.next();
                String value = (String) tempStore.get(key);
                torqueConfiguration.setProperty(key, value);
            }

            // remove the configuration for the original datasource
            connectionConfiguration.clear();
            Configuration poolConfiguration
                = torqueConfiguration.subset(
                        Torque.TORQUE_KEY + "."
                        + DataSourceFactory.DSFACTORY_KEY + "."
                        + defaultDatabase + "."
                        + AbstractDataSourceFactory.POOL_KEY);
            poolConfiguration.clear();
        }

        //System.out.println("Configuration for testTorqueBindTorqueLookup:");
        //debugConfiguration(torqueConfiguration);
        //System.out.println();

        try
        {
            // initialize torque with the created configuration
            // and check that we can connect to the database.
            try
            {
                Torque.init(torqueConfiguration);
                torqueConnect();
            }
            finally
            {
                Torque.shutdown();
            }
        }
        finally
        {
            unbindDataSource();
        }
    }

    /**
     * creates and binds a BasicDataSource into jndi.
     * @throws Exception if DataSource creation or binding fails.
     */
    protected void bindDataSource() throws Exception
    {
        BasicDataSource dataSource = getDataSource();
        Context context = getInitialContext();
        context.createSubcontext(JNDI_SUBCONTEXT);
        context.bind(JNDI_PATH, dataSource);
    }

    /**
     * Retrieves a BasicDataSource from jndi.
     * @throws Exception if the jndi lookup fails or no DataSource is bound.
     */
    protected BasicDataSource retrieveDataSource() throws Exception
    {
        Context context = getInitialContext();
        BasicDataSource dataSource
                = (BasicDataSource) context.lookup(JNDI_PATH);

        if (dataSource == null)
        {
            fail("DataSource should not be null");
        }
        return dataSource;
    }

    /**
     * unbinds and closes the BasicDataSource in jndi.
     * @throws Exception if creation or binfding fails.
     */
    protected void unbindDataSource() throws Exception
    {
        Context context = getInitialContext();
        BasicDataSource dataSource
                = (BasicDataSource) context.lookup(JNDI_PATH);

        try
        {
            if (dataSource != null)
            {
                dataSource.close();
            }
        }
        finally
        {
            context.unbind(JNDI_PATH);
        }
    }

    /**
     * Creates a Data Source from the Torque configuration without using Torque.
     * @return a SharedPoolDataSource source.
     * @throws Exception if we cannot create a Data source.
     */
    protected BasicDataSource getDataSource() throws Exception
    {
        Configuration torqueConfiguration = getTorqueConfiguraton();
        String defaultDatabase = getDefaultDatabase(torqueConfiguration);
        Configuration dsfactoryConfiguration = torqueConfiguration.subset(
                Torque.TORQUE_KEY + "."
                + DataSourceFactory.DSFACTORY_KEY + "."
                + defaultDatabase + "."
                + AbstractDataSourceFactory.CONNECTION_KEY);

        BasicDataSource dataSource = new BasicDataSource();
        for (Iterator i = dsfactoryConfiguration.getKeys(); i.hasNext();)
        {
            String key = (String) i.next();
            String stringValue = dsfactoryConfiguration.getString(key);

            if ("user".equals(key))
            {
                // setUser() in SharedPoolDataSouce corresponds to
                // setUsername() in BasicDataSourceFactory
                key = "username";
            }
            else if ("driver".equals(key))
            {
                // setDriver() in SharedPoolDataSouce corresponds to
                // setDriverClassName() in BasicDataSourceFactory
                key = "driverClassName";
            }

            Class propertyType =
                    PropertyUtils.getPropertyType(dataSource, key);
            Object value =
                    ConvertUtils.convert(stringValue, propertyType);
            PropertyUtils.setSimpleProperty(dataSource, key, value);
        }

        return dataSource;
    }

    /**
     * checks whether we can retrieve a connection from a DataSource.
     * @throws Exception if no connection can be established.
     */
    protected void dataSourceConnect(DataSource dataSource) throws Exception
    {
        Connection connection = null;
        try
        {
            connection = dataSource.getConnection();
            connection.close();
            connection = null;
        }
        finally
        {
            if (connection != null)
            {
                connection.close();
            }
        }
    }

    /**
     * checks whether we can connect to the database via Torque.
     * @throws Exception if no connection can be established.
     */
    protected void torqueConnect() throws Exception
    {
        Connection connection = null;
        try
        {
        	connection = Torque.getConnection();
            BookPeer.doSelect(new Criteria(), connection);
            connection.close();
            connection = null;
        }
        finally
        {
        	if (connection != null)
            {
        		connection.close();
            }
        }
    }

    /**
     * Retrieves (or creates if it does not exist) an InitialContext.
     * @return the InitialContext.
     * @throws NamingException if the InitialContext cannot be retrieved
     *         or created.
     */
    protected InitialContext getInitialContext() throws NamingException
    {
        Hashtable environment = new Hashtable();
        environment.put(
                Context.INITIAL_CONTEXT_FACTORY,
                org.apache.naming.java.javaURLContextFactory.class.getName());
        InitialContext context = new InitialContext(environment);
        return context;
    }

    /**
     * Retrieves the path for Torque's configuration from the System Properties,
     * loads the configuration and returns it.
     * @return Torque's configuration.
     * @throws ConfigurationException if the configuration cannot be loaded.
     */
    protected static Configuration getTorqueConfiguraton()
            throws ConfigurationException
    {
        Configuration configuration = new PropertiesConfiguration(
                System.getProperty(
                        BaseDatabaseTestCase.CONFIG_FILE_SYSTEM_PROPERTY));
        return configuration;
    }

    /**
     * extraxts the default Database out of Torque's configuration.
     * @param torqueConfiguration the Torque configuration
     * @return the default Data Source.
     */
    protected static String getDefaultDatabase(
            Configuration torqueConfiguration)
    {
        String defaultDatabase = torqueConfiguration.getString(
                Torque.TORQUE_KEY + "."
                + Torque.DATABASE_KEY + "."
                + Torque.DEFAULT_KEY);
        return defaultDatabase;
    }

    /**
     * Prints the contents of the configuration to System.out
     * @param configuration the configuration to be debugged.
     */
    public static void debugConfiguration(Configuration configuration)
    {
        for (Iterator dsKeyIt = configuration.getKeys();
        dsKeyIt.hasNext(); )
        {

            String key = (String) dsKeyIt.next();
            System.out.println(key + " = " + configuration.getString(key));
        }
    }
}
