/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2016 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports 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 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports 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 JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.data.hibernate;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.connection.ConnectionProvider;
import org.hibernate.connection.ConnectionProviderFactory;
import org.hibernate.connection.DriverManagerConnectionProvider;
import org.hibernate.util.PropertiesHelper;
import org.hibernate.util.ReflectHelper;

/**
 * This connection provider is a modified version of the original {@link DriverManagerConnectionProvider} class
 * in order to be used inside an Eclipse based environment like Jaspersoft Studio.
 * 
 * This connection provider does <b>NOT USE</b> the {@link DriverManager}.
 * A rudimentary connection pool is implemented like in the original class.
 *  
 * @author Massimo Rabbi (mrabbi@users.sourceforge.net)
 *
 */
public class HibernateConnectionProvider implements ConnectionProvider {

    private String url;
    private Properties connectionProps;
    private Integer isolation;
    private final ArrayList<Connection> pool = new ArrayList<Connection>();
    private int poolSize;
    private int checkedOut = 0;
    private boolean autocommit;
    private Driver driver;

    private static final Log log = LogFactory.getLog(HibernateConnectionProvider.class);

    @Override
    public void configure(Properties props) throws HibernateException {

        String driverClass = props.getProperty(Environment.DRIVER);

        poolSize = PropertiesHelper.getInt(Environment.POOL_SIZE, props, 20); //default pool size 20
        log.info("Using Hibernate built-in connection pool (not for production use!)");
        log.info("Hibernate connection pool size: " + poolSize);

        autocommit = PropertiesHelper.getBoolean(Environment.AUTOCOMMIT, props);
        log.info("autocommit mode: " + autocommit);

        isolation = PropertiesHelper.getInteger(Environment.ISOLATION, props);
        if (isolation!=null)
        log.info( "JDBC isolation level: " + Environment.isolationLevelToString( isolation.intValue() ) );

        if (driverClass==null) {
            log.warn("no JDBC Driver class was specified by property " + Environment.DRIVER);
        }
        else {
            try {
                // trying via forName() first to be as close to DriverManager's semantics
            	// NOTE for JSS: we use the context class loader because it will be able to locate the database drivers
            	// already loaded in our plug-ins or projects
                driver = (Driver) Class.forName(driverClass, true, Thread.currentThread().getContextClassLoader()).newInstance();
            }
            catch (Exception e) {
                try {
                    driver = (Driver) ReflectHelper.classForName(driverClass).newInstance();
                }
                catch (Exception e1) {
                    log.error(e1.getMessage());
                    throw new HibernateException(e1);
                }
            }
        }

        url = props.getProperty( Environment.URL );
        if ( url == null ) {
            String msg = "JDBC URL was not specified by property " + Environment.URL;
            log.error( msg );
            throw new HibernateException( msg );
        }

        connectionProps = ConnectionProviderFactory.getConnectionProperties( props );

        log.info( "using driver: " + driverClass + " at URL: " + url );
        // if debug level is enabled, then log the password, otherwise mask it
        if ( log.isDebugEnabled() ) {
            log.info( "connection properties: " + connectionProps );
        }
        else if ( log.isInfoEnabled() ) {
            log.info( "connection properties: " + PropertiesHelper.maskOut(connectionProps, "password") );
        }

    }

    @Override
    public Connection getConnection() throws SQLException {

        if ( log.isTraceEnabled() ) log.trace( "total checked-out connections: " + checkedOut );

        synchronized (pool) {
            if ( !pool.isEmpty() ) {
                int last = pool.size() - 1;
                if ( log.isTraceEnabled() ) {
                    log.trace("using pooled JDBC connection, pool size: " + last);
                    checkedOut++;
                }
                Connection pooled = pool.remove(last);
                if (isolation!=null) pooled.setTransactionIsolation( isolation.intValue() );
                if ( pooled.getAutoCommit()!=autocommit ) pooled.setAutoCommit(autocommit);
                return pooled;
            }
        }

        log.debug("opening new JDBC connection");
        Connection conn = driver.connect(url, connectionProps);
        if (isolation!=null) conn.setTransactionIsolation( isolation.intValue() );
        if ( conn.getAutoCommit()!=autocommit ) conn.setAutoCommit(autocommit);

        if ( log.isDebugEnabled() ) {
            log.debug( "created connection to: " + url + ", Isolation Level: " + conn.getTransactionIsolation() );
        }
        if ( log.isTraceEnabled() ) checkedOut++;

        return conn;
    }

    @Override
    public void closeConnection(Connection conn) throws SQLException {

        if ( log.isDebugEnabled() ) checkedOut--;

        synchronized (pool) {
            int currentSize = pool.size();
            if ( currentSize < poolSize ) {
                if ( log.isTraceEnabled() ) log.trace("returning connection to pool, pool size: " + (currentSize + 1) );
                pool.add(conn);
                return;
            }
        }

        log.debug("closing JDBC connection");

        conn.close();

    }

    @Override
    public void close() {

        log.info("cleaning up connection pool: " + url);

        Iterator<Connection> iter = pool.iterator();
        while ( iter.hasNext() ) {
            try {
                iter.next().close();
            }
            catch (SQLException sqle) {
                log.warn("problem closing pooled connection", sqle);
            }
        }
        pool.clear();

    }

	@Override
    public boolean supportsAggressiveRelease() {
        return false;
    }

}
