/**
* This package houses the optional extra database server for the XCDE server. The database
* server will capture all events sent through the server into a database to supply the server
* admin screen with historical data and the data to client replay views. Replay views will
* not function without this database being populated with change history data.
*/
package ca.uwaterloo.fydp.db;
import ca.uwaterloo.fydp.ossp.OSSPStimulus;
import ca.uwaterloo.fydp.ossp.impl.OSSPSimpleSynchronizer;
import ca.uwaterloo.fydp.ossp.std.*;
import ca.uwaterloo.fydp.xcde.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Iterator;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
/**
* The root class of the XCDE database server. The main method is run to start
* the server, register the XCDE client listener. Terminate the execution of
* this program to shutdown the server.
*/
public class serverDB implements XCDERootDirClientCallbacks, XCDEDocClientCallbacks, Runnable
{
private XCDEDirClient client;
private XCDEDirClient root;
private OSSPSimpleSynchronizer sync;
private Connection conn;
private long timeInterval;
private InetAddress XCDEaddr;
private int XCDEport;
private String MySQLname;
private int MySQLport;
private String dbUsername;
private String dbPassword;
private static final String DEFAULT_XCDE_SERVER = "localhost";
private static final String DEFAULT_MYSQL_SERVER = "localhost";
private static final int DEFAULT_XCDE_SERVER_PORT = 48879;
private static final int DEFAULT_MYSQL_SERVER_PORT = 3306;
private static final String DEFAULT_DB_USERNAME = "serverDB";
private static final String DEFAULT_DB_PASSWORD = "db1user";
private static final long DEFAULT_TIME_INTERVAL = 3600000;
/**
* Initializes the database logger to listen for XCDE change events.
*
* The mysql JDBC driver is loaded and the server and database are connected
* to. The JDBC connection assumes that an XCDE database has already been
* created with the appropriate tables setup. There must also be a user that
* can connect to and modify the database; this should not be root due to
* the security and stability considerations of having an automated program
* run as the database superuser.
*
* @author Andrew Craik
* @param serverAddr The address of the XCDE server to connect to
* @param serverPort The port to connect to the XCDE server on
* @param DBServer The hostname of the MySQL server to connect to
* @param DBport The port to connect to the MySQL server on
* @param DBuser The username to use to connect to the MySQL server
* @param DBpasswd The password to use to connect to the MySQL server
* @param timeInterval The time interval between changes that are sent to replay client backend
*/
public serverDB(InetAddress serverAddr, int serverPort, String DBServer, int DBport, String DBuser, String DBpasswd, long timeInterval)
{
this.MySQLname = DBServer;
this.MySQLport = DBport;
this.dbPassword = DBpasswd;
this.dbUsername = DBuser;
this.timeInterval = timeInterval;
this.XCDEaddr = serverAddr;
this.XCDEport = serverPort;
// we need a synchronization object so that all our changes are atomic.
// we will use the same sync object for all changes to ensure stability
// and atomicity
sync = new OSSPSimpleSynchronizer();
// we have to load the com.mysql.jdbc.Driver class so that when we go to
// make an SQL
// connection we are able to find the specific driver that we need
try
{
Class.forName("com.mysql.jdbc.Driver");
}
catch (ClassNotFoundException e)
{
throw new RuntimeException("Could not locate the com.mysql.jdbc.Driver class!", e);
}
// now we have the MySQL JDBC dirver loaded we need to connect up using
// either the user
// supplied or default values for the different parameters
try
{
conn = DriverManager.getConnection("jdbc:mysql://" + MySQLname
+ ":" + MySQLport + "/xcde", dbUsername, dbPassword);
conn.setAutoCommit(true);
}
catch (SQLException e)
{
throw new RuntimeException("Unable to connect to jdbc:mysql://"
+ MySQLname + ":" + MySQLport + "/xcde as user "
+ dbUsername, e);
}
// The SQL database is ready for use so now we startup the XCDE server
// we also register ourselves to handle changes to the root directory
// structure and
// download the contents of the same to initiate the recurisve open and
// registration
// for all directories and files on the server
client = XCDE.connectToServer(XCDEaddr, XCDEport, sync);
root = client;
client.setCallbacks(this);
// We call pace() to let OSSP block us 'till an appropriate time to send.
client.pace();
synchronized(sync)
{
client.beginDownloadState();
}
if (timeInterval >= 0)
{
Thread contentUpdate = new Thread(this, "updates doc contents periodically");
contentUpdate.start();
}
}
/**
* Run this method to start the server and register the XCDEClient
*
* @param args Command line arguments to override default connection values.
*/
public static void main(String[] args)
{
// set default servernames for address lookup
String XCDEHost = DEFAULT_XCDE_SERVER;
// set defaults
int XCDEport = DEFAULT_XCDE_SERVER_PORT;
int MySQLport = DEFAULT_MYSQL_SERVER_PORT;
String dbUsername = DEFAULT_DB_USERNAME;
String dbPassword = DEFAULT_DB_PASSWORD;
String MySQLname = DEFAULT_MYSQL_SERVER;
long timeInterval = DEFAULT_TIME_INTERVAL;
// We are going to construct the listeners we need
// first we process the command line to see if there are any options
// there
// to change the default configuration
for (int i = 0; i < args.length; i += 2)
{
if (args[i].equals("--XCDEHost"))
XCDEHost = args[i + 1];
else if (args[i].equals("--XCDEPort"))
XCDEport = Integer.getInteger(args[i + 1]).intValue();
else if (args[i].equals("--MySQLHost"))
MySQLname = args[i + 1];
else if (args[i].equals("--MySQLHostPort"))
MySQLport = Integer.getInteger(args[i + 1]).intValue();
else if (args[i].equals("--dbUsername"))
dbUsername = args[i + 1];
else if (args[i].equals("--dbPassword"))
dbPassword = args[i + 1];
else if (args[i].equals("--timeInterval"))
timeInterval = Long.parseLong(args[i + 1]);
else if (args[i].equals("-h") || args[i].equals("--help"))
{
System.out.println("Starts the XCDE database logging server. Usage:\n");
System.out.println("\t--XCDEHost\t-\tSpecify the hostname running the XCDE server to listen to");
System.out.println("\t--XCDEHostPort\t-\tSpecify the port on which the XCDE server is listening");
System.out.println("\t--MySQLHost\t-\tSpecify the hostname running the MySQL server to log to");
System.out.println("\t--MySQLPort\t-\tSpecify the port the MySQL server is listening to");
System.out.println("\t--dbUsername\t-\tSpecify the username to be used to connect to the MySQL database");
System.out.println("\t--dbPassword\t-\tSpecify the password to be used to connect to the MySQL database");
System.out.println("\t--timeInterval\t-\tSpecify the time interval in msec between file content downloads, negative to disable");
System.out.println("\t-h or --help\t-\tPrint this help");
return;
}
else
throw new IllegalArgumentException("Unrecognized argument "
+ args[i] + ". Use --help to print command options!");
}
InetAddress XCDEaddr;
try
{
XCDEaddr = InetAddress.getByName(XCDEHost);
}
catch (UnknownHostException e)
{
throw new IllegalArgumentException("Could not find host " + XCDEHost, e);
}
// we create our object and will ensure that we keep the reference to
// ourselves by
// using the synchronization object feature of XCDEClient, ugly but
// functional
new serverDB(XCDEaddr, XCDEport, MySQLname, MySQLport, dbUsername, dbPassword, timeInterval);
}
/**
* This handler processes the initial list of files and directories in a
* directory. We have to process the directory contents and set ourselves as
* the callback for all directory contents. We also need to get the initial
* state of diretories so we can register to listen for changes to files and
* directories it contains.
*
* @param state
* List of files and directories contained in the current
* directory on the server
*/
public void receiveState(XCDEDirClient client, List state)
{
for (Iterator i = state.iterator(); i.hasNext();)
{
OSSPDirectoryListingElement item = (OSSPDirectoryListingElement) i.next();
// if we have a subdir open it, register for as the callback listner
// and download
// the directory contents
if (item.className.equals(OSSPDirectory.class.getName()))
{
if (!runExistanceSQLQuery(buildDirectoryExistsStatement(client.getPath(), item.name)))
runSQLInsertOrUpdate(buildDirectoryInsertStatement(client.getPath(), item.name));
final XCDEDirClient dir = client.opendir(item.name);
dir.setCallbacks(this);
// We already hold the lock, so we must use asyncExec() in order to get
// pacing.
dir.asyncExec(new Runnable()
{
public void run()
{
if (dir.isConnected())
dir.beginDownloadState();
}
});
}
// if we have a file we just want to register to listen to changes
// to the file
else if (item.className.equals(XCDEDocument.class.getName()))
{
if (!runExistanceSQLQuery(buildFileExistsStatement(client.getPath(), item.name)))
runSQLInsertOrUpdate(buildFileCreateStatement(client.getPath(), item.name));
final XCDEDocClient file = client.openfile(item.name);
file.setCallback(this);
// We already hold the lock, so we must use asyncExec() in order to get
// pacing.
file.asyncExec(new Runnable()
{
public void run()
{
if (file.isConnected())
file.beginDownloadState();
}
});
}
else
{
// Ignore things in the server that aren't recognized
// by XCDE.
continue;
}
}
}
/**
* The initial state receiver for files. This isn't going to be used by the
* database server normally since we are capturing the deltas and not the
* full contents of the file.
*
* @param state
* The text of the change
*/
public void receiveState(XCDEDocClient client, XCDEDocument document)
{
String filename = client.getPath().substring(client.getPath().lastIndexOf("/")+1);
String path = client.getPath().substring(0,client.getPath().lastIndexOf("/")+1);
runSQLInsertOrUpdate(buildFileContentStatement(path, filename, document.content));
}
public void notifyOfDisconnect(XCDEDirClient client)
{
// QUESTION do I need to do anything here, I don't think so
}
public void notifyOfDisconnect(XCDEDocClient client)
{
// QUESTION do I need to do anything here, I don't think so
}
// this will be the one that gets the file object passed in so we will rough
// out
// the code for what to do to log these changes and we can test later
/**
* Receives file changes from the server, processes them, and stores them
* into the database
*
* @param ch the change
*/
public void notifyOfChange(XCDEDocClient client, OSSPStimulus ch)
{
String insert = null;
String filename = client.getPath().substring(client.getPath().lastIndexOf("/")+1);
String path = client.getPath().substring(0,client.getPath().lastIndexOf("/")+1);
if (ch instanceof XCDEDocChangeInsertion)
{
if(!runExistanceSQLQuery(buildUserExistanceStatement(((XCDEDocChangeInsertion)ch).user)))
runSQLInsertOrUpdate(buildUserCreationStatement(((XCDEDocChangeInsertion)ch).user));
insert = buildFileChangeStatement(path, filename, 'I', ((XCDEDocChangeInsertion)ch).pos,
((XCDEDocChangeInsertion)ch).text, ((XCDEDocChangeInsertion)ch).user);
}
else if (ch instanceof XCDEDocChangeDeletion)
{
if(!runExistanceSQLQuery(buildUserExistanceStatement(((XCDEDocChangeDeletion)ch).user)))
runSQLInsertOrUpdate(buildUserCreationStatement(((XCDEDocChangeDeletion)ch).user));
insert = buildFileChangeStatement(path, filename, 'D', ((XCDEDocChangeDeletion)ch).pos,
Integer.toString(((XCDEDocChangeDeletion)ch).num), ((XCDEDocChangeDeletion)ch).user);
}
else if (ch instanceof OSSPCompoundStimulus)
{
notifyOfChange(client, ((OSSPCompoundStimulus) ch).getFirst());
notifyOfChange(client, ((OSSPCompoundStimulus) ch).getSecond());
return;
}
else if (ch instanceof XCDEDocumentAnnotationStimulusAdd)
{
//TODO: store annotation additions
}
else if (ch instanceof XCDEDocumentAnnotationStimulusChange)
{
//TODO: store annotation changes
}
else if (ch instanceof XCDEDocumentAnnotationStimulusDelete)
{
//TODO: store annotation deletes
}
else
throw new RuntimeException("Unknown XCDEDocChange received: " + ch.getClass().getName());
if (insert != null)
{
System.out.println("========FileChange=========");
System.out.println(insert);
System.out.flush();
runSQLInsertOrUpdate(insert);
}
}
/**
* Receives directory changes from the server, processes them, and stores
* them into the database
*
* @param ch the change
*/
public void notifyOfChange(XCDEDirClient client, OSSPStimulus ch)
{
String path = client.getPath();
// we have to declare the holders for the different SQL components
// so we can safely unwind in the finally block incase we have an
// exception during exceution
String sql = null;
if (ch instanceof OSSPDirectoryStimulusNotifyCreate)
{
OSSPDirectoryStimulusNotifyCreate newDir = (OSSPDirectoryStimulusNotifyCreate) ch;
if (newDir.getCreatedClassName().equals(OSSPDirectory.class.getName()))
{
//New thing is a directory.
final XCDEDirClient newDirClient = client.opendir(newDir.getAffectedElementName());
newDirClient.setCallbacks(this);
// We already hold the lock, so we must use asyncExec() in order to get
// pacing.
newDirClient.asyncExec(new Runnable()
{
public void run()
{
if (newDirClient.isConnected())
newDirClient.beginDownloadState();
}
});
sql = buildDirectoryInsertStatement(path, newDir.getAffectedElementName());
}
else if (newDir.getCreatedClassName().equals(XCDEDocument.class.getName()))
{
//New thing is a document.
final XCDEDocClient newFileClient = client.openfile(newDir.getAffectedElementName());
newFileClient.setCallback(this);
// We already hold the lock, so we must use asyncExec() in order to get
// pacing.
newFileClient.asyncExec(new Runnable()
{
public void run()
{
if (newFileClient.isConnected())
newFileClient.beginDownloadState();
}
});
sql = buildFileCreateStatement(path, newDir.getAffectedElementName());
}
else
{
// Ignore things in the server that aren't recognized by
// XCDE.
return;
}
}
else if (ch instanceof OSSPDirectoryStimulusDelete)
{
OSSPDirectoryStimulusDelete delDir = (OSSPDirectoryStimulusDelete) ch;
// we do not have to specifically unregister as the callback will be
// destroyed
// when the client is freed.
if (client.getOpenChild(delDir.name) instanceof XCDEDirClient)
{
sql = buildDirectoryDeleteStatement(path, delDir.name);
}
else if (client.getOpenChild(delDir.name) instanceof XCDEDocClient)
{
sql = buildFileDeleteStatement(path, delDir.name);
}
else
throw new RuntimeException("Unknown client type!");
}
else if (ch instanceof OSSPCompoundStimulus)
{
notifyOfChange(client, ((OSSPCompoundStimulus) ch).getFirst());
notifyOfChange(client, ((OSSPCompoundStimulus) ch).getSecond());
return;
}
else if (ch instanceof XCDERootDirectoryUserStimulusAdd)
{
runSQLInsertOrUpdate(buildUserCreationStatement(((XCDERootDirectoryUserStimulusAdd)ch).state.userName));
sql = buildUserEventStatement('A',((XCDERootDirectoryUserStimulusAdd)ch).state.userName);
}
else if (ch instanceof XCDERootDirectoryUserStimulusRemove)
{
runSQLInsertOrUpdate(buildUserEventStatement('R',((XCDERootDirectoryUserStimulusRemove)ch).userName));
sql = buildUserRemovalStatement(((XCDERootDirectoryUserStimulusRemove)ch).userName);
}
else if (ch instanceof XCDERootDirectoryUserStimulusChange)
{
XCDERootDirectoryUserStimulusChange change = (XCDERootDirectoryUserStimulusChange)ch;
if (!change.currUserName.equals(change.newState.userName))
{
runSQLInsertOrUpdate(buildUserEventStatement('R',change.currUserName));
runSQLInsertOrUpdate(buildUserRemovalStatement(change.currUserName));
runSQLInsertOrUpdate(buildUserCreationStatement(change.newState.userName));
sql = buildUserEventStatement('A', change.newState.userName);
}
else
return;
}
else
throw new RuntimeException("UnknownXCDEDirChange received!");
System.out.println("=======Directory Change=======");
System.out.println(sql);
System.out.flush();
runSQLInsertOrUpdate(sql);
}
//---------------------------------Private internal functions start here-------------------------------
/**
* Creates a SQL statement to add a File to the XCDE database
* @param path The fully qualified path name of the directory containing the file
* @param filename The name of the file created
* @return A string that is the SQL statement
*/
private String buildFileCreateStatement(String path, String filename)
{
//TODO hardcoded user, not sure if we can ever get a user for this in all cases
return "INSERT INTO Files VALUES (NULL, '" + filename
+ "', (SELECT dirID FROM Directories WHERE FullName = '" + path + "'), NULL, NULL, 1, NULL)";
}
private String buildFileContentStatement(String path, String filename, String contents)
{
contents = replaceStr(contents);
return "INSERT INTO FileContents VALUES (NULL, " +
"(SELECT fileID FROM Files INNER JOIN Directories ON Files.DirLoc = Directories.dirID WHERE Files.FileName = '" + filename + "' AND Directories.FullName = '" + path + "'" +
" AND Files.DropDate IS NULL AND Directories.DropDate IS NULL), NULL, '" + contents + "')";
}
/**
* Create a SQL update statement to mark a File in the XCDE database as deleted
* @param path The fully qualified path name of the directory containing the file
* @param filename The name of the file to mark as deleted
* @return A string that is the SQL statement
*/
private String buildFileDeleteStatement(String path, String filename)
{
//TODO hardcoded user, not sure if we can ever get a user for this in all cases
return "UPDATE Files,Directories SET Files.DropDate = NOW(), Files.DropUserID = 1 WHERE Files.FileName = '" + filename +
"' AND Directories.dirID = Files.DirLoc AND Files.DropDate IS NULL AND Directories.FullName = '" + path
+ "' AND Directories.DropDate IS NULL";
}
/**
* Create a SQL query statement that will return a row if the specified file exists in
* the database
* @param path The fully qualified path name of the directory that could contain the file
* @param filename The name of the file being queried for
* @return A string that is the SQL statement
*/
private String buildFileExistsStatement(String path, String filename)
{
return "SELECT * FROM Files WHERE Files.FileName = '" + filename + "' AND Files.DropDate IS NULL AND Files.DirLoc = " +
"(SELECT dirID FROM Directories WHERE FullName = '" + path + "' AND DropDate IS NULL)";
}
private String replaceStr(String data)
{
data = data.replace("\\", "\\\\");
return data.replace("'", "\\'");
}
/**
* Create a SQL query to add a FileChange event to the XCDE database
* @param path The fully qualified path name of the directory containing the modified file
* @param filename The name of file modified
* @param type The type of change made (one of I for insert or D for deletion)
* @param pos The integer character position of the change
* @param data The data associated with the change
* @param user The name of the user who effected the change
* @return A string that is the SQL statement
*/
private String buildFileChangeStatement(String path, String filename, char type, int pos, String data, String user)
{
data = replaceStr(data);
return "INSERT INTO FileChanges VALUES(NULL, " +
"(SELECT fileID FROM Files INNER JOIN Directories ON " +
"Directories.dirID = Files.DirLoc WHERE Files.FileName = '" +
filename + "' AND Files.DropDate IS NULL AND Directories.FullName = '"+ path + "' AND Directories.DropDate IS NULL)" +
", '" + type + "', " + pos + ", '" + data + "', NULL, (SELECT usersID FROM Users WHERE ShortName = '" + user + "'))";
}
/**
* Create a SQL insert statement to add a directory to the XCDE database
* @param path The fully qualified path of the parent directory
* @param newDir The name of the new directory
* @return A string that is the SQL statement
*/
private String buildDirectoryInsertStatement(String path, String newDir)
{
//TODO hardcoded user, not sure if we can ever get a user for this in all cases
return "INSERT INTO Directories VALUES(NULL, '" + path + newDir + "/" + "', NULL, NULL, 1, NULL)";
}
/**
* Create a SQL update statement to mark a directory as deleted in the XCDE database
* @param path The fully qualified name of the parent directory
* @param dirname The name of the directory to delete
* @return A string that is the SQL statement
*/
private String buildDirectoryDeleteStatement(String path, String dirname)
{
//TODO hardcoded user, not sure if we can ever get a user for this in all cases
return "UPDATE Directories SET DropDate = NOW(), DropUserID = 1 WHERE FullName = '" + path + dirname + "/' AND DropDate IS NULL";
}
/**
* Create a SQL query to determine if a directory exists in the current XCDE database
* @param path The fully qualified name of the parent directory
* @param dirname The name of the directory being queried for
* @return A string that is the SQL query
*/
private String buildDirectoryExistsStatement(String path, String dirname)
{
return "SELECT * FROM Directories WHERE FullName = '" + path + dirname + "/' AND DropDate IS NULL";
}
/**
* Create a SQL query to determine if a user exists in the current XCDE database
* @param username The short username
* @return A string that is the SQL query
*/
private String buildUserExistanceStatement(String username)
{
return "SELECT * FROM Users WHERE ShortName = '" + username + "' AND DropDate IS NULL";
}
/**
* Create a SQL insert statement to add a user to the XCDE database
* @param username The short username of the new user
* @return A string that is the SQL insert statement
*/
private String buildUserCreationStatement(String username)
{
return "INSERT INTO Users VALUES(NULL, '" + username + "', '" + username + "', '" + username + "', NULL, NULL)";
}
/**
* Create a SQL update statement to mark a user as dropped from the XCDE database
* @param username The short username of the user to drop
* @return A string that is the SQL update statement
*/
private String buildUserRemovalStatement(String username)
{
return "UPDATE Users SET Users.DropDate = Now() WHERE Users.shortname = '" + username + "' AND Users.DropDate IS NULL";
}
/**
* Create a SQL statement to add a user even to the XCDE database
* @param eventType Character for the event type
* @param username The short username of the user associated with the event
* @return The SQL string that is the insert statement
*/
private String buildUserEventStatement(char eventType, String username)
{
return "INSERT INTO UserEvents VALUES(NULL, '" + eventType + "', NULL, (SELECT usersID FROM Users WHERE ShortName = '" + username + "' AND DropDate IS NULL), '')";
}
/**
* Runs the SQL update or insert statement passed
* @param sql The update or insert statement to be run
*/
private void runSQLInsertOrUpdate(String sql)
{
Statement stmt = null;
try
{
stmt = conn.createStatement();
stmt.executeUpdate(sql);
if (stmt.getUpdateCount() == 0)
throw new RuntimeException("No rows affected by update " + sql);
stmt.close();
}
catch (SQLException e)
{
throw new RuntimeException("SQLException thrown when trying to run " + sql, e);
}
finally
{
try
{
if (stmt != null)
stmt.close();
}
catch (SQLException e)
{
// ignore failure since we are in finally and something else
// has already
// happened and we were just trying to do the right thing
}
}
}
/**
* Runs the SQL query provided and returns true if 1 or more rows are returned, or false otherwise
* @param query The SQL query to run
* @return true if 1 or more rows are returned when the query is run, false otherwise
*/
private boolean runExistanceSQLQuery(String query)
{
boolean toReturn = false;
try
{
Statement check = conn.createStatement();
ResultSet rs = check.executeQuery(query);
rs.last();
if (rs.getRow() > 0)
toReturn = true;
rs.close();
check.close();
}
catch (SQLException e)
{
throw new RuntimeException("Error running query " + query, e);
}
return toReturn;
}
/**
* The main method run by the file content capture thread, sleeps between complete
* file content downloads.
*/
public void run()
{
while (true)
{
try
{
Thread.sleep(timeInterval);
}
catch (InterruptedException e)
{
System.err.println(e);
}
System.out.println("########WAKEUP#########");
synchronized (sync)
{
processDirectory(root);
}
}
}
/**
* Used by the file content download thread to process directories recursively and
* trigger download the document contents
* @param dir The directory client whose children we want to process
*/
private void processDirectory(XCDEDirClient dir)
{
Iterator i = dir.getOpenChildren();
while (i.hasNext())
{
final Object child = i.next();
if (child instanceof XCDEDirClient)
{
System.out.println("Found Dir " + ((XCDEDirClient)child).getPath());
processDirectory((XCDEDirClient)child);
}
else if (child instanceof XCDEDocClient)
{
System.out.println("Found File " + ((XCDEDocClient)child).getPath());
// We already hold the lock, so we must use asyncExec() in order to get
// pacing.
((XCDEDocClient)child).asyncExec(new Runnable()
{
public void run()
{
if (((XCDEDocClient)child).isConnected())
((XCDEDocClient)child).beginDownloadState();
}
});
}
else
throw new RuntimeException("Unexpected class type in directory processing " + child.getClass().getName());
}
}
public void receiveUsers(XCDEDirClient client, List users)
{
for (Iterator i = users.iterator(); i.hasNext(); )
{
XCDERootDirectoryUserState state = (XCDERootDirectoryUserState)i.next();
if(runExistanceSQLQuery(buildUserExistanceStatement(state.userName)))
{
continue;
}
else
{
runSQLInsertOrUpdate(buildUserCreationStatement(state.userName));
runSQLInsertOrUpdate(buildUserEventStatement('A',state.userName));
}
}
}
}