/* ***************************************************************************+
 * 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.util.*;

/**
 * This class provides directory service object a communication mechanism to the remote named servers.  
 * NOTE: Users should not directly call any methods of this class.
 */
class DirectoryStub
{
	// private data members
	private UserID m_ID;
	private Digits m_Extension;
	private int m_AccessLevel;
	private DSComm m_Stub;
	private Digits m_gatewaysrvExt=null;
	private boolean m_AutoCleanUp = true;

    /**
	 * Returns the formatted byte array, to ensure valid passing of null-terminated byte array into DSComm.dll
	 * @param str is the String to be converted into a byte array
	 * @return formatted byte array
	 */
	private byte[] getFormatByteArray(String str) {
		String tmp = str + (char)0;
		return tmp.getBytes();
	}

    /**
     * Constructor
     * @param filepath is used to set the bind client's config path
     * @param dsComm is a DSComm object which will be our directory client
     */
	public DirectoryStub(String filepath, DSComm dsComm) {
		m_ID = null;
		m_Extension = null;
		m_AccessLevel = DirectoryService.NULL_ACCESS_LEVEL;
		if (dsComm == null)
			m_Stub = new DSComm();
		else
			m_Stub = dsComm;

		m_Stub.setConfigPath(getFormatByteArray(filepath));
	}

    /**
     * sets/unsets the auto cleanup field of dynamic location entries
     * @param bCleanup if false will turn off automatic directory cleanup
     */
	public void setCleanUp(boolean bCleanup) {
		m_AutoCleanUp = bCleanup;
	}
	
	public int getAccessLevel(){
		return m_AccessLevel;
	}
	
	public UserID getID(){
		return m_ID;
	}
	
	public Digits getExtension(){
		return m_Extension;
	}
	
