From a747c2de56ac6947e4c9dcd999ec85d2bfd2ee2b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 16:47:40 +0200 Subject: [PATCH 01/12] Add: HandlerCheck functional interface for pre-execution checks --- .../network/command/execution/checks/HandlerCheck.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/checks/HandlerCheck.java 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..cabe7f3 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/checks/HandlerCheck.java @@ -0,0 +1,10 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.checks; + +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 java.util.Optional; + +@FunctionalInterface +public interface HandlerCheck { + Optional check(Request request); +} From cf09a70fb88966a3da8d236fd1fe9bcce0515896 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 16:49:47 +0200 Subject: [PATCH 02/12] Add: Change CommandHandler to abstract class and add support for pre-execution checks --- .../command/execution/CommandHandler.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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..f6c1a58 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,21 @@ 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; -public interface CommandHandler { - void execute(T request); +public abstract class CommandHandler { + private final List checks = new ArrayList<>(); + + protected final void addCheck(HandlerCheck check) { + checks.add(check); + } + + public final List getChecks() { + return Collections.unmodifiableList(checks); + } + + public void execute(T request) {} } From a62da1235dab9c932e4489f6d55fc4bc7eeb79bb Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 17:10:20 +0200 Subject: [PATCH 03/12] Refactor: Move CommandRouter dependency from SessionManager to NetworkManager --- .../ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java | 7 +++---- .../dbis/cs108/casono/server/network/NetworkManager.java | 7 +++++-- .../casono/server/network/sessions/SessionManager.java | 8 +++----- 3 files changed, 11 insertions(+), 11 deletions(-) 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..b553a36 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 @@ -46,11 +46,11 @@ public class ServerApp { EventBus eventBus = new EventBus(); CommandParserDispatcher dispatcher = new CommandParserDispatcher(); + SessionManager sessionManager = new SessionManager(eventBus, dispatcher); + ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); CommandRouter router = new CommandRouter(); - 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 +71,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/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/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); From ebb2e267bf89af4ddc0da130f0f6c91dc4d5819b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 17:23:38 +0200 Subject: [PATCH 04/12] Add: Implement CommandHandlerExecutor for evaluation of pre-execution checks in CommandRouter --- .../dbis/cs108/casono/server/ServerApp.java | 4 ++- .../execution/CommandHandlerExecutor.java | 27 +++++++++++++++++++ .../command/execution/CommandRouter.java | 8 +++++- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandlerExecutor.java 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 b553a36..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; @@ -48,7 +49,8 @@ public class ServerApp { CommandParserDispatcher dispatcher = new CommandParserDispatcher(); SessionManager sessionManager = new SessionManager(eventBus, dispatcher); ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); - CommandRouter router = new CommandRouter(); + CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); + CommandRouter router = new CommandRouter(handlerExecutor); eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); 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..7526c1b --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/command/execution/CommandHandlerExecutor.java @@ -0,0 +1,27 @@ +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; + +public class CommandHandlerExecutor { + private final ResponseDispatcher responseDispatcher; + + public CommandHandlerExecutor(ResponseDispatcher responseDispatcher) { + this.responseDispatcher = responseDispatcher; + } + + 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..366db11 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 @@ -6,6 +6,11 @@ import java.util.Map; public class CommandRouter { private final Map, CommandHandler> handlers = new HashMap<>(); + private final CommandHandlerExecutor handlerExecutor; + + public CommandRouter(CommandHandlerExecutor commandHandlerExecutor) { + this.handlerExecutor = commandHandlerExecutor; + } public void register(Class request, CommandHandler handler) { handlers.put(request, handler); @@ -23,6 +28,7 @@ public class CommandRouter { throw new UnknownRequestException( "Unable to execute request " + requestName + ". Type unknown", requestName); } - handler.execute(request); + + handlerExecutor.execute(handler, request); } } From 77d9ded3d3e4f02fa108b065ae1d31cab791e541 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 17:24:26 +0200 Subject: [PATCH 05/12] Refactor: Change CommandHandler implementations to extend the abstract class --- .../server/app/commands/check_nick/CheckUsernameHandler.java | 2 +- .../cs108/casono/server/app/commands/login/LoginHandler.java | 2 +- .../cs108/casono/server/app/commands/logout/LogoutHandler.java | 2 +- .../dbis/cs108/casono/server/app/commands/ping/PingHandler.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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..95debb0 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,7 +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 { +public class CheckUsernameHandler extends CommandHandler { private final ResponseDispatcher responseDispatcher; private final UserRegistry userRegistry; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java index 5d139f8..4215b3b 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java @@ -13,7 +13,7 @@ import java.util.Optional; * Handles {@link LoginRequest}s to create a user for a session, if the session has not assigned one * already */ -public class LoginHandler implements CommandHandler { +public class LoginHandler extends CommandHandler { private final ResponseDispatcher responseDispatcher; private final UserRegistry userRegistry; private final UserFactory userFactory; 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..4ef9f98 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,7 +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 { +public class LogoutHandler extends CommandHandler { private final ResponseDispatcher responseDispatcher; private final 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..aed42e5 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,7 +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 { +public class PingHandler extends CommandHandler { private final ResponseDispatcher responseDispatcher; /** From ffca8e5e2984de48539d9315e56523cfe1f63838 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 17:45:36 +0200 Subject: [PATCH 06/12] Add: UserLoggedinCheck to validate that Session has User associated with it --- .../server/app/checks/UserLoggedinCheck.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/UserLoggedinCheck.java 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..fed3d0a --- /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")); + } +} From 15062df711ff4f6eff165f720779e6ed30d9bb32 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 18:06:55 +0200 Subject: [PATCH 07/12] Docs: Write JavaDoc for added or modified components --- .../command/execution/CommandHandler.java | 24 ++++++++++++++++ .../execution/CommandHandlerExecutor.java | 20 +++++++++++++ .../command/execution/CommandRouter.java | 28 +++++++++++++++++++ .../execution/checks/HandlerCheck.java | 13 +++++++++ 4 files changed, 85 insertions(+) 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 f6c1a58..aa9c4da 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 @@ -6,16 +6,40 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * 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<>(); + /** + * 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 index 7526c1b..6f097c9 100644 --- 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 @@ -6,13 +6,33 @@ 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 + * @param the type of request being processed + * @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); 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 366db11..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,18 +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") 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 index cabe7f3..5db599f 100644 --- 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 @@ -1,10 +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); } From eb2a67986881160d24f097fc113e8c52692fa9b6 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 18:18:17 +0200 Subject: [PATCH 08/12] Docs: Modify guide on implementing a command by mentioning pre-execution checks --- .../networking/commands/guide-on-implementing-a-command.md | 3 +++ 1 file changed, 3 insertions(+) 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). From 6ee2c4f901398dd01d2af8787eab3a5def652cbf Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 18:19:55 +0200 Subject: [PATCH 09/12] Docs: Add to the command deep-dive by adding a section for the HandlerCheck and CommandHandlerExecutor, including diagrams --- .../networking/commands/commands-deep-dive.md | 26 +++++++++++++-- .../networking/commands/handler_check.png | Bin 0 -> 4957 bytes .../networking/commands/handler_check.puml | 8 +++++ .../commands/handler_check_executor.png | Bin 0 -> 45597 bytes .../commands/handler_check_executor.puml | 31 ++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 documents/images/docs/networking/commands/handler_check.png create mode 100644 documents/images/docs/networking/commands/handler_check.puml create mode 100644 documents/images/docs/networking/commands/handler_check_executor.png create mode 100644 documents/images/docs/networking/commands/handler_check_executor.puml 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/images/docs/networking/commands/handler_check.png b/documents/images/docs/networking/commands/handler_check.png new file mode 100644 index 0000000000000000000000000000000000000000..4824d15963029b845c8c891fadb479cd7ba4e994 GIT binary patch literal 4957 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 +} + +@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 0000000000000000000000000000000000000000..aa3a4444aa374e921101b390f0d70c1cc42913b5 GIT binary patch literal 45597 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 j8U%FEe>)zDeBEWrZ75e39, 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 From 1b3ba0000a1048c1e7ce844357b68e60d5f1ade9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 18:27:10 +0200 Subject: [PATCH 10/12] Refactor: Correct naming for UserLoggedInCheck --- .../cs108/casono/server/app/checks/UserLoggedinCheck.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index fed3d0a..8bc305f 100644 --- 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 @@ -8,10 +8,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorRes import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response; import java.util.Optional; -public class UserLoggedinCheck implements HandlerCheck { +public class UserLoggedInCheck implements HandlerCheck { private final UserRegistry userRegistry; - public UserLoggedinCheck(UserRegistry userRegistry) { + public UserLoggedInCheck(UserRegistry userRegistry) { this.userRegistry = userRegistry; } From e23a9817e287c0e7a6b98877707c15a2bf3ef592 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 18:31:44 +0200 Subject: [PATCH 11/12] Refactor: Rename file to match class name --- .../app/checks/{UserLoggedinCheck.java => UserLoggedInCheck.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/{UserLoggedinCheck.java => UserLoggedInCheck.java} (100%) 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 similarity index 100% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/UserLoggedinCheck.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/checks/UserLoggedInCheck.java From 3de20e3d6c983cddafd559eaa192f7ab50680747 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 8 Apr 2026 18:54:12 +0200 Subject: [PATCH 12/12] Docs: Remove unnecessary generic type parameter in JavaDoc from execute method in CommandHandlerExecutor --- .../server/network/command/execution/CommandHandlerExecutor.java | 1 - 1 file changed, 1 deletion(-) 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 index 6f097c9..0de6c97 100644 --- 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 @@ -30,7 +30,6 @@ public class CommandHandlerExecutor { * * @param handler the command handler containing checks and execution logic * @param request the incoming request to validate and process - * @param the type of request being processed * @throws NullPointerException if handler or request is null */ public void execute(CommandHandler handler, Request request) {