+
+## Getting Started
+
+### Cloning the repository
+
+```
+git clone https://github.com/boostvolt/zhaw-multichat.git
+```
+
+### Start the program
+
+There are two ways of starting the program:
+
+#### Starting with IntelliJ:
+
+1. Make sure to have installed gradle version 8.0.1 or newer.
+2. Open a command line prompt in the directory of the repository. Do this once for the server and as
+ many times as you want to have clients. Use the following commands to start the server or client:
+
+For the client
+
+```bash
+$ ./gradlew client:run
+```
+
+For the server
+
+```bash
+$ ./gradlew server:run
+```
+
+3. The program should compile and start accordingly.
+
+### Stop the program
+
+To stop the client just close the window.
+To stop the client or the server close the window and press CTRL + C in the command line.
+
+
+
+## Class Diagram
+
+In the following class diagram we only included 1 PayloadHandler class on server and client side for
+better readability.
+
+![multichat_class_diagram.png](.github%2Fassets%2Fmultichat_class_diagram.png)
+
+
+
+## Application Structure
+
+### Client
+
+#### MVC Model
+
+![MVC Diagram](.github/assets/mvc.png)
+
+The classes `Client` and `ClientUI` were left untouched, since there was no refactoring needed.
+However, a major structural flaw of the client was, that the MVC pattern was not implemented. The
+existing classes were tightly coupled together in many places, violating the principle of separation
+of concerns. For example, parts of the view, model, and controller were mixed together in the
+`ChatWindowController` class. To implement the MVC pattern, the code was split into the
+following classes:
+
+##### Controller
+
+The `ChatWindowController` class should contain all the controls. Since there is only one controller
+in the application, ChatWindowController is the only class that falls under this scope. The
+following features were implemented in it:
+
+- JavaFX properties were used to allow the model classes to access the FXML fields saved in the
+ ChatWindowController. These properties were bound in the ChatWindowController using the Observer
+ pattern.
+- All methods that didn't belong to any of the FXML controls were moved to either model or
+ view classes, adhering to the Single Responsibility Principle.
+
+#### Model
+
+The Model should contain the logic that executes changes in the software. The following classes were
+created therefore:
+
+##### `ChatWindowModel`
+
+This class contains the core logic of the client, responsible for sending and receiving messages as
+well as connecting and disconnecting from the server. It also stores most of the bindings and lets
+the other classes access them through public getter methods. With that, other classes stay updated
+about changes without giving them too much access.
+
+##### `MessageListModel`
+
+This class originated from the ClientMessageList class. It extends
+`javafx.collections.ObservableListBase<>`, which provides built-in support for the Observer pattern,
+allowing other classes to track changes in the list without explicit listening to it. It contains
+methods to modify the message list.
+
+###### `FilteredMessageListModel`
+
+This class is similar to the MessageListModel class. It handles the features needed to filter
+messages. Unlike in the original class, the filter was separated into a different class.
+With the help of an ObjectProperty, the filter is adjustable in various classes. As well as in the
+ChatWindowModel, the property is available through a public getter method. This class mainly
+contains methods to adjust the filter.
+
+##### `MessageListView`
+
+The View only contains code to display updated information on the screen. It was implemented in the
+MessageListView class. The MessageListView class gets the updated information through a binding with
+the FilteredMessageListModel. With that, the view was updated when the FilteredMessageList,
+respectively the MessageListModel, was updated. The only purpose of this class is to display the
+information.
+
+#### Reasoning
+
+The reason for this refactor is on one hand that the implementation of the MVC model is considered
+best practice and also a requirement for this project. On the other hand, we could improve the code
+by splitting it up into sections. It got easier to understand how single classes work, and we could
+adhere to fundamental principles of software design, such as the principle of separation of
+concerns, the Single Responsibility Principle, and the Observer pattern.
+
+### Protocol
+
+#### Connection
+
+![Connection Diagram](.github/assets/connection.png)
+
+The protocol-level `Connection` class is a fundamental part of the application architecture. It
+contains serializable connection information such as username, status and network connection, which
+can be easily accessed with its getter and setter methods. In addition, the class contains the
+sendPayload and closeConnection methods, which allow generic data packets to be sent over the
+network, reacting appropriately to problems during transmission, and connections to be closed by
+either the client or the server.
+
+The `ConnectionStateType` enum at the protocol level ensures that both the client and the server use
+the same set of connection states, avoiding potential confusion and errors that could arise from
+using different connection states.
+
+In addition, the abstract `ConnectionListener` class has been established at the protocol level to
+handle incoming user data requests, handle method execution and error handling. The class is
+implemented as runnable and is started as a thread on both the client and the server, keeping the
+application stable and reliable.
+
+#### Payload
+
+![Payload Diagram](.github/assets/payload.png)
+
+The application has initially sent data via a string. This approach is not ideal for several
+reasons. First, sending data as a string makes it difficult to standardise the format of the
+exchanged data, which can lead to errors and data corruption. Second, sending data as a string can
+be inefficient, especially when sending large amounts of data.
+
+By introducing a payload record that contains the type, sender, receiver and message, the format of
+the exchanged data is standardised and the possibility of errors or data corruption is reduced. The
+use of a PayloadFactory to create payload data and a PayloadHandler to handle payload data also
+creates a more efficient and scalable way to handle communication between client and server.
+
+The `PayloadFactory` class provides a standardised way to create payloads for the various message
+types, including Connect, Confirm, Disconnect and Message Types. By implementing
+the `PayloadFactory`at the protocol level and using it on both the client and server side, it
+ensures that payloads are created in a consistent and standardised manner throughout the
+application.
+
+Using an enum set to specify the available payload types simplifies implementation and reduces
+redundancy in the code. It allows the different payload types to be specified in one place, making
+it easier to maintain and update the application in the future.
+
+The `PayloadHandler` interface provides a generic and extensible way to handle the different types
+of payloads on the client and server side. By implementing the `PayloadHandler` for each payload
+type, the payloads can be handled in a consistent manner, reducing errors and improving reliability.
+
+### Server
+
+#### Connection Registry
+
+![Connection Registry Diagram](.github/assets/connection-registry.png)
+
+The `ConnectionRegistry` class on the server side manages all active connections. It performs
+various functions such as checking the availability of a username, ensuring that it matches the
+username format and generating an available anonymous username. By standardising the format of the
+username and ensuring that it is unique, the application can avoid potential errors that could arise
+from duplicate usernames or incorrect formats.
+
+In addition to managing usernames, the ConnectionRegistry class also provides a mechanism for
+registering and unregistering connections in the registry. This allows the application to keep track
+of which clients are currently active and connected, which enables efficient processing of incoming
+messages and ensures that the appropriate clients receive the relevant messages.
+
+
+
+## Issue List
+
+### Bugs
+
+All bugs have been assigned with the label fix
+
+1. Server connection of several clients not possible
+2. Allocated anonymous usernames are not released on disconnect
+3. Connected clients should disconnect on server shutdown
+4. Server exception on non-port specified connection
+5. Client connection thread not terminated on already taken username
+
+### Structural Issues
+
+All structural issues have been assigned with the label refactor
+
+1. Refactor and unify `proccesData()` method
+2. Implementation of MVC model
+3. Introduce connection registry class
+4. Extract client and server `ConnectoinHanlder` into abstract class
+5. Ensure proper exception handling and logging
+
+
+
+## GitHub Workflow
+
+### Code of Conduct
+
+Our [CODE OF CONDUCT](CODE_OF_CONDUCT.md) described the rules and guidelines for contributing to
+our
+project.
+
+### Codeowners
+
+We used the CODEOWNERS file to define the code owners for the repository.
+
+### Issues / Pull Requests
+
+Project management was done using GitHub Projects
+
+We used the following templates for issues and pull requests:
+
+- Issue labels
+- [Issue template](.github%2FISSUE_TEMPLATE)
+- [Pull request template](.github%2Fpull_request_template.md)
+
+### Branching Model
+
+We used the feature branching workflow for several reasons:
+
+- Firstly, it promotes better organization and management of code changes, especially those
+ involving multiple team members working on different features or tasks simultaneously. By creating
+ separate branches for each feature or task, developers can work on their code changes
+ independently without interfering with the work of others. This reduces the likelihood of
+ conflicts arising between different changes, which can be time-consuming to resolve.
+
+- Secondly, the feature branching workflow also enables better tracking of code changes and easier
+ identification of issues or bugs. Since each feature branch contains changes related to a specific
+ feature or task, it is easier to pinpoint issues and resolve them quickly.
+
+- Finally, the feature branching workflow also facilitates better quality control and helps ensure
+ that the project's overall codebase remains stable and functional. Changes are tested and reviewed
+ before they are merged back into the main branch, reducing the risk of introducing bugs or errors
+ into the production code.
+
+
{@link NetworkServer} is used on the server side to open a port and wait for connection request from clients
+ *
{@link NetworkConnection} represents a bidirectional connection between client and server, to send and
+ * receive Objects
+ *
+ *
The typical process works as follows
+ *
+ *
The server creates a {@link NetworkServer} instance using the factory method
+ * {@link NetworkHandler#createServer(int port)}. This creates and opens a port (range: 0 - 65535)
+ * on all available interfaces (IP-networks, incl. localhost loopback) of the current host.
+ *
As soon the server is ready to receive requests it calls the method {@link NetworkServer#waitForConnection()}
+ * which is blocking and waiting for client to open a connection.
+ *
On the client side, a new {@link NetworkConnection} instance is created to connect to the server using the
+ * factory method {@link NetworkHandler#openConnection(String host, int port)}, which opens a connection to the
+ * given host (domainname or ip-address) on the specified server port
+ *
On the server side, the waiting method {@link NetworkServer#waitForConnection()} returns an instance of
+ * {@link NetworkConnection} which represents the specific connection to the calling client.
+ *
This connection can be used to send and receive data between server and client.
+ *
On the server side, the handling of each interaction ('session') with a specific client should be handled in
+ * a separate {@link Thread}, after starting the thread, the server can go back and wait for the next connection
+ * request.
+ *
Both sides (server & client) need to handle sending and receiving of data separately
+ *
+ *
reading data: call {@link NetworkConnection#receive()}, which is blocking until a data object is
+ * received. As soon the object has been received, the method returns an instance of the object.
+ * This object (request) can be processed (on the server side, usually a response is sent back;
+ * on the client side, usually the result is displayed to the user). After processing is finished the
+ * process calls {@link NetworkConnection#receive()} again to wait for the next request.
+ *
+ *
sending data: call {@link NetworkConnection#send(Serializable data)}, which sends the given data
+ * object to the remote side. The method returns as soon the object has been transmitted.
+ * Important: {@link NetworkConnection} is not thread safe, therefore make sure that only one thread
+ * at a time is sending data.
+ *
+ * Important:Sending and receiving of data is completely asynchronous and can happen in parallel.
+ *
+ *
The connection stays open until one of the peers decides to close it using {@link NetworkConnection#close()}.
+ * In this case, all waiting method calls (e.g. {@link NetworkConnection#receive()} on the opposite side are
+ * interrupted and a {@link EOFException} is thrown.
+ * On the local side, waiting method calls (threads) are also interrupted and a {@link java.net.SocketException}
+ * is thrown.
+ *
To stop receiving new connection requests on the server side, the server may call
+ * {@link NetworkServer#close()} which will close all currently open {@link NetworkConnection} objects.
+ *
+ *
{@link NetworkServer} and {@link NetworkConnection} are typed using generics. This means, when creating an
+ * instance it has to be specified, what types of objects can be sent between server and client. The type has to be
+ * identical on both sides of the connection. These Objects have to be of type {@link Serializable}, which is a
+ * marker interface specifying that an object can be serialized/deserialized. As long all properties within a
+ * class are also Serializable, your class simply can be marked using it. All standard Java data-types are by default
+ * Serializable.
+ */
+public class NetworkHandler {
+
+ /**
+ * Default network address used to open a connection to: localhost (domainname), 127.0.0.1
+ * (IPv4), ::1 (IPv6)
+ */
+ public static final InetAddress DEFAULT_ADDRESS = InetAddress.getLoopbackAddress();
+ /**
+ * Default port on the server side to listen for requests
+ */
+ public static final int DEFAULT_PORT = 22243;
+
+ /**
+ * private Constructor to avoid initialization. Use the static factory methods to create
+ * {@link NetworkServer} or {@link NetworkConnection} instances.
+ */
+ private NetworkHandler() {
+ }
+
+ /**
+ * Creates an instance of a {@link NetworkServer} listening on the specified port for connection
+ * request for Objects of type T.
+ *
+ * @param port port to open on the server host (range: 1 - 65535)
+ * @param type of the Objects to be transmitted in the created {@link NetworkConnection}
+ * @return {@link NetworkServer} object to be used to wait for connections.
+ * @throws IOException if an error occured opening the port, e.g. the port number is already
+ * used.
+ */
+ public static NetworkServer createServer(int port)
+ throws IOException {
+ return new NetworkServer<>(port);
+ }
+
+ /**
+ * Creates an instance of a {@link NetworkServer} listening on the default port (22243) for
+ * connection request for Objects of type T.
+ *
+ * @param type of the Objects to be transmitted in the created {@link NetworkConnection}
+ * @return {@link NetworkServer} object to be used to wait for connections.
+ * @throws IOException if an error occured opening the port, e.g. the port number is already
+ * used.
+ */
+ public static NetworkServer createServer() throws IOException {
+ return new NetworkServer<>();
+ }
+
+ /**
+ * Creates an instance of a {@link NetworkConnection} connecting to the specified host/port to
+ * send and receive objects of type T.
+ *
+ * @param address {@link InetAddress} object for the host
+ * @param port port number the server is waiting for connection requests
+ * @param type of Objects to be transmitted trough this connection
+ * @return {@link NetworkConnection} object representing the bidirectional channel between
+ * client and server.
+ * @throws IOException if an error occurred opening the connection, e.g. server is not
+ * responding.
+ */
+ public static NetworkConnection openConnection(InetAddress address,
+ int port)
+ throws IOException {
+ Socket socket = new Socket(address, port);
+ socket.setKeepAlive(true);
+ return new NetworkConnection<>(socket);
+ }
+
+ /**
+ * Creates an instance of a {@link NetworkConnection} connecting to the specified host/port to
+ * send and receive objects of type T.
+ *
+ * @param hostname server host name or address in String representation (e.g. "www.zhaw.ch",
+ * "160.85.104.112")
+ * @param port port number the server is waiting for connection requests
+ * @param type of Objects to be transmitted trough this connection
+ * @return {@link NetworkConnection} object representing the bidirectional channel between
+ * client and server.
+ * @throws IOException if an error occurred opening the connection, e.g. server is not
+ * responding.
+ */
+ public static NetworkConnection openConnection(String hostname,
+ int port)
+ throws IOException {
+ return openConnection(InetAddress.getByName(hostname), port);
+ }
+
+ /**
+ * Creates an instance of a {@link NetworkConnection} connecting to the default host
+ * ("localhost",127.0.0.1,::1) and port (22243) to send and receive objects of type T.
+ *
+ * @param type of Objects to be transmitted trough this connection
+ * @return {@link NetworkConnection} object representing the bidirectional channel between
+ * client and server.
+ * @throws IOException if an error occurred opening the connection, e.g. server is not
+ * responding.
+ */
+ public static NetworkConnection openConnection()
+ throws IOException {
+ return openConnection(DEFAULT_ADDRESS, DEFAULT_PORT);
+ }
+
+
+ /**
+ * Network communication class used on the server side to handle connection request from
+ * clients. The class opens a port on the server host and allows the server process to wait for
+ * connection requests. As soon a request comes in a {@link NetworkConnection} object is
+ * created, which is used to handle all the communication between the two peers.
+ *
+ * @param type of the Objects to be transmitted in the created {@link NetworkConnection}
+ */
+ public static class NetworkServer implements Closeable {
+
+ private final ServerSocket serverSocket;
+
+ /**
+ * Private constructor: use {@link NetworkHandler#createServer(int port)} factory method
+ * to create an instance Open a server port an the given port number. The port number
+ * must be unique (i.e. not used by another process)
+ *
+ * @param port port number (range: 1 - 65535) to open to wait for requests.
+ * @throws IOException if an error occurred opening the port, e.g. the port number is
+ * already used.
+ */
+ private NetworkServer(int port) throws IOException {
+ this.serverSocket = new ServerSocket(port);
+ }
+
+ /**
+ * Private constructor: use {@link NetworkHandler#createServer(int port)} factory method
+ * to create an instance Open a server port an the default port (22243).
+ *
+ * @throws IOException if an error occurred opening the port, e.g. the port number is
+ * already used.
+ */
+ private NetworkServer() throws IOException {
+ this(DEFAULT_PORT);
+ }
+
+ /**
+ * Blocks the current thread and waits for connection requests on the declared port of the
+ * {@link NetworkServer} object. Returns a {@link NetworkConnection} object representing the
+ * connection to a client if a successfull connection has been established.
+ *
+ * @return {@link NetworkConnection} object representing the connection to the connecting
+ * client.
+ * @throws IOException if an error occurred while waiting (e.g. throws a
+ * {@link java.net.SocketException} if the port has been closed using
+ * the {@link NetworkServer#close()} method.
+ */
+ public NetworkConnection waitForConnection() throws IOException {
+ Socket socket = serverSocket.accept();
+ socket.setKeepAlive(true);
+ return new NetworkConnection<>(socket);
+ }
+
+ /**
+ * Does indicate if the server is ready and bound to the declared port.
+ *
+ * @return true if the server is ready and bound to the declared port, false otherwise
+ */
+ public boolean isAvailable() {
+ return serverSocket != null && serverSocket.isBound();
+ }
+
+ /**
+ * Does indicate if the server has been closed. A closed server can not be reopened. To
+ * reopen a port, a new Instance must be created.
+ *
+ * @return true if the server is closed, false otherwise.
+ */
+ public boolean isClosed() {
+ return serverSocket == null || serverSocket.isClosed();
+ }
+
+ /**
+ * Returns the port number on which the server is listening for requests.
+ *
+ * @return returns the port number (range: 1 - 65535) if the server is available, 0
+ * otherwise.
+ */
+ public int getHostPort() {
+ return isAvailable() ? serverSocket.getLocalPort() : 0;
+ }
+
+ /**
+ * Returns the host address in String format on which the server is listening for requests.
+ *
+ * @return host address in String format or "unbound" if not available.
+ */
+ public String getHostAddress() {
+ return isAvailable() ? serverSocket.getInetAddress().getHostAddress() : "unbound";
+ }
+
+ /**
+ * Closes this Server and releases any system resources associated with it. Closing the
+ * Server, closes also all {@link NetworkConnection} objects created by the server and
+ * throws a {@link java.net.SocketException} on all blocking calls (e.g.
+ * {@link NetworkServer#waitForConnection()}) on the server. If the Server is already closed
+ * then invoking this method has no effect.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ if (serverSocket != null && !serverSocket.isClosed()) {
+ serverSocket.close();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NetworkServer> that = (NetworkServer>) o;
+ return serverSocket.equals(that.serverSocket);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(serverSocket);
+ }
+ }
+
+ /**
+ * Network communication class representing a bidirectional connection between two peers (client
+ * and server), to send and receive Objects of type T. The client can open a new connection
+ * using the factory method {@link NetworkHandler#openConnection(String hostname, int port)} to
+ * connect to the specified server. On the server side, the
+ * {@link NetworkServer#waitForConnection()} method is creating a matching instance for the
+ * connecting client.
+ *
+ *
On an open connection, both sides (server & client) need to handle sending and receiving
+ * of data separately
+ *
+ *
reading data: call {@link NetworkConnection#receive()}, which is blocking until a data object is
+ * received. As soon the object has been received, the method returns an instance of the object.
+ * This object (request) can be processed (on the server side, usually a response is sent back;
+ * on the client side, usually the result is displayed to the user). After processing is finished the
+ * process calls {@link NetworkConnection#receive()} again to wait for the next request.
+ *
+ *
sending data: call {@link NetworkConnection#send(Serializable data)}, which sends the given data
+ * object to the remote side. The method returns as soon the object has been transmitted.
+ * Important: {@link NetworkConnection} is not thread safe, therefore make sure that only one thread
+ * at a time is sending data.
+ *
+ *
+ *
Important: Sending and receiving of data is completely asynchronous and can happen in parallel.
+ * The connection stays open until one of the peers decides to close it using {@link NetworkConnection#close()}.
+ * In this case, all waiting method calls (e.g. {@link NetworkConnection#receive()} on the opposite side are
+ * interrupted and a {@link EOFException} is thrown.
+ * On the local side, waiting method calls (threads) are also interrupted and a {@link java.net.SocketException}
+ * is thrown.
+ *
+ * @param type of Objects to be transmitted trough this connection
+ */
+ public static class NetworkConnection implements Closeable {
+
+ private final Socket socket;
+
+ /**
+ * Privat constructor: Use
+ * {@link NetworkHandler#openConnection(String hostname, int port)} and similar factory
+ * methods to create instances of {@link NetworkConnection}
+ *
+ * @param socket operating system socket to use for the communication.
+ */
+ private NetworkConnection(Socket socket) {
+ this.socket = socket;
+ }
+
+ /**
+ * Method to send data to the opposite side. The call is sending out the requests
+ * immediately and returns if submitted successfully. Data can also be sent, while another
+ * thread is waiting for requests, but it has to be made sure that only one thread is
+ * sending data at a time (not thread-safe). If an error occurs a {@link IOException} is
+ * thrown.
+ *
+ * @param data data object of type T to be submitted through the connection.
+ * @throws IOException if an error occurs (e.g. connection interrupted while sending, ...)
+ */
+ public void send(T data) throws IOException {
+ ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
+ outputStream.writeObject(data);
+ }
+
+ /**
+ * Method to receive data from the opposite side. The call is blocking until a requests
+ * comes in, and the transferred object is returned. If the connection is closed during
+ * waiting, a {@link java.net.SocketException} is thrown, if the close was initiated locally
+ * or {@link EOFException} is thrown if the connection is closed from the remote side. Other
+ * {@link IOException} may be thrown on any another communication error.
+ *
+ * @return data object of type T received through the connection.
+ * @throws IOException if an error occours. (e.g. terminated locally/remotely)
+ * see above.
+ * @throws ClassNotFoundException if the data object received does not match any class in
+ * the local classpath
+ */
+ public T receive() throws IOException, ClassNotFoundException {
+ ObjectInputStream inputStream = new ObjectInputStream(this.socket.getInputStream());
+ return (T) inputStream.readObject();
+ }
+
+ /**
+ * Indicates if the connection is open and connected to the peer.
+ *
+ * @return true if the connection is open and connected, false otherwise
+ */
+ public boolean isAvailable() {
+ return !isClosed() && socket.isConnected();
+ }
+
+ /**
+ * Indicate if the connection has been closed. A closed connection can not be reopened. To
+ * re-open, a new Instance must be created.
+ *
+ * @return true if the connection is closed, false otherwise.
+ */
+ public boolean isClosed() {
+ return socket == null || socket.isClosed();
+ }
+
+ /**
+ * Returns the port number of the remote host, if the connection is available.
+ *
+ * @return port number (range: 1 - 65535) of the port on the remote host, 0 if not
+ * connected.
+ */
+ public int getRemotePort() {
+ return isAvailable() ? socket.getPort() : 0;
+ }
+
+ /**
+ * Returns the host name of the remote peer. If available looks up the hostname (e.g.
+ * "www.zhaw.ch"), otherwise returns a string representation of the IP address (e.g.
+ * "160.85.104.112").
+ *
+ * @return host name of the remote peer, "not connected" if connection is not available.
+ */
+ public String getRemoteHost() {
+ return isAvailable() ? socket.getInetAddress().getHostName() : "not connected";
+ }
+
+ /**
+ * Closes this NetworkConnection and releases any system resources associated with it. If
+ * the connection is closed a {@link java.net.SocketException} is thrown on all local
+ * waiting threads (e.g. in {@link NetworkConnection#receive()}), and on the remote side an
+ * {@link EOFException} is thrown on all waiting threads. If the connection is already
+ * closed then invoking this method has no effect.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ if (!isClosed()) {
+ socket.close();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NetworkConnection> that = (NetworkConnection>) o;
+ return socket.equals(that.socket);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(socket);
+ }
+ }
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/Connection.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/Connection.java
new file mode 100644
index 0000000..2162535
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/Connection.java
@@ -0,0 +1,128 @@
+package ch.zhaw.pm2.multichat.protocol.connection;
+
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.DISCONNECTED;
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.NEW;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import ch.zhaw.pm2.multichat.protocol.NetworkHandler.NetworkConnection;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.SocketException;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * The Connection class represents a connection between two peers in the network. It provides
+ * methods to manage the state of the connection, send and receive data, and close the connection.
+ * It is a generic class that can handle different types of serializable payloads.
+ */
+@Slf4j
+public class Connection {
+
+ private final NetworkConnection networkConnection;
+
+ private ConnectionStateType state = NEW;
+ private String username;
+
+ /**
+ * Creates a new Connection object with the specified NetworkConnection object. The
+ * ConnectionStateType is initialized to NEW by default.
+ *
+ * @param networkConnection the NetworkConnection object to use for this connection
+ */
+ public Connection(NetworkConnection networkConnection) {
+ this.networkConnection = requireNonNull(networkConnection);
+ }
+
+ /**
+ * Gets the network connection.
+ *
+ * @return the network connection
+ */
+ public NetworkConnection getNetworkConnection() {
+ return networkConnection;
+ }
+
+ /**
+ * Gets the current state of the connection.
+ *
+ * @return the current ConnectionStateType of the connection
+ */
+ public ConnectionStateType getState() {
+ return state;
+ }
+
+ /**
+ * Sets the state of the connection.
+ *
+ * @param state the new ConnectionStateType to set
+ */
+ public void setState(ConnectionStateType state) {
+ this.state = requireNonNull(state);
+ }
+
+ /**
+ * Gets the username of the local peer.
+ *
+ * @return the username of the local peer
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Sets the username of the local peer.
+ *
+ * @param username the new username to set
+ */
+ public void setUsername(String username) {
+ this.username = requireNonNull(username);
+ }
+
+ /**
+ * Checks whether the current state of the connection matches the specified
+ * ConnectionStateType.
+ *
+ * @param expectedState the expected ConnectionStateType
+ * @return true if the current state matches the expected state, false otherwise
+ */
+ public boolean isState(ConnectionStateType expectedState) {
+ return state == expectedState;
+ }
+
+ /**
+ * Sends the provided payload if the connection is available.
+ *
+ * @param payload The payload to send.
+ */
+ public void sendPayload(T payload) {
+ if (networkConnection.isAvailable()) {
+ try {
+ log.info(format("Sending payload: %s", payload.toString()));
+ networkConnection.send(payload);
+ } catch (SocketException | EOFException e) {
+ log.error(format("Connection closed: %s", e.getMessage()));
+ closeConnection();
+ } catch (IOException e) {
+ log.error(format("Communication error: %s", e.getMessage()));
+ closeConnection();
+ }
+ }
+ }
+
+ /**
+ * Stops receiving data from the network connection.
+ */
+ public void closeConnection() {
+ log.info(format("Closing Connection Handler for %s...", username));
+ try {
+ setState(DISCONNECTED);
+ networkConnection.close();
+ } catch (IOException e) {
+ log.error(format("Failed to close connection: %s", e.getMessage()));
+ }
+ log.info(format("Closed Connection Handler for %s", username));
+ }
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/ConnectionListener.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/ConnectionListener.java
new file mode 100644
index 0000000..e25e68e
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/ConnectionListener.java
@@ -0,0 +1,108 @@
+package ch.zhaw.pm2.multichat.protocol.connection;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import ch.zhaw.pm2.multichat.protocol.exception.ChatProtocolException;
+import ch.zhaw.pm2.multichat.protocol.exception.ConnectionException;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadType;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.SocketException;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * An abstract class representing a listener for a connection, which handles incoming payloads and
+ * delegates them to the appropriate payload handlers.
+ *
+ * @param the type of payload that this connection listener can handle
+ */
+@Slf4j
+public abstract class ConnectionListener implements Runnable {
+
+ private final Connection connection;
+ private final Map> payloadHandlers;
+
+ /**
+ * Constructs a new connection listener with the given connection and payload handlers.
+ *
+ * @param connection the connection to listen to
+ * @param payloadHandlers the payload handlers to delegate incoming payloads to
+ */
+ protected ConnectionListener(Connection connection,
+ Map> payloadHandlers) {
+ this.connection = requireNonNull(connection);
+ this.payloadHandlers = requireNonNull(payloadHandlers);
+ }
+
+ /**
+ * Gets the connection that this listener is listening to.
+ *
+ * @return the connection that this listener is listening to
+ */
+ protected Connection getConnection() {
+ return connection;
+ }
+
+ /**
+ * Continuously receives payloads from the connection and delegates them to the appropriate
+ * payload handlers.
+ */
+ @Override
+ public void run() {
+ try {
+ log.info("Start receiving data...");
+ while (!Thread.currentThread().isInterrupted() && connection.getNetworkConnection()
+ .isAvailable()) {
+ T payload = connection.getNetworkConnection().receive();
+ PayloadType payloadType = getPayloadType(payload);
+ handlePayload(payload, payloadType);
+ }
+ } catch (ConnectionException e) {
+ connection.sendPayload(
+ performErrorPayloadCreation(e.getMessage()));
+ performDisconnection();
+ } catch (SocketException | EOFException e) {
+ performDisconnection();
+ } catch (IOException e) {
+ log.error(format("Communication error: %s", e.getMessage()));
+ } catch (ClassNotFoundException e) {
+ log.error(format("Received object of unknown type: %s", e.getMessage()));
+ }
+ log.info("Ended Connection Listener");
+ }
+
+ private void handlePayload(T payload, PayloadType payloadType) throws ConnectionException {
+ try {
+ payloadHandlers.get(payloadType).handle(payload, connection);
+ } catch (ChatProtocolException e) {
+ connection.sendPayload(
+ performErrorPayloadCreation(e.getMessage()));
+ }
+ }
+
+ /**
+ * Gets the type of payload for the given payload object.
+ *
+ * @param payload the payload object to get the type for
+ * @return the type of the payload object
+ */
+ protected abstract PayloadType getPayloadType(T payload);
+
+ /**
+ * Performs the creation of an error payload for the given error message.
+ *
+ * @param message the error message to include in the error payload
+ * @return the error payload object
+ */
+ protected abstract T performErrorPayloadCreation(String message);
+
+ /**
+ * Performs the disconnection of the connection.
+ */
+ protected abstract void performDisconnection();
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/ConnectionStateType.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/ConnectionStateType.java
new file mode 100644
index 0000000..01cf5f9
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/connection/ConnectionStateType.java
@@ -0,0 +1,31 @@
+package ch.zhaw.pm2.multichat.protocol.connection;
+
+/**
+ * An enumeration representing the different states of a chat connection.
+ */
+public enum ConnectionStateType {
+ /**
+ * A new connection state.
+ */
+ NEW,
+
+ /**
+ * A connection state indicating that the connection has been confirmed.
+ */
+ CONFIRM_CONNECT,
+
+ /**
+ * A connection state indicating that the connection is established.
+ */
+ CONNECTED,
+
+ /**
+ * A connection state indicating that the disconnect has been confirmed.
+ */
+ CONFIRM_DISCONNECT,
+
+ /**
+ * A connection state indicating that the connection has been terminated.
+ */
+ DISCONNECTED
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/exception/ChatProtocolException.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/exception/ChatProtocolException.java
new file mode 100644
index 0000000..c700bd1
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/exception/ChatProtocolException.java
@@ -0,0 +1,17 @@
+package ch.zhaw.pm2.multichat.protocol.exception;
+
+/**
+ * Checked exception class for exceptions thrown by the chat protocol.
+ */
+public class ChatProtocolException extends Exception {
+
+ /**
+ * Constructs a new chat protocol exception with the specified detail message.
+ *
+ * @param message the detail message of the exception
+ */
+ public ChatProtocolException(String message) {
+ super(message);
+ }
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/exception/ConnectionException.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/exception/ConnectionException.java
new file mode 100644
index 0000000..c5c9794
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/exception/ConnectionException.java
@@ -0,0 +1,17 @@
+package ch.zhaw.pm2.multichat.protocol.exception;
+
+/**
+ * Checked exception class for exceptions thrown by the chat protocol.
+ */
+public class ConnectionException extends Exception {
+
+ /**
+ * Constructs a new chat protocol exception with the specified detail message.
+ *
+ * @param message the detail message of the exception
+ */
+ public ConnectionException(String message) {
+ super(message);
+ }
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/Payload.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/Payload.java
new file mode 100644
index 0000000..53599a5
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/Payload.java
@@ -0,0 +1,32 @@
+package ch.zhaw.pm2.multichat.protocol.payload;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * A record representing a payload, consisting of a type, sender, receiver, and content.
+ */
+public record Payload(PayloadType type, String sender, String receiver, String content) implements
+ Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 4417145662818974403L;
+
+ /**
+ * Constructs a new Payload object with the specified type, sender, receiver, and content.
+ *
+ * @param type the type of the payload
+ * @param sender the sender of the payload
+ * @param receiver the receiver of the payload
+ * @param content the content of the payload
+ */
+ public Payload(PayloadType type, String sender, String receiver, String content) {
+ this.type = requireNonNull(type);
+ this.sender = requireNonNull(sender);
+ this.receiver = requireNonNull(receiver);
+ this.content = requireNonNull(content);
+ }
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadFactory.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadFactory.java
new file mode 100644
index 0000000..f5d45aa
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadFactory.java
@@ -0,0 +1,74 @@
+package ch.zhaw.pm2.multichat.protocol.payload;
+
+import static ch.zhaw.pm2.multichat.protocol.Identifiers.SYSTEM;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.CONFIRM;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.CONNECT;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.DISCONNECT;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.ERROR;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.MESSAGE;
+
+/**
+ * A factory class for creating different types of Payload objects.
+ */
+public class PayloadFactory {
+
+ private PayloadFactory() {
+ // private constructor to prevent instantiation
+ }
+
+ /**
+ * Creates a new Payload object of type CONNECT with the specified sender and content.
+ *
+ * @param sender the sender of the payload
+ * @return the new Payload object
+ */
+ public static Payload createConnectPayload(String sender) {
+ return new Payload(CONNECT, sender, SYSTEM, "");
+ }
+
+ /**
+ * Creates a new Payload object of type CONFIRM with the specified receiver and content.
+ *
+ * @param receiver the receiver of the payload
+ * @param content the content of the payload
+ * @return the new Payload object
+ */
+ public static Payload createConfirmPayload(String receiver, String content) {
+ return new Payload(CONFIRM, SYSTEM, receiver, content);
+ }
+
+ /**
+ * Creates a new Payload object of type DISCONNECT with the specified sender and content.
+ *
+ * @param sender the sender of the payload
+ * @return the new Payload object
+ */
+ public static Payload createDisconnectPayload(String sender) {
+ return new Payload(DISCONNECT, sender, SYSTEM, "");
+ }
+
+ /**
+ * Creates a new Payload object of type MESSAGE with the specified sender, receiver, and
+ * content.
+ *
+ * @param sender the sender of the payload
+ * @param receiver the receiver of the payload
+ * @param content the content of the payload
+ * @return the new Payload object
+ */
+ public static Payload createMessagePayload(String sender, String receiver, String content) {
+ return new Payload(MESSAGE, sender, receiver, content);
+ }
+
+ /**
+ * Creates a new Payload object of type ERROR with the specified receiver and content.
+ *
+ * @param receiver the receiver of the payload
+ * @param content the content of the payload
+ * @return the new Payload object
+ */
+ public static Payload createErrorPayload(String receiver, String content) {
+ return new Payload(ERROR, SYSTEM, receiver, content);
+ }
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadHandler.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadHandler.java
new file mode 100644
index 0000000..0305793
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadHandler.java
@@ -0,0 +1,26 @@
+package ch.zhaw.pm2.multichat.protocol.payload;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.exception.ChatProtocolException;
+import ch.zhaw.pm2.multichat.protocol.exception.ConnectionException;
+import java.io.Serializable;
+
+/**
+ * An interface representing a payload handler for incoming payloads.
+ *
+ * @param the type of the payload that this handler can handle
+ */
+public interface PayloadHandler {
+
+ /**
+ * Handles the given payload received through the given connection.
+ *
+ * @param payload the payload to handle
+ * @param connection the connection through which the payload was received
+ * @throws ChatProtocolException if there is an error in the chat protocol
+ * @throws ConnectionException if there is an error in the connection
+ */
+ void handle(T payload, Connection connection)
+ throws ChatProtocolException, ConnectionException;
+
+}
diff --git a/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadType.java b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadType.java
new file mode 100644
index 0000000..cd0a55d
--- /dev/null
+++ b/protocol/src/main/java/ch/zhaw/pm2/multichat/protocol/payload/PayloadType.java
@@ -0,0 +1,32 @@
+package ch.zhaw.pm2.multichat.protocol.payload;
+
+/**
+ * An enum representing the different types of payloads.
+ */
+public enum PayloadType {
+ /**
+ * A payload type for connecting to a system.
+ */
+ CONNECT,
+
+ /**
+ * A payload type for confirming a connection.
+ */
+ CONFIRM,
+
+ /**
+ * A payload type for disconnecting from a system.
+ */
+ DISCONNECT,
+
+ /**
+ * A payload type for sending a message.
+ */
+ MESSAGE,
+
+ /**
+ * A payload type for indicating an error.
+ */
+ ERROR
+}
+
diff --git a/server/build.gradle b/server/build.gradle
new file mode 100644
index 0000000..0334333
--- /dev/null
+++ b/server/build.gradle
@@ -0,0 +1,24 @@
+/*
+ * Gradle build configuration for specific lab module / exercise
+ */
+// enabled plugins
+plugins {
+ // Support for Java applications
+ id 'ch.zhaw.pm2.multichat.application-conventions'
+}
+
+// Project/Module information
+description = 'Uebung Multichat – Server'
+group = 'ch.zhaw.pm2'
+version = '2023'
+
+dependencies {
+ // dependency to the protocol library
+ implementation project(':protocol')
+}
+
+// Configuration for Application plugin
+application {
+ // Define the main class for the application.
+ mainClass = 'ch.zhaw.pm2.multichat.server.Server'
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/Server.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/Server.java
new file mode 100644
index 0000000..6e1699c
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/Server.java
@@ -0,0 +1,147 @@
+package ch.zhaw.pm2.multichat.server;
+
+import static ch.zhaw.pm2.multichat.protocol.NetworkHandler.DEFAULT_PORT;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadFactory.createErrorPayload;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.CONFIRM;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.CONNECT;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.DISCONNECT;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.ERROR;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadType.MESSAGE;
+import static java.lang.String.format;
+
+import ch.zhaw.pm2.multichat.protocol.NetworkHandler;
+import ch.zhaw.pm2.multichat.protocol.NetworkHandler.NetworkServer;
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.exception.ChatProtocolException;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadType;
+import ch.zhaw.pm2.multichat.server.connection.ConnectionRegistry;
+import ch.zhaw.pm2.multichat.server.connection.ServerConnectionListener;
+import ch.zhaw.pm2.multichat.server.payload.ConfirmPayloadHandler;
+import ch.zhaw.pm2.multichat.server.payload.ConnectPayloadHandler;
+import ch.zhaw.pm2.multichat.server.payload.DisconnectPayloadHandler;
+import ch.zhaw.pm2.multichat.server.payload.ErrorPayloadHandler;
+import ch.zhaw.pm2.multichat.server.payload.MessagePayloadHandler;
+import java.io.IOException;
+import java.net.SocketException;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A class representing a server that listens for incoming network connections and handles client
+ * requests.
+ */
+@Slf4j
+public class Server {
+
+ private final ConnectionRegistry connectionRegistry = new ConnectionRegistry();
+ private final ExecutorService executorService = Executors.newCachedThreadPool();
+ private final Map> payloadHandlers = new EnumMap<>(
+ PayloadType.class);
+ private NetworkServer networkServer;
+
+ /**
+ * Constructor that creates a new Server instance with the given port number.
+ *
+ * @param port The port number the server should listen on.
+ */
+ private Server(int port) {
+ try {
+ log.info("Create server connection...");
+ networkServer = NetworkHandler.createServer(port);
+ initializePayloadHandlers();
+ log.info(format("Listening on <%s:%s>", networkServer.getHostAddress(),
+ networkServer.getHostPort()));
+ } catch (IOException e) {
+ log.error(format("Could not create server on port %s", port));
+ }
+ }
+
+ /**
+ * Main method that creates a new Server instance and starts it with the given port number.
+ *
+ * @param args The command line arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ final Server server = new Server(getPort(args));
+ server.start();
+ } catch (IllegalArgumentException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ /**
+ * Parses the port number from the command line arguments or returns the default value if no
+ * argument is given.
+ *
+ * @param args The command line arguments.
+ * @return The port number to listen on.
+ * @throws IllegalArgumentException if the number of arguments is illegal or the argument is not
+ * a valid integer.
+ */
+ private static int getPort(String[] args) throws IllegalArgumentException {
+ return switch (args.length) {
+ case 0 -> DEFAULT_PORT;
+ case 1 -> Integer.parseInt(args[0].trim());
+ default -> throw new IllegalArgumentException(
+ format("Illegal number of arguments: %s", args.length));
+ };
+ }
+
+ /**
+ * Initializes the payload handlers for the server.
+ */
+ private void initializePayloadHandlers() {
+ payloadHandlers.put(CONNECT, new ConnectPayloadHandler(connectionRegistry));
+ payloadHandlers.put(CONFIRM, new ConfirmPayloadHandler());
+ payloadHandlers.put(DISCONNECT, new DisconnectPayloadHandler(connectionRegistry));
+ payloadHandlers.put(MESSAGE, new MessagePayloadHandler(connectionRegistry));
+ payloadHandlers.put(ERROR, new ErrorPayloadHandler());
+ }
+
+ /**
+ * Starts the server and waits for incoming connections.
+ */
+ private void start() {
+ log.info("Server started");
+ try {
+ while (!networkServer.isClosed()) {
+ NetworkHandler.NetworkConnection networkConnection = networkServer.waitForConnection();
+ Connection connection = new Connection<>(networkConnection);
+
+ executorService.execute(
+ new ServerConnectionListener(connection, payloadHandlers, connectionRegistry));
+ }
+ } catch (SocketException e) {
+ log.error(format("Server connection terminated: %s", e.getMessage()));
+ } catch (IOException e) {
+ log.error(format("Communication error: %s", e.getMessage()));
+ } finally {
+ terminate();
+ log.info("Server terminated");
+ }
+ }
+
+ /**
+ * Terminates the server and closes all connections.
+ */
+ private void terminate() {
+ try {
+ connectionRegistry.getAllConnections().forEach(connection -> connection.sendPayload(
+ createErrorPayload(connection.getUsername(),
+ "Disconnected due to communication error")));
+
+ connectionRegistry.unregisterAllConnections();
+ networkServer.close();
+ log.info("Closed server connection");
+ } catch (IOException | ChatProtocolException e) {
+ log.error(format("Failed to close server connection: %s", e.getMessage()));
+ }
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/connection/ConnectionRegistry.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/connection/ConnectionRegistry.java
new file mode 100644
index 0000000..5dcbed2
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/connection/ConnectionRegistry.java
@@ -0,0 +1,128 @@
+package ch.zhaw.pm2.multichat.server.connection;
+
+import static ch.zhaw.pm2.multichat.protocol.Identifiers.ANONYMOUS;
+import static ch.zhaw.pm2.multichat.protocol.Identifiers.EVERYONE;
+import static ch.zhaw.pm2.multichat.protocol.Identifiers.SYSTEM;
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableList;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.exception.ChatProtocolException;
+import ch.zhaw.pm2.multichat.protocol.exception.ConnectionException;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing a registry for managing connections to the chat server.
+ */
+public class ConnectionRegistry {
+
+ private final List> connections = new ArrayList<>();
+
+ /**
+ * Registers a new connection with the given username to the registry.
+ *
+ * @param connection The connection to be registered.
+ * @throws ChatProtocolException If the username is already registered.
+ * @throws ConnectionException If the username is not compliant with the expected format.
+ */
+ public synchronized void registerConnection(Connection connection)
+ throws ChatProtocolException, ConnectionException {
+ String username = connection.getUsername();
+
+ if (isUsernameRegistered(username)) {
+ throw new ChatProtocolException(format("Username %s is already registered", username));
+ }
+
+ if (!isUsernameFormatCompliant(username)) {
+ throw new ConnectionException(format("Username %s is not compliant", username));
+ }
+
+ connections.add(connection);
+ }
+
+ /**
+ * Unregisters the connection with the given username from the registry and closes it.
+ *
+ * @param username The username of the connection to be unregistered.
+ */
+ public synchronized void unregisterConnection(String username) {
+ connections.stream()
+ .filter(connection -> connection.getUsername().equalsIgnoreCase(username))
+ .findFirst()
+ .ifPresent(connection -> {
+ connection.closeConnection();
+ connections.remove(connection);
+ });
+ }
+
+ /**
+ * Unregisters all connections from the registry and closes them.
+ */
+ public synchronized void unregisterAllConnections() {
+ for (Connection connection : connections) {
+ connection.closeConnection();
+ }
+
+ connections.clear();
+ }
+
+ /**
+ * Retrieves the connection with the given username from the registry.
+ *
+ * @param username The username of the connection to be retrieved.
+ * @return The connection with the given username.
+ * @throws ChatProtocolException If the username is not registered.
+ */
+ public synchronized Connection getConnection(String username)
+ throws ChatProtocolException {
+ return connections.stream()
+ .filter(connection -> connection.getUsername().equalsIgnoreCase(username))
+ .findFirst()
+ .orElseThrow(
+ () -> new ChatProtocolException(format("Username %s is not registered", username)));
+ }
+
+ /**
+ * Retrieves a list of all connections registered in the registry.
+ *
+ * @return A list of all connections registered in the registry.
+ * @throws ChatProtocolException If there are no connections registered in the registry.
+ */
+ public synchronized List> getAllConnections()
+ throws ChatProtocolException {
+ if (connections.isEmpty()) {
+ throw new ChatProtocolException("No connections registered");
+ }
+
+ return unmodifiableList(connections);
+ }
+
+ /**
+ * Generates and retrieves a new anonymous username that is not yet registered in the registry.
+ *
+ * @return A new anonymous username.
+ */
+ public synchronized String getAnonymousUsername() {
+ int i = 0;
+ String username;
+ do {
+ username = format("%s%s", ANONYMOUS, i++);
+ } while (isUsernameRegistered(username));
+
+ return username;
+ }
+
+ private boolean isUsernameRegistered(String username) {
+ return connections.stream()
+ .anyMatch(connection -> connection.getUsername().equalsIgnoreCase(username));
+ }
+
+ private boolean isUsernameFormatCompliant(String username) {
+ String lowerCaseUsername = username.toLowerCase();
+ return !lowerCaseUsername.matches(".*\\s+.*") && !lowerCaseUsername.equals(
+ SYSTEM.toLowerCase()) && !lowerCaseUsername.equals(EVERYONE.toLowerCase());
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/connection/ServerConnectionListener.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/connection/ServerConnectionListener.java
new file mode 100644
index 0000000..a462be9
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/connection/ServerConnectionListener.java
@@ -0,0 +1,73 @@
+package ch.zhaw.pm2.multichat.server.connection;
+
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadFactory.createDisconnectPayload;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadFactory.createErrorPayload;
+import static java.util.Objects.requireNonNull;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.connection.ConnectionListener;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadType;
+import java.util.Map;
+
+/**
+ * A class representing a listener for the server connection. It extends the ConnectionListener
+ * class and adds functionality to perform error payload creation and disconnection. It also holds a
+ * reference to the ConnectionRegistry for registering and unregistering connections.
+ */
+public class ServerConnectionListener extends ConnectionListener {
+
+ private final ConnectionRegistry connectionRegistry;
+
+ /**
+ * Constructs a new ServerConnectionListener with the given Connection, payloadHandlers and
+ * ConnectionRegistry.
+ *
+ * @param connection the Connection to listen to
+ * @param payloadHandlers the payloadHandlers to use for processing payloads
+ * @param connectionRegistry the ConnectionRegistry for registering and unregistering
+ * connections
+ */
+ public ServerConnectionListener(Connection connection,
+ Map> payloadHandlers,
+ ConnectionRegistry connectionRegistry) {
+ super(connection, payloadHandlers);
+ this.connectionRegistry = requireNonNull(connectionRegistry);
+ }
+
+ /**
+ * Returns the payload type of the given payload.
+ *
+ * @param payload the payload to get the type from
+ * @return the payload type of the given payload
+ */
+ @Override
+ protected PayloadType getPayloadType(Payload payload) {
+ return payload.type();
+ }
+
+ /**
+ * Performs the creation of an error payload with the given message.
+ *
+ * @param message the message to create the error payload with
+ * @return the error payload with the given message
+ */
+ @Override
+ protected Payload performErrorPayloadCreation(String message) {
+ return createErrorPayload(getConnection().getUsername(), message);
+ }
+
+ /**
+ * Performs the disconnection of the current connection. Sends a disconnect payload to the
+ * client and unregisters the connection from the ConnectionRegistry.
+ */
+ @Override
+ protected void performDisconnection() {
+ String username = getConnection().getUsername();
+
+ getConnection().sendPayload(createDisconnectPayload(username));
+ connectionRegistry.unregisterConnection(username);
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ConfirmPayloadHandler.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ConfirmPayloadHandler.java
new file mode 100644
index 0000000..069c788
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ConfirmPayloadHandler.java
@@ -0,0 +1,28 @@
+package ch.zhaw.pm2.multichat.server.payload;
+
+import static java.lang.String.format;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+
+/**
+ * A class representing a ConfirmPayloadHandler that is a server-side implementation of the
+ * PayloadHandler interface that handles ConfirmPayloads.
+ */
+public class ConfirmPayloadHandler implements PayloadHandler {
+
+ /**
+ * Throws an exception since the ConfirmPayload is not supported on server side.
+ *
+ * @param payload the payload to handle
+ * @param connection the connection to handle the payload on
+ * @throws UnsupportedOperationException this method is not supported server-side
+ */
+ @Override
+ public void handle(Payload payload, Connection connection) {
+ throw new UnsupportedOperationException(
+ format("%s is not supported server side", getClass().getCanonicalName()));
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ConnectPayloadHandler.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ConnectPayloadHandler.java
new file mode 100644
index 0000000..a9d5cfa
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ConnectPayloadHandler.java
@@ -0,0 +1,73 @@
+package ch.zhaw.pm2.multichat.server.payload;
+
+import static ch.zhaw.pm2.multichat.protocol.Identifiers.ANONYMOUS;
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.CONNECTED;
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.NEW;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadFactory.createConfirmPayload;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.exception.ChatProtocolException;
+import ch.zhaw.pm2.multichat.protocol.exception.ConnectionException;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import ch.zhaw.pm2.multichat.server.connection.ConnectionRegistry;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A class representing a ConnectPayloadHandler that handles a connect payload and registers a new
+ * connection to the {@link ConnectionRegistry}. If the connection is not in the NEW state, a
+ * {@link ConnectionException} is thrown. The sender's username is set to the connection's username,
+ * or an anonymous username is generated. if the sender is anonymous. The connection is then
+ * registered with the ConnectionRegistry. Finally, a confirm-payload is created and sent to the
+ * connection.
+ */
+@Slf4j
+public class ConnectPayloadHandler implements PayloadHandler {
+
+ private final ConnectionRegistry connectionRegistry;
+
+ /**
+ * Creates a new ConnectPayloadHandler instance with the specified connection registry.
+ *
+ * @param connectionRegistry the connection registry to be used for registering new connections
+ */
+ public ConnectPayloadHandler(ConnectionRegistry connectionRegistry) {
+ this.connectionRegistry = requireNonNull(connectionRegistry);
+ }
+
+ /**
+ * Handles the specified payload and connection. If the connection is not in the NEW state, a
+ * {@link ConnectionException} is thrown. The sender's username is set to the connection's
+ * username, or an anonymous username is generated if the sender is anonymous. The connection is
+ * then registered with the ConnectionRegistry. Finally, a confirm-payload is created and sent
+ * to the connection.
+ *
+ * @param payload the payload to be handled
+ * @param connection the connection that the payload was received from
+ * @throws ChatProtocolException if there is an issue with the chat protocol
+ * @throws ConnectionException if there is an issue with the connection
+ */
+ @Override
+ public void handle(Payload payload, Connection connection)
+ throws ChatProtocolException, ConnectionException {
+ if (!connection.isState(NEW)) {
+ throw new ConnectionException(
+ format("Illegal state for connect request: %s", connection.getState()));
+ }
+
+ if (!ANONYMOUS.equalsIgnoreCase(payload.sender())) {
+ connection.setUsername(payload.sender());
+ } else {
+ connection.setUsername(connectionRegistry.getAnonymousUsername());
+ }
+
+ connectionRegistry.registerConnection(connection);
+
+ connection.sendPayload(createConfirmPayload(connection.getUsername(),
+ format("Registration successful for %s", connection.getUsername())));
+ connection.setState(CONNECTED);
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/DisconnectPayloadHandler.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/DisconnectPayloadHandler.java
new file mode 100644
index 0000000..28393fc
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/DisconnectPayloadHandler.java
@@ -0,0 +1,63 @@
+package ch.zhaw.pm2.multichat.server.payload;
+
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.CONNECTED;
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.DISCONNECTED;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadFactory.createConfirmPayload;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.exception.ConnectionException;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import ch.zhaw.pm2.multichat.server.connection.ConnectionRegistry;
+
+/**
+ * A class representing a DisconnectPayloadHandler that handles a disconnect payload and unregisters
+ * the connection from the {@link ConnectionRegistry}. If the connection is already in the
+ * DISCONNECTED state, a {@link ConnectionException} is thrown. A confirm-payload is created and
+ * sent to the connection, and the connection is unregistered from the ConnectionRegistry if it was
+ * previously registered.
+ */
+public class DisconnectPayloadHandler implements PayloadHandler {
+
+ private final ConnectionRegistry connectionRegistry;
+
+ /**
+ * Creates a new DisconnectPayloadHandler instance with the specified connection registry.
+ *
+ * @param connectionRegistry the connection registry to be used for unregistering connections
+ */
+ public DisconnectPayloadHandler(ConnectionRegistry connectionRegistry) {
+ this.connectionRegistry = requireNonNull(connectionRegistry);
+ }
+
+ /**
+ * Handles the specified payload and connection. If the connection is already in the
+ * DISCONNECTED state, a {@link ConnectionException} is thrown. A confirm payload is created and
+ * sent to the connection, and the connection is unregistered from the ConnectionRegistry if it
+ * was previously registered.
+ *
+ * @param payload the payload to be handled
+ * @param connection the connection that the payload was received from
+ * @throws ConnectionException if there is an issue with the connection
+ */
+ @Override
+ public void handle(Payload payload, Connection connection)
+ throws ConnectionException {
+ String username = connection.getUsername();
+
+ if (connection.isState(DISCONNECTED)) {
+ throw new ConnectionException(
+ format("Illegal state for disconnect request: %s", connection.getState()));
+ }
+
+ connection.sendPayload(
+ createConfirmPayload(username, format("Confirm disconnect of %s", username)));
+
+ if (connection.isState(CONNECTED)) {
+ connectionRegistry.unregisterConnection(username);
+ }
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ErrorPayloadHandler.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ErrorPayloadHandler.java
new file mode 100644
index 0000000..7dca030
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/ErrorPayloadHandler.java
@@ -0,0 +1,29 @@
+package ch.zhaw.pm2.multichat.server.payload;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A class representing an ErrorPayloadHandler that handles an error payload by logging the error
+ * message and the associated connection.
+ */
+@Slf4j
+public class ErrorPayloadHandler implements PayloadHandler {
+
+ /**
+ * Handles the specified payload and connection by logging the error message and the associated
+ * connection.
+ *
+ * @param payload the payload to be handled
+ * @param connection the connection that the payload was received from
+ */
+ @Override
+ public void handle(Payload payload, Connection connection) {
+ log.error(
+ String.format("Received error from connection: %s with message: %s", connection,
+ payload.content()));
+ }
+
+}
diff --git a/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/MessagePayloadHandler.java b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/MessagePayloadHandler.java
new file mode 100644
index 0000000..291fc43
--- /dev/null
+++ b/server/src/main/java/ch/zhaw/pm2/multichat/server/payload/MessagePayloadHandler.java
@@ -0,0 +1,68 @@
+package ch.zhaw.pm2.multichat.server.payload;
+
+import static ch.zhaw.pm2.multichat.protocol.Identifiers.EVERYONE;
+import static ch.zhaw.pm2.multichat.protocol.connection.ConnectionStateType.CONNECTED;
+import static ch.zhaw.pm2.multichat.protocol.payload.PayloadFactory.createMessagePayload;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import ch.zhaw.pm2.multichat.protocol.connection.Connection;
+import ch.zhaw.pm2.multichat.protocol.exception.ChatProtocolException;
+import ch.zhaw.pm2.multichat.protocol.exception.ConnectionException;
+import ch.zhaw.pm2.multichat.protocol.payload.Payload;
+import ch.zhaw.pm2.multichat.protocol.payload.PayloadHandler;
+import ch.zhaw.pm2.multichat.server.connection.ConnectionRegistry;
+
+/**
+ * A class representing MessagePayloadHandler that handles a message payload by sending it to the
+ * appropriate recipient(s) or broadcasting it to all connections, depending on the contents of the
+ * payload.
+ */
+public class MessagePayloadHandler implements PayloadHandler {
+
+ private final ConnectionRegistry connectionRegistry;
+
+ /**
+ * Creates a new MessagePayloadHandler instance with the specified connection registry.
+ *
+ * @param connectionRegistry the connection registry to be used for handling message payloads
+ */
+ public MessagePayloadHandler(ConnectionRegistry connectionRegistry) {
+ this.connectionRegistry = requireNonNull(connectionRegistry);
+ }
+
+ /**
+ * Handles the specified payload and connection. If the connection is not in the CONNECTED
+ * state, a {@link ConnectionException} is thrown. If the payload is addressed to "everyone", it
+ * is broadcast to all connections. If the payload is addressed to a specific recipient, it is
+ * sent to that recipient and a copy is sent back to the sender.
+ *
+ * @param payload the payload to be handled
+ * @param connection the connection that the payload was received from
+ * @throws ChatProtocolException if there is an issue with the chat protocol
+ * @throws ConnectionException if there is an issue with the connection
+ */
+ @Override
+ public void handle(Payload payload, Connection connection)
+ throws ChatProtocolException, ConnectionException {
+ if (!connection.isState(CONNECTED)) {
+ throw new ConnectionException(
+ format("Illegal state for message request: %s", connection.getState()));
+ }
+
+ if (EVERYONE.equalsIgnoreCase(payload.receiver())) {
+ connectionRegistry.getAllConnections().forEach(c -> c.sendPayload(payload));
+ } else {
+ connectionRegistry.getConnection(
+ payload.receiver()).sendPayload(
+ createMessagePayload(connection.getUsername(), payload.receiver(),
+ payload.content()));
+
+ connectionRegistry.getConnection(
+ payload.sender()).sendPayload(
+ createMessagePayload(connection.getUsername(), payload.receiver(),
+ payload.content()));
+ }
+ }
+
+}
diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml
new file mode 100644
index 0000000..d91ec6a
--- /dev/null
+++ b/server/src/main/resources/logback.xml
@@ -0,0 +1,12 @@
+
+
+
+ %gray(%d{HH:mm:ss}) %highlight(%-5level) [%thread] %gray(%logger{36}) - %msg%n
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..c49cbf8
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,11 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user manual at https://docs.gradle.org/6.1/userguide/multi_project_builds.html
+ */
+
+rootProject.name = 'multichat'
+include('protocol', 'server', 'client')