package org.jcon.df;

import org.jcon.ba.system.BeanActionStart;
import org.jcon.df.request.Performer;
import org.jcon.util.GenLib;
import java.rmi.Naming;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.util.Hashtable;

/**
 * The entry point for "data storage" services. This class
 * is a singleton. It's main responsiblity is to provide
 * Database instances.
 *
 * ----- To run unit test: -----
 *
 * Generate the Skel and Stub files if necessary:
 * >rmic org.jcon.df.Datastore
 *
 * >start RMIRegistry (must do before server, so it can bind)
 * >java Datastore (on the server - must do before client)
 * >java DatastoreServices (a unit test)
 *
 * Note - For local testing TCP may need to be enabled by:
 * Dialup Networking, TCP/IP Properties, check "Specify an IP Address",
 * IP Address 192 168 1 1, Subnet Mask 255 255 255 0.
 * This allows an IP address reserved for non Internet use
 * to be used locally.
 *
 * @author Jack Harich
 */
public class Datastore extends UnicastRemoteObject
    implements DatastoreRemote, BeanActionStart {

//---------- Private Fields ------------------------------
private DatabaseFactory databaseFactory;
private String          remoteName = "DatastoreRemote";
private boolean         bound;

// Key = dbName, Object = DatabaseServer
private Hashtable databaseServers = new Hashtable();

// Key = Alias used by business objects, Object = dbName
private Hashtable dbNameAliases = new Hashtable();

//---------- Initialization ------------------------------
public Datastore() throws RemoteException {
    super(); // Required to match superclass constructor
    // Possible initialization later
}
/**
 * Binds new Datastore() to RMI Registry as the first
 * command line argument. The default is "DatastoreRemote":
 */ // OBSOLETE ***
/** public static void main(String args[]) {
    try {
        Datastore datastore = new Datastore();
        if (args.length > 0) datastore.remoteName = args[0];
        datastore.bindToRemoteRegistry();
    } catch(RemoteException ex) {
        System.out.println("Datastore.main() Exception");
        ex.printStackTrace();
    }
} */
//---------- DatastoreRemote Implementation --------------
/**
 * Adds a user to the users that may use the Datastore.
 * This is also known as "logon". Returns null if okay or a
 * "logonResult" String explaining the problem if not okay.
 */
/** public String addUser(String name, String password) throws RemoteException {
    // *** do
    print(".addUser() - Entered, not implemented, returning null for okay");
    return null;
} */
/**
 * Removes a user from the Datastore. AKA logoff.
 */
 /**
public void removeUser(String name) throws RemoteException {
    print(".removeUser() - Entered, not implemented");
} */
public Database createDatabase(String aliasDBName) throws RemoteException {

    String dbName = translateAlias(aliasDBName);

    Database database = databaseFactory.createDatabase(dbName);

    // One server per dbName, ie per physical database
    DatabaseServer server = (DatabaseServer)databaseServers.get(dbName);
    if (server == null) {
        server = new DatabaseServer();
        server.setDatabaseName(dbName);
        // Schema and translator not cloned since read only
        server.setSchema(database.getSchema());
        server.setTranslator(database.getTranslator());

        server.setConnector((Connector)databaseFactory
            .createMutableConnector(dbName));
        databaseServers.put(dbName, server);
    }
    // *** was database.setServer(server); // **** drop
    // Each issued database has its own performer, since
    // each is on a different client.
    database.setPerformer(server.checkOutPerformer());

    return database;
}
public String[] loadDatabaseNames() throws RemoteException {
    return databaseFactory.loadDatabaseNames();
}
public Entity getEntity(String schemaName, String entityName) {
    return databaseFactory.getEntity(schemaName, entityName);
}
//----- Other
//---------- BeanActionStart Implementation --------------
public void start() {
    bindToRemoteRegistry();
}
//---------- Properties ----------------------------------
//----- remoteName
public void setRemoteName(String remoteName) {
    this.remoteName = remoteName;
}
public String getRemoteName() {
    return remoteName;
}
//----- DatabaseFactory
public void setDatabaseFactory(DatabaseFactory databaseFactory) {
    this.databaseFactory = databaseFactory;
}
//---------- Public Methods ------------------------------
/**
 * Adds an alias for the databaseName. If the alias name is
 * used, such as in createDatabase(), the alias will be
 * translated to the databaseName first. This allows
 * business object to use the alias name, such as "atsdr",
 * and the actual database used to be different database
 * names, such as "ATSDR_LocalAccessProduction" or
 * "ATSDR_SybaseProduction" or "ATSDR_SybaseTest".
 *
 * These must be provided before this class is used by clients.
 */
public void addDatabaseNameAlias(String alias, String databaseName) {
    dbNameAliases.put(alias, databaseName);
}
//---------- Private Methods -----------------------------
/**
 * Translates the alias to a databaseName. If the alias
 * maps to a provided one, that alias's dbName is used.
 * Otherwise the alias is not translated.
 */
private String translateAlias(String alias) {
    String name = (String)dbNameAliases.get(alias);
    if (name != null) {
        return name;
    } else {
        return alias;
    }
}
private void bindToRemoteRegistry() {
    if (bound) return;
    try {
        // Install security manager
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        // Start registry, ignore if port already in use since that's the registry
        try {
            int PORT = 1099;
            print(" - Creating registry on port " + PORT);
            LocateRegistry.createRegistry(PORT);
        } catch(Exception ex) {
            print("Ignoring " + ex.getMessage());
        }

        // Bind myself
        print(" - Binding '" + remoteName + "', please wait....");
        Naming.rebind(remoteName, this);

        // Done
        print(" - Binding complete. Datastore is ready as a remote object.");
        bound = true;

    } catch(Exception ex) {
        GenLib.exception("Datastore.bindToRemoteRegistry()",
            "Failure rebinding '" + remoteName + "'.", ex);
    }
}
//--- Std
private void print(String text) {
    System.out.println("Datastore" + text);
}

} // End class