	public Digits getExtension(UserID id)
		throws AccessDeniedException, RecordNotFoundException{
		ArrayRecords strExt = m_Stub.getRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2ExtDomain()));
		if (strExt.count() > 0)
			return new Digits(strExt.first());
		else
			throw new RecordNotFoundException("Error: No record found");		
	}

	public UserID getID(Digits extension)
		throws AccessDeniedException, RecordNotFoundException{
		ArrayRecords strCustomer = m_Stub.getRecord(getFormatByteArray(extension.toString() + m_Stub.getExt2UserDomain()));
		if (strCustomer.count() > 0) {
			CustomerRecord customerRec = new CustomerRecord(strCustomer.first());
			return customerRec.getUserID();
		}
		else
			throw new RecordNotFoundException("Error: No record found");
	}

	public String getCustomMessage()
		throws AccessDeniedException, RecordNotFoundException{
		//ArrayRecords strCustomer = m_Stub.getRecord(getFormatByteArray(m_ID.toString() + m_Stub.getExt2UserDomain())); Fixed, 8/19/99 (djb) 
		ArrayRecords strCustomer = m_Stub.getRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2UserDomain()));
		if (strCustomer.count() > 0) {
			CustomerRecord customerRec = new CustomerRecord(strCustomer.first());
			return customerRec.getCustomMsg();
		}
		else
			throw new RecordNotFoundException("Error: No record found");		
	}
	
		public String getCustomMessageByExt(Digits ext)
		throws AccessDeniedException, RecordNotFoundException{
		//ArrayRecords strCustomer = m_Stub.getRecord(getFormatByteArray(m_ID.toString() + m_Stub.getExt2UserDomain())); Fixed, 8/19/99 (djb) 
		ArrayRecords strCustomer = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2UserDomain()));
		if (strCustomer.count() > 0) {
			CustomerRecord customerRec = new CustomerRecord(strCustomer.first());
			return customerRec.getCustomMsg();
		}
		else
			throw new RecordNotFoundException("Error: No record found");		
	}


	public void setCustomMessage(String newCustomMsg)
		throws AccessDeniedException, RecordNotFoundException{
		CustomerRecord customerRec = new CustomerRecord(m_ID, newCustomMsg);
		m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2UserDomain()), null);
		m_Stub.addRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2UserDomain()), getFormatByteArray(customerRec.toString()));
	}

	public void setPIN (UserID id, Password newPin)
		throws AccessDeniedException, RecordNotFoundException, AuthenticationException{
		if (m_AccessLevel >= DirectoryService.ADM_ACCESS_LEVEL) {
			ArrayRecords strEntry = m_Stub.getRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()));
			if (strEntry.count() <= 0)
				throw new AuthenticationException("Fail to locate user record associated with" + id.toString());
			AuthenticateRecord oldSecRec = new AuthenticateRecord(strEntry.first());
			
			AuthenticateRecord newSecRec = new AuthenticateRecord(newPin, oldSecRec.getAccessLevel());
			m_Stub.deleteRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()), null);
			m_Stub.addRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()), getFormatByteArray(newSecRec.toString()));
		}
		else
			throw new AccessDeniedException("Failed to setPIN: Access Denied.");		
	}
	
	public void setPIN (UserID id, Password currentPin, Password newPin)
		throws AccessDeniedException, RecordNotFoundException, AuthenticationException{
		
		if (m_ID.equals(id)) {
			if (currentPin.verified(newPin))
				throw new AuthenticationException("Fail to change the password: new password is identical to the current password.");
			ArrayRecords strEntry = m_Stub.getRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()));
			if (strEntry.count() <= 0)
				throw new AuthenticationException("Fail to locate user record associated with" + id.toString());
			AuthenticateRecord sec = new AuthenticateRecord(strEntry.first());
			if (currentPin.verified(sec)) {
				AuthenticateRecord newSecRec = new AuthenticateRecord(newPin, m_AccessLevel);
				m_Stub.deleteRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()), null);
				m_Stub.addRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()), getFormatByteArray(newSecRec.toString()));
			}
			else
				throw new AuthenticationException("Fail to change the password: current password input is invalid.");
		}
		else
			throw new AccessDeniedException("Fail to change the password: user ID input is invalid.");
	}

	public boolean authenticated(){
		return (m_AccessLevel != DirectoryService.NULL_ACCESS_LEVEL);
	}
	
	public void declareIdentity(UserID id, Password pin)
		throws AuthenticationException{
		ArrayRecords strEntry = m_Stub.getRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()));
		if (strEntry.count() <= 0)
			throw new AuthenticationException("Fail to locate user record associated with " + id.toString());
		AuthenticateRecord sec = new AuthenticateRecord(strEntry.first());
		if (pin.verified(sec)) {
			// authentication successful
			m_ID = id;
			int nAccess = sec.getAccessLevel();
			if (nAccess>DirectoryService.NULL_ACCESS_LEVEL && nAccess<=DirectoryService.USER_ACCESS_LEVEL)
				m_AccessLevel = DirectoryService.USER_ACCESS_LEVEL;
			else if (nAccess>DirectoryService.USER_ACCESS_LEVEL && nAccess<=DirectoryService.SERVER_ACCESS_LEVEL)
				m_AccessLevel = DirectoryService.SERVER_ACCESS_LEVEL;
			else if (nAccess >= DirectoryService.ADM_ACCESS_LEVEL)
				m_AccessLevel = DirectoryService.ADM_ACCESS_LEVEL;
			else
				m_AccessLevel = DirectoryService.NULL_ACCESS_LEVEL;
			
			// retrieve extension
			ArrayRecords strExt = m_Stub.getRecord(getFormatByteArray(m_ID.toString() + m_Stub.getUserID2ExtDomain()));
			if (strExt.count() > 0)
				m_Extension = new Digits(strExt.first());
			else
				throw new AuthenticationException("Fail to retrieve user extension during declareIdentity");
			
			// Create a cleanup thread to remove invalid dyanmic location(s)
			if (m_AutoCleanUp) {
				CleanUpThread thread = new CleanUpThread(m_Extension, m_Stub);
				try {
					thread.setPriority(Thread.MIN_PRIORITY);
				}
				catch (SecurityException se){
				}
				catch (IllegalArgumentException iae){
				}
				thread.start();
			}
		}
		else {
			throw new AuthenticationException("Fail to authenticate from a given user ID and password");
		}
	}

	public void registerLocation(int locationType, Location newLocation)
		throws AccessDeniedException, RecordNotFoundException{
		if (locationType == Location.ROAMING ) {
			if (newLocation.isDialable()) {
				ArrayRecords roamingloc = m_Stub.getRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2RoamLocDomain()));
				if (roamingloc.count() > 0) {
					// add roamingloc into Dynamic location list
					m_Stub.addRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2DynamicLocListDomain()), getFormatByteArray(roamingloc.first()));
					// delete roamingloc from Roaming location and add the newLocation into Roaming location
					byte[] entry = getFormatByteArray(m_Extension.toString() + m_Stub.getExt2RoamLocDomain());
					m_Stub.deleteRecord(entry, null);
					m_Stub.addRecord(entry, getFormatByteArray(newLocation.toString()));
				}
				else {	// no record found
					m_Stub.addRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2RoamLocDomain()), getFormatByteArray(newLocation.toString()));
				}
			}
			else
				throw new AccessDeniedException("Error: Only dialable location is allowed to be added as roaming location.");
		}
		else if (locationType == Location.DEFAULT ) {
			if (!newLocation.isDialable())
				m_Stub.addRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2DefaultLocListDomain()), getFormatByteArray(newLocation.toString()));
			else
				throw new AccessDeniedException("Error: Dialable location is not allowed to be added as default location.");
		}
		else if (locationType == Location.DYNAMIC ) {
			if (newLocation.isDialable())
				m_Stub.addRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2DynamicLocListDomain()), getFormatByteArray(newLocation.toString()));
			else
				throw new AccessDeniedException("Error: Only dialable location is allowed to be added as dynamic location.");
		}
		else
			throw new AccessDeniedException("Error: Not supported in register location");
	}
	
	public void unregisterLocation(int locationType, Location newLocation)
		throws AccessDeniedException, RecordNotFoundException{
		if (locationType == Location.ROAMING ) {
			m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString()+ m_Stub.getExt2RoamLocDomain()), getFormatByteArray(newLocation.toString()));
			m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString()+ m_Stub.getExt2DynamicLocListDomain()), getFormatByteArray(newLocation.toString()));
			//Move one location (if any) from dynamicloclist to roamloc 
			ArrayRecords dynamicloc = m_Stub.getRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2DynamicLocListDomain()));
			if (dynamicloc.count() > 0) {
				// get the first location string
				String loc = dynamicloc.first();
				// add loc to roaming location 
				m_Stub.addRecord(getFormatByteArray(m_Extension.toString()+ m_Stub.getExt2RoamLocDomain()), getFormatByteArray(loc));
				// delete loc from dynamic location list
				m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString()+ m_Stub.getExt2DynamicLocListDomain()), getFormatByteArray(loc));
			}
		}
		else if (locationType == Location.DEFAULT ) {
			m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2DefaultLocListDomain()), getFormatByteArray(newLocation.toString()));
		}
		else if (locationType == Location.DYNAMIC ) {
			m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2DynamicLocListDomain()), getFormatByteArray(newLocation.toString()));		
		}
		else
			throw new AccessDeniedException("Error: location type is not supported");
	}

	public Vector getDefaultLocationByExtension(Digits ext) 
		throws AccessDeniedException, RecordNotFoundException{
		
		Vector outVector = new Vector();
		ArrayRecords arrLocs = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2DefaultLocListDomain()));
		if (arrLocs.count() > 0){
			String value = arrLocs.first();
			while (value != null) {
				Location newLoc = new Location(value);
				outVector.addElement(newLoc);
				value = arrLocs.next();					
			}
		}		
		return outVector;
	}


        /**
         * Method to change the Default Location.  There should be only
         * (at most) one default location.  If there are more than one,
         * they are all replaced by the new location, which must be dialable
         *
         * @param newLocation is the string description of the location
         *    which should become the default
         * @exception AccessDeniedException is thrown if the new location
         *    is dialable (e.g. is an internet address)
         */
        public void setDefaultLocation(String newLocation)
        throws AccessDeniedException {
           byte[] entry = getFormatByteArray(m_Extension.toString() +
              m_Stub.getExt2DefaultLocListDomain() );
           ArrayRecords arrLocs = m_Stub.getRecord(entry);
           Location newLoc = new Location ( newLocation );

           if ( !newLoc.isDialable() ) {
              // delete the current default location before adding the new one
              m_Stub.deleteRecord (entry, null);
              m_Stub.addRecord ( entry, getFormatByteArray ( newLocation ) );
           } else {
              throw new AccessDeniedException("Error: Dialable location "+
                 "is not allowed to be added as the default location.");
           }
        }

	
	public Vector getCustomLocationByExtension(Digits ext)
		throws AccessDeniedException, RecordNotFoundException{
		
		Vector outVector = new Vector();
		ArrayRecords arrLocs = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2CustomLocListDomain()));
		if (arrLocs.count() > 0){
			String value = arrLocs.first();
			while (value != null) {
				ArrayRecords arrLocs1 = m_Stub.getRecord(getFormatByteArray(value + m_Stub.getCustomLocDomain()));
				if (arrLocs1.count() == 1) {
					CustomLocation newCustomLoc = new CustomLocation(arrLocs1.first());
					
					// Create a CustomLocationRecord object
					CustomLocationRecord rec = new CustomLocationRecord(value, newCustomLoc);
					outVector.addElement(rec);
				}
				value = arrLocs.next();
			}
		}
		return outVector;		
	}
	
	public void addCustomLocation(CustomLocation lstLocation)
		throws AccessDeniedException, RecordNotFoundException{
		ArrayRecords customlist = m_Stub.getRecord(getFormatByteArray(m_Extension.toString()+ m_Stub.getExt2CustomLocListDomain()));
		int nMax = 1;
		if (customlist.count() > 0) {
			// find for maximum ID
			String field = null;
			String element = customlist.first();
			while (element != null) {
				StringTokenizer st = new StringTokenizer(element,".");
				st.nextToken();
				field = st.nextToken();
				int nVal = Integer.parseInt(field);
				if (nVal > nMax)
					nMax = nVal;

				element = customlist.next();
			}
			nMax++;
		}
		
		String newID = m_Extension.toString() + "." + String.valueOf(nMax) + (char)0;
		m_Stub.addRecord(getFormatByteArray(m_Extension.toString() + m_Stub.getExt2CustomLocListDomain()), getFormatByteArray(newID));
		m_Stub.addRecord(getFormatByteArray(newID + m_Stub.getCustomLocDomain()), getFormatByteArray(lstLocation.toString()));
	}

	public void deleteCustomLocation(String customLocationID)
		throws AccessDeniedException, RecordNotFoundException{
		m_Stub.deleteRecord(getFormatByteArray(m_Extension.toString()+ m_Stub.getExt2CustomLocListDomain()), getFormatByteArray(customLocationID));
		m_Stub.deleteRecord(getFormatByteArray(customLocationID + m_Stub.getCustomLocDomain()), null);
	}
	
	//
	// Get the location list from a given UserID.  UserID can be one of the following:
	//   1. email (physical person registered in our directory database)
	//   2. server application name (Gatewaysrv, PBXsrv, Vmailsrv, Conferencesrv)
	//   3. phone number
	// 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
	//
	public LocationList getLocationListByID(UserID id)
		throws AccessDeniedException, RecordNotFoundException{

		LocationList locList = new LocationList();
		ArrayRecords customerRec = m_Stub.getRecord(getFormatByteArray(id.toString()+ m_Stub.getUserID2ExtDomain()));
		if (customerRec.count() > 0) {
			// user record found -> get all location associated with this user
			Digits ext = new Digits(customerRec.first());
			locList = getLocationList(ext);
		}
		else {
			try {
				long nVal = Long.parseLong(id.toString());
			}catch (NumberFormatException ae) {
				throw new RecordNotFoundException("Error: user record not found");
			}
			// id is all digits -> get location list of gatewaysrv
			if (m_gatewaysrvExt==null) {
				//get gatewaysrv extension number from the directory database
				ArrayRecords strExt = m_Stub.getRecord(getFormatByteArray(m_Stub.getGatewayUserID() + m_Stub.getUserID2ExtDomain()));
				if (strExt.count() <= 0)
					throw new RecordNotFoundException("Failed: gatwaysrv is not registered in the directory database !!\n");
				else
					m_gatewaysrvExt = new Digits(strExt.first());
			}
			locList = getLocationList(m_gatewaysrvExt);
		}
		return locList;
	}

	//
	// Get the location list from a given 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
	//
	public LocationList getLocationListByExtension(Digits ext)
		throws AccessDeniedException, RecordNotFoundException{
		
		LocationList locList = new LocationList();
		// Determine if there is a user record associated with this extension
		ArrayRecords strCustomer = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2UserDomain()));
		if (strCustomer.count() <= 0) {
			// No user record associated with the given extension -> get location list of gatewaysrv
			if (m_gatewaysrvExt==null) {
				//get gatewaysrv extension number from the directory database
				ArrayRecords strExt = m_Stub.getRecord(getFormatByteArray(m_Stub.getGatewayUserID() + m_Stub.getUserID2ExtDomain()));
				if (strExt.count() <= 0)
					throw new RecordNotFoundException("Failed: gatwaysrv is not registered in the directory database !!\n");
				else
					m_gatewaysrvExt = new Digits(strExt.first());
			}
			locList = getLocationList(m_gatewaysrvExt);
			return locList;
		}

		// ext is associated with a valid user -> get all location associated with this user
		locList = getLocationList(ext);
		return locList;
	}
	
	// Helper method for getLocationListByID and getLocationListByExtension
	private LocationList getLocationList(Digits ext) {
		LocationList locList = new LocationList();
		String value = null;
		
		ArrayRecords arrLocs = null;		
		arrLocs = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2RoamLocDomain()));
		if (arrLocs.count() > 0){
			value = arrLocs.first();
			while (value != null) {
				Location newLoc = new Location(value);
				locList.add(newLoc);
				value = arrLocs.next();					
			}
		}

		arrLocs = null;
		arrLocs = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2DynamicLocListDomain()));
		if (arrLocs.count() > 0){
			value = arrLocs.first();
			while (value != null) {
				Location newLoc = new Location(value);
				locList.add(newLoc);
				value = arrLocs.next();					
			}
		}

		arrLocs = null;
		arrLocs = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2CustomLocListDomain()));
		if (arrLocs.count() > 0){
			value = arrLocs.first();
			while (value != null) {
				ArrayRecords arrLocs1 = m_Stub.getRecord(getFormatByteArray(value + m_Stub.getCustomLocDomain()));
				if (arrLocs1.count() > 0) {
					CustomLocation newCustomLoc = new CustomLocation(arrLocs1.first());
					if (newCustomLoc.currentValid())
						locList.add(newCustomLoc.getLocationList());
				}
				value = arrLocs.next();
			}
		}			

		arrLocs = null;
		arrLocs = m_Stub.getRecord(getFormatByteArray(ext.toString() + m_Stub.getExt2DefaultLocListDomain()));
		if (arrLocs.count() > 0){
			value = arrLocs.first();
			while (value != null) {
				Location newLoc = new Location(value);
				locList.add(newLoc);
				value = arrLocs.next();					
			}
		}
		
		return locList;
	}

	public void addUser(UserID id, Digits newExtension, Password newPin, int accessLevel, String customMsg)
		throws AccessDeniedException, RecordAlreadyExistsException, DirectoryServiceException{
		
		if (newExtension.count() < DirectoryService.MIN_EXTENSION_SIZE || newExtension.count() > DirectoryService.MAX_EXTENSION_SIZE)
			throw new DirectoryServiceException("Invalid input for the 2nd parameter.");
		if (m_AccessLevel >= DirectoryService.ADM_ACCESS_LEVEL) {
			try {
				UserID uid = getID(newExtension);
				// newExtension has already been assigned to another user
				throw new RecordAlreadyExistsException("The input extension has already been assigned.  Please choose another new extension.");
			}catch (RecordNotFoundException ex) {
				addRecordsForAUser(id, newExtension, newPin, accessLevel, customMsg);	
			}
		}
		else
			throw new AccessDeniedException("Failed to addUser: Access Denied.");
	}
	
	public Digits addUser(UserID id, int numberOfDigitForExtension, Password newPin, int accessLevel, String customMsg)
		throws AccessDeniedException, RecordAlreadyExistsException, DirectoryServiceException{

		if (numberOfDigitForExtension < DirectoryService.MIN_EXTENSION_SIZE || numberOfDigitForExtension > DirectoryService.MAX_EXTENSION_SIZE)
			throw new DirectoryServiceException("Invalid input for the 2nd parameter.");
		if (m_AccessLevel >= DirectoryService.ADM_ACCESS_LEVEL) {	
			try {
				Digits digit = getExtension(id);
				// UserID already exists
				throw new RecordAlreadyExistsException("Failed to addUser: The input UserID already exists in the directory.  Please choose another new UserID.");
			}catch (RecordNotFoundException ex) {
				// No record found -> add the new user
				int nNewExtension = 1;
				for (int count = numberOfDigitForExtension; count>DirectoryService.MIN_EXTENSION_SIZE; count--)
					nNewExtension = nNewExtension * 10;
				ArrayRecords arrExtensions = m_Stub.getRecord(getFormatByteArray(m_Stub.getExtDistribDomain(numberOfDigitForExtension)));
				if (arrExtensions.count() > 0) {
					String value = arrExtensions.first();
					while (value != null) {
						int nval = Integer.parseInt(value);
						if (nNewExtension < nval)
							nNewExtension = nval;
						value = arrExtensions.next();
					}
					nNewExtension++;
				}
				else if (arrExtensions.count() < 0) {
					throw new DirectoryServiceException("Failed: DS_DOMAIN errors.");
				}
				// Found the maximum extension assigned -> add the new user
				Digits newExtension = new Digits(String.valueOf(nNewExtension));
				if (newExtension.count() > numberOfDigitForExtension)
					throw new AccessDeniedException("Failed to addUser.");
				addRecordsForAUser(id, newExtension, newPin, accessLevel, customMsg);
				return newExtension;
			}
		}
		else
			throw new AccessDeniedException("Failed to addUser: Access Denied.");
	}

	// helper method for addUser
	private void addRecordsForAUser(UserID id, Digits newExtension, Password newPin, int accessLevel, String customMsg) {
		AuthenticateRecord authRec = new AuthenticateRecord(newPin, accessLevel);
		CustomerRecord userRec = new CustomerRecord(id, customMsg);

		// Four entries needs to be created in the directory database for a new user
		m_Stub.addRecord(getFormatByteArray(m_Stub.getExtDistribDomain(newExtension.count())), getFormatByteArray(newExtension.toString()) );
		m_Stub.addRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2SecDomain()), getFormatByteArray(authRec.toString()));
		m_Stub.addRecord(getFormatByteArray(id.toString() + m_Stub.getUserID2ExtDomain()), getFormatByteArray(newExtension.toString()));
		m_Stub.addRecord(getFormatByteArray(newExtension.toString() + m_Stub.getExt2UserDomain()), getFormatByteArray(userRec.toString()));
	}
	
	public void removeUser(Digits extension)
		throws AccessDeniedException, RecordNotFoundException, DirectoryServiceException{
		if (m_AccessLevel >= DirectoryService.ADM_ACCESS_LEVEL) {
			ArrayRecords user = m_Stub.getRecord(getFormatByteArray(extension + m_Stub.getExt2UserDomain()));
			if (user.count() == 1) {
				CustomerRecord userRec = new CustomerRecord(user.first());
				m_Stub.deleteRecord(getFormatByteArray(m_Stub.getExtDistribDomain(extension.count())), getFormatByteArray(extension.toString()) );
				m_Stub.deleteRecord(getFormatByteArray(userRec.getUserID().toString() + m_Stub.getUserID2SecDomain()), null);
				m_Stub.deleteRecord(getFormatByteArray(userRec.getUserID().toString() + m_Stub.getUserID2ExtDomain()), null);
				m_Stub.deleteRecord(getFormatByteArray(extension.toString() + m_Stub.getExt2UserDomain()), null);
				
				m_Stub.deleteRecord(getFormatByteArray(extension.toString() + m_Stub.getExt2RoamLocDomain()), null);
				m_Stub.deleteRecord(getFormatByteArray(extension.toString() + m_Stub.getExt2DynamicLocListDomain()), null);
				m_Stub.deleteRecord(getFormatByteArray(extension.toString() + m_Stub.getExt2DefaultLocListDomain()), null);
				
				ArrayRecords customLocList = m_Stub.getRecord(getFormatByteArray(extension.toString() + m_Stub.getExt2CustomLocListDomain()));
				m_Stub.deleteRecord(getFormatByteArray(extension.toString()+ m_Stub.getExt2CustomLocListDomain()), null);
				if (customLocList.count() > 0) {
					String value = customLocList.first();
					while (value != null) {
						m_Stub.deleteRecord(getFormatByteArray(value + m_Stub.getCustomLocDomain()), null);
						value = customLocList.next();
					}
				}
			}
			else if (user.count() == 0) {
				throw new DirectoryServiceException("ERROR: No user is associated with this extension !!");
			}
			else if (user.count() > 1) {
				throw new DirectoryServiceException("ERROR: More than one user is associated with this extension !!");
				// clean up needed in USERID2SEC_DOMAIN and USERID2EXT_DOMAIN
			}	
		}
		else
			throw new AccessDeniedException("Failed: Access Denied.");
	}
	
	/**
	 * Dump all users' records from the directory database to a 
	 * UserProperty vector.  
	 */
	public Vector dumpAllUsers()
		throws AccessDeniedException, RecordNotFoundException, DirectoryServiceException{
		Vector outVector = new Vector();

		int nStartInfix = m_Stub.getMinSubDomain()+1;
		if (m_AccessLevel == DirectoryService.ADM_ACCESS_LEVEL)
			nStartInfix = m_Stub.getMinSubDomain();		

		for (int ii=nStartInfix; ii <= m_Stub.getMaxSubDomain(); ii++) {
			ArrayRecords arrExtensions = m_Stub.getRecord(getFormatByteArray(m_Stub.getExtDistribDomain(ii)));
			if (arrExtensions.count() > 0) {
				String value = arrExtensions.first();
				while (value != null) {
					ArrayRecords user = m_Stub.getRecord(getFormatByteArray(value + m_Stub.getExt2UserDomain()));
					if (user.count() == 1) {
						CustomerRecord userRec = new CustomerRecord(user.first());

						ArrayRecords roam = m_Stub.getRecord(getFormatByteArray(value + m_Stub.getExt2RoamLocDomain()));
						Location roamLoc = null;
						if (roam.count() > 0)
							roamLoc = new Location(roam.first());
						if (roam.count() > 1) {
							// Sanity check: user normally have 1 roaming location; this situation does not hurt
							System.out.println("In dumpAllUsers: User " + (userRec.getUserID()).toString() + " has more than one roaming location!!");
						}
						//Create an UserProperty object
						UserProperty usrProperty = new UserProperty(userRec.getUserID(), Integer.parseInt(value), roamLoc, userRec.getCustomMsg());
						outVector.addElement(usrProperty);
					}
					else if (user.count() == 0) {
						// Adm should clean it up
						System.out.println("ERROR: No user is associated with this extension: " + value + " !!");
					}
					else if (user.count() > 1) {
						throw new DirectoryServiceException("ERROR: More than one user is associated with this extension: " + value + " !!");
					}
					value = arrExtensions.next();
				}
			}
		}
		return outVector;		
	}

}
