Merge remote-tracking branch 'origin/main' into feat/game-engine
This commit is contained in:
@@ -10,15 +10,15 @@ import org.apache.logging.log4j.Logger;
|
||||
/**
|
||||
* Entry point for the Casono client application. Handles client startup and connection parameters.
|
||||
*
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
* <p>Default constructor for the application.
|
||||
*/
|
||||
public class ClientApp {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ClientApp.class);
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
/** Default constructor. */
|
||||
public ClientApp() {
|
||||
// Standardkonstruktor
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,13 +6,13 @@ import javafx.application.Application;
|
||||
/**
|
||||
* Launcher for the Casono main UI.
|
||||
*
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
* <p>Default constructor for the application.
|
||||
*/
|
||||
public class Launcher {
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
/** Default constructor. */
|
||||
public Launcher() {
|
||||
// Standardkonstruktor
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,11 @@ public class ChatController {
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
|
||||
/** Standardkonstruktor. Wird von FXML verwendet. */
|
||||
public ChatController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode wird vom Senden-Button oder Enter ausgelöst. Sie gibt die eigene Nachricht an
|
||||
* das Netzwerkprotokoll weiter.
|
||||
|
||||
+11
@@ -15,11 +15,22 @@ import javafx.scene.layout.VBox;
|
||||
*/
|
||||
public class CasinoGameController {
|
||||
|
||||
/** Standardkonstruktor. Wird von FXML verwendet. */
|
||||
public CasinoGameController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
@FXML private Label welcomeText;
|
||||
@FXML private VBox casinoTable;
|
||||
|
||||
// TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt,
|
||||
// sobald die GameEngine fertig ist
|
||||
|
||||
/**
|
||||
* Temporäre Test-Methode, die bei Klick auf den Tisch eine Platzhalteraktion ausführt.
|
||||
*
|
||||
* <p>Wird in der finalen Implementierung durch die Spiel-Logik ersetzt.
|
||||
*/
|
||||
@FXML
|
||||
public void onTableClick() {
|
||||
welcomeText.setText("Einsatz akzeptiert!");
|
||||
|
||||
@@ -17,6 +17,11 @@ import javafx.stage.Stage;
|
||||
*/
|
||||
public class CasinoGameUI extends Application {
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
public CasinoGameUI() {
|
||||
// default no-arg constructor
|
||||
}
|
||||
|
||||
private static final int DEFAULT_WIDTH = 1200;
|
||||
private static final int DEFAULT_HEIGHT = 800;
|
||||
|
||||
@@ -29,7 +34,7 @@ public class CasinoGameUI extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader =
|
||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/casinogameui.fxml"));
|
||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
stage.setTitle("Casono (GAME)");
|
||||
|
||||
|
||||
+16
-8
@@ -27,6 +27,8 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Experimenteller integrierter Browser für Casono.
|
||||
@@ -50,13 +52,17 @@ import javafx.stage.Stage;
|
||||
*/
|
||||
public class CasinoBrowserController {
|
||||
|
||||
/** Standardkonstruktor. Initialisiert den CasinoBrowserController. */
|
||||
public CasinoBrowserController() {
|
||||
// Intentionally left blank; controller initialization is FXML-driven.
|
||||
}
|
||||
|
||||
private static final Set<String> TRUSTED_DOMAINS = new HashSet<>();
|
||||
|
||||
private static final CookieManager COOKIE_MANAGER =
|
||||
new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
|
||||
private static final org.apache.logging.log4j.Logger LOGGER =
|
||||
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
private static final int LOGO_HEIGHT = 40;
|
||||
private static final int CORNER_RADIUS = 40;
|
||||
@@ -354,8 +360,9 @@ public class CasinoBrowserController {
|
||||
fwdBtn.getStyleClass().add("gray-button");
|
||||
fwdBtn.setOnAction(
|
||||
e -> {
|
||||
if (engine.getHistory().getCurrentIndex()
|
||||
< engine.getHistory().getEntries().size() - 1) {
|
||||
int currentIndex = engine.getHistory().getCurrentIndex();
|
||||
int lastIndex = engine.getHistory().getEntries().size() - 1;
|
||||
if (currentIndex < lastIndex) {
|
||||
engine.getHistory().go(1);
|
||||
}
|
||||
});
|
||||
@@ -423,7 +430,7 @@ public class CasinoBrowserController {
|
||||
|
||||
Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/casinogameui.css");
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/Casinogameui.css");
|
||||
|
||||
if (css != null) {
|
||||
scene.getStylesheets().add(css.toExternalForm());
|
||||
@@ -512,10 +519,11 @@ public class CasinoBrowserController {
|
||||
|
||||
alert.setTitle("Unbekannte Website");
|
||||
alert.setHeaderText("Diese Website ist nicht bekannt");
|
||||
alert.setContentText(
|
||||
String content =
|
||||
host
|
||||
+ "\n\nDiese Seite ist nicht vom "
|
||||
+ "Casono Browser verifiziert.\nMöchten Sie sie trotzdem öffnen?");
|
||||
+ "\n\nDiese Seite ist nicht vom Casono Browser verifiziert.\n"
|
||||
+ "Möchten Sie sie trotzdem öffnen?";
|
||||
alert.setContentText(content);
|
||||
|
||||
var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
|
||||
Image logo = new Image(stream);
|
||||
|
||||
+22
-10
@@ -1,12 +1,14 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Controller für die interaktive Taskleiste innerhalb der Poker-UI.
|
||||
@@ -16,8 +18,12 @@ import javafx.scene.layout.HBox;
|
||||
*/
|
||||
public class TaskbarController {
|
||||
|
||||
private static final org.apache.logging.log4j.Logger LOGGER =
|
||||
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
|
||||
/** Standardkonstruktor. Wird von FXML verwendet. */
|
||||
public TaskbarController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
@FXML private HBox taskbar;
|
||||
@FXML private TextField taskbarInput;
|
||||
@@ -92,15 +98,21 @@ public class TaskbarController {
|
||||
processBet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn der Exit-Button in der Taskleiste gedrückt wird.
|
||||
*
|
||||
* <p>TODO: Logik implementieren, um zur Lobby zurückzukehren, ohne die gesamte Anwendung zu
|
||||
* schließen (kein System.exit/Platform.exit).
|
||||
*/
|
||||
@FXML
|
||||
private void onExitButtonClick() {
|
||||
Platform.exit();
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
// Close game stage
|
||||
javafx.stage.Stage currentStage =
|
||||
(javafx.stage.Stage) taskbar.getScene().getWindow();
|
||||
currentStage.close();
|
||||
// Lobby-UI starten
|
||||
try {
|
||||
new Casinomainui().start(new javafx.stage.Stage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Starten der Lobby-UI: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,13 +10,13 @@ import javafx.stage.Stage;
|
||||
/**
|
||||
* JavaFX Application class for the Casono main UI.
|
||||
*
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
* <p>Default constructor for the application.
|
||||
*/
|
||||
public class Casinomainui extends Application {
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
/** Default constructor. */
|
||||
public Casinomainui() {
|
||||
// Standardkonstruktor
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
private static final int SCENE_WIDTH = 1200;
|
||||
|
||||
+4
-3
@@ -28,6 +28,7 @@ public class CasinomainuiController {
|
||||
private LobbyButtonGridManager gridManager;
|
||||
private int nextButtonId = 1;
|
||||
|
||||
/** Default constructor for dependency injection by FXMLLoader. */
|
||||
public CasinomainuiController() {
|
||||
// Default constructor
|
||||
}
|
||||
@@ -39,7 +40,7 @@ public class CasinomainuiController {
|
||||
subtitleLabel.setText("Texas Hold'em Poker");
|
||||
logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm()));
|
||||
|
||||
translationManager = new LobbyButtonTranslationManager();
|
||||
translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
gridManager =
|
||||
new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager);
|
||||
casinoTable.getChildren().clear();
|
||||
@@ -57,7 +58,7 @@ public class CasinomainuiController {
|
||||
@FXML
|
||||
public void handleCreateLobbyButton() {
|
||||
if (translationManager.isFull()) {
|
||||
LOGGER.warn("Grid voll! Keine weiteren Lobbys moeglich.");
|
||||
LOGGER.warn("Grid is full! No more lobbies available.");
|
||||
return;
|
||||
}
|
||||
int buttonId = nextButtonId++;
|
||||
@@ -67,7 +68,7 @@ public class CasinomainuiController {
|
||||
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
|
||||
gridManager.renderLobbyButtons();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Hinzufügen: {}", e.getMessage());
|
||||
LOGGER.error("Error while adding lobby button: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-4
@@ -17,6 +17,8 @@ import org.apache.logging.log4j.Logger;
|
||||
* ButtonID to LobbyID.
|
||||
*/
|
||||
public class LobbyButtonGridManager {
|
||||
private static final double BUTTON_WIDTH_MARGIN = 20.0;
|
||||
private static final double BUTTON_MIN_SIZE = 10.0;
|
||||
private static final Logger LOGGER = LogManager.getLogger(LobbyButtonGridManager.class);
|
||||
|
||||
/** GridPane for the button grid. */
|
||||
@@ -46,7 +48,8 @@ public class LobbyButtonGridManager {
|
||||
public LobbyButtonGridManager(
|
||||
GridPane gridPane, LobbyButtonTranslationManager translationManager) {
|
||||
this.gridPane = gridPane;
|
||||
this.translationManager = translationManager;
|
||||
// Singleton immer verwenden
|
||||
this.translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,8 +68,21 @@ public class LobbyButtonGridManager {
|
||||
int buttonId = entry.getKey();
|
||||
Button btn = new Button();
|
||||
btn.setId("lobbyBtn-" + buttonId);
|
||||
btn.setGraphic(
|
||||
new ImageView(new Image(getClass().getResourceAsStream(BUTTON_IMAGE_PATH))));
|
||||
ImageView imageView =
|
||||
new ImageView(new Image(getClass().getResourceAsStream(BUTTON_IMAGE_PATH)));
|
||||
imageView.setPreserveRatio(true);
|
||||
// Dynamische Breite: Bindung an die Zellengröße
|
||||
imageView
|
||||
.fitWidthProperty()
|
||||
.bind(gridPane.widthProperty().divide(COLS).subtract(BUTTON_WIDTH_MARGIN));
|
||||
imageView.setSmooth(true);
|
||||
btn.setGraphic(imageView);
|
||||
btn.setMaxWidth(Double.MAX_VALUE);
|
||||
btn.setMaxHeight(Double.MAX_VALUE);
|
||||
btn.setMinWidth(BUTTON_MIN_SIZE);
|
||||
btn.setMinHeight(BUTTON_MIN_SIZE);
|
||||
GridPane.setHgrow(btn, javafx.scene.layout.Priority.ALWAYS);
|
||||
GridPane.setVgrow(btn, javafx.scene.layout.Priority.ALWAYS);
|
||||
btn.setOnAction(
|
||||
e -> {
|
||||
Integer lobbyId = translationManager.getLobbyIdForButton(buttonId);
|
||||
@@ -99,8 +115,22 @@ public class LobbyButtonGridManager {
|
||||
* @param lobbyId The lobbyId to join
|
||||
*/
|
||||
public void joinLobby(int lobbyId) {
|
||||
// TODO: Replace with actual join logic
|
||||
// Game-UI starten und Lobby-UI schließen
|
||||
LOGGER.info("Joining lobby: {}", lobbyId);
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
// Lobby-Stage schließen
|
||||
javafx.stage.Stage currentStage =
|
||||
(javafx.stage.Stage) gridPane.getScene().getWindow();
|
||||
currentStage.close();
|
||||
// Game-UI starten
|
||||
try {
|
||||
new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI()
|
||||
.start(new javafx.stage.Stage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Starten der Game-UI: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+37
-20
@@ -4,36 +4,53 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. Keine Dateioperationen,
|
||||
* nur Laufzeitdatenstruktur.
|
||||
* Manages the mapping between Button IDs and Lobby IDs in memory only. No file operations,
|
||||
* runtime-only data structure.
|
||||
*/
|
||||
public class LobbyButtonTranslationManager {
|
||||
/** Maximale Anzahl an Buttons/Lobbys */
|
||||
|
||||
// Singleton instance
|
||||
private static LobbyButtonTranslationManager instance;
|
||||
|
||||
// Singleton access
|
||||
/**
|
||||
* Returns the singleton instance of the manager.
|
||||
*
|
||||
* @return the single instance of {@code LobbyButtonTranslationManager}
|
||||
*/
|
||||
public static LobbyButtonTranslationManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new LobbyButtonTranslationManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** Maximum number of buttons/lobbies */
|
||||
private static final int MAX_BUTTONS = 8;
|
||||
|
||||
/** Zuordnung ButtonID → LobbyID */
|
||||
/** Mapping ButtonID → LobbyID */
|
||||
private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>();
|
||||
|
||||
/** Konstruktor: initialisiert die Zuordnung leer. */
|
||||
public LobbyButtonTranslationManager() {
|
||||
// Zuordnung bleibt leer beim Start
|
||||
/** Private constructor for the singleton pattern */
|
||||
private LobbyButtonTranslationManager() {
|
||||
// Mapping is empty at startup
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht).
|
||||
* Checks whether the grid is full (MAX_BUTTONS reached).
|
||||
*
|
||||
* @return true, wenn Grid voll; sonst false
|
||||
* @return true if the grid is full; otherwise false
|
||||
*/
|
||||
public boolean isFull() {
|
||||
return buttonIdToLobbyId.size() >= MAX_BUTTONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eine Zuordnung ButtonID → LobbyID hinzu.
|
||||
* Adds a mapping ButtonID → LobbyID.
|
||||
*
|
||||
* @param buttonId Die ID des Buttons
|
||||
* @param lobbyId Die ID der Lobby
|
||||
* @throws Exception wenn das Grid voll ist
|
||||
* @param buttonId the ID of the button
|
||||
* @param lobbyId the ID of the lobby
|
||||
* @throws Exception when the grid is full
|
||||
*/
|
||||
public void addLobbyButton(int buttonId, int lobbyId) throws Exception {
|
||||
if (isFull()) {
|
||||
@@ -43,28 +60,28 @@ public class LobbyButtonTranslationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt eine Zuordnung für die gegebene ButtonID.
|
||||
* Removes the mapping for the given ButtonID.
|
||||
*
|
||||
* @param buttonId Die ID des zu entfernenden Buttons
|
||||
* @param buttonId the ID of the button to remove
|
||||
*/
|
||||
public void removeLobbyButton(int buttonId) {
|
||||
buttonIdToLobbyId.remove(buttonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die LobbyID für eine gegebene ButtonID zurück.
|
||||
* Returns the LobbyID for a given ButtonID.
|
||||
*
|
||||
* @param buttonId Die ButtonID
|
||||
* @return Die zugehoerige LobbyID oder null, falls nicht vorhanden
|
||||
* @param buttonId the ButtonID
|
||||
* @return the associated LobbyID or null if not present
|
||||
*/
|
||||
public Integer getLobbyIdForButton(int buttonId) {
|
||||
return buttonIdToLobbyId.get(buttonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die gesamte Zuordnung ButtonID → LobbyID zurück.
|
||||
* Returns the full mapping ButtonID → LobbyID.
|
||||
*
|
||||
* @return Map aller Zuordnungen
|
||||
* @return Map of all mappings
|
||||
*/
|
||||
public Map<Integer, Integer> getButtonIdToLobbyId() {
|
||||
return buttonIdToLobbyId;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingParser;
|
||||
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;
|
||||
@@ -7,6 +13,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandR
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionDisconnectJob;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||
import java.time.Duration;
|
||||
@@ -58,6 +65,31 @@ public class ServerApp {
|
||||
SESSION_DISCONNECT_JOB_PERIOD,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager);
|
||||
|
||||
registerCommands(dispatcher, router, responseDispatcher, userRegistry);
|
||||
|
||||
networkManager.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers command parsers and handlers.
|
||||
*
|
||||
* @param parserDispatcher the dispatcher responsible for parsing incoming commands
|
||||
* @param commandRouter the router that dispatches parsed commands to appropriate handlers
|
||||
* @param responseDispatcher the dispatcher responsible for sending responses back to clients
|
||||
*/
|
||||
private static void registerCommands(
|
||||
CommandParserDispatcher parserDispatcher,
|
||||
CommandRouter commandRouter,
|
||||
ResponseDispatcher responseDispatcher,
|
||||
UserRegistry userRegistry) {
|
||||
parserDispatcher.register("PING", new PingParser());
|
||||
commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher));
|
||||
|
||||
parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser());
|
||||
commandRouter.register(
|
||||
CheckUsernameRequest.class,
|
||||
new CheckUsernameHandler(responseDispatcher, userRegistry));
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
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.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Handles {@link CheckUsernameRequest}s to check whether a username is available. */
|
||||
public class CheckUsernameHandler implements CommandHandler<CheckUsernameRequest> {
|
||||
private final ResponseDispatcher responseDispatcher;
|
||||
private final UserRegistry userRegistry;
|
||||
|
||||
/**
|
||||
* Creates a new handler for checking username availability.
|
||||
*
|
||||
* @param responseDispatcher the dispatcher used to send the response
|
||||
* @param userRegistry the registry used to look up existing users
|
||||
*/
|
||||
public CheckUsernameHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
|
||||
this.responseDispatcher = responseDispatcher;
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the username availability check for the given request.
|
||||
*
|
||||
* <p>If no user exists for the requested username, the username is reported as {@link
|
||||
* UsernameAvailability#FREE}; otherwise, it is reported as {@link UsernameAvailability#TAKEN}.
|
||||
*
|
||||
* @param request the request to execute
|
||||
*/
|
||||
@Override
|
||||
public void execute(CheckUsernameRequest request) {
|
||||
Optional<User> user = userRegistry.getByUsername(request.getUsername());
|
||||
UsernameAvailability availability;
|
||||
if (user.isEmpty()) {
|
||||
availability = UsernameAvailability.FREE;
|
||||
} else {
|
||||
availability = UsernameAvailability.TAKEN;
|
||||
}
|
||||
responseDispatcher.dispatch(new CheckUsernameResponse(request.getContext(), availability));
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
|
||||
/** Parses a primitive request into a {@link CheckUsernameRequest}. */
|
||||
public class CheckUsernameParser implements CommandParser<CheckUsernameRequest> {
|
||||
/**
|
||||
* Extracts the required {@code USERNAME} parameter from the incoming request.
|
||||
*
|
||||
* @param primitiveRequest the request to parse
|
||||
* @return {@link CheckUsernameRequest} containing the username
|
||||
*/
|
||||
@Override
|
||||
public CheckUsernameRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor =
|
||||
new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
return new CheckUsernameRequest(primitiveRequest.context(), accessor.require("USERNAME"));
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request implementation used to check whether a username is available or already taken */
|
||||
public class CheckUsernameRequest extends Request {
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Constructs a new CheckUsernameRequest with the given context and username to check
|
||||
*
|
||||
* @param context the {@link RequestContext} containing information for responding to the
|
||||
* request
|
||||
* @param username the username to check for availability
|
||||
*/
|
||||
public CheckUsernameRequest(RequestContext context, String username) {
|
||||
super(context);
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided username in the request
|
||||
*
|
||||
* @return username to check
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
|
||||
|
||||
/** Response indicating the availability status of a username check. */
|
||||
public class CheckUsernameResponse extends SuccessResponse {
|
||||
/**
|
||||
* Creates a new response to respond to the username availability check to
|
||||
*
|
||||
* @param context the {@link RequestContext} associated with the request
|
||||
* @param availability the availability status of the requested username
|
||||
*/
|
||||
public CheckUsernameResponse(RequestContext context, UsernameAvailability availability) {
|
||||
super(context, new ResponseBodyBuilder().param("STATUS", availability).build());
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
/** Represents the availability status of a username */
|
||||
enum UsernameAvailability {
|
||||
/** Username is available */
|
||||
FREE,
|
||||
|
||||
/** Username is already in use */
|
||||
TAKEN
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
/** Handler for {@link PingRequest}. */
|
||||
public class PingHandler implements CommandHandler<PingRequest> {
|
||||
private final ResponseDispatcher responseDispatcher;
|
||||
|
||||
/**
|
||||
* Create a new PingHandler to execute {@link PingRequest}s
|
||||
*
|
||||
* @param responseDispatcher dispatcher used to send responses back to clients
|
||||
*/
|
||||
public PingHandler(ResponseDispatcher responseDispatcher) {
|
||||
this.responseDispatcher = responseDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the ping request.
|
||||
*
|
||||
* @param request the ping request to handle
|
||||
*/
|
||||
@Override
|
||||
public void execute(PingRequest request) {
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
|
||||
/**
|
||||
* Parser for the Ping command.
|
||||
*
|
||||
* <p>Converts a low-level {@link PrimitiveRequest} into a {@link PingRequest}.
|
||||
*/
|
||||
public class PingParser implements CommandParser<PingRequest> {
|
||||
/**
|
||||
* Parse the given primitive request into a {@link PingRequest}.
|
||||
*
|
||||
* @param primitiveRequest the raw request to parse
|
||||
* @return {@link PingRequest}
|
||||
*/
|
||||
@Override
|
||||
public PingRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
return new PingRequest(primitiveRequest.context());
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/**
|
||||
* Represents a "PING" request sent by a client to check server availability and keep the connection
|
||||
* alive.
|
||||
*/
|
||||
public class PingRequest extends Request {
|
||||
/**
|
||||
* Constructs a new PingRequest with the given context.
|
||||
*
|
||||
* @param context the request context associated with this request
|
||||
*/
|
||||
public PingRequest(RequestContext context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -86,15 +86,38 @@ public class UserRegistry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by their session ID.
|
||||
* Looks up a user by their {@link SessionId}.
|
||||
*
|
||||
* @param sessionId the session ID to look up
|
||||
* @return an Optional containing the user, or empty if no user is associated with this session
|
||||
* @param sessionId the SessionId to look up
|
||||
* @return an Optional containing the {@link User}, or empty if no user is associated with this
|
||||
* SessionId
|
||||
*/
|
||||
public Optional<User> findBySessionId(SessionId sessionId) {
|
||||
public Optional<User> getBySessionId(SessionId sessionId) {
|
||||
return Optional.ofNullable(bySessionId.get(sessionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by their {@link UserId}.
|
||||
*
|
||||
* @param userId the UserId to look up
|
||||
* @return an Optional containing the {@link User}, or empty if no user is associated with this
|
||||
* UserId
|
||||
*/
|
||||
public Optional<User> getByUserId(UserId userId) {
|
||||
return Optional.ofNullable(byId.get(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by their username.
|
||||
*
|
||||
* @param username the username to look up
|
||||
* @return an Optional containing the {@link User}, or empty if no user is associated with this
|
||||
* username
|
||||
*/
|
||||
public Optional<User> getByUsername(String username) {
|
||||
return Optional.ofNullable(byName.get(username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all currently registered users.
|
||||
*
|
||||
|
||||
+2
-2
@@ -7,12 +7,12 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
* Parser to convert the PrimitiveRequest to a Request and performing checks for required fields and
|
||||
* data types
|
||||
*/
|
||||
public interface CommandParser {
|
||||
public interface CommandParser<T extends Request> {
|
||||
/**
|
||||
* Parses the provided PrimitiveRequest into a command-specific request
|
||||
*
|
||||
* @param primitiveRequest
|
||||
* @return
|
||||
*/
|
||||
Request parse(PrimitiveRequest primitiveRequest);
|
||||
T parse(PrimitiveRequest primitiveRequest);
|
||||
}
|
||||
|
||||
+4
-7
@@ -1,23 +1,20 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/** Response representing an error outcome for a client's request. */
|
||||
public class ErrorResponse extends Response {
|
||||
/**
|
||||
* Construct an error response with a code and message.
|
||||
*
|
||||
* @param sessionId the target session id
|
||||
* @param requestId the originating request id
|
||||
* @param context the RequestContext of the request
|
||||
* @param errorCode a short error code identifying the failure
|
||||
* @param errorMessage a human readable error message
|
||||
*/
|
||||
public ErrorResponse(
|
||||
SessionId sessionId, int requestId, String errorCode, String errorMessage) {
|
||||
public ErrorResponse(RequestContext context, String errorCode, String errorMessage) {
|
||||
super(
|
||||
sessionId,
|
||||
requestId,
|
||||
context,
|
||||
ResponseBody.builder().param("CODE", errorCode).param("MSG", errorMessage).build());
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -1,7 +1,7 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/**
|
||||
* A simple success response with an empty body.
|
||||
@@ -12,10 +12,9 @@ public class OkResponse extends SuccessResponse {
|
||||
/**
|
||||
* Create a minimal successful response (no body content).
|
||||
*
|
||||
* @param sessionId the target session id
|
||||
* @param requestId the originating request id
|
||||
* @param context the RequestContext of the request
|
||||
*/
|
||||
public OkResponse(SessionId sessionId, int requestId) {
|
||||
super(sessionId, requestId, ResponseBody.builder().build());
|
||||
public OkResponse(RequestContext context) {
|
||||
super(context, ResponseBody.builder().build());
|
||||
}
|
||||
}
|
||||
|
||||
+8
-10
@@ -1,24 +1,22 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/** Abstract base class for all server responses sent to clients. */
|
||||
public abstract class Response {
|
||||
private final SessionId sessionId;
|
||||
private final int requestId;
|
||||
private final RequestContext context;
|
||||
private final ResponseBody body;
|
||||
|
||||
/**
|
||||
* Create a new {@code Response}.
|
||||
*
|
||||
* @param sessionId the id of the session this response targets
|
||||
* @param requestId the request identifier this response corresponds to
|
||||
* @param context the RequestContext of the request
|
||||
* @param body the structured response body
|
||||
*/
|
||||
protected Response(SessionId sessionId, int requestId, ResponseBody body) {
|
||||
this.sessionId = sessionId;
|
||||
this.requestId = requestId;
|
||||
protected Response(RequestContext context, ResponseBody body) {
|
||||
this.context = context;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@@ -30,12 +28,12 @@ public abstract class Response {
|
||||
public abstract String prefix();
|
||||
|
||||
/**
|
||||
* Returns the session id that should receive this response.
|
||||
* Returns the session id of the session that should receive this response.
|
||||
*
|
||||
* @return the target {@link SessionId}
|
||||
*/
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
return context.sessionId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +42,7 @@ public abstract class Response {
|
||||
* @return the numeric request id
|
||||
*/
|
||||
public int getRequestId() {
|
||||
return requestId;
|
||||
return context.requestId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-5
@@ -1,7 +1,7 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/**
|
||||
* Abstract {@link Response} specialization indicating a successful outcome.
|
||||
@@ -13,12 +13,11 @@ public abstract class SuccessResponse extends Response {
|
||||
/**
|
||||
* Create a successful response with the provided body.
|
||||
*
|
||||
* @param sessionId the session id this response targets
|
||||
* @param requestId the originating request id
|
||||
* @param context the RequestContext of the request
|
||||
* @param body the response body
|
||||
*/
|
||||
protected SuccessResponse(SessionId sessionId, int requestId, ResponseBody body) {
|
||||
super(sessionId, requestId, body);
|
||||
protected SuccessResponse(RequestContext context, ResponseBody body) {
|
||||
super(context, body);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher;
|
||||
|
||||
public class ResponseDispatchException extends RuntimeException {
|
||||
public ResponseDispatchException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
+9
-4
@@ -27,12 +27,17 @@ public class ResponseDispatcher {
|
||||
* the target session's response queue.
|
||||
*
|
||||
* @param response the response to dispatch
|
||||
* @throws InterruptedException if the thread is interrupted while waiting to enqueue the
|
||||
* primitive response
|
||||
* @throws ResponseDispatchException wraps any exceptions that occur during dispatching, such as
|
||||
* the {@link InterruptedException}
|
||||
*/
|
||||
public void dispatch(Response response) throws InterruptedException {
|
||||
public void dispatch(Response response) {
|
||||
PrimitiveResponse primitiveResponse = ResponseEncoder.encode(response);
|
||||
Session session = sessionManager.getSessionById(response.getSessionId());
|
||||
session.getResponseQueue().put(primitiveResponse);
|
||||
try {
|
||||
session.getResponseQueue().put(primitiveResponse);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ResponseDispatchException("Interrupted while dispatching response", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-9
@@ -12,8 +12,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Primitive
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RawRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.MissingParameterException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatchException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseEncoder;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
@@ -46,11 +48,13 @@ public class SessionReader implements Runnable {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
RawPacket rawPacket = null;
|
||||
RawRequest rawRequest = null;
|
||||
RequestContext requestContext = null;
|
||||
try {
|
||||
// Step 1: Read from transport
|
||||
rawPacket = transport.read();
|
||||
session.updateLastInboundActivity();
|
||||
logger.debug("Recieved: {}", rawPacket);
|
||||
requestContext = new RequestContext(session.getId(), rawPacket.requestId());
|
||||
|
||||
// Step 2: Syntax validation and conversion into transport object
|
||||
rawRequest = ProtocolParser.parse(rawPacket.payload());
|
||||
@@ -58,9 +62,7 @@ public class SessionReader implements Runnable {
|
||||
|
||||
PrimitiveRequest primitiveRequest =
|
||||
new PrimitiveRequest(
|
||||
new RequestContext(session.getId(), rawPacket.requestId()),
|
||||
rawRequest.command(),
|
||||
rawRequest.parameters());
|
||||
requestContext, rawRequest.command(), rawRequest.parameters());
|
||||
logger.debug("Converted to {}", primitiveRequest);
|
||||
|
||||
// Step 3: Parse into Request and execute Request
|
||||
@@ -76,8 +78,7 @@ public class SessionReader implements Runnable {
|
||||
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(
|
||||
session.getId(),
|
||||
rawPacket.requestId(),
|
||||
requestContext,
|
||||
"PARSING_ERROR",
|
||||
"Error occured during parsing. Likely due to malformed payload."));
|
||||
|
||||
@@ -85,11 +86,23 @@ public class SessionReader implements Runnable {
|
||||
logger.error("Recieved unknown command '{}' from client", rawRequest.command(), e);
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(
|
||||
session.getId(),
|
||||
rawPacket.requestId(),
|
||||
requestContext,
|
||||
"UNKNOWN_COMMAND",
|
||||
"This command is unknown to the server."));
|
||||
|
||||
} catch (ResponseDispatchException e) {
|
||||
logger.error(
|
||||
"Unexpected ResponseDispatchException exception while dispatching request",
|
||||
e);
|
||||
|
||||
} catch (MissingParameterException e) {
|
||||
logger.error(
|
||||
"Recieved request for command '{}' was missing the '{}' parameter",
|
||||
rawRequest.command(),
|
||||
e.getParameterKey());
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(requestContext, "MISSING_PARAMETER", e.getMessage()));
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Unexpected IO exception while reading from transport", e);
|
||||
|
||||
@@ -97,8 +110,7 @@ public class SessionReader implements Runnable {
|
||||
logger.error("Unexpected RuntimeException occured", e);
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(
|
||||
session.getId(),
|
||||
rawPacket.requestId(),
|
||||
requestContext,
|
||||
"INTERNAL_ERROR",
|
||||
"Unexpected internal server error occured."));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user