diff --git a/documents/docs/networking/commands/README.md b/documents/docs/networking/commands/README.md index 391ec9d..3a85340 100644 --- a/documents/docs/networking/commands/README.md +++ b/documents/docs/networking/commands/README.md @@ -17,6 +17,10 @@ The concrete implementations for each command live in `app/commands/` and are wi - [Command Infrastructure](./commands-deep-dive.md) - Technical deep-dive into the `CommandParser`, `CommandParserDispatcher`, `CommandHandler`, `CommandRouter`, `Request`, and the response hierarchy. +### Protocol document +- [Protocol Document](./protocol-document.md) - + List of all supported commands by the server, along with descriptions, required pre-execution checks, and an example for both request and response. + ## Key Concepts **Each command is self-contained.** A command's Parser, Request, Handler, and Response all live in the same package under `app/commands//`. diff --git a/documents/docs/networking/commands/commands-deep-dive.md b/documents/docs/networking/commands/commands-deep-dive.md index 5a0178c..0540dcb 100644 --- a/documents/docs/networking/commands/commands-deep-dive.md +++ b/documents/docs/networking/commands/commands-deep-dive.md @@ -64,11 +64,31 @@ Concrete subclasses add command-specific fields, all set via constructor. Reques ### `CommandHandler` Class diagram of the CommandHandler -The `CommandHandler` is a single-method interface responsible for executing a typed request. +The `CommandHandler` is an abstract base class responsible for executing a typed request. The handler contains the domain logic: reading from registries and managers, modifying state, and dispatching a response via the `ResponseDispatcher`. -Handlers receive their dependencies (the `ResponseDispatcher`, registries and managers etc.) through constructor injection. -This keeps them fully testable without the network layer. +Handlers receive their dependencies (the `ResponseDispatcher`, registries and managers etc.) through constructor injection. +They can also register reusable pre-execution checks via `addCheck(...)`. These checks are stored on the handler and are evaluated before `execute(...)` runs. + +When a piece of trivial validation is shared by multiple handlers, it should be extracted into a dedicated `HandlerCheck` instead of being duplicated in each handler. + +### `HandlerCheck` +Class diagram of HandlerCheck and CommandHandlerExecutor + +The `HandlerCheck` is a functional interface for reusable pre-execution validation. +Its `check(...)` method receives the incoming `Request` and returns an empty `Optional` if the request may continue. +If the check fails, it returns an `ErrorResponse` wrapped in the `Optional`, which will be dispatched to the client instead of calling the handler. + +This is the right place for small shared checks such as "is the user logged in?" or other simple preconditions that multiple handlers need. + +### `CommandHandlerExecutor` +Class diagram of HandlerCheck and CommandHandlerExecutor + +The `CommandHandlerExecutor` runs all checks registered on a handler before invoking `execute(...)`. +If any `HandlerCheck` returns a response, the executor dispatches it immediately and aborts execution. +Otherwise, the handler is executed normally. + +This keeps precondition handling separate from the actual domain logic inside the handler. ### `CommandRouter` Class diagram of the CommandRouter diff --git a/documents/docs/networking/commands/guide-on-implementing-a-command.md b/documents/docs/networking/commands/guide-on-implementing-a-command.md index 037251f..e45b6fd 100644 --- a/documents/docs/networking/commands/guide-on-implementing-a-command.md +++ b/documents/docs/networking/commands/guide-on-implementing-a-command.md @@ -159,6 +159,9 @@ public class GreetHandler implements CommandHandler { **Rules for the Handler class:** - Declare all dependencies as `private final` fields, injected through the constructor. +- Use `addCheck(...)` to attach pre-execution checks that should run before `execute(...)`. +- If several handlers share the same trivial logic, such as verifying that the user is logged in, + extract that logic into a separate `HandlerCheck` and reuse it instead of duplicating the code. - Always dispatch exactly one response per execution path. Every branch must end with a `responseDispatcher.dispatch(...)` call. - Use `ErrorResponse` for domain-level failures (e.g. entity not found, precondition not met). diff --git a/documents/docs/networking/commands/protocol-document.md b/documents/docs/networking/commands/protocol-document.md new file mode 100644 index 0000000..a546a97 --- /dev/null +++ b/documents/docs/networking/commands/protocol-document.md @@ -0,0 +1,289 @@ +# Protocol Document +This document describes the protocol for client-server communication in our application. It defines the structure of requests and responses, the supported commands along with their request parameters, response formats, and possible errors. + + +# Table of Contents +- [Protocol Document](#protocol-document) +- [Table of Contents](#table-of-contents) +- [General structure of requests](#general-structure-of-requests) +- [Preconditions](#preconditions) + - [Parsing](#parsing) + - [Error Response](#error-response) + - [Command dispatching](#command-dispatching) + - [Error Response](#error-response-1) + - [Command parsing](#command-parsing) + - [Error Response](#error-response-2) +- [Pre-execution checks](#pre-execution-checks) + - [UserLoggedInCheck](#userloggedincheck) + - [Error Response](#error-response-3) +- [Commands](#commands) + - [PING command](#ping-command) + - [Required pre-execution checks](#required-pre-execution-checks) + - [Request Parameters](#request-parameters) + - [Success Response](#success-response) + - [Example Request](#example-request) + - [Example Response](#example-response) + - [CHECK\_USERNAME command](#check_username-command) + - [Required pre-execution checks](#required-pre-execution-checks-1) + - [Request Parameters](#request-parameters-1) + - [Success Response](#success-response-1) + - [Example Request](#example-request-1) + - [Example Response](#example-response-1) + - [LOGIN command](#login-command) + - [Required pre-execution checks](#required-pre-execution-checks-2) + - [Request Parameters](#request-parameters-2) + - [Success Response](#success-response-2) + - [Error Response](#error-response-4) + - [Example Request](#example-request-2) + - [Example Response](#example-response-2) + - [LOGOUT command](#logout-command) + - [Required pre-execution checks](#required-pre-execution-checks-3) + - [Request Parameters](#request-parameters-3) + - [Success Response](#success-response-3) + - [Error Response](#error-response-5) + - [Example Request](#example-request-3) + - [Example Response](#example-response-3) + + + +# General structure of requests +As mentioned before, our protocol is based on POP3. +Each command is represented as a single line of text, starting with the command name followed by parameters. The server responds with a status line indicating success or failure, followed by the body. + +Requests can have parameters that provide additional information for the command. Parameters are key-value pairs separated by an equal sign (`=`). + +Responses are collections of key-value pairs, containing either a value or another collection, allowing for nested structures. +Each collection is ended with the `END` keyword. + + + +# Preconditions +The serverside pipeline to process incoming requests consists of multiple stages. +Each of these stages can yield an error response if the request does not meet the requirements of that stage. + +## Parsing +One of these stages is the parsing. It is responsible for parsing the raw request into a structured format that can be easily processed by the command handlers. It validates the syntax of the request as well. + +### Error Response +| Code | Description | +| :-------------- | :---------------------------------------------------------------------------------------------- | +| `PARSING_ERROR` | The body of the request contains syntax errors (see message field of response for more details) | + + +## Command dispatching +After the request has been successfully parsed, the next stage is to dispatch the `PrimitiveRequest` to the appropriate `CommandParser`. This is done by the `CommandDispatcherDispatcher`, which uses the command name to determine which parser to use. + +### Error Response +| Code | Description | +| :---------------- | :---------------------------------------------------------------- | +| `UNKNOWN_COMMAND` | The command is unknown to this server. No parser has been defined | + + +## Command parsing +Once the `PrimitiveRequest` has been dispatched to the appropriate `CommandParser`, the parser is responsible for parsing the parameters of the request and creating a `Request` that can be executed by the responsible `CommandHandler`. + +### Error Response +| Code | Description | +| :------------------ | :------------------------------------------------------------------------------------------------ | +| `MISSING_PARAMETER` | A required parameter is missing from the request (see message field of response for more details) | + + + +# Pre-execution checks +Pre-execution checks are reusable validation steps that can be registered on command handlers. +They are implemented as `HandlerCheck` instances and are executed by the `CommandHandlerExecutor` before the handler's main logic is invoked. + + + +## UserLoggedInCheck +The `UserLoggedInCheck` is a common pre-execution check that verifies whether the user is logged in (i.e. has a user associated with his session). + +### Error Response +| Code | Description | +| :------------------- | :------------------------ | +| `USER_NOT_LOGGED_IN` | The user is not logged in | + + + +# Commands +Commands are the core of our protocol, representing the various actions that clients can request from the server. Each command has a unique name and may require specific parameters in addition to pre-execution checks. +The server processes these commands and responds accordingly. + + + +## PING command +The `PING` command is a simple command that can be used to check if the server is responsive. + +### Required pre-execution checks +None. + +### Request Parameters +No parameters. + +### Success Response +No response fields. + +### Example Request +``` +PING +``` + +### Example Response +``` ++OK +END +``` + + +## CHECK_USERNAME command +The `CHECK_USERNAME` command is used to check if a username is already taken by another user. Additional users can still log in with the same username, but their name will be substituted with a suffix. + +### Required pre-execution checks +None. + +### Request Parameters +| Parameter Name | Type | Optional | Description | +| :------------- | :------- | :------- | :------------------------------------- | +| `USERNAME` | `String` | no | The username to check for availability | + +### Success Response +| Field | Type | Description | +| :------- | :--------------------------- | :---------------------------------------------------------------------- | +| `STATUS` | `Enum` | Member of enum indicating if the username is available or already taken | + +| Members of `UsernameAvailability` | Description | +| :-------------------------------- | :------------------------- | +| `FREE` | Username is available | +| `TAKEN` | Username is already in use | + +### Example Request +``` +CHECK_USERNAME USERNAME='Lars' +``` + +### Example Response +``` ++OK + STATUS=FREE +END +``` + +## LOGIN command +The `LOGIN` command is used to log in a user with a specified username. If the username is already taken by another user, the server will append a suffix to the username to make it unique. + +### Required pre-execution checks +None. + +### Request Parameters +| Parameter Name | Type | Optional | Description | +| :------------- | :------- | :------- | :----------------------------------- | +| `USERNAME` | `String` | no | The username to create the user with | + +### Success Response +| Field | Type | Description | +| :--------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- | +| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one | +| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user | + +### Error Response +| Code | Description | +| :------------------ | :----------------------------------------------------------------------------- | +| `ALREADY_LOGGED_IN` | The session is already associated with a user, logging in again is prohibited. | + +### Example Request +``` +LOGIN USERNAME='Lars' +``` + +### Example Response +``` ++OK + USERNAME='Lars_1234' + ID=e47a671e-2b2a-42df-bb82-953fe2ebd307 +END +``` + + +## LOGOUT command +Description of the command, what it does, and when it should be used. + +### Required pre-execution checks +None. + +### Request Parameters +No parameters. + +### Success Response +No response fields. + +### Error Response +| Code | Description | +| :------------------- | :--------------------------------------------------------------- | +| `NO_USER_ASSOCIATED` | The session has no user associated, logging out is not possible. | + +### Example Request +``` +LOGOUT +``` + +### Example Response +``` ++OK +END +``` diff --git a/documents/images/docs/networking/commands/handler_check.png b/documents/images/docs/networking/commands/handler_check.png new file mode 100644 index 0000000..4824d15 Binary files /dev/null and b/documents/images/docs/networking/commands/handler_check.png differ diff --git a/documents/images/docs/networking/commands/handler_check.puml b/documents/images/docs/networking/commands/handler_check.puml new file mode 100644 index 0000000..8343c1b --- /dev/null +++ b/documents/images/docs/networking/commands/handler_check.puml @@ -0,0 +1,8 @@ +@startuml +skinparam backgroundColor transparent + +interface HandlerCheck { + + check(request: Request): Optional +} + +@enduml \ No newline at end of file diff --git a/documents/images/docs/networking/commands/handler_check_executor.png b/documents/images/docs/networking/commands/handler_check_executor.png new file mode 100644 index 0000000..aa3a444 Binary files /dev/null and b/documents/images/docs/networking/commands/handler_check_executor.png differ diff --git a/documents/images/docs/networking/commands/handler_check_executor.puml b/documents/images/docs/networking/commands/handler_check_executor.puml new file mode 100644 index 0000000..07dc033 --- /dev/null +++ b/documents/images/docs/networking/commands/handler_check_executor.puml @@ -0,0 +1,31 @@ +@startuml +skinparam backgroundColor transparent + +class CommandHandlerExecutor { + - responseDispatcher: ResponseDispatcher + + CommandHandlerExecutor(responseDispatcher: ResponseDispatcher) + + execute(handler: CommandHandler, request: Request): void +} + +interface HandlerCheck { + + check(request: Request): Optional +} + +abstract class CommandHandler { + - checks: List + # addCheck(check: HandlerCheck): void + + getChecks(): List + + execute(request: T): void +} + +interface ResponseDispatcher +class Request +class Response + +CommandHandlerExecutor --> CommandHandler : executes +CommandHandler "1" o-- "0..*" HandlerCheck : registered checks +CommandHandlerExecutor --> HandlerCheck : evaluates +HandlerCheck ..> Request : inspects +HandlerCheck ..> Response : returns failure response +CommandHandlerExecutor --> ResponseDispatcher : dispatches failures +@enduml \ No newline at end of file diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index ae52485..3e71904 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -5,17 +5,17 @@ import javafx.scene.control.Label; import javafx.scene.layout.VBox; /** - * Controller für die Casino-Spielfläche. + * Controller for the casino gaming area. * - *

