/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY.                         *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.  Contact information: bergmark@cs.cornell.edu     *
 ******************************************************************************/
package cnrg.itx.ds;

import java.io.*;
import java.util.*;
import java.security.*;
import java.util.Vector;

/**
 * This class provides clients an interface to communicate with the directory server. <br>
 * <p>
 * A typical itx application has one directory service object, one signaling component object, and one data exchange object. <br>
 * An application can be in one of the following type: <br>
 * - custom per-user application (the user is whoever is logged on using the application) <br>
 * - gateway (the user is Gatewaysrv only) <br>
 * - PBX (the user is PBXsrv only) <br>
 * - voice mail server (the user is Vmailsrv only) <br>
 * - conference server (the user is Conferencesrv only) <br>
 * NOTE: user refers to userID, who needs to be pre-registered into the directory server database. <br>
 * <p>
 * Scenario of how to use interface with directory service: <br>
 * When a registered user (e.g. bergmark@cs.cornell.edu, Adm, Gatewaysrv, PBXsrv, Vmailsrv, Conferencesrv) starts up an application 
 * using the directory service, the following things NEED to happen: <br>
 * 1. The desktop signaling component object associated with the application needs to call DirectoryService.declareIdentity (with his/her 
 *    userID and password as arguments) on the directory service object to authenticate and verify his/her identity before being allowed to 
 *    use the directory serivice object. <br>
 * 2. The desktop signaling component then calls Directory.Service.registerLocation to register the application's location associated with 
 *    the user. <br>
 *    (NOTE: For an itx application which may not support data exchange object, registerLocation does not need to be called.) <br>
 * 3. At this point, the application can use directory service object. <br>
 * 4. When application exits, the desktop signaling component is responsible to remove this location associated with the user. <br>
 *    (NOTE: If application exit adnormally, Adm needs to manually clean up the database in the directory server.) <br>
 * 
 * @version 1.0
 * @author Char Shing Wilson Ng
 */
public class DirectoryService
{
	public static final int NULL_ACCESS_LEVEL = 0;
	public static final int USER_ACCESS_LEVEL = 9;  // [1..9]
	public static final int SERVER_ACCESS_LEVEL = 99;  // [10..99]
	public static final int ADM_ACCESS_LEVEL = 100;

	public static final int MIN_EXTENSION_SIZE = 1;
	public static final int MAX_EXTENSION_SIZE = 7;
	
	// private data members
	private static final String CONFIG_FILE = "resolv.conf";
	private DirectoryStub m_DSstub;
	
	/**
	 * Default constructor <br>
	 * NOTE: It also sets up where to find the config file.  Config file is used to locate the Directory Server. <br>
	 * The default location of the config file is in the same directory as DSComm.dll resides.
	 */
	public DirectoryService()
		throws DirectoryServiceException {
		m_DSstub = new DirectoryStub(CONFIG_FILE, null);
	}

	/**
	 * Constructor for a given config file path to locate the config file.  Config file is used to locate the Directory Server. <br>
	 * NOTE: If default constructor is used instead, the config file (called resolv.conf) is assumed to be resided in the same 
	 * directory as DSComm.dll resides. <br>
	 * @param configFilePath a full file path to locate the config file.
	 */
	public DirectoryService(String configFilePath) 
		throws DirectoryServiceException {
		m_DSstub = new DirectoryStub(configFilePath, null);
	}

	/**
	 * Constructor for a given DSComm-derived object.  <br>
	 * NOTE: It also sets up where to find the config file.  Config file is used to locate the Directory Server. <br>
	 * The default location of the config file is in the same directory as DSComm.dll resides.
	 * @param newDSComm a DSComm-derived object to provide customized interface communicating to user-defined directory database server.
	 */
	public DirectoryService(DSComm newDSComm)
		throws DirectoryServiceException {
		m_DSstub = new DirectoryStub(CONFIG_FILE, newDSComm);
	}
	
	/**
	 * Constructor for a given config file path to locate the config file and a given DSComm-derived object.  <br>
	 * @param configFilePath a full file path to locate the config file.
	 * @param newDSComm a DSComm-derived object to provide customized interface communicating to user-defined directory database server.
	 */
	public DirectoryService(String configFilePath, DSComm newDSComm)
		throws DirectoryServiceException {
		m_DSstub = new DirectoryStub(configFilePath, newDSComm);
	}

	/**
	 * Turn off or turn on the cleanup thread.  The default is ON, which means when a user call declareIdentity, the cleanup thread starts automatically. <br>
	 * @param bCleanup false to turn off the cleanup thread; true to turn it on
	 */
	public void setCleanUp(boolean bCleanup) {
		m_DSstub.setCleanUp(bCleanup);
	}
	
	/**
	 * Authenticate this directory service with the ID and corresponding PIN of the client BEFORE calling any methods.<br>
	 * Client only needs to call this method once. <br>
	 * New user needs to register his/her ID and PIN via the administrator of the directory server.
	 * @param id user's ID
	 * @param pin user's PIN
	 */
	public void declareIdentity(UserID id, Password pin)
		throws AuthenticationException{
		if (!m_DSstub.authenticated()) {
			try {
				m_DSstub.declareIdentity(id, pin);
			}catch (AuthenticationException ae) {
				throw ae;
			}
		}
	}

