/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed 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.

====================================================================== */

package org.bodington.database;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.commons.dbcp.BasicDataSource;

/**
 * Wraps up the commons-dbcp for use in Bodington. This class attempts to set
 * the configuration of the commons-dbcp through reflection so that all the 
 * properties supported by commons-dbcp are supported here. All properties should
 * be prefixed with <code>sqldatabase.dbcp</code>.
 * <p>
 * 
 * Example configuration:<br>
 * <pre>
 * sqldatabase.dbcp.url=jdbc:postgres://localhost/weblearn
 * sqldatabase.dbcp.username=myusername
 * sqldatabase.dbcp.password=mypassword
 * sqldatabase.dbcp.driverClassName=org.postgresql.Driver
 * </pre>
 * 
 * @author buckett
 */
public class CommonsConnectionPool implements ConnectionPool
{
    
    private final static String PROPERTIES_PREFIX = "sqldatabase.dbcp.";
    
    BasicDataSource dataSource;

    /**
     * Create a connection pool using the commons-dbcp library.
     * @throws ConnectionPoolException If we unable to find the commons-dbcp classes.
     */
    public CommonsConnectionPool( Properties bodingtonProperties ) throws ConnectionPoolException
    {
      
        try
        {
            Class.forName("org.apache.commons.dbcp.BasicDataSource");
        }
        catch (ClassNotFoundException cnfe)
        {
            throw new ConnectionPoolException("Unable to find the commons dbcp classes.");
        }
        
        dataSource =  new BasicDataSource();
        
        Properties properties = beginingWith(bodingtonProperties, PROPERTIES_PREFIX);
        Enumeration keys = properties.keys();
            
        while (keys.hasMoreElements())
        {
            final String key = (String)keys.nextElement();
            final String value = properties.getProperty(key);
            
            // Not interested in empty values.
            if (value == null || value.length() < 1)
                continue;
            
            try
            {
                // Rather than throwing exceptions we should return null from
                // the various getNumber/getBoolean routines
                Long longValue = new Long(0);
                try
                {
                   longValue = getNumber(value);
                    callMethod(key, Long.TYPE, longValue);
                    continue;
                }
                catch (NoSuchMethodException nsme)
                {
                    callMethod(key, Integer.TYPE, new Integer(longValue.intValue()));
                    continue;
                }
                catch (IllegalArgumentException iae)
                {}
                try
                {
                    final Boolean booleanValue = getBoolean(value);
                    callMethod(key, Boolean.TYPE, booleanValue);
                    continue;
                }
                catch (IllegalArgumentException iae)
                {}
                callMethod(key, String.class, value);
            }
            catch (Exception e)
            {}
        }
    }

    /**
     * Gets a connection object.
     * @throws ConnectionPoolException If there we are unable to get a connection. 
     */
    public Connection getConnection(Object user) throws ConnectionPoolException
    {
        try
        {
            return dataSource.getConnection();
        }
        catch (SQLException sqle)
        {
            throw new ConnectionPoolException(sqle.getMessage());
        }
    }

    /**
     * Returns the connection to the pool.
     */
    public void freeConnection(Connection free_con)
        throws ConnectionPoolException
    {
        try
        {
            free_con.close();
        }
        catch (SQLException sqle)
        {}
    }

    /**
     * Gets only the properties that begin with the selected prefix.
     * The returned properties object keys don't have the prefix any more.
     * @param allProperties The properties to look through.
     * @param prefix The prefix to look for.
     * @return A new properties object containing onlye those that started with the prefix.
     */
    private static Properties beginingWith(Properties allProperties, String prefix)
    {
        Properties selectedProperties = new Properties();
        Enumeration keys = allProperties.keys();
        while (keys.hasMoreElements())
        {
            final String key = (String)keys.nextElement();
            if (key.startsWith(prefix))
            {
                selectedProperties.put(key.substring(prefix.length()), allProperties.get(key));
            }
            
        }
        return selectedProperties;
    }
    
    private static Long getNumber(String value)
    {
        try 
        {
           return new Long(Long.parseLong(value)); 
        }
        catch (NumberFormatException nfe)
        {
          throw new IllegalArgumentException();  
        }
    }
    
    private static Boolean getBoolean(String value)
    {
        if (value.equalsIgnoreCase("true"))
            return Boolean.valueOf(true);
        if (value.equalsIgnoreCase("false"))
            return Boolean.valueOf(false);
        throw new IllegalArgumentException();
    }
    
    private static String getMethodForProperty(String property)
    {
        return "set"+ Character.toUpperCase(property.charAt(0))+ property.substring(1);
    }
    
    private void callMethod(String property, Class clazz, Object value) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
    {
        BasicDataSource.class.getMethod(getMethodForProperty(property),
            new Class[]{clazz}).invoke(dataSource, new Object[]{value});
    }
    
    /**
     * Method to allow unit testing of the configuration.
     * @return The internal DataSource object.
     */
    BasicDataSource getBasicDataSource()
    {
        return dataSource;
    }
}
