/*
 * 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.
 */
package org.apache.jetspeed.components.datasource;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * DBCPDatasourceComponent
 * </p>
 * 
 * 
 * @
 * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
 * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
 * @version $Id: DBCPDatasourceComponent.java 996614 2010-09-13 17:47:49Z rwatler $
 *
 */
public class DBCPDatasourceComponent implements DatasourceComponent
{

    private static final Logger log = LoggerFactory.getLogger(DBCPDatasourceComponent.class);

    protected PoolingDataSource dataSource;
    //protected DataSource dataSource;
    
    private String user;
    
    private String password;
    
    private String driverName;
    
    private String connectURI;    
    
    private int maxActive;
    
    private int maxWait;
    
    private byte whenExhausted;
    
    private boolean autoCommit;
    
    private PoolableConnectionFactory dsConnectionFactory;
    /**
     * 
     * Creates a simple commons DBCP connection pool using the following
     * parameters.
     * <p>
     * If you need to bind the datasource of this component to 
     * JNDI please @see org.apache.jetspeed.components.jndi.JNDIComponent
     * </p>
     * 
     * @param user User name that will be used to connect to the DB
     * @param password Password that will be used to connect to the DB
     * @param driverName Fully qualified driver to be used by the connection pool
     * @param connectURI Fully qualified URI to the DB.
     * @param maxActive Maximum active connection
     * @param maxWait if <code>whenExhausted</code> is set to GenericObjectPool.WHEN_EXHAUSTED_BLOCK
     * the length of time to block while waiting for a connection to become 
     * available.
     * @param whenExhausted GenericObjectPool.WHEN_EXHAUSTED_BLOCK, GenericObjectPool.WHEN_EXHAUSTED_GROW or
     * GenericObjectPool.WHEN_EXHAUSTED_FAIL. @see org.apache.commons.pooling.GenericObjectPool
     * for more information on these settings
     * @param autoCommit Whether or not this datasource will autocommit
     * @throws ClassNotFoundException If the <code>driverName</code> could not be
     * located within any classloaders.
     */
    public DBCPDatasourceComponent(
        String user,
        String password,
        String driverName,
        String connectURI,
        int maxActive,
        int maxWait,
        byte whenExhausted,
        boolean autoCommit)
        
    {

        log.info("Setting up data source pooling for " + driverName);

        log.info("Max active connnections set to: " + maxActive);

        log.info("Pool is set to \"" + whenExhausted + "\" when all connections are exhausted.");
		
		this.user = user;
		this.password = password;
		this.driverName = driverName;
		this.connectURI = connectURI;
		this.maxActive = maxActive;
		this.maxWait = maxWait;
		this.autoCommit = autoCommit;
    }

    /**
     * 
     * <p>
     * getDatasource
     * </p>
     * 
     * <p>
     *   returns the datasource created by this component
     * </p>
     * @return
     *
     */
    public DataSource getDatasource()
    {
        return dataSource;
    }

    public void start()
    {

        try
        {
            log.info("Attempting to start DBCPCDatasourceComponent.");            
            Class.forName(driverName);
            
            // Validate the connection before we go any further
            try
            {
                Connection conn = DriverManager.getConnection(connectURI, user, password);
                conn.close();
            }
            catch(Exception e)
            {
                log.error("Unable to obtain a connection database via URI: "+connectURI, e);
                throw e;
            }
            
            ObjectPool connectionPool = new GenericObjectPool(null, maxActive, whenExhausted, maxWait);
            
            ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectURI, user, password);
            
            dsConnectionFactory = new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, autoCommit);
            
            dataSource = new PoolingDataSource(connectionPool);            
            
            log.info("DBCPCDatasourceComponent successfuly started!");
        }
        catch (Throwable e)
        {
            CharArrayWriter caw = new CharArrayWriter();
            e.printStackTrace(new PrintWriter(caw));
            String msg = "Unable to start DBCPCDatasourceComponent: ";
            log.error(msg+e.toString(), e);
            throw new IllegalStateException(msg+caw.toString());
        }
    }

    public void stop()
    {
        try
        {
        	if (log.isInfoEnabled()) {
	        	log.info("Stopping DBCPCDatasourceComponent");
        	}
            dsConnectionFactory.getPool().close();

            // Support for using an embedded Derby database multiple times from one JVM
            // by properly shutting it down after each (test) run
            if (driverName.equals("org.apache.derby.jdbc.EmbeddedDriver"))
            {
                if (log.isInfoEnabled()) {
                	log.info("Shutting down derby ...");
                }
                String shutDownURI = null;
                int parIndex = connectURI.indexOf(";");
                if (parIndex > -1)
                {
                    shutDownURI = connectURI.substring(0, parIndex)+";shutdown=true";
                }
                else
                {
                    shutDownURI = connectURI+";shutdown=true";
                }
                Class dc = Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
                Driver driver = (Driver)dc.newInstance();                
                Properties info = new Properties();
                info.put( "user", user );
                info.put( "password", password );
                
                driver.connect(shutDownURI, info);
            }

        }
		 catch (SQLException e)  {
		    if ( driverName.equals("org.apache.derby.jdbc.EmbeddedDriver") && (e.getSQLState().equals("XJ015") ||  e.getSQLState().equals("08006"))) {
		    	if (log.isDebugEnabled()) {
		    		log.debug("Database shut down normally with sql state '" + e.getSQLState() + "'.");
		    	}
		    } else {
	        	IllegalStateException ise =
	                new IllegalStateException("Unable to safely shutdown the DBCPConnection pool: " + e.toString());
	            ise.initCause(e);
	            try
	            {
	                log.error("Unable to safely shutdown the DBCPConnection pool", ise);
	            }
	            catch (Exception e1)
	            {
	                // ignore if logger itself is gone too
	            }
		    }
		 }
        catch (Exception e)
        {
        	IllegalStateException ise =
                new IllegalStateException("Unable to safely shutdown the DBCPConnection pool: " + e.toString());
            ise.initCause(e);
            try
            {
                log.error("Unable to safely shutdown the DBCPConnection pool", ise);
            }
            catch (Exception e1)
            {
                // ignore if logger itself is gone too
            }
        }
    }
}