	/**
	 * Get the access level of the user authenticated with this directory service
	 * @return the access level of the user (e.g. DirectoryService.USER_ACCESS_LEVEL, DirectoryService.SERVER_ACCESS_LEVEL, DirectoryService.ADM_ACCESS_LEVEL, DirectoryService.NULL_ACCESS_LEVEL)
	 */
	public int getAccessLevel()
		throws AuthenticationException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before retrieving the access level.");
		else
			return m_DSstub.getAccessLevel();
	}
	
	/**
	 * Get the ID of the user authenticated with this directory service
	 * @return user's ID
	 */
	public UserID getID()
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before retrieving the ID.");
		else
			return m_DSstub.getID();
	}

	/**
	 * Get the Extension of the user authenticated with this directory service
	 * @return Digits object to store user's extension
	 */
	public Digits getExtension()
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before retrieving the Extension.");
		else
			return m_DSstub.getExtension();
	}
	
	/**
	 * Change the PIN of a registered user by the directory service administrator
	 * NOTE: Only the administrator has privilege to use this method; otherwise, AccessDeniedException will throw.
	 * @param id user's ID
	 * @param newPin new user's PIN
	 */
	public void setPIN (UserID id, Password newPin)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before changing the PIN.");
		else {
			m_DSstub.setPIN(id, newPin);
		}
	}
	
	/**
	 * Change the PIN of the user authenticated with this directory service.
	 * @param id user's ID
	 * @param currentPin user's PIN
	 * @param newPin new user's PIN
	 */
	public void setPIN (UserID id, Password currentPin, Password newPin)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before changing the PIN.");
		else {
			m_DSstub.setPIN(id, currentPin, newPin);
		}
	}

	/**
	 * Get the custom message of the user authenticated with this directory service
	 * @return message string
	 */
	public String getCustomMessage()
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before retrieving custom message.");
		else {
			return m_DSstub.getCustomMessage();
		}
	}
	
	/**
	 * Get the custom message of the specified userID 
	 * @param uid the userID corresponding to the custom message
	 * @return message string
	 */
	public String getCustomMessageByID(UserID uid)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before retrieving custom message.");
		else {
			return m_DSstub.getCustomMessageByExt(getExtension(uid));
		}
	}

	/**
	 * Set the custom message of the user authenticated with this directory service
	 * @param newCustomMsg new custom message
	 */
	public void setCustomMessage(String newCustomMsg)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before setting custom message.");
		else {
			m_DSstub.setCustomMessage(newCustomMsg);
		}
	}
	
	/**
	 * Register the location of the user (authenticated with this directory service) when he/she starts up an application. <br>
	 * Dialable location is allowed to be added ONLY as ROAMING or DYNAMIC location.  Non-Dialable location is allowed to be added ONLY as DEFAULT location.  Otherwise, an AccessDeniedException is thrown.
	 * @param locationType (e.g. Location.ROAMING, Location.DEFAULT, Location.DYNAMIC)
	 * @param newLocation a new location object to be registered
	 */
	public void registerLocation(int locationType, Location newLocation)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before registering a location.");
		else {
			m_DSstub.registerLocation(locationType, newLocation);
		}
	}

	/**
	 * Unregister the location of the user (authenticated with this directory service) when he/she exits from an application
	 * @param locationType (e.g. Location.ROAMING, Location.DEFAULT, Location.DYNAMIC)
	 * @param newLocation a location object to be unregistered
	 */
	public void unregisterLocation(int locationType, Location newLocation)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before un-registering a location.");
		else {
			m_DSstub.unregisterLocation(locationType, newLocation);
		}
	}
	
	/**
	 * Gets all default locations of the user authenticated with this directory service
	 * @param extension user's extension
	 * @return vector of location objects
	 */
	public Vector getDefaultLocationByExtension(Digits extension)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before getting default locations.");
		else {
			return m_DSstub.getDefaultLocationByExtension(extension);
		}
	}

	/**
	 * Set the default location of the user authenticated with this directory service
	 * @param newLocation is the description of the new Default Location
	 */
	public void setDefaultLocation(String newLocation)
	throws AuthenticationException, AccessDeniedException, 
	RecordNotFoundException{
           if (!m_DSstub.authenticated())
              throw new AuthenticationException("Directory service has not been authenticated before setting custom location.");
           else {
              m_DSstub.setDefaultLocation(newLocation);
           }
	}
	
	/**
	 * Gets all custom locations of the user authenticated with this directory service
	 * @param extension user's extension
	 * @return vector of CustomLocationRecord objects
	 */
	public Vector getCustomLocationByExtension(Digits extension)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before getting custom locations.");
		else {
			return m_DSstub.getCustomLocationByExtension(extension);
		}
	}		
		
	/**
	 * Adds the custom location list of the user authenticated with this directory service
	 * @param newCustomLocation a new custom location object
	 */
	public void addCustomLocation(CustomLocation newCustomLocation)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before adding the custom location.");
		else {
			m_DSstub.addCustomLocation(newCustomLocation);
		}
	}

	/**
	 * Deletes the custom location of the user authenticated with this directory service
	 * @param customLocationID ID of the custom location to be deleted
	 */
	public void deleteCustomLocation(String customLocationID)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before deleting the custom location.");
		else {
			m_DSstub.deleteCustomLocation(customLocationID);
		}
	}
	
	/**
	 * Get the extension from a given UserID 
	 * @param id user's ID
	 * @return user's extension represented by Digits object
	 */
	public Digits getExtension(UserID id)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before getting a extension of a given user ID.");
		else {
			return m_DSstub.getExtension(id);
		}
	}

	/**
	 * Get the UserID from a given extension
	 * @param extension user's extension
	 * @return user's ID
	 */
	public UserID getID(Digits extension)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before getting a user ID of a given extension.");
		else {
			return m_DSstub.getID(extension);
		}
	}

	/**
	 * Get the location list from a given UserID.  UserID can be one of the following: <br>
	 *   1. email (physical person registered in our directory database) <br>
	 *   2. server application (e.g. Gatewaysrv, PBXsrv, Vmailsrv, etc.) <br>
	 *   3. phone number <br>
	 * @param id user's ID
	 * @return a list of location
	 */
	public LocationList getLocationListByID(UserID id)
		// Searching order: 
		//   1. Roaming Location
		//   2. Dynamic Location List
		//   3. Custom Location list
		//   4. Default Location list
		// NOTE: 
		//  If empty list returned
		//       If UserID is not all in digits
		//             throws RecordNotFoundExcetion
		//       Else
		//             returns LocationList of Gatewaysrv
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before getting location list from a given user ID.");
		else {
			return m_DSstub.getLocationListByID(id);
		}
	}

	/**
	 * Get the location list from a given extension
	 * @param extension user's extension
	 * @return a list of location
	 */
	public LocationList getLocationListByExtension(Digits extension)
		// Searching order: 
		//     1. Roaming Location
		//     2. Dynamic Location List
		//     3. Custom Location list
		//     4. Default Location list
		// NOTE: 
		// If no user record associated with the given extension
		//     returns LocationList for gatewaysrv
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before getting location list from a given extension.");
		else {
			return m_DSstub.getLocationListByExtension(extension);
		}
	}

	/**
	 * Adds a new user into the directory database based on an input extension number. <br>
	 * If the input extension number is already assigned, RecordAlreadyExistsException will be thrown. <br>
	 * NOTE: Only the administrator has privilege to use this method; otherwise, AccessDeniedException will throw.
	 * @param id new user's id
	 * @param newExtension new extension number to be assigned to the new user
	 * @param newPin new user's password
	 * @param accessLevel new user's access level (USER_ACCESS_LEVEL, SERVER_ACCESS_LEVEL, ADM_ACCESS_LEVEL)
	 * @param customMsg custom message (can be empty)
	 */
	public void addUser(UserID id, Digits newExtension, Password newPin, int accessLevel, String customMsg)
		throws AuthenticationException, AccessDeniedException, RecordAlreadyExistsException, DirectoryServiceException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before adding a new user.");
		else {
			m_DSstub.addUser(id, newExtension, newPin, accessLevel, customMsg);
		}		
	}
	
	/**
	 * Adds a new user into the directory database. Returns a new extension associated with the new user. <br>
	 * If user already exists in the database, RecordAlreadyExistsException will be thrown. <br>
	 * If there is no available extension number, AccessDeniedException will be thrown. <br>
	 * NOTE: Only the administrator has privilege to use this method; otherwise, AccessDeniedException will throw.
	 * @param id new user's id
	 * @param numberOfDigitForExtension requested number of extension digits [DirectoryService.MIN_EXTENSION_SIZE...DirectoryService.MAX_EXTENSION_SIZE]
	 * @param newPin new user's password
	 * @param accessLevel new user's access level (USER_ACCESS_LEVEL, SERVER_ACCESS_LEVEL, ADM_ACCESS_LEVEL)
	 * @param customMsg custom message (can be empty)
	 * @return extension of the new user
	 */
	public Digits addUser(UserID id, int numberOfDigitForExtension, Password newPin, int accessLevel, String customMsg)
		throws AuthenticationException, AccessDeniedException, RecordAlreadyExistsException, DirectoryServiceException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before adding a new user.");
		else {
			return m_DSstub.addUser(id, numberOfDigitForExtension, newPin, accessLevel, customMsg);
		}		
	}

	/** 
	 * Removes a user from the directory database. <br>
	 * NOTE: Only the administrator has privilege to use this method
	 * @param extension extension of the user to be removed
	 */
	public void removeUser(Digits extension)
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException, DirectoryServiceException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before removing a user.");
		else {
			m_DSstub.removeUser(extension);
		}
	}
	
	/**
	 * Dump all users' records from the directory database to the terminal output. 
	 * @return a Vector of UserProperty objects
	 */
	public Vector dumpAllUsers()
		throws AuthenticationException, AccessDeniedException, RecordNotFoundException, DirectoryServiceException{
		if (!m_DSstub.authenticated())
			throw new AuthenticationException("Directory service has not been authenticated before dumping all users.");
		else {
			return m_DSstub.dumpAllUsers();
		}
	}
}