Verantwortlich für: - die Darstellung des Pokertisches und der Spieleroberfläche, - die - * Verarbeitung von Benutzereingaben, - die Schnittstelle zur GameEngine und zum Netzwerkprotokoll. + *

Responsible for: - the display of the poker table and the player interface, - the processing + * of user input, - the interface to the game engine and the network protocol. * - *

Hinweise: - Die Methode `onTableClick()` dient aktuell nur als Test-Logik. Sie ist ggf. nicht - * mehr funktionsfähig und wird zukünftig durch die finale Spielinteraktion ersetzt. + *

Notes: - The `onTableClick()` method currently serves only as test logic. It may no longer be + * functional and will be replaced by the final game interaction in the future. */ public class CasinoGameController { - /** Standardkonstruktor. Wird von FXML verwendet. */ + /** Standard constructor. Used by FXML. */ public CasinoGameController() { // default constructor for FXML } @@ -23,13 +23,13 @@ public class CasinoGameController { @FXML private Label welcomeText; @FXML private VBox casinoTable; - // TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt, - // sobald die GameEngine fertig ist + // TODO: Test logic: will be replaced by real game interactions, + // once the game engine is finished /** - * Temporäre Test-Methode, die bei Klick auf den Tisch eine Platzhalteraktion ausführt. + * Temporary test method that performs a placeholder action when the table is clicked. * - *

Wird in der finalen Implementierung durch die Spiel-Logik ersetzt. + *

In the final implementation, this will be replaced by the game logic. */ @FXML public void onTableClick() { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 5083ee6..2e91921 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -7,17 +7,17 @@ import javafx.scene.Scene; import javafx.stage.Stage; /** - * Hauptklasse für das Casino-Spiel-UI. + * Main class for the Casono Game UI. * - *

Startet die JavaFX-Anwendung, lädt die grafische Oberfläche aus der FXML-Datei und - * initialisiert die Haupt-Stage für das Spiel. + *

Starts the JavaFX application, loads the graphical user interface from the FXML file, and + * initializes the main stage for the game. * - *

Aufgaben: - Lädt die FXML-Oberfläche "/ui-structure/Casinogameui.fxml". - Lädt das - * Anwendungs-Icon aus "/images/logoinverted.png". - Startet die Anwendung im Vollbildmodus. + *

Tasks: - Loads the FXML interface "/ui-structure/Casinogameui.fxml". - Loads the application + * icon from "/images/logoinverted.png". - Starts the application in full-screen mode. */ public class CasinoGameUI extends Application { - /** Standardkonstruktor. */ + /** default constructor */ public CasinoGameUI() { // default no-arg constructor } @@ -26,10 +26,10 @@ public class CasinoGameUI extends Application { private static final int DEFAULT_HEIGHT = 800; /** - * Startet die Haupt-Stage der Anwendung. + * Starts the main stage of the application. * - * @param stage Die vom System bereitgestellte Haupt-Stage. - * @throws IOException Wenn die FXML-Datei oder Ressourcen nicht geladen werden können. + * @param stage The main stage provided by the system. + * @throws IOException If the FXML file or resources cannot be loaded. */ @Override public void start(Stage stage) throws IOException { @@ -46,9 +46,9 @@ public class CasinoGameUI extends Application { } /** - * Startpunkt der Anwendung. + * Starting point of the application. * - * @param args Befehlszeilenargumente. + * @param args Command line arguments. */ public static void main(String[] args) { launch(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/CasinoBrowserController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/CasinoBrowserController.java index 03c4644..72b0730 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/CasinoBrowserController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/CasinoBrowserController.java @@ -31,28 +31,27 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** - * Experimenteller integrierter Browser für Casono. + * Experimental embedded browser for Casono. * - *

Diese Klasse implementiert einen einfachen eingebetteten Webbrowser auf Basis von {@link - * javafx.scene.web.WebView}. Der Browser dient primär als Hilfswerkzeug innerhalb des Spiels, um - * externe Inhalte wie Webseiten oder Videos anzuzeigen. + *

This class implements a simple embedded web browser based on {@link javafx.scene.web.WebView}. + * The browser primarily serves as a utility within the game to display external content such as web + * pages or videos. * - *

Status Der Browser befindet sich derzeit in einer experimentellen Phase. Einige - * Sicherheitsmechanismen basieren auf experimentellen KI-gestützten Empfehlungen und können sich in - * zukünftigen Versionen noch ändern. + *

Status The browser is currently in an experimental phase. Some security mechanisms are based + * on experimental AI-driven recommendations and may change in future versions. * - *

Zweck Der Browser wird aktuell experimentell genutzt, um: Pokerregeln direkt im Spiel zu - * erklären Hilfeseiten oder Dokumentationen anzuzeigen Videos (z.B. Tutorials oder Erklärungen) - * über Plattformen wie YouTube abzuspielen + *

Purpose The browser is currently being used experimentally to: Explain poker rules directly + * within the game Display help pages or documentation Play videos (e.g., tutorials or explanations) + * via platforms such as YouTube * - *

Sicherheitsmechanismen Da externe Webseiten geladen werden können, wurden einige grundlegende - * Schutzmaßnahmen integriert: - HTTPS-Zwang für Webseiten - Whitelist für bekannte Domains - - * Warnung bei unbekannten Webseiten - JavaScript standardmäßig deaktiviert (man kann es jedoch für - * Google etc. einschalten) - Popup-Blocker - Automatische Cookie-Löschung beim Schließen + *

Security Mechanisms Since external websites can be loaded, some basic protective measures have + * been integrated: - Mandatory HTTPS for websites - Whitelist for known domains - Warning for + * unknown websites - JavaScript disabled by default (but can be enabled for Google, etc.) - Pop-up + * blocker - Automatic cookie deletion upon closing */ public class CasinoBrowserController { - /** Standardkonstruktor. Initialisiert den CasinoBrowserController. */ + /** Default constructor. Initializes the CasinoBrowserController. */ public CasinoBrowserController() { // Intentionally left blank; controller initialization is FXML-driven. } @@ -101,7 +100,7 @@ public class CasinoBrowserController { private static boolean javascriptEnabled = false; - /** Löscht alle gespeicherten Cookies der aktuellen Browser-Sitzung. */ + /** Deletes all cookies stored during the current browser session. */ private static void clearCookies() { try { COOKIE_MANAGER.getCookieStore().removeAll(); @@ -110,12 +109,12 @@ public class CasinoBrowserController { } /** - * Öffnet ein neues Browserfenster und lädt eine angegebene Webseite. + * Opens a new browser window and loads a specified webpage. * - *

Falls die Webseite nicht zur Liste vertrauenswürdiger Domains gehört, wird der Benutzer - * gefragt, ob die Seite dennoch geladen werden soll. + *

If the webpage is not on the list of trusted domains, the user will be asked whether the + * page should be loaded anyway. * - * @param url die Startadresse der Webseite, die geladen werden soll + * @param url the URL of the webpage to be loaded */ public static void open(String url) { Platform.runLater( @@ -182,10 +181,10 @@ public class CasinoBrowserController { } /** - * WebView Container + * WebView container * - * @param webView WebView Inhalt - * @return StackPane Container + * @param webView WebView content + * @return StackPane container */ public static StackPane createWebContainer(WebView webView) { StackPane webContainer = new StackPane(webView); @@ -198,7 +197,7 @@ public class CasinoBrowserController { /** * Popup Blocker * - * @param engine WebEngine nutzen + * @param engine Use WebEngine */ public static void configurePopupBlocker(WebEngine engine) { engine.setCreatePopupHandler( @@ -212,8 +211,8 @@ public class CasinoBrowserController { var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH); Image logo = new Image(stream); - // Variable 'streamM' abgekürzt, um das 100-Zeichen-Limit (LineLength) - // einzuhalten + // Variable ‘streamM’ abbreviated to comply with the 100-character + // limit (LineLength) var streamM = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH_MAIN); Image logomain = new Image(streamM); @@ -232,7 +231,7 @@ public class CasinoBrowserController { alert.setGraphic(logoView); } } catch (Exception e) { - LOGGER.error("Logo konnte nicht geladen werden"); + LOGGER.error("The logo could not be loaded"); } alert.showAndWait(); @@ -241,9 +240,9 @@ public class CasinoBrowserController { } /** - * Logos laden + * Load logos * - * @param stage Fenster Stage + * @param stage Window Stage * @return ImageView Logo */ private static ImageView loadLogos(Stage stage) { @@ -253,8 +252,7 @@ public class CasinoBrowserController { var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH); Image logo = new Image(stream); - // Variable 'streamM' abgekürzt, um das 100-Zeichen-Limit (LineLength) - // einzuhalten + // Variable ‘streamM’ abbreviated to comply with the 100-character limit (LineLength) var streamM = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH_MAIN); Image logomain = new Image(streamM); @@ -268,17 +266,17 @@ public class CasinoBrowserController { browserLogo.setPreserveRatio(true); } } catch (Exception e) { - LOGGER.error("Logo konnte nicht geladen werden"); + LOGGER.error("The logo could not be loaded"); } return browserLogo; } /** - * URL Feld + * URL field * * @param url Start URL - * @return TextField Eingabe + * @return TextField input */ public static TextField createUrlField(String url) { TextField urlField = new TextField(url); @@ -289,12 +287,12 @@ public class CasinoBrowserController { } /** - * Sicherheits Label + * Security Label * - * @return Label Anzeige + * @return Label display */ public static Label createSecurityLabel() { - Label securityLabel = new Label("SICHER"); + Label securityLabel = new Label("SAFE"); securityLabel.getStyleClass().add("security-label"); return securityLabel; @@ -303,8 +301,8 @@ public class CasinoBrowserController { /** * JS Toggle * - * @param engine WebEngine nutzen - * @return Button Umschalten + * @param engine Use WebEngine + * @return Toggle button */ public static Button createJsToggle(WebEngine engine) { Button jsToggle = new Button("JS EINSCHALTEN"); @@ -316,7 +314,7 @@ public class CasinoBrowserController { engine.setJavaScriptEnabled(javascriptEnabled); if (javascriptEnabled) { - jsToggle.setText("JS AUSSCHALTEN"); + jsToggle.setText("JS TURN OFF"); jsToggle.getStyleClass().removeAll("red-button"); jsToggle.getStyleClass().add("yellow-button"); @@ -331,10 +329,10 @@ public class CasinoBrowserController { } /** - * Zurück Button + * Back Button * - * @param engine WebEngine nutzen - * @return Button Zurück + * @param engine Use WebEngine + * @return Back button */ public static Button createBackButton(WebEngine engine) { Button backBtn = new Button("<"); @@ -350,10 +348,10 @@ public class CasinoBrowserController { } /** - * Vorwärts Button + * Forward Button * - * @param engine WebEngine nutzen - * @return Button Vorwärts + * @param engine Use WebEngine + * @return Forward button */ public static Button createForwardButton(WebEngine engine) { Button fwdBtn = new Button(">"); @@ -373,8 +371,8 @@ public class CasinoBrowserController { /** * Reload Button * - * @param engine WebEngine nutzen - * @return Button Reload + * @param engine Use WebEngine + * @return Reload button */ public static Button createReloadButton(WebEngine engine) { Button reloadBtn = new Button("⟳"); @@ -386,9 +384,9 @@ public class CasinoBrowserController { /** * Close Button * - * @param stage Fenster Stage - * @param webView WebView Inhalt - * @return Button Schließen + * @param stage Window stage + * @param webView WebView content + * @return Close button */ public static Button createCloseButton(Stage stage, WebView webView) { Button closeBtn = new Button("X"); @@ -406,9 +404,9 @@ public class CasinoBrowserController { /** * URL Events * - * @param engine WebEngine nutzen - * @param urlField URL Textfeld - * @param securityLabel Sicherheits Label + * @param engine Use WebEngine + * @param urlField URL text field + * @param securityLabel Security label */ public static void configureUrlEvents( WebEngine engine, TextField urlField, Label securityLabel) { @@ -417,11 +415,11 @@ public class CasinoBrowserController { } /** - * Scene erstellen + * Create a scene * * @param taskbar Taskbar HBox - * @param webContainer Web Container - * @return Scene Fenster + * @param webContainer Web container + * @return Scene window */ public static Scene createScene(HBox taskbar, StackPane webContainer) { VBox root = new VBox(VBOX_SPACING, taskbar, webContainer); @@ -442,8 +440,8 @@ public class CasinoBrowserController { /** * Key Events * - * @param scene Scene Fenster - * @param engine WebEngine nutzen + * @param scene Scene window + * @param engine Use WebEngine */ public static void configureKeyEvents(Scene scene, WebEngine engine) { scene.setOnKeyPressed( @@ -455,18 +453,18 @@ public class CasinoBrowserController { } /** - * Lädt eine URL in den Browser, nachdem grundlegende Sicherheitsprüfungen durchgeführt wurden. + * Loads a URL in the browser after performing basic security checks. * - *

Vor dem Laden einer Seite werden folgende Prüfungen durchgeführt: - Überprüfung des - * Protokolls (nur HTTPS erlaubt) - Überprüfung der Domain gegen eine Whitelist - Schutz vor - * Domain-Spoofing (Domain-Vortäuschung) + *

Before loading a page, the following checks are performed: - Verification of the protocol + * (only HTTPS allowed) - Verification of the domain against a whitelist - Protection against + * domain spoofing * - *

Falls eine Domain nicht als vertrauenswürdig eingestuft wird, muss der Benutzer - * bestätigen, dass die Seite dennoch geöffnet werden darf. + *

If a domain is not classified as trustworthy, the user must confirm that the page may + * still be opened. * - * @param engine der WebEngine-Renderer des Browsers - * @param url die zu ladende Webadresse - * @param securityLabel Label zur Anzeige des aktuellen Sicherheitsstatus + * @param engine the browser's WebEngine renderer + * @param url the web address to be loaded + * @param securityLabel label for displaying the current security status */ private static void loadUrlSafely(WebEngine engine, String url, Label securityLabel) { try { @@ -476,9 +474,9 @@ public class CasinoBrowserController { URI uri = new URI(url); - // HTTPS Pflicht + // HTTPS required if (!"https".equalsIgnoreCase(uri.getScheme())) { - securityLabel.setText("BLOCKIERT"); + securityLabel.setText("BLOCKED"); return; } @@ -488,19 +486,19 @@ public class CasinoBrowserController { return; } - // Sicherer Domain Check + // Secure Domain Check boolean trusted = TRUSTED_DOMAINS.stream() .anyMatch(domain -> host.equals(domain) || host.endsWith("." + domain)); if (!trusted) { if (!showUnknownWebsiteAlert(host)) { - securityLabel.setText("BLOCKIERT"); + securityLabel.setText("BLOCKED"); return; } securityLabel.setText("UNBEKANNT"); } else { - securityLabel.setText("SICHER"); + securityLabel.setText("SAFE"); } engine.load(uri.toString()); } catch (Exception e) { @@ -509,20 +507,20 @@ public class CasinoBrowserController { } /** - * Zeigt Warnung an. + * Displays a warning. * - * @param host Website Host - * @return OK gedrückt + * @param host Website host + * @return OK pressed */ private static boolean showUnknownWebsiteAlert(String host) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("Unbekannte Website"); - alert.setHeaderText("Diese Website ist nicht bekannt"); + alert.setTitle("Unknown website"); + alert.setHeaderText("This website is unknown"); String content = host - + "\n\nDiese Seite ist nicht vom Casono Browser verifiziert.\n" - + "Möchten Sie sie trotzdem öffnen?"; + + "\n\nThis site has not been verified by the Casono browser.\n" + + "Do you still want to open it?"; alert.setContentText(content); var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java index b446f3a..233cd98 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java @@ -11,14 +11,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** - * Controller für die interaktive Taskleiste innerhalb der Poker-UI. + * Controller for the interactive taskbar within the poker UI. * - *

Verantwortlich für: - Drag-and-Drop-Verschieben der Taskleiste, - Eingabe und Verwaltung von - * Spieleinsätzen, - Steuerung allgemeiner Menüfunktionen wie Exit. + *

Responsible for: - Drag-and-drop movement of the taskbar, - Input and management of game + * stakes, - Control of general menu functions such as Exit. */ public class TaskbarController { - /** Standardkonstruktor. Wird von FXML verwendet. */ + /** Standard constructor. Used by FXML. */ public TaskbarController() { // default constructor for FXML } @@ -36,10 +36,10 @@ public class TaskbarController { private static final int CREDIT_STEP = 5; /** - * Wird aufgerufen, wenn die Taskleiste mit der Maus gedrückt wird. Speichert die relative - * Position, um später korrekt zu verschieben. + * Called when the taskbar is clicked with the mouse. Saves the relative position for later, + * correct repositioning. * - * @param event Das Mausereignis + * @param event The mouse event */ @FXML private void onTaskbarPressed(MouseEvent event) { @@ -48,11 +48,10 @@ public class TaskbarController { } /** - * Wird aufgerufen, während die Taskleiste mit der Maus gezogen wird. Aktualisiert die Position - * und skaliert die Taskleiste leicht zur visuellen Rückmeldung. + * Called while dragging the taskbar with the mouse. Updates the position and slightly scales + * the taskbar for visual feedback. * - *

TODO: Es muss noch gefixt werden, dass die Taskleiste nicht aus dem Fenster verschwinden - * kann. + *

TODO: It still needs to be fixed that the taskbar cannot disappear out of the window. * * @param event Das Mausereignis */ @@ -66,10 +65,10 @@ public class TaskbarController { } /** - * Wird aufgerufen, wenn die Maus über der Taskleiste losgelassen wird. Setzt die Skalierung der - * Taskleiste wieder auf Normalgröße. + * Called when the mouse cursor is released over the taskbar. Resets the taskbar scaling to + * normal size. * - * @param event Das Mausereignis + * @param event The mouse event */ @FXML private void onTaskbarReleased(MouseEvent event) { @@ -78,9 +77,9 @@ public class TaskbarController { } /** - * Wird aufgerufen, wenn im Textfeld die Enter-Taste gedrückt wird. + * Called up when the Enter key is pressed in the text field. * - * @param event Das Tastaturereignis + * @param event The keyboard event */ @FXML private void onInputSubmitted(KeyEvent event) { @@ -90,8 +89,8 @@ public class TaskbarController { } /** - * Wird aufgerufen, wenn der Submit-Button in der Taskleiste gedrückt wird. Löst die - * Verarbeitung des Einsatzes aus. + * Called when the submit button in the taskbar is pressed. Triggers the processing of the + * deployment. */ @FXML private void onInputSubmittedAction() { @@ -106,7 +105,7 @@ public class TaskbarController { javafx.stage.Stage currentStage = (javafx.stage.Stage) taskbar.getScene().getWindow(); currentStage.close(); - // Lobby-UI starten + // Start lobby UI try { new Casinomainui().start(new javafx.stage.Stage()); } catch (Exception e) { @@ -116,9 +115,9 @@ public class TaskbarController { } /** - * Verarbeitet den im Textfeld eingegebenen Einsatz. Es werden ausschließlich ganzzahlige Werte - * im Bereich von 5 bis 100.000 Credits akzeptiert, die einem Vielfachen von 5 entsprechen - * (5er-Schritte). Der Einsatz wird aktuell nur auf der Konsole ausgegeben. + * Processes the stake entered in the text field. Only integer values between 5 and 100,000 + * credits are accepted, in multiples of 5 (in increments of 5). The stake is currently only + * displayed on the console. */ private void processBet() { String input = taskbarInput.getText(); @@ -126,22 +125,22 @@ public class TaskbarController { int credits = Integer.parseInt(input.trim()); if (credits >= MIN_CREDITS && credits <= MAX_CREDITS && credits % CREDIT_STEP == 0) { - // TODO: Credits müssen an die GameEngine gesendet werden - LOGGER.info("Einsatz gesetzt: {} Casono Credits", credits); + // TODO: Credits must be sent to the GameEngine + LOGGER.info("Bet set: {} Casono Credits", credits); taskbarInput.clear(); } else { - LOGGER.info("Fehler: Nur 5er-Schritte (5, 10, ... 100.000) erlaubt!"); + LOGGER.error("Error: Only increments of 5 (5, 10, ... 100,000) are allowed!"); } } catch (NumberFormatException e) { - LOGGER.info("Fehler: Bitte nur eine Zahl eingeben!"); + LOGGER.error("Error: Please enter only one number!"); } } /** - * Öffnet den integrierten Casono Webbrowser. + * Opens the integrated Casono web browser. * - *

TODO: Ersetze die Start-URL durch die offizielle Projekt-Website (z.B. Tipps & Tricks - * Seite), sobald die Inhalte für Strategien und Support bereitstehen. + *

TODO: Replace the start URL with the official project website (e.g., Tips & Tricks page) + * once the content for strategies and support is available. */ @FXML private void onBrowserButtonClick() { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 1b5f62a..febc9a9 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -15,6 +15,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandlerExecutor; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher; import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; @@ -46,11 +47,12 @@ public class ServerApp { EventBus eventBus = new EventBus(); CommandParserDispatcher dispatcher = new CommandParserDispatcher(); - CommandRouter router = new CommandRouter(); + SessionManager sessionManager = new SessionManager(eventBus, dispatcher); + ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); + CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); + CommandRouter router = new CommandRouter(handlerExecutor); - SessionManager sessionManager = new SessionManager(eventBus, dispatcher, router); eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); - NetworkManager networkManager = new NetworkManager(port, sessionManager); UserRegistry userRegistry = new UserRegistry(); eventBus.subscribe( @@ -71,10 +73,9 @@ public class ServerApp { SESSION_DISCONNECT_JOB_PERIOD, TimeUnit.SECONDS); - ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); - registerCommands(dispatcher, router, responseDispatcher, userRegistry); + NetworkManager networkManager = new NetworkManager(port, sessionManager, router); networkManager.start(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/UserLoggedInCheck.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/UserLoggedInCheck.java new file mode 100644 index 0000000..8bc305f --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/UserLoggedInCheck.java @@ -0,0 +1,30 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.checks; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.checks.HandlerCheck; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response; +import java.util.Optional; + +public class UserLoggedInCheck implements HandlerCheck { + private final UserRegistry userRegistry; + + public UserLoggedInCheck(UserRegistry userRegistry) { + this.userRegistry = userRegistry; + } + + @Override + public Optional check(Request request) { + Optional user = userRegistry.getBySessionId(request.getSessionId()); + if (user.isPresent()) { + return Optional.empty(); + } + return Optional.of( + new ErrorResponse( + request.getContext(), + "USER_NOT_LOGGED_IN", + "The execution of this command requires the user to be logged in")); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/check_nick/CheckUsernameHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/check_nick/CheckUsernameHandler.java index c238ba1..0c70e55 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/check_nick/CheckUsernameHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/check_nick/CheckUsernameHandler.java @@ -7,8 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatch import java.util.Optional; /** Handles {@link CheckUsernameRequest}s to check whether a username is available. */ -public class CheckUsernameHandler implements CommandHandler { - private final ResponseDispatcher responseDispatcher; +public class CheckUsernameHandler extends CommandHandler { private final UserRegistry userRegistry; /** @@ -18,7 +17,7 @@ public class CheckUsernameHandler implements CommandHandler { - private final ResponseDispatcher responseDispatcher; +public class LoginHandler extends CommandHandler { private final UserRegistry userRegistry; private final UserFactory userFactory; @@ -25,7 +24,7 @@ public class LoginHandler implements CommandHandler { * @param userRegistry the registry used to look up existing users and create the new one */ public LoginHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { - this.responseDispatcher = responseDispatcher; + super(responseDispatcher); this.userRegistry = userRegistry; this.userFactory = new UserFactory(userRegistry); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java index c5ca9fd..199c207 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java @@ -7,8 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkRespon import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; /** Handles {@link LogoutRequest} to logout connected user. */ -public class LogoutHandler implements CommandHandler { - private final ResponseDispatcher responseDispatcher; +public class LogoutHandler extends CommandHandler { private final UserRegistry userRegistry; /** @@ -18,7 +17,7 @@ public class LogoutHandler implements CommandHandler { * @param userRegistry registry responsible for tracking connected user sessions */ public LogoutHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { - this.responseDispatcher = responseDispatcher; + super(responseDispatcher); this.userRegistry = userRegistry; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/ping/PingHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/ping/PingHandler.java index 1669cd8..69f389e 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/ping/PingHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/ping/PingHandler.java @@ -5,8 +5,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkRespon import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; /** Handler for {@link PingRequest}. */ -public class PingHandler implements CommandHandler { - private final ResponseDispatcher responseDispatcher; +public class PingHandler extends CommandHandler { /** * Create a new PingHandler to execute {@link PingRequest}s @@ -14,7 +13,7 @@ public class PingHandler implements CommandHandler { * @param responseDispatcher dispatcher used to send responses back to clients */ public PingHandler(ResponseDispatcher responseDispatcher) { - this.responseDispatcher = responseDispatcher; + super(responseDispatcher); } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/deck/Deck.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/deck/Deck.java index f75a738..2e2c6c9 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/deck/Deck.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/deck/Deck.java @@ -51,8 +51,8 @@ public class Deck { } /** - * Sets the cards of the deck. This method replaces the internal list - * with a copy of the provided list. + * Sets the cards of the deck. This method replaces the internal list with a copy of the + * provided list. * * @param cards the new list of cards to set */ diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index d9382a4..83a39d3 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport; import java.io.IOException; @@ -15,6 +16,7 @@ public class NetworkManager implements Runnable { private Thread thread; private Boolean running; private SessionManager sessionManager; + private CommandRouter router; /** * Creates a new NetworkManager with the given port, session manager, and event bus. @@ -22,12 +24,13 @@ public class NetworkManager implements Runnable { * @param port the port to listen on * @param sessionManager the session manager to use */ - public NetworkManager(Integer port, SessionManager sessionManager) { + public NetworkManager(Integer port, SessionManager sessionManager, CommandRouter router) { this.port = port; this.logger = LogManager.getLogger(NetworkManager.class); this.thread = new Thread(this, "networkManager"); this.running = true; this.sessionManager = sessionManager; + this.router = router; } /** Starts the internal thread to accept new connections. */ @@ -45,7 +48,7 @@ public class NetworkManager implements Runnable { logger.debug("Accepted connection from {}", clientSocket.getRemoteSocketAddress()); - sessionManager.create(new TcpTransport(clientSocket)); + sessionManager.create(new TcpTransport(clientSocket), router); } } catch (IOException e) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandler.java index 0fdae3c..f0f8b97 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandler.java @@ -1,7 +1,56 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.checks.HandlerCheck; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -public interface CommandHandler { - void execute(T request); +/** + * Abstract base class for handling requests of a specific type. + * + * @param the type of request this handler processes, must extend {@link Request} + */ +public abstract class CommandHandler { + private final List checks = new ArrayList<>(); + protected final ResponseDispatcher responseDispatcher; + + /** + * Constructs a CommandHandler with the specified ResponseDispatcher. + * + * @param responseDispatcher the ResponseDispatcher instance used to send responses to clients. + */ + public CommandHandler(ResponseDispatcher responseDispatcher) { + this.responseDispatcher = responseDispatcher; + } + + /** + * Adds a handler check to be performed before request execution. + * + *

The {@code execute} Method is only invoked, if all specified checks passed. + * + * @param check the {@link HandlerCheck} to add + */ + protected final void addCheck(HandlerCheck check) { + checks.add(check); + } + + /** + * Returns an unmodifiable list of all registered handler checks. + * + * @return an unmodifiable list of {@link HandlerCheck} objects + */ + public final List getChecks() { + return Collections.unmodifiableList(checks); + } + + /** + * Executes the request. + * + *

Subclasses should override this method to implement their specific request handling logic. + * + * @param request the request to execute, of type T + */ + public void execute(T request) {} } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandlerExecutor.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandlerExecutor.java new file mode 100644 index 0000000..0de6c97 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandlerExecutor.java @@ -0,0 +1,46 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.checks.HandlerCheck; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import java.util.Optional; + +/** + * Invokes execution of the {@link CommandHandler} if all defined pre-execution checks pass. + * + *

If any check fails, the response returned by the check is dispatched immediately and execution + * is aborted. Otherwise, the handler will invoke execution normally. + */ +public class CommandHandlerExecutor { + private final ResponseDispatcher responseDispatcher; + + /** + * Creates a new CommandHandlerExecutor to execute pre-execution checks and invoke the {@link + * CommandHandler} if all checks pass. + * + * @param responseDispatcher to dispatch the response with if an check fails + */ + public CommandHandlerExecutor(ResponseDispatcher responseDispatcher) { + this.responseDispatcher = responseDispatcher; + } + + /** + * Executes a command handler if all defined pre-execution checks pass. + * + * @param handler the command handler containing checks and execution logic + * @param request the incoming request to validate and process + * @throws NullPointerException if handler or request is null + */ + public void execute(CommandHandler handler, Request request) { + for (HandlerCheck check : handler.getChecks()) { + Optional result = check.check(request); + if (result.isPresent()) { + responseDispatcher.dispatch(result.get()); + return; + } + } + + handler.execute(request); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandRouter.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandRouter.java index 269c6a4..5b177ed 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandRouter.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandRouter.java @@ -4,13 +4,46 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; import java.util.HashMap; import java.util.Map; +/** Routes incoming requests to their corresponding {@link CommandHandler} implementations. */ public class CommandRouter { private final Map, CommandHandler> handlers = new HashMap<>(); + private final CommandHandlerExecutor handlerExecutor; + /** + * Creates a new CommandRouter with the specified handler executor. + * + * @param commandHandlerExecutor to delegate execution of checks and invocation of {@link + * CommandHandler} to + */ + public CommandRouter(CommandHandlerExecutor commandHandlerExecutor) { + this.handlerExecutor = commandHandlerExecutor; + } + + /** + * Registers a {@link CommandHandler} for a specific request type. + * + *

This method establishes a type-safe mapping between a request class and its handler. + * + *

Only one handler can be registered per request type; subsequent registrations will + * overwrite previous ones. + * + * @param the type of request handled by the provided handler + * @param request the request class to associate with the given command handler + * @param handler the command handler for the given request type + */ public void register(Class request, CommandHandler handler) { handlers.put(request, handler); } + /** + * Executes the appropriate handler for the given request. + * + *

Looks up the handler registered for the request's class and executes it through the + * configured {@link CommandHandlerExecutor}. + * + * @param request the request to be executed + * @throws UnknownRequestException if no handler is registered for the request type + */ // Safe, because during registration, it's ensured that the provided CommandHandler only // receives requests it can handle. @SuppressWarnings("unchecked") @@ -23,6 +56,7 @@ public class CommandRouter { throw new UnknownRequestException( "Unable to execute request " + requestName + ". Type unknown", requestName); } - handler.execute(request); + + handlerExecutor.execute(handler, request); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/checks/HandlerCheck.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/checks/HandlerCheck.java new file mode 100644 index 0000000..5db599f --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/checks/HandlerCheck.java @@ -0,0 +1,23 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.checks; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response; +import java.util.Optional; + +/** + * Functional interface used by pre-execution checks. Executed before the execute method of the + * {@link CommandHandler} is called + */ +@FunctionalInterface +public interface HandlerCheck { + /** + * Checks that the requirements outlined by this HandlerCheck are fulfilled. + * + * @param request to execute the check with + * @return Optional containing no value if the check was successful. And a {@link ErrorResponse} + * if the check failed, the response will be dispatched to the client. + */ + Optional check(Request request); +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java index c0ffe55..569b828 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java @@ -19,16 +19,13 @@ public class SessionManager { private final EventBus eventBus; private final Logger logger; private final CommandParserDispatcher dispatcher; - private final CommandRouter router; /** Constructs a new SessionManager. */ - public SessionManager( - EventBus eventBus, CommandParserDispatcher dispatcher, CommandRouter router) { + public SessionManager(EventBus eventBus, CommandParserDispatcher dispatcher) { this.sessions = new ConcurrentHashMap<>(); this.eventBus = eventBus; this.logger = LogManager.getLogger(SessionManager.class); this.dispatcher = dispatcher; - this.router = router; } /** @@ -37,9 +34,10 @@ public class SessionManager { *

Will create both worker threads and start them. * * @param transport to create session from + * @param router the command router used by the created session * @return newly created session */ - public Session create(TransportLayer transport) { + public Session create(TransportLayer transport, CommandRouter router) { Session session = new Session(transport, eventBus, dispatcher, router); SessionReader reader = new SessionReader(session, eventBus); SessionWriter writer = new SessionWriter(session); diff --git a/src/main/resources/ui-structure/Casinogameui.css b/src/main/resources/ui-structure/Casinogameui.css index a512edc..7218af8 100644 --- a/src/main/resources/ui-structure/Casinogameui.css +++ b/src/main/resources/ui-structure/Casinogameui.css @@ -1,12 +1,12 @@ .root { -fx-background-color: #000000; - -fx-font-family: "Monospaced", "Courier New"; /* Schriftart */ + -fx-font-family: "Monospaced", "Courier New"; /* font */ } .background { -fx-background-image: url("/images/background.png"); - -fx-background-size: cover; /* Bild füllt das ganze Fenster aus */ - /* Zentriert das Bild und verhindert, dass es sich kachelartig wiederholt */ + -fx-background-size: cover; /* Image fills the entire window */ + /* Centers the image and prevents it from repeating in a tile-like pattern. */ -fx-background-position: center center; -fx-background-repeat: no-repeat; } @@ -181,20 +181,20 @@ .status-label-small { -fx-text-fill: #ffffff; - -fx-font-family: "Monospaced"; /* Schriftart */ + -fx-font-family: "Monospaced"; /* font */ -fx-font-size: 15px; } .status-value-text { -fx-text-fill: #ffffff; - -fx-font-family: "Monospaced"; /* Schriftart */ + -fx-font-family: "Monospaced"; /* font */ -fx-font-size: 15px; -fx-font-weight: bold; } .status-value-money { -fx-text-fill: #ffffff; - -fx-font-family: "Monospaced"; /* Schriftart */ + -fx-font-family: "Monospaced"; /* font */ -fx-font-size: 15px; -fx-font-weight: bold; } diff --git a/src/main/resources/ui-structure/Casinogameui.fxml b/src/main/resources/ui-structure/Casinogameui.fxml index 08bbf56..0a9e17d 100644 --- a/src/main/resources/ui-structure/Casinogameui.fxml +++ b/src/main/resources/ui-structure/Casinogameui.fxml @@ -5,7 +5,7 @@ - + - + - + - + - + - + - + - + - + @@ -52,7 +52,7 @@ - + - + - + - + - + - + - + @@ -103,7 +103,7 @@ - + @@ -118,7 +118,7 @@ - + diff --git a/src/main/resources/ui-structure/components/Chatbox.fxml b/src/main/resources/ui-structure/components/Chatbox.fxml index c61e28e..d9e16e0 100644 --- a/src/main/resources/ui-structure/components/Chatbox.fxml +++ b/src/main/resources/ui-structure/components/Chatbox.fxml @@ -5,16 +5,16 @@ - + @@ -38,16 +38,16 @@