diff --git a/code/Testcases_Fortin_6338858.docx b/code/Testcases_Fortin_6338858.docx new file mode 100644 index 0000000..e6b7f17 Binary files /dev/null and b/code/Testcases_Fortin_6338858.docx differ diff --git a/code/simplechat1/ClientConsole.java b/code/simplechat1/ClientConsole.java index c9bb4e9..5da272b 100644 --- a/code/simplechat1/ClientConsole.java +++ b/code/simplechat1/ClientConsole.java @@ -41,17 +41,16 @@ public class ClientConsole implements ChatIF * @param host The host to connect to. * @param port The port to connect on. */ - public ClientConsole(String host, int port) + public ClientConsole(String clientId, String host, int port) { try { - client= new ChatClient(host, port, this); + client= new ChatClient(clientId, host, port, this); } catch(IOException exception) { - System.out.println("Error: Can't setup connection!" - + " Terminating client."); - System.exit(1); + System.out.println("Cannot open connection." + + " Awaiting command."); } } @@ -91,7 +90,18 @@ public void accept() */ public void display(String message) { - System.out.println("> " + message); + //For a message coming from the server to not have a > prepended + try{ + if (message.charAt(10)=='>'){ + System.out.println(message); + } + else{ + System.out.println("> " + message); + } + } + catch(IndexOutOfBoundsException e){ + System.out.println("> " + message); + } } @@ -106,16 +116,48 @@ public static void main(String[] args) { String host = ""; int port = 0; //The port number - + String clientId = ""; //The user's loginId + + + //Question 7a + //Assigns the first argument as the loginId + try { + clientId=args[0]; + } + //If no loginId is provided an error message is printed and the user is disconnected + catch(ArrayIndexOutOfBoundsException e0){ + System.out.println("Cannot connect to server without login Id."); + System.exit(0); + } + try { - host = args[0]; + //Question 7 a + //changed to second arg since adding clientId + host = args[1]; } - catch(ArrayIndexOutOfBoundsException e) + catch(ArrayIndexOutOfBoundsException e1) { host = "localhost"; } - ClientConsole chat= new ClientConsole(host, DEFAULT_PORT); + + //Question 5b + //This try loop checks to see if there is a second argument + //If that's the case, it will assign it as the port number + try + { + //Question 7 a + //changed to third arg since adding clientId + port = Integer.valueOf(args[2]); + } + //If there's no 2nd argument, it will use the DEFAULT_PORT + catch(ArrayIndexOutOfBoundsException e2) + { + port = DEFAULT_PORT; + } + //will use the port variable defined above instead of DEFAULT_PORT + //added the loginId to the constructor (Question 7a) + ClientConsole chat= new ClientConsole(clientId, host, port); chat.accept(); //Wait for console data } } diff --git a/code/simplechat1/EchoServer.java b/code/simplechat1/EchoServer.java index d4f3a1a..9554ace 100644 --- a/code/simplechat1/EchoServer.java +++ b/code/simplechat1/EchoServer.java @@ -24,6 +24,8 @@ public class EchoServer extends AbstractServer */ final public static int DEFAULT_PORT = 5555; + + //Constructors **************************************************** /** @@ -34,11 +36,59 @@ public class EchoServer extends AbstractServer public EchoServer(int port) { super(port); + } //Instance methods ************************************************ + /** + * Hook method called each time an exception is thrown in a + * ConnectionToClient thread. + * The method may be overridden by subclasses but should remains + * synchronized. + * + * @param client the client that raised the exception. + * @param Throwable the exception thrown. + */ + + //Question 5c + //when an exception is thrown, it calls the clientDisconnected hook method to print the appropriate message. + synchronized protected void clientException( + ConnectionToClient client, Throwable exception) { + clientDisconnected(client); + } + + /** + * Hook method called each time a new client connection is + * accepted. The default implementation does nothing. + * @param client the connection connected to the client. + */ + + //Question 5c + //prints a message when the client logs on. + protected void clientConnected(ConnectionToClient client) { + System.out.println("A new client is attempting to connect to the server."); + } + + /** + * Hook method called each time a client disconnects. + * The default implementation does nothing. The method + * may be overridden by subclasses but should remains synchronized. + * + * @param client the connection with the client. + */ + + //Question 5c + //prints a message when the client logs off + //changed to comply with the testcases + synchronized protected void clientDisconnected(ConnectionToClient client) { + this.sendToAllClients(client.getInfo("loginId").toString() + " has logged off "); + System.out.println(client.getInfo("loginId").toString()+" has logged off"); + + } + + /** * This method handles any messages received from the client. * @@ -48,8 +98,63 @@ public EchoServer(int port) public void handleMessageFromClient (Object msg, ConnectionToClient client) { - System.out.println("Message received: " + msg + " from " + client); - this.sendToAllClients(msg); + //converts the message Object into a string + String message = msg.toString(); + //splits the string into words + String[] idArray = message.split(" "); + + //Question 7 c i + //This if statement recognizes the #login command + if(idArray[0].equals("#login")){ + try{ + //Question 7 iv + //Tries to get loginId, if there is none, then (#login ) is the first message + // it would then jump to the catch Exception e + String test = client.getInfo("loginId").toString(); + try{ + //An error message would be printed if the loginId already exists + client.sendToClient("Error: you have already given us a login Id"); + } + catch(IOException e){} + } + catch(Exception f){ + //Question 7ii + //identifies the client by adding the loginId to the user info hashmap. + client.setInfo("loginId", idArray[1]); + //Question 5c + //Message that tells all users when someone logs onto the server + this.sendToAllClients(client.getInfo("loginId").toString() + " has logged on "); + System.out.println(client.getInfo("loginId").toString()+" has logged on"); + + } + } + else{ + try{ + //Tries to get loginId, if there is none, then the first message (#login ) was not recieved + // it would then jump to the catch Exception g + String test = client.getInfo("loginId").toString(); + //Question 7 c iii + //Prints message in server and sends the message out to all users with the loginId prepended. + System.out.println("Message received: " + msg + " from " + client.getInfo("loginId").toString()); + this.sendToAllClients(client.getInfo("loginId").toString() + " " + msg); + } + catch(Exception g){ + //Question 7 c v + //If the loginId has not already been recieved as 1st message it prints and error message + //and closes the connection to that client. + try{ + client.sendToClient("Error: no login Id provided by the system"); + } + catch(IOException h){} + finally{ + try{ + client.close(); + } + catch(IOException i){} + } + } + } + } /** @@ -69,7 +174,120 @@ protected void serverStarted() protected void serverStopped() { System.out.println - ("Server has stopped listening for connections."); + ("Server has stopped listening for new connections."); + } + + + /** + * Hook method called when the server is clased. + * The default implementation does nothing. This method may be + * overriden by subclasses. When the server is closed while still + * listening, serverStopped() will also be called. + */ + protected void serverClosed(){ + System.out.println + ("Server has closed all connections."); } + + + /** + * + *This method handles the case when a user enters a string starting with a # + *It parses the message to see what command it was then it executes said command + * + * @param message The message from the UI. + */ + + //Question 6 c + //Method that recieves a message that begins with # (a command) + //and parses which command was issued and what to do + public void commandHelperMethod(String message){ + + //seperates the command from the arguments (if pertinent - for #setport) + String[] argumentsArray = message.split(" "); + + switch(argumentsArray[0]){ + //Question 6 c i + case "#quit": //closes the server then quits the system + try{ + close(); + } + catch(IOException e){ + + } + System.out.print("Shutting down server"); + System.exit(0); + break; + //Question 6 c ii + case "#stop": //stops listening for new clients + stopListening(); + break; + //Question 6 c iii + case "#close": //closes the server (stops listening + kicks the current users off) + try{ + close(); + } + catch(IOException e){ + + } + break; + //Question 6 c iv + case "#setport": //allows the user to change the port (only available when not connected to a server) + if(!isListening()&&getNumberOfClients()==0){ + try{ + setPort(Integer.parseInt(argumentsArray[1])); + } + catch(ArrayIndexOutOfBoundsException e){ + System.out.println("Port number not specified. Please use the following format: #setport "); + } + } + else{ + System.out.println("The server is currently open. You must first close the server using #close"); + } + break; + //Question 6 c v + case "#start": //opens the server + try{ + listen(); + } + catch(IOException e){ + + } + break; + //Question 6 c vi + case "#getport": // prints the port number + System.out.println("The port number is: "+Integer.toString(getPort())); + break; + + default: //if someone typed the wrong command they would get an error message and a list of accepted commands + System.out.println("This is not a recognised command"); + System.out.println("Please use one of the following:"); + System.out.println("#quit, #stop, #close, #setport, #start or #getport"); + break; + } + + } + + /** + * This method handles all data coming from the UI + * + * @param message The message from the UI. + */ + + + public void handleMessageFromServerUI(String message) + { + //Checks if the message is a command and redirects it to the helper method + if(message.charAt(0)=='#'){ + commandHelperMethod(message); + } + else{ + //Question 6b ii + //makes the messages comming from the server begin with 'SERVER MSG>' + + sendToAllClients("SERVER MSG> "+message); + + System.out.println("SERVER MSG> " + message); + } } //Class methods *************************************************** diff --git a/code/simplechat1/ServerConsole.java b/code/simplechat1/ServerConsole.java new file mode 100644 index 0000000..226e3e2 --- /dev/null +++ b/code/simplechat1/ServerConsole.java @@ -0,0 +1,120 @@ +//Code written for Question 6b. Creates a ServerConsole which implements EchoServer +//And allows it to communicate with the other users + + + +import java.io.*; +import common.*; + +/** + * This class constructs the UI for a server. It implements the + * chat interface in order to activate the display() method. +*/ + +public class ServerConsole implements ChatIF +{ + //Class variables ************************************************* + + /** + * The default port to connect on. + */ + final public static int DEFAULT_PORT = 5555; + + //Instance variables ********************************************** + + /** + * The instance of the Server that created this ConsoleChat. + */ + EchoServer server; + + + //Constructors **************************************************** + + /** + * Constructs an instance of the ServerConsole UI. + * + * @param host The host to connect to. + * @param port The port to connect on. + */ + public ServerConsole(int port) + { + server= new EchoServer(port); + try + { + server.listen(); //Start listening for connections + } + catch (Exception ex) + { + System.out.println("ERROR - Could not listen for clients!"); + } + + } + + + //Instance methods ************************************************ + + /** + * This method waits for input from the console. Once it is + * received, it sends it to the client's message handler. + */ + public void accept() + { + try + { + BufferedReader fromConsole = + new BufferedReader(new InputStreamReader(System.in)); + String message; + + while (true) + { + message = fromConsole.readLine(); + server.handleMessageFromServerUI(message); + } + } + catch (Exception ex) + { + System.out.println + ("Unexpected error while reading from console!"); + } + } + + /** + * This method overrides the method in the ChatIF interface. It + * displays a message onto the screen. + * + * @param message The string to be displayed. + */ + public void display(String message) + { + //I don't actually need to call this method in EchoServer but it is implemented for future use + //makes the messages comming from the server begin with 'SERVER MSG>' + System.out.println("SERVER MSG> " + message); + } + + + //Class methods *************************************************** + + /** + * This method is responsible for the creation of the Server UI. + * + * @param args[0] The host to connect to. + */ + public static void main(String[] args) + { + String host = ""; + int port = 0; //The port number + + try + { + port = Integer.valueOf(args[0]); + } + //If there's no 1st argument, it will use the DEFAULT_PORT + catch(ArrayIndexOutOfBoundsException e) + { + port = DEFAULT_PORT; + } + ServerConsole chat= new ServerConsole(port); //will use the port variable instead of DEFAULT_PORT + chat.accept(); //Wait for console data + } +} +//End of ConsoleChat class diff --git a/code/simplechat1/client/ChatClient.java b/code/simplechat1/client/ChatClient.java index fe1401e..ee5cc65 100644 --- a/code/simplechat1/client/ChatClient.java +++ b/code/simplechat1/client/ChatClient.java @@ -26,6 +26,9 @@ public class ChatClient extends AbstractClient * the display method in the client. */ ChatIF clientUI; + //Question 7a + //Created a loginId variable to store the user's loginId + String loginId; //Constructors **************************************************** @@ -38,11 +41,15 @@ public class ChatClient extends AbstractClient * @param clientUI The interface type variable. */ - public ChatClient(String host, int port, ChatIF clientUI) + //Question 7 a + //the login Id is added as a parameter to the constructor + + public ChatClient(String loginId, String host, int port, ChatIF clientUI) throws IOException { - super(host, port); //Call the superclass constructor + super(host, port); //Call the superclass constructor this.clientUI = clientUI; + this.loginId = loginId; openConnection(); } @@ -57,7 +64,149 @@ public ChatClient(String host, int port, ChatIF clientUI) public void handleMessageFromServer(Object msg) { clientUI.display(msg.toString()); - } + } + + /** + * Hook method called after a connection has been established. The default + * implementation does nothing. It may be overridden by subclasses to do + * anything they wish. + */ + + //Question 7 b + //sends message to server with login Id + + protected void connectionEstablished() { + + try{ + sendToServer("#login "+ loginId); + } + catch(IOException e){ + + } + } + + /** + * Hook method called after the connection has been closed. The default + * implementation does nothing. The method may be overriden by subclasses to + * perform special processing such as cleaning up and terminating, or + * attempting to reconnect. + */ + + //Question 5 a + //cleanup method called whenever the user disconnects from the server. + //It prints a message telling the user so + protected void connectionClosed() { + System.out.println("You have logged off from the server."); + } + + /** + * Hook method called each time an exception is thrown by the client's + * thread that is waiting for messages from the server. The method may be + * overridden by subclasses. + * + * @param exception + * the exception raised. + */ + + //Question 5a + //When an exception is raised, it tells the user that they are no longer connected to the server + //then it closes the connection to the server formally + //without quitting (so that the user could log back on if they so chose + protected void connectionException(Exception exception) { + System.out.println("Connection to server terminated"); + try{closeConnection();} + catch(IOException e) {} + } + + + /** + * + *This method handles the case when a user enters a string starting with a # + *It parses the message to see what command it was then it executes said command + * + * @param message The message from the UI. + */ + + //Question 6 a + //Method that recieves a message that begins with # (a command) + //and parses which command was issued and what to do + + public void commandHelperMethod(String message){ + + //seperates the command from the arguments (if pertinent - for #sethost and #setport) + String[] argumentsArray = message.split(" "); + + switch(argumentsArray[0]){ + + //Question 6a i + case "#quit": //calls the quit method to logoff without having to use ctrl+c + quit(); + break; + //Question 6a ii + case "#logoff": //allows the user to logoff from the server without exiting completely + try{closeConnection(); } + catch(IOException e) {} + break; + //Question 6a iii + case "#sethost": //allows the user to change the host (only available when not connected to a server) + if(!isConnected()){ + try{ + setHost(argumentsArray[1]); + } + catch(ArrayIndexOutOfBoundsException e){ + System.out.println("Host name not specified. Please use the following format: #sethost "); + } + } + else{ + System.out.println("You are currently connected to a server. You must first #logoff to change hosts"); + } + break; + //Question 6a iv + case "#setport": //allows the user to change the port (only available when not connected to a server) + if(!isConnected()){ + try{ + setPort(Integer.parseInt(argumentsArray[1])); + } + catch(ArrayIndexOutOfBoundsException e){ + System.out.println("Port number not specified. Please use the following format: #setport "); + } + } + else{ + System.out.println("You are currently connected to a server. You must first #logoff to change ports"); + } + break; + //Question 6a v + case "#login": //allows the user to log into a server (cannot be used when already connected) + if(!isConnected()){ + try{ + openConnection(); + } + catch(IOException e) {} + + } + else{ + System.out.println("You are already connected to a server."); + } + break; + //Question 6a vi + case "#gethost": //prints the host name + System.out.println("The host name is: "+getHost()); + break; + //Question 6a v + case "#getport": // prints the port number + System.out.println("The port number is: "+Integer.toString(getPort())); + break; + + default: //if someone typed the wrong command they would get an error message and a list of accepted commands + System.out.println("This is not a recognised command"); + System.out.println("Please use one of the following:"); + System.out.println("#quit, #logoff, #sethost, #setport, #login, #gethost or #getport"); + break; + } + + } + + /** * This method handles all data coming from the UI @@ -66,16 +215,23 @@ public void handleMessageFromServer(Object msg) */ public void handleMessageFromClientUI(String message) { - try - { - sendToServer(message); - } - catch(IOException e) - { - clientUI.display - ("Could not send message to server. Terminating client."); - quit(); - } + //Question 6a + //Checks if the message is a command and redirects it to the helper method + if(message.charAt(0)=='#'){ + commandHelperMethod(message); + } + else{ + try + { + sendToServer(message); + } + catch(IOException e) + { + clientUI.display + ("Could not send message to server. Terminating client."); + quit(); + } + } } /** diff --git a/code/simplechat1/ocsf/client/AbstractClient.java b/code/simplechat1/ocsf/client/AbstractClient.java new file mode 100644 index 0000000..e905636 --- /dev/null +++ b/code/simplechat1/ocsf/client/AbstractClient.java @@ -0,0 +1,333 @@ +// This file contains material supporting section 3.7 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.client; + +import java.io.*; +import java.net.*; + +/** + * The AbstractClient contains all the methods necessary to set + * up the client side of a client-server architecture. When a client is thus + * connected to the server, the two programs can then exchange + * Object instances. + *

+ * Method handleMessageFromServer must be defined by a concrete + * subclass. Several other hook methods may also be overriden. + *

+ * Several public service methods are provided to application that use this + * framework. + *

+ * Project Name: OCSF (Object Client-Server Framework) + *

+ * + * @author Dr. Robert Laganière + * @author Dr. Timothy C. Lethbridge + * @author François Bél;langer + * @author Paul Holden + * @version February 2001 (2.12) + */ +public abstract class AbstractClient implements Runnable { + + // INSTANCE VARIABLES *********************************************** + + /** + * Sockets are used in the operating system as channels of communication + * between two processes. + * + * @see java.net.Socket + */ + private Socket clientSocket; + + /** + * The stream to handle data going to the server. + */ + private ObjectOutputStream output; + + /** + * The stream to handle data from the server. + */ + private ObjectInputStream input; + + /** + * The thread created to read data from the server. + */ + private Thread clientReader; + + /** + * Indicates if the thread is ready to stop. Needed so that the loop in the + * run method knows when to stop waiting for incoming messages. + */ + private boolean readyToStop = false; + + /** + * The server's host name. + */ + private String host; + + /** + * The port number. + */ + private int port; + + // CONSTRUCTORS ***************************************************** + + /** + * Constructs the client. + * + * @param host + * the server's host name. + * @param port + * the port number. + */ + public AbstractClient(String host, int port) { + // Initialize variables + this.host = host; + this.port = port; + } + + // INSTANCE METHODS ************************************************* + + /** + * Opens the connection with the server. If the connection is already + * opened, this call has no effect. + * + * @exception IOException + * if an I/O error occurs when opening. + */ + final public void openConnection() throws IOException { + // Do not do anything if the connection is already open + if (isConnected()) + return; + + // Create the sockets and the data streams + try { + clientSocket = new Socket(host, port); + output = new ObjectOutputStream(clientSocket.getOutputStream()); + input = new ObjectInputStream(clientSocket.getInputStream()); + } catch (IOException ex) + // All three of the above must be closed when there is a failure + // to create any of them + { + try { + closeAll(); + } catch (Exception exc) { + } + + throw ex; // Rethrow the exception. + } + + clientReader = new Thread(this); // Create the data reader thread + readyToStop = false; + clientReader.start(); // Start the thread + } + + /** + * Sends an object to the server. This is the only way that methods should + * communicate with the server. + * + * @param msg + * The message to be sent. + * @exception IOException + * if an I/O error occurs when sending + */ + final public void sendToServer(Object msg) throws IOException { + if (clientSocket == null || output == null) + throw new SocketException("socket does not exist"); + + output.writeObject(msg); + } + + /** + * Reset the object output stream so we can use the same + * buffer repeatedly. This would not normally be used, but is necessary + * in some circumstances when Java refuses to send data that it thinks has been sent. + */ + final public void forceResetAfterSend() throws IOException { + output.reset(); + } + + /** + * Closes the connection to the server. + * + * @exception IOException + * if an I/O error occurs when closing. + */ + final public void closeConnection() throws IOException { + // Prevent the thread from looping any more + readyToStop = true; + + try { + closeAll(); + } finally { + // Call the hook method + connectionClosed(); + } + } + + // ACCESSING METHODS ------------------------------------------------ + + /** + * @return true if the client is connnected. + */ + final public boolean isConnected() { + return clientReader != null && clientReader.isAlive(); + } + + /** + * @return the port number. + */ + final public int getPort() { + return port; + } + + /** + * Sets the server port number for the next connection. The change in port + * only takes effect at the time of the next call to openConnection(). + * + * @param port + * the port number. + */ + final public void setPort(int port) { + this.port = port; + } + + /** + * @return the host name. + */ + final public String getHost() { + return host; + } + + /** + * Sets the server host for the next connection. The change in host only + * takes effect at the time of the next call to openConnection(). + * + * @param host + * the host name. + */ + final public void setHost(String host) { + this.host = host; + } + + /** + * returns the client's description. + * + * @return the client's Inet address. + */ + final public InetAddress getInetAddress() { + return clientSocket.getInetAddress(); + } + + // RUN METHOD ------------------------------------------------------- + + /** + * Waits for messages from the server. When each arrives, a call is made to + * handleMessageFromServer(). Not to be explicitly called. + */ + final public void run() { + connectionEstablished(); + + // The message from the server + Object msg; + + // Loop waiting for data + + try { + while (!readyToStop) { + // Get data from Server and send it to the handler + // The thread waits indefinitely at the following + // statement until something is received from the server + msg = input.readObject(); + + // Concrete subclasses do what they want with the + // msg by implementing the following method + handleMessageFromServer(msg); + } + } catch (Exception exception) { + if (!readyToStop) { + try { + closeAll(); + } catch (Exception ex) { + } + + connectionException(exception); + } + } finally { + clientReader = null; + } + } + + // METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES --------- + + /** + * Hook method called after the connection has been closed. The default + * implementation does nothing. The method may be overriden by subclasses to + * perform special processing such as cleaning up and terminating, or + * attempting to reconnect. + */ + protected void connectionClosed() { + } + + /** + * Hook method called each time an exception is thrown by the client's + * thread that is waiting for messages from the server. The method may be + * overridden by subclasses. + * + * @param exception + * the exception raised. + */ + protected void connectionException(Exception exception) { + } + + /** + * Hook method called after a connection has been established. The default + * implementation does nothing. It may be overridden by subclasses to do + * anything they wish. + */ + protected void connectionEstablished() { + } + + /** + * Handles a message sent from the server to this client. This MUST be + * implemented by subclasses, who should respond to messages. + * + * @param msg + * the message sent. + */ + protected abstract void handleMessageFromServer(Object msg); + + // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ---------------- + + /** + * Closes all aspects of the connection to the server. + * + * @exception IOException + * if an I/O error occurs when closing. + */ + private void closeAll() throws IOException { + try { + // Close the socket + if (clientSocket != null) + clientSocket.close(); + + // Close the output stream + if (output != null) + output.close(); + + // Close the input stream + if (input != null) + input.close(); + } finally { + // Set the streams and the sockets to NULL no matter what + // Doing so allows, but does not require, any finalizers + // of these objects to reclaim system resources if and + // when they are garbage collected. + output = null; + input = null; + clientSocket = null; + } + } +} +// end of AbstractClient class \ No newline at end of file diff --git a/code/simplechat1/ocsf/client/AdaptableClient.java b/code/simplechat1/ocsf/client/AdaptableClient.java new file mode 100644 index 0000000..143592e --- /dev/null +++ b/code/simplechat1/ocsf/client/AdaptableClient.java @@ -0,0 +1,82 @@ +// This file contains material supporting section 6.13 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.client; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** +* The AdaptableClient is a class +* that extends the AbstractClient in place of +* the ObservableClient .

+* +* Project Name: OCSF (Object Client-Server Framework)

+* +* @author Dr. Robert Laganière +* @version Febuary 2001 +*/ +class AdaptableClient extends AbstractClient +{ + //Instance variables ********************************************** + + /** + * The proxy used to simulate multiple class inheritance. + */ + private ObservableClient client; + +// CONSTRUCTORS ***************************************************** + + /** + * Constructs the client adapter. + * + * @param host the server's host name. + * @param port the port number. + */ + public AdaptableClient(String host, int port, ObservableClient client) + { + super(host, port); + this.client = client; + } + +// OVERRIDDEN METHODS ************************************************* + + /** + * Hook method called after the connection has been closed. + */ + final protected void connectionClosed() + { + client.connectionClosed(); + } + + /** + * Hook method called after an exception + * is raised by the client listening thread. + * + * @param exception the exception raised. + */ + final protected void connectionException(Exception exception) + { + client.connectionException(exception); + } + + /** + * Hook method called after a connection has been established. + */ + final protected void connectionEstablished() + { + client.connectionEstablished(); + } + + /** + * Handles a message sent from the server to this client. + * + * @param msg the message sent. + */ + final protected void handleMessageFromServer(Object msg) + { + client.handleMessageFromServer(msg); + } +} diff --git a/code/simplechat1/ocsf/client/ObservableClient.java b/code/simplechat1/ocsf/client/ObservableClient.java new file mode 100644 index 0000000..a816488 --- /dev/null +++ b/code/simplechat1/ocsf/client/ObservableClient.java @@ -0,0 +1,181 @@ +// This file contains material supporting section 6.13 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.client; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * This class acts as a subclass of AbstractClient + * and is also an Observable class. + * Each time a new message is received, observers are notified. + * + * @author Dr Robert Laganière + * @author Dr Timothy C. Lethbridge + * @author François Bélange + * @version Febuary 2001 + */ +public class ObservableClient extends Observable +{ + // Class variables *************************************************** + + /** + * Indicates occurence of a connection exception. + */ + public static final String CONNECTION_EXCEPTION = "#OC:Connection error."; + + /** + * Indicates a close of the connection to server. + */ + public static final String CONNECTION_CLOSED = "#OC:Connection closed."; + + /** + * Indicates establishment of a connection to server. + */ + public static final String CONNECTION_ESTABLISHED = "#OC:Connection established."; + + //Instance variables ********************************************** + + /** + * The service instance used to simulate multiple class inheritance. + */ + private AdaptableClient service; + + //Constructor ***************************************************** + + public ObservableClient(String host, int port) + { + service = new AdaptableClient(host, port, this); + } + + //Instance methods ************************************************ + + /** + * Opens the connections with the server. + */ + final public void openConnection() throws IOException + { + service.openConnection(); + } + + /** + * Closes the connection to the server. + */ + final public void closeConnection() throws IOException + { + service.closeConnection(); + } + + /** + * Sends an object to the server. This is the only way that + * methods should communicate with the server. + * + * @param msg The message to be sent. + */ + final public void sendToServer(Object msg) throws IOException + { + service.sendToServer(msg); + } + +// ACCESSING METHODS ------------------------------------------------ + + /** + * @used to find out if the client is connnected. + */ + final public boolean isConnected() + { + return service.isConnected(); + } + + /** + * @return the port number. + */ + final public int getPort() + { + return service.getPort(); + } + + /** + * Sets the server port number for the next connection. + * Only has effect if the client is not currently connected. + * + * @param port the port number. + */ + final public void setPort(int port) + { + service.setPort(port); + } + + /** + * @return the host name. + */ + final public String getHost() + { + return service.getHost(); + } + + /** + * Sets the server host for the next connection. + * Only has effect if the client is not currently connected. + * + * @param host the host name. + */ + final public void setHost(String host) + { + service.setHost(host); + } + + /** + * @return the client's Inet address. + */ + final public InetAddress getInetAddress() + { + return service.getInetAddress(); + } + + + /** + * This method is used to handle messages from the server. This method + * can be overriden but should always call notifyObservers(). + * + * @param message The message received from the client. + */ + protected void handleMessageFromServer(Object message) + { + setChanged(); + notifyObservers(message); + } + + /** + * Hook method called after the connection has been closed. + */ + protected void connectionClosed() + { + setChanged(); + notifyObservers(CONNECTION_CLOSED); + } + + /** + * Hook method called each time an exception + * is raised by the client listening thread. + * + * @param exception the exception raised. + */ + protected void connectionException(Exception exception) + { + setChanged(); + notifyObservers(CONNECTION_EXCEPTION); + } + + /** + * Hook method called after a connection has been established. + */ + protected void connectionEstablished() + { + setChanged(); + notifyObservers(CONNECTION_ESTABLISHED); + } +} diff --git a/code/simplechat1/ocsf/index.html b/code/simplechat1/ocsf/index.html new file mode 100644 index 0000000..13025a0 --- /dev/null +++ b/code/simplechat1/ocsf/index.html @@ -0,0 +1,22 @@ + + +Installing and Running OCSF + + + +

To install OCSF, simply compile all the .java files in the client and +server directories.

+ +

OCSF is a Framework, so this directory contains no main program. To +learn how it works, consult the book +"Object-Oriented Software Engineering: Practical Software Development +using UML and Java" by Lethbridge and Laganière.

+ +

To use OCSF, import the "ocsf.client" or "ocsf.server" package in your +application code. Make sure that the ocsf directory is in your classpath +when you compile your application.

+ +

Back to the source code page.

+ + + diff --git a/code/simplechat1/ocsf/server/AbstractServer.java b/code/simplechat1/ocsf/server/AbstractServer.java new file mode 100644 index 0000000..0134b22 --- /dev/null +++ b/code/simplechat1/ocsf/server/AbstractServer.java @@ -0,0 +1,459 @@ +// This file contains material supporting section 3.8 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +import java.net.*; +import java.util.*; +import java.io.*; + +/** +* The AbstractServer class maintains a thread that waits +* for connection attempts from clients. When a connection attempt occurs +* it creates a new ConnectionToClient instance which +* runs as a thread. When a client is thus connected to the +* server, the two programs can then exchange Object +* instances.

+* +* Method handleMessageFromClient must be defined by +* a concrete subclass. Several other hook methods may also be +* overriden.

+* +* Several public service methods are provided to applications that use +* this framework, and several hook methods are also available

+* +* Project Name: OCSF (Object Client-Server Framework)

+* +* @author Dr Robert Laganière +* @author Dr Timothy C. Lethbridge +* @author François Bélanger +* @author Paul Holden +* @version February 2001 (2.12) +* @see ocsf.server.ConnectionToClient +*/ +public abstract class AbstractServer implements Runnable +{ + // INSTANCE VARIABLES ********************************************* + + /** + * The server socket: listens for clients who want to connect. + */ + private ServerSocket serverSocket = null; + + /** + * The connection listener thread. + */ + private Thread connectionListener; + + /** + * The port number + */ + private int port; + + /** + * The server timeout while for accepting connections. + * After timing out, the server will check to see if a command to + * stop the server has been issued; it not it will resume accepting + * connections. + * Set to half a second by default. + */ + private int timeout = 500; + + /** + * The maximum queue length; i.e. the maximum number of clients that + * can be waiting to connect. + * Set to 10 by default. + */ + private int backlog = 10; + + /** + * The thread group associated with client threads. Each member of the + * thread group is a ConnectionToClient . + */ + private ThreadGroup clientThreadGroup; + + /** + * Indicates if the listening thread is ready to stop. Set to + * false by default. + */ + private boolean readyToStop = false; + + +// CONSTRUCTOR ****************************************************** + + /** + * Constructs a new server. + * + * @param port the port number on which to listen. + */ + public AbstractServer(int port) + { + this.port = port; + + this.clientThreadGroup = + new ThreadGroup("ConnectionToClient threads") + { + // All uncaught exceptions in connection threads will + // be sent to the clientException callback method. + public void uncaughtException( + Thread thread, Throwable exception) + { + clientException((ConnectionToClient)thread, exception); + } + }; + } + + +// INSTANCE METHODS ************************************************* + + /** + * Begins the thread that waits for new clients. + * If the server is already in listening mode, this + * call has no effect. + * + * @exception IOException if an I/O error occurs + * when creating the server socket. + */ + final public void listen() throws IOException + { + if (!isListening()) + { + if (serverSocket == null) + { + serverSocket = new ServerSocket(getPort(), backlog); + } + + serverSocket.setSoTimeout(timeout); + readyToStop = false; + connectionListener = new Thread(this); + connectionListener.start(); + } + } + + /** + * Causes the server to stop accepting new connections. + */ + final public void stopListening() + { + readyToStop = true; + } + + /** + * Closes the server socket and the connections with all clients. + * Any exception thrown while closing a client is ignored. + * If one wishes to catch these exceptions, then clients + * should be individually closed before calling this method. + * The method also stops listening if this thread is running. + * If the server is already closed, this + * call has no effect. + * + * @exception IOException if an I/O error occurs while + * closing the server socket. + */ + final synchronized public void close() throws IOException + { + if (serverSocket == null) + return; + stopListening(); + try + { + serverSocket.close(); + } + finally + { + // Close the client sockets of the already connected clients + Thread[] clientThreadList = getClientConnections(); + for (int i=0; iThread containing + * ConnectionToClient instances. + */ + synchronized final public Thread[] getClientConnections() + { + Thread[] clientThreadList = new + Thread[clientThreadGroup.activeCount()]; + + clientThreadGroup.enumerate(clientThreadList); + + return clientThreadList; + } + + /** + * Counts the number of clients currently connected. + * + * @return the number of clients currently connected. + */ + final public int getNumberOfClients() + { + return clientThreadGroup.activeCount(); + } + + /** + * Returns the port number. + * + * @return the port number. + */ + final public int getPort() + { + return port; + } + + /** + * Sets the port number for the next connection. + * The server must be closed and restarted for the port + * change to be in effect. + * + * @param port the port number. + */ + final public void setPort(int port) + { + this.port = port; + } + + /** + * Sets the timeout time when accepting connections. + * The default is half a second. This means that stopping the + * server may take up to timeout duration to actually stop. + * The server must be stopped and restarted for the timeout + * change to be effective. + * + * @param timeout the timeout time in ms. + */ + final public void setTimeout(int timeout) + { + this.timeout = timeout; + } + + /** + * Sets the maximum number of waiting connections accepted by the + * operating system. The default is 20. + * The server must be closed and restarted for the backlog + * change to be in effect. + * + * @param backlog the maximum number of connections. + */ + final public void setBacklog(int backlog) + { + this.backlog = backlog; + } + +// RUN METHOD ------------------------------------------------------- + + /** + * Runs the listening thread that allows clients to connect. + * Not to be called. + */ + final public void run() + { + // call the hook method to notify that the server is starting + serverStarted(); + + try + { + // Repeatedly waits for a new client connection, accepts it, and + // starts a new thread to handle data exchange. + while(!readyToStop) + { + try + { + // Wait here for new connection attempts, or a timeout + Socket clientSocket = serverSocket.accept(); + + // When a client is accepted, create a thread to handle + // the data exchange, then add it to thread group + + synchronized(this) + { + ConnectionToClient c = new ConnectionToClient( + this.clientThreadGroup, clientSocket, this); + } + } + catch (InterruptedIOException exception) + { + // This will be thrown when a timeout occurs. + // The server will continue to listen if not ready to stop. + } + } + + // call the hook method to notify that the server has stopped + serverStopped(); + } + catch (IOException exception) + { + if (!readyToStop) + { + // Closing the socket must have thrown a SocketException + listeningException(exception); + } + else + { + serverStopped(); + } + } + finally + { + readyToStop = true; + connectionListener = null; + } + } + + +// METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES --------- + + /** + * Hook method called each time a new client connection is + * accepted. The default implementation does nothing. + * @param client the connection connected to the client. + */ + protected void clientConnected(ConnectionToClient client) {} + + /** + * Hook method called each time a client disconnects. + * The default implementation does nothing. The method + * may be overridden by subclasses but should remains synchronized. + * + * @param client the connection with the client. + */ + synchronized protected void clientDisconnected( + ConnectionToClient client) {} + + /** + * Hook method called each time an exception is thrown in a + * ConnectionToClient thread. + * The method may be overridden by subclasses but should remains + * synchronized. + * + * @param client the client that raised the exception. + * @param Throwable the exception thrown. + */ + synchronized protected void clientException( + ConnectionToClient client, Throwable exception) {} + + /** + * Hook method called when the server stops accepting + * connections because an exception has been raised. + * The default implementation does nothing. + * This method may be overriden by subclasses. + * + * @param exception the exception raised. + */ + protected void listeningException(Throwable exception) {} + + /** + * Hook method called when the server starts listening for + * connections. The default implementation does nothing. + * The method may be overridden by subclasses. + */ + protected void serverStarted() {} + + /** + * Hook method called when the server stops accepting + * connections. The default implementation + * does nothing. This method may be overriden by subclasses. + */ + protected void serverStopped() {} + + /** + * Hook method called when the server is clased. + * The default implementation does nothing. This method may be + * overriden by subclasses. When the server is closed while still + * listening, serverStopped() will also be called. + */ + protected void serverClosed() {} + + /** + * Handles a command sent from one client to the server. + * This MUST be implemented by subclasses, who should respond to + * messages. + * This method is called by a synchronized method so it is also + * implcitly synchronized. + * + * @param msg the message sent. + * @param client the connection connected to the client that + * sent the message. + */ + protected abstract void handleMessageFromClient( + Object msg, ConnectionToClient client); + + +// METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ---------------- + + /** + * Receives a command sent from the client to the server. + * Called by the run method of ConnectionToClient + * instances that are watching for messages coming from the server + * This method is synchronized to ensure that whatever effects it has + * do not conflict with work being done by other threads. The method + * simply calls the handleMessageFromClient slot method. + * + * @param msg the message sent. + * @param client the connection connected to the client that + * sent the message. + */ + final synchronized void receiveMessageFromClient( + Object msg, ConnectionToClient client) + { + this.handleMessageFromClient(msg, client); + } +} +// End of AbstractServer Class diff --git a/code/simplechat1/ocsf/server/AdaptableServer.java b/code/simplechat1/ocsf/server/AdaptableServer.java new file mode 100644 index 0000000..24462d4 --- /dev/null +++ b/code/simplechat1/ocsf/server/AdaptableServer.java @@ -0,0 +1,129 @@ +// This file contains material supporting section 6.13 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** +* The AdaptableServer is an adapter class +* that extends the AbstractServer class in place of +* the AbstractObservableServer .

+* +* Project Name: OCSF (Object Client-Server Framework)

+* +* @author Dr. Robert Laganière +* @version Febuary 2001 +*/ +class AdaptableServer extends AbstractServer +{ + //Instance variables ********************************************** + + /** + * The adapter used to simulate multiple class inheritance. + */ + private ObservableServer server; + +// CONSTRUCTORS ***************************************************** + + /** + * Constructs the server adapter. + * + * @param host the server's host name. + * @param port the port number. + */ + public AdaptableServer(int port, ObservableServer server) + { + super(port); + this.server = server; + } + +// OVERRIDDEN METHODS --------- + + /** + * Hook method called each time a new client connection is + * accepted. + * + * @param client the connection connected to the client. + */ + final protected void clientConnected(ConnectionToClient client) + { + server.clientConnected(client); + } + + /** + * Hook method called each time a client disconnects. + * + * @param client the connection with the client. + */ + final protected void clientDisconnected(ConnectionToClient client) + { + server.clientDisconnected(client); + } + + /** + * Hook method called each time an exception + * is raised in a client thread. + * + * @param client the client that raised the exception. + * @param exception the exception raised. + */ + final protected void clientException(ConnectionToClient client, + Throwable exception) + { + server.clientException(client, exception); + } + + /** + * Hook method called when the server stops accepting + * connections because an exception has been raised. + * + * @param exception the exception raised. + */ + final protected void listeningException(Throwable exception) + { + server.listeningException(exception); + } + + /** + * Hook method called when the server stops accepting + * connections. + */ + final protected void serverStopped() + { + server.serverStopped(); + } + + /** + * Hook method called when the server starts listening for + * connections. + */ + final protected void serverStarted() + { + server.serverStarted(); + } + + /** + * Hook method called when the server is closed. + */ + final protected void serverClosed() + { + server.serverClosed(); + } + + /** + * Handles a command sent from the client to the server. + * + * @param msg the message sent. + * @param client the connection connected to the client that + * sent the message. + */ + final protected void handleMessageFromClient(Object msg, + ConnectionToClient client) + { + server.handleMessageFromClient(msg, client); + } +} diff --git a/code/simplechat1/ocsf/server/ConnectionToClient.java b/code/simplechat1/ocsf/server/ConnectionToClient.java new file mode 100644 index 0000000..4394b37 --- /dev/null +++ b/code/simplechat1/ocsf/server/ConnectionToClient.java @@ -0,0 +1,270 @@ +// This file contains material supporting section 3.8 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +import java.io.*; +import java.net.*; +import java.util.HashMap; + +/** + * An instance of this class is created by the server when a client connects. It + * accepts messages coming from the client and is responsible for sending data + * to the client since the socket is private to this class. The AbstractServer + * contains a set of instances of this class and is responsible for adding and + * deleting them. + *

+ * Project Name: OCSF (Object Client-Server Framework) + *

+ * + * @author Dr Robert Laganière + * @author Dr Timothy C. Lethbridge + * @author François Bélanger + * @author Paul Holden + * @version February 2001 (2.12) + */ +public class ConnectionToClient extends Thread { + // INSTANCE VARIABLES *********************************************** + + /** + * A reference to the Server that created this instance. + */ + private AbstractServer server; + + /** + * Sockets are used in the operating system as channels of communication + * between two processes. + * + * @see java.net.Socket + */ + private Socket clientSocket; + + /** + * Stream used to read from the client. + */ + private ObjectInputStream input; + + /** + * Stream used to write to the client. + */ + private ObjectOutputStream output; + + /** + * Indicates if the thread is ready to stop. Set to true when closing of the + * connection is initiated. + */ + private boolean readyToStop; + + /** + * Map to save information about the client such as its login ID. The + * initial size of the map is small since it is not expected that concrete + * servers will want to store many different types of information about each + * client. Used by the setInfo and getInfo methods. + */ + private HashMap savedInfo = new HashMap(10); + + // CONSTRUCTORS ***************************************************** + + /** + * Constructs a new connection to a client. + * + * @param group + * the thread groupSystem.out.println("Client at "+ client + + * "connected"); that contains the connections. + * @param clientSocket + * contains the client's socket. + * @param server + * a reference to the server that created this instance + * @exception IOException + * if an I/O error occur when creating the connection. + */ + ConnectionToClient(ThreadGroup group, Socket clientSocket, AbstractServer server) throws IOException { + super(group, (Runnable) null); + // Initialize variables + this.clientSocket = clientSocket; + this.server = server; + + clientSocket.setSoTimeout(0); // make sure timeout is infinite + + // Initialize the objects streams + try { + input = new ObjectInputStream(clientSocket.getInputStream()); + output = new ObjectOutputStream(clientSocket.getOutputStream()); + } catch (IOException ex) { + try { + closeAll(); + } catch (Exception exc) { + } + + throw ex; // Rethrow the exception. + } + + readyToStop = false; + start(); // Start the thread waits for data from the socket + } + + // INSTANCE METHODS ************************************************* + + /** + * Sends an object to the client. + * + * @param msg + * the message to be sent. + * @exception IOException + * if an I/O error occur when sending the message. + */ + final public void sendToClient(Object msg) throws IOException { + if (clientSocket == null || output == null) + throw new SocketException("socket does not exist"); + + output.writeObject(msg); + } + + /** + * Reset the output stream so we can use the same + * buffer repeatedly. This would not normally be used, but is necessary + * in some circumstances when Java refuses to send data that it thinks has been sent. + */ + final public void forceResetAfterSend() throws IOException { + output.reset(); + } + + /** + * Closes the client. If the connection is already closed, this call has no + * effect. + * + * @exception IOException + * if an error occurs when closing the socket. + */ + final public void close() throws IOException { + readyToStop = true; // Set the flag that tells the thread to stop + + try { + closeAll(); + } finally { + server.clientDisconnected(this); + } + } + + // ACCESSING METHODS ------------------------------------------------ + + /** + * Returns the address of the client. + * + * @return the client's Internet address. + */ + final public InetAddress getInetAddress() { + return clientSocket == null ? null : clientSocket.getInetAddress(); + } + + /** + * Returns a string representation of the client. + * + * @return the client's description. + */ + public String toString() { + return clientSocket == null ? null : clientSocket.getInetAddress().getHostName() + " (" + + clientSocket.getInetAddress().getHostAddress() + ")"; + } + + /** + * Saves arbitrary information about this client. Designed to be used by + * concrete subclasses of AbstractServer. Based on a hash map. + * + * @param infoType + * identifies the type of information + * @param info + * the information itself. + */ + public void setInfo(String infoType, Object info) { + savedInfo.put(infoType, info); + } + + /** + * Returns information about the client saved using setInfo. Based on a hash + * map. + * + * @param infoType + * identifies the type of information + */ + public Object getInfo(String infoType) { + return savedInfo.get(infoType); + } + + // RUN METHOD ------------------------------------------------------- + + /** + * Constantly reads the client's input stream. Sends all objects that are + * read to the server. Not to be called. + */ + final public void run() { + server.clientConnected(this); + + // This loop reads the input stream and responds to messages + // from clients + try { + // The message from the client + Object msg; + + while (!readyToStop) { + // This block waits until it reads a message from the client + // and then sends it for handling by the server + msg = input.readObject(); + server.receiveMessageFromClient(msg, this); + } + } catch (Exception exception) { + if (!readyToStop) { + try { + closeAll(); + } catch (Exception ex) { + } + + server.clientException(this, exception); + } + } + } + + // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ---------------- + + /** + * Closes all connection to the server. + * + * @exception IOException + * if an I/O error occur when closing the connection. + */ + private void closeAll() throws IOException { + try { + // Close the socket + if (clientSocket != null) + clientSocket.close(); + + // Close the output stream + if (output != null) + output.close(); + + // Close the input stream + if (input != null) + input.close(); + } finally { + // Set the streams and the sockets to NULL no matter what + // Doing so allows, but does not require, any finalizers + // of these objects to reclaim system resources if and + // when they are garbage collected. + output = null; + input = null; + clientSocket = null; + } + } + + /** + * This method is called by garbage collection. + */ + protected void finalize() { + try { + closeAll(); + } catch (IOException e) { + } + } +} +// End of ConnectionToClient class \ No newline at end of file diff --git a/code/simplechat1/ocsf/server/ObservableOriginatorServer.java b/code/simplechat1/ocsf/server/ObservableOriginatorServer.java new file mode 100644 index 0000000..4333694 --- /dev/null +++ b/code/simplechat1/ocsf/server/ObservableOriginatorServer.java @@ -0,0 +1,165 @@ +// This file contains material supporting the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +/** +* The ObservableOriginatorServer is a subclass +* of ObservableServer that sends +* OriginatorMessage instances to its observers. +* This class should be used when the observers need to know +* the orginator of the messages received. The originator +* is null when the message sent concerns the server. +* +* Project Name: OCSF (Object Client-Server Framework)

+* +* @author Dr Robert Laganière +* @author Dr Timothy C. Lethbridge +* @author François Bélanger +* @author Paul Holden +* @version February 2001 (2.12) +* @see ocsf.server.OriginatorMessage +*/ +public class ObservableOriginatorServer extends ObservableServer +{ + // Constructor ****************************************************** + + /** + * Constructs a new server. + * + * @param port the port on which to listen. + */ + public ObservableOriginatorServer(int port) + { + super(port); + } + + // Instance methods ************************************************ + + /** + * This method is used to handle messages coming from the client. + * Observers are notfied by receiveing an instance of OriginatorMessage + * that contains both the message received and a reference to the + * client who sent the message. + * + * @param message The message received from the client. + * @param client The connection to the client. + */ + protected synchronized void handleMessageFromClient + (Object message, ConnectionToClient client) + { + setChanged(); + notifyObservers(new OriginatorMessage(client, message)); + } + + /** + * Method called each time a new client connection is + * accepted. It notifies observers by sending an + * OriginatorMessage instance + * containing a reference to that client and + * the message defined by the static variable CLIENT_CONNECTED. + * + * @param client the connection connected to the client. + */ + protected synchronized void clientConnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(new OriginatorMessage(client, CLIENT_CONNECTED)); + } + + /** + * Method called each time a client connection is + * disconnected. It notifies observers by sending an + * OriginatorMessage instance + * containing a reference to that client and + * the message defined by the static variable CLIENT_DISCONNECTED. + * + * @param client the connection connected to the client. + */ + synchronized protected void clientDisconnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(new OriginatorMessage(client, CLIENT_DISCONNECTED)); + } + + + /** + * Method called each time an exception is raised + * by a client connection. + * It notifies observers by sending an + * OriginatorMessage instance + * containing a reference to that client and + * the message defined by the static variable CLIENT_EXCEPTION + * to which is appended the exception message. + * + * @param client the client that raised the exception. + * @param Throwable the exception thrown. + */ + synchronized protected void clientException( + ConnectionToClient client, Throwable exception) + { + setChanged(); + notifyObservers( + new OriginatorMessage(client, + CLIENT_EXCEPTION + exception.getMessage())); + } + + /** + * Method called each time an exception is raised + * while listening. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable LISTENING_EXCEPTION + * to which is appended the exception message. + * The originator is set to null. + * + * @param exception the exception raised. + */ + protected synchronized void listeningException(Throwable exception) + { + setChanged(); + notifyObservers( + new OriginatorMessage(null, + LISTENING_EXCEPTION + exception.getMessage())); + } + + /** + * Method called each time the server is started. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable SERVER_STARTED. + * The originator is set to null. + */ + protected synchronized void serverStarted() + { + setChanged(); + notifyObservers(new OriginatorMessage(null, SERVER_STARTED)); + } + + /** + * Method called each time the server is stopped. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable SERVER_STOPPED. + * The originator is set to null. + */ + synchronized protected void serverStopped() + { + setChanged(); + notifyObservers(new OriginatorMessage(null, SERVER_STOPPED)); + } + + /** + * Method called each time the server is closed. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable SERVER_CLOSED. + * The originator is set to null. + */ + synchronized protected void serverClosed() + { + setChanged(); + notifyObservers(new OriginatorMessage(null, SERVER_CLOSED)); + } +} diff --git a/code/simplechat1/ocsf/server/ObservableServer.java b/code/simplechat1/ocsf/server/ObservableServer.java new file mode 100644 index 0000000..5b16752 --- /dev/null +++ b/code/simplechat1/ocsf/server/ObservableServer.java @@ -0,0 +1,309 @@ +// This file contains material supporting section 6.13 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * This class acts as a subclass of AbstractServer + * and is also an Observable class. + * This means that when a message is received, all observers + * are notified. + * + * @author François Bélange + * @author Dr Timothy C. Lethbridge + * @author Dr Robert Laganière + * @version August 2000 + */ + +public class ObservableServer extends Observable +{ + // Class variables ************************************************ + + /** + * The string sent to the observers when a client has connected. + */ + public static final String CLIENT_CONNECTED= "#OS:Client connected."; + + /** + * The string sent to the observers when a client has disconnected. + */ + public static final String CLIENT_DISCONNECTED= "#OS:Client disconnected."; + + /** + * The string sent to the observers when an exception occurred with a client. + * The error message of that exception will be appended to this string. + */ + public static final String CLIENT_EXCEPTION= "#OS:Client exception."; + + /** + * The string sent to the observers when a listening exception occurred. + * The error message of that exception will be appended to this string. + */ + public static final String LISTENING_EXCEPTION= "#OS:Listening exception."; + + /** + * The string sent to the observers when the server has closed. + */ + public static final String SERVER_CLOSED= "#OS:Server closed."; + + /** + * The string sent to the observers when the server has started. + */ + public static final String SERVER_STARTED= "#OS:Server started."; + + /** + * The string sent to the observers when the server has stopped. + */ + public static final String SERVER_STOPPED= "#OS:Server stopped."; + + + //Instance variables ********************************************** + + /** + * The service used to simulate multiple class inheritance. + */ + private AdaptableServer service; + + + //Constructor ***************************************************** + + /** + * Constructs a new server. + * + * @param port the port on which to listen. + */ + public ObservableServer(int port) + { + service = new AdaptableServer(port, this); + } + + //Instance methods ************************************************ + + /** + * Begins the thread that waits for new clients + */ + final public void listen() throws IOException + { + service.listen(); + } + + /** + * Causes the server to stop accepting new connections. + */ + final public void stopListening() + { + service.stopListening(); + } + + /** + * Closes the server's connections with all clients. + */ + final public void close() throws IOException + { + service.close(); + } + + /** + * Sends a message to every client connected to the server. + * + * @param msg The message to be sent + */ + public void sendToAllClients(Object msg) + { + service.sendToAllClients(msg); + } + +// ACCESSING METHODS ------------------------------------------------ + + /** + * Used to find out if the server is accepting new clients. + */ + final public boolean isListening() + { + return service.isListening(); + } + + /** + * Returns an array of containing the existing + * client connections. This can be used by + * concrete subclasses to implement messages that do something with + * each connection (e.g. kill it, send a message to it etc.) + * + * @return an array of Thread containing + * ConnectionToClient instances. + */ + final public Thread[] getClientConnections() + { + return service.getClientConnections(); + } + + /** + * @return the number of clients currently connected. + */ + final public int getNumberOfClients() + { + return service.getNumberOfClients(); + } + + /** + * @return the port number. + */ + final public int getPort() + { + return service.getPort(); + } + + /** + * Sets the port number for the next connection. + * Only has effect if the server is not currently listening. + * + * @param port the port number. + */ + final public void setPort(int port) + { + service.setPort(port); + } + + /** + * Sets the timeout time when accepting connection. + * The default is half a second. + * The server must be stopped and restarted for the timeout + * change be in effect. + * + * @param timeout the timeout time in ms. + */ + final public void setTimeout(int timeout) + { + service.setTimeout(timeout); + } + + /** + * Sets the maximum number of + * waiting connections accepted by the operating system. + * The default is 20. + * The server must be closed and restart for the backlog + * change be in effect. + * + * @param backlog the maximum number of connections. + */ + final public void setBacklog(int backlog) + { + service.setBacklog(backlog); + } + + /** + * Hook method called each time a new client connection is + * accepted. The method may be overridden by subclasses. + * + * @param client the connection connected to the client. + */ + protected synchronized void clientConnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(CLIENT_CONNECTED); + } + + /** + * Hook method called each time a client disconnects. + * The method may be overridden by subclasses. + * + * @param client the connection with the client. + */ + protected synchronized void clientDisconnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(CLIENT_DISCONNECTED); + } + + /** + * Hook method called each time an exception + * is raised in a client thread. + * This implementation simply closes the + * client connection, ignoring any exception. + * The method may be overridden by subclasses. + * + * @param client the client that raised the exception. + * @param exception the exception raised. + */ + protected synchronized void clientException(ConnectionToClient client, + Throwable exception) + { + setChanged(); + notifyObservers(CLIENT_EXCEPTION); + try + { + client.close(); + } + catch (Exception e) {} + } + + /** + * This method is called when the server stops accepting + * connections because an exception has been raised. + * This implementation + * simply calls stopListening. + * This method may be overriden by subclasses. + * + * @param exception the exception raised. + */ + protected synchronized void listeningException(Throwable exception) + { + setChanged(); + notifyObservers(LISTENING_EXCEPTION); + stopListening(); + } + + /** + * This method is called when the server stops accepting + * connections for any reason. This method may be overriden by + * subclasses. + */ + synchronized protected void serverStopped() + { + setChanged(); + notifyObservers(SERVER_STOPPED); + } + + /** + * This method is called when the server is closed. + * This method may be overriden by subclasses. + */ + synchronized protected void serverClosed() + { + setChanged(); + notifyObservers(SERVER_CLOSED); + } + + /** + * This method is called when the server starts listening for + * connections. The method may be overridden by subclasses. + */ + protected synchronized void serverStarted() + { + setChanged(); + notifyObservers(SERVER_STARTED); + } + + /** + * This method is used to handle messages coming from the client. + * Observers are notfied by receiveing the transmitted message. + * Note that, in this implementation, the information concerning + * the client that sent the message is lost. + * It can be overriden, but is still expected to call notifyObservers(). + * + * @param message The message received from the client. + * @param client The connection to the client. + * @see ocsf.server.ObservableOriginatorServer + */ + protected synchronized void handleMessageFromClient + (Object message, ConnectionToClient client) + { + setChanged(); + notifyObservers(message); + } +} diff --git a/code/simplechat1/ocsf/server/OriginatorMessage.java b/code/simplechat1/ocsf/server/OriginatorMessage.java new file mode 100644 index 0000000..a25d2b4 --- /dev/null +++ b/code/simplechat1/ocsf/server/OriginatorMessage.java @@ -0,0 +1,61 @@ +// This file contains material supporting the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +/** + * A message class used by the Observable layer of the OCSF in order to conserve + * information about the originator of a message. + * + * @author Dr. Robert Laganière + * @version July 2001 + */ +public class OriginatorMessage +{ + /** + * The connection that originated the message + */ + private ConnectionToClient originator; + + /** + * The message. + */ + private Object message; + +// Constructor *************************************************************** + + /** + * Constructs an instance of an OriginatorMessage + * + * @param originator The client who created this message + * @param message The contents of the message + */ + public OriginatorMessage(ConnectionToClient originator, Object message) + { + this.originator = originator; + this.message = message; + } + +// Accessor methods ********************************************************* + + /** + * Returns the originating connection. + * + * @return The connection from which the message originated. + */ + public ConnectionToClient getOriginator() + { + return originator; + } + + /** + * Returns the message's contents. + * + * @return The content of the message. + */ + public Object getMessage() + { + return message; + } +}