package com.fuse.storage.sql;

/*
	FUSE Light Server - multiuser server application
	Copyright (C) 2000 Aapo Kyrola / Sulake Oy  Helsinki, Finland

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program 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 General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;

import com.fuse.*;


/**
  * DatabaseConnectionManager, singleton. Use getInstance() to 
  * get an instance of this
  */
public class DatabaseConnectionManager {

	static protected DatabaseConnectionManager instance;
	protected Properties props;
	protected Driver driver;
	protected int createdConnections = 0;
	protected int maxConnections;
	
	/* Database access info */
	protected String url, username, password;
	
	/* Lock for thrads which wait for a connection */
	protected Object lock = new Object();
	
	/* Connections which are available for user */
	private Vector availableConnections = new Vector();
   
   
   /**
     * A constructor
     */
    private DatabaseConnectionManager(String propFile) {
    	try {
        	props = new Properties();
        	props.load(new FileInputStream(propFile));
        	
        	url = props.getProperty("db.url");
        	username = props.getProperty("db.user");
        	password = props.getProperty("db.password");
        	
        	/* Load driver */
        	String driverClass = props.getProperty("drivers");
        	try {
        	    driver = (Driver) Class.forName(driverClass).newInstance();
        	    DriverManager.registerDriver(driver);	
        	} catch (Exception e) {
                Log.error("Error in loading driver:" + driverClass);
                throw e;
            }
            
            maxConnections = Integer.parseInt(props.getProperty("database_manager.maxconnections", "20"));
            Log.status("Max database connections = " + maxConnections);
        } catch (Exception e) {
        	Log.error("DatabaseConnectionManager init failed!");
        	Log.error(e);
        }
    }
	
	/**
	  * Returns a singleton instance of pool
	  */
	public static DatabaseConnectionManager getInstance() {
		synchronized(DatabaseConnectionManager.class) {
		    if (instance == null) {
			 	instance = new DatabaseConnectionManager(FUSEEnvironment.getProperty("db.properties", "db.properties"));
			 	
			}
		}	
		return instance;
	}
	
	/**
	  * Return used connection back
	  */
	public void freeConnection(Connection conn) {
		if (conn == null) return;
		Log.debug("free connection: " + conn + "/" + Thread.currentThread());
		synchronized(lock) {
			availableConnections.addElement(conn);
			/* Inform waiting threads that there is connection */
			Log.debug("notify");
			lock.notifyAll();
		}
    }
    
    /**
      * Returns a new connection
      */
	public Connection getConnection() {
		Connection conn = null;
		synchronized(lock) {
			if (availableConnections.size() > 0) {
				conn = (Connection) availableConnections.lastElement();
				availableConnections.removeElementAt(availableConnections.size()-1);	
				try {
					if (conn.isClosed()) {
						Log.status("Closed/damaged connection in stack!");
						conn = getConnection();
						createdConnections--;
					}
				} catch (SQLException sqe) {
					createdConnections--;
					conn = getConnection();	
				}
			} else if (maxConnections < 1 || createdConnections < maxConnections) {
				conn = createNewConnection();
			} else {
				/* Wait at most 10000 ms for new connection */
				try {
					Log.status("Obliged to wait for database connection! " + Thread.currentThread());
					lock.wait(10000);
					Log.status("Waiting for connection timeouted!");
				} catch (InterruptedException ie) {
					Log.status("Waiting interrupted");
					conn = getConnection();
				}
				
			}
		}
		if (conn == null) Log.status("DatabaseConnection was null!!");
		Log.debug("get connection " + conn + "/" + Thread.currentThread());
		return conn;
	}
	
	   /**
         * Creates a new connection, using a username and password
         * from props.
         */
        protected Connection createNewConnection() {
        	Connection conn = null;
        	createdConnections++;
        	
            try {
                if (username == null) {
                    conn = DriverManager.getConnection(url);
                }
                else {
                    conn = DriverManager.getConnection(url, username, password);
                }
            }
            catch (SQLException e) {
                Log.error("Can't create a new connection for " + url);
                Log.error(e);
                return null;
            }
            return conn;
        }
}
