package org.jcon.df;

import org.jcon.util.GenLib;
import java.sql.*;

/**
 * Handles the connection to a database. A timeout is used
 * to speed database access by leaving the connection open
 * for the timeout period.
 *
 * @author Jack Harich
 */
public class Connector implements
    java.io.Serializable, Runnable {

//---------- Private Fields ------------------------------
private static final int TIMEOUT = 3000;

private static final int NO_CONNECTION = 1;
private static final int IN_USE        = 2;
private static final int NOT_IN_USE    = 3;
private int       state = NO_CONNECTION;

private ConnectorDef connectorDef;
private Connection   connection;
private boolean      driverLoaded;
private long         releaseTime;
private boolean      timerRunning;

//---------- Initialization ------------------------------
public Connector(ConnectorDef connectorDef) {
    this.connectorDef = connectorDef;
}
//---------- Superclass Overrides ------------------------
public Object clone() {
    Connector conn = new Connector(connectorDef);
    conn.driverLoaded = false; // For safety
    return conn;
}
//---------- Runnable Implementation ---------------------
/**
 * For internal use only. Clients should not call this method.
 */
public void run() {
  try {
        // Check timout every second
        Thread.currentThread().sleep(1000);
        timeoutConnection();

    } catch(InterruptedException ex) {
        timeoutConnection();
    } finally {
        timerRunning = false;
    }
}
// Not synchronized causes bug
private synchronized void timeoutConnection() {
    if (state == IN_USE) {
        // Do nothing, let timer die

    } else if (state == NOT_IN_USE) {
        long currentTime = System.currentTimeMillis();
        if (currentTime > releaseTime + TIMEOUT) {
            // Close connection since timed out
            toNoConnectionState();
        } else {
            startTimer();
        }
    }
}
//---------- Public Methods ------------------------------
// All public methods must be synchronized
public synchronized Connection retrieveConnection() {
    if (connection == null) createConnection();
    toInUseState();
    return connection;
}
public synchronized void releaseConnection() {
    releaseTime = System.currentTimeMillis();
    if (state == IN_USE) {
        toNotInUseState();
    }
}
//---------- Private Methods -----------------------------
private void toInUseState() {
    state = IN_USE;
}
private void toNotInUseState() {
    state = NOT_IN_USE;
    if (! timerRunning) {
        timerRunning = true;
        startTimer();
    }
}
private void startTimer() {
    new Thread(this).start();
}
private void toNoConnectionState() {
    try {
    //print(".toNoConnectionState() - closing connection " + connection);
        if (connection != null) {
            if (! connection.isClosed()) connection.close();
            connection = null;
        }
    } catch(SQLException ex) {
        GenLib.exception("Connector.timeoutConnection()",
            "Failure in closing connection.", ex);
    } finally {
        state = NO_CONNECTION;
    }
}
private void createConnection() {

if (connection != null) return;
try {
    if (! driverLoaded) {
        // Load the driver class
        Class.forName(connectorDef.getDriverClass());
        driverLoaded = true;
    }
    // Get the connection
    connection = DriverManager.getConnection(
        connectorDef.getURL(),
        connectorDef.getUserID(),
        connectorDef.getPassword());
    //print(" - Created connection for '" + connectorDef.getName() + "'.");

} catch(Exception ex) {
    GenLib.exception("Connector.createConnection()",
        "Failed to create JDBC connection for '" +
            connectorDef.getDatabaseName() + "' " + connectorDef.getURL() , ex);
}
} // End method
//--- Std
private static void print(String text) {
    System.out.println("Connector" + text);
}

} // End class
