// ConnectionManager.java
// $Id: ConnectionManager.java,v 1.11 2000/07/10 08:38:52 ylafon Exp $
// (c) COPYRIGHT MIT, INRIA and Keio, 2000.
// Please first read the full copyright statement in file COPYRIGHT.html
package org.w3c.tools.jdbc;

import java.sql.SQLException;
import java.util.Properties;

import org.w3c.util.LRUList;
import org.w3c.util.SyncLRUList;
import org.w3c.util.PropertyMonitoring;
import org.w3c.util.ObservableProperties;

/**
 * @version $Revision: 1.11 $
 * @author  Benoît Mahé (bmahe@w3.org)
 */

class ManagerDescription {
    ConnectionManager manager = null;
    Properties  properties = null;
	
    final ConnectionManager getManager() {
	return manager;
    }
	
    final boolean sameProperties(Properties props) {
	return (Jdbc.getMaxConn(props) == Jdbc.getMaxConn(properties));
    }
	
    ManagerDescription(ConnectionManager manager, Properties props) {
	this.manager    = manager;
	this.properties = props;
    }
}

public class ConnectionManager implements PropertyMonitoring {

    public final static boolean debug = false;

    protected int conn_count = 0;
    protected int conn_max;

    Properties props = null;

    /**
     * To keep track of existing managers
     */
    private static ManagerDescription managers[] = null;

    /**
     * The LRU list of connections.
     */
    protected LRUList connectionsLru = null;

    /**
     * PropertyMonitoring implementation.
     */
    public boolean propertyChanged(String name) {
	if (name.equals(Jdbc.MAX_CONNECTIONS_P)) {
	   conn_max = Jdbc.getMaxConn(props);
	   return true;
	}
	return false;
    }

    /**
     * Get a connection to the given server.
     * @param server the jdbc server.
     * @return a JdbcConnection or null if there is no connection available.
     */
    protected JdbcConnection getConnection(JdbcServer server) {
	JdbcServerState ss   = server.getState();
	JdbcConnection  conn = null;

	while ( true ) {
	    synchronized (this) {
		while ( true ) {
		    conn = ss.getConnection();
		    if ( conn == null )
			break;
		    if (debug) {
			System.out.println("Reusing connection. ");
		    }
		    if (conn.markUsed())
			return conn;
		}
		if (negotiateConnection(server)) {
		    conn = allocateConnection(server);
		    if ( conn.markUsed() ) {
			return conn;
		    } else {
			// Failed to establish a fresh connection !
			return null;
		    }
		}
	    }
	    // Wait for a connection to become available:
	    try {
		waitForConnection(server);
	    } catch (InterruptedException ex) {
	    }	
	}
    }

    /**
     * Connections management - Allocate a new connection for this server.
     * @param server the JdbcServer
     * @return a newly created connection or null
     */
    protected JdbcConnection allocateConnection(JdbcServer server) {
	if (debug) {
	    System.out.println("Allocating new connection to "+server);
	}
	JdbcConnection conn = new JdbcConnection(server);
	notifyConnection(conn);
	return conn;
    }

    protected boolean negotiateConnection(JdbcServer server) {
	if (conn_count >= conn_max) {
	    // remove the oldest idle Connection
	    JdbcConnection conn = (JdbcConnection) connectionsLru.removeTail();
	    if (conn == null) { // no idle connection
		return false;
	    } else {
		if (debug) {
		    System.out.println("DELETING IDLE CONNECTION!!!");
		}
		conn.delete();
	    }
	}
	return true;
    }

    protected void deleteConnection(JdbcConnection conn) {
	JdbcServerState ss = conn.getServer().getState();
	ss.deleteConnection(conn);
	synchronized(this) {
	    --conn_count;
	    if (debug)
		System.out.println("+++ delete conn_count: " + conn_count);
	    notify();
	}
    }
    
    /**
     * A new connection has just been created.
     * @param conn the new connection
     */
    protected synchronized void notifyConnection(JdbcConnection conn) {
	++conn_count;
    }

    /**
     * The given connection is about to be used.
     * Update our list of available servers.
     * @param conn The idle connection.
     */
    public void notifyUse(JdbcConnection conn) {
	if (debug)
	    System.out.println("+++ JdbcConnection used");
	connectionsLru.remove(conn);
    }

    /**
     * The given connection can be reused, but is now idle.
     * @param conn The connection that is now idle.
     */
    public synchronized void notifyIdle(JdbcConnection conn) {
	if (debug)
	    System.out.println("+++ JdbcConnection idle");
	connectionsLru.toHead(conn);
	JdbcServerState ss = conn.getServer().getState();
	ss.registerConnection(conn);
	notify();
    }

    /**
     * Wait for a connection to come up.
     * @param server, the target server.
     * @exception InterruptedException If interrupted..
     */

    protected synchronized void waitForConnection(JdbcServer server)
	throws InterruptedException
    {
	wait();
    }

    /**
     * Get an instance of the Jdbc manager.
     * This method returns an actual instance of the Jdbc manager. It may
     * return different managers, if it decides to distribute the load on
     * different managers (avoid the ConnectionManager being a bottleneck).
     * @return An application wide instance of the Jdbc manager.
     */

    public static synchronized ConnectionManager getManager(Properties p) { 
	if (managers != null) {
	    for (int i = 0 ; i < managers.length ; i++) {
		if ( managers[i] == null )
		    continue;
		if ( managers[i].sameProperties(p) ) {
		    return managers[i].getManager();
		}
	    }
	}
	ConnectionManager manager = new ConnectionManager(p);
	if (managers != null) {
	    ManagerDescription nm[]= new ManagerDescription[managers.length+1];
	    System.arraycopy(managers, 0, nm, 0, managers.length);
	    nm[managers.length] = new ManagerDescription(manager, p);
	} else {
	    managers    = new ManagerDescription[1];
	    managers[0] = new ManagerDescription(manager, p);
	}
	return manager;
    }

    public static ConnectionManager getManager() {
	return getManager(System.getProperties());
    }

    private ConnectionManager(Properties p) {
	this.props          = p;
	this.connectionsLru = new SyncLRUList();
	if (props instanceof ObservableProperties) {
	    ((ObservableProperties) props).registerObserver(this);
	}
	this.conn_max = Jdbc.getMaxConn(props);
    }
    
}
