diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/User.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/User.java index 01fd428..904112b 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/User.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/User.java @@ -4,12 +4,20 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; import java.time.Instant; import java.util.Optional; +/** Represents an authenticated user on the server. */ public class User { private final UserId id; private final String name; private SessionId sessionId; private Instant disconnectedAt; + /** + * Creates a new User with the given ID, name and session. + * + * @param id the unique identifier for this user + * @param name the display name of this user + * @param sessionId the session currently associated with this user + */ public User(UserId id, String name, SessionId sessionId) { this.id = id; this.name = name; @@ -17,27 +25,53 @@ public class User { this.disconnectedAt = null; } + /** + * Returns the ID of this user. + * + * @return the user ID + */ public UserId getId() { return id; } + /** + * Returns the display name of this user. + * + * @return the user name + */ public String getName() { return name; } + /** + * Returns the session currently associated with this user, if any. + * + * @return an Optional containing the session ID, or empty if disconnected + */ public Optional getSessionId() { return Optional.ofNullable(sessionId); } + /** + * Returns the time at which this user disconnected, if applicable. + * + * @return an Optional containing the disconnect timestamp, or empty if connected + */ public Optional getDisconnectedAt() { return Optional.ofNullable(disconnectedAt); } + /** + * Associates this user with a new session, clearing the disconnect timestamp. + * + * @param sessionId the new session ID + */ public void reassignSession(SessionId sessionId) { this.sessionId = sessionId; this.disconnectedAt = null; } + /** Marks this user as disconnected by clearing the session and recording the timestamp. */ public void markDisconnected() { this.sessionId = null; this.disconnectedAt = Instant.now(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserCleanupJob.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserCleanupJob.java index d39db57..976670c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserCleanupJob.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserCleanupJob.java @@ -5,6 +5,10 @@ import java.time.Instant; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * Periodically periodicly run job to remove disconnected users who have exceeded the reconnect + * threshold. + */ public class UserCleanupJob implements Runnable { private static final Logger logger = LogManager.getLogger(UserCleanupJob.class); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserFactory.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserFactory.java index cd94ae2..84ab1ce 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserFactory.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserFactory.java @@ -2,13 +2,28 @@ package ch.unibas.dmi.dbis.cs108.casono.server.domain.user; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; +/** Creates new users, resolving name conflicts automatically. */ public class UserFactory { private final UserRegistry registry; + /** + * Creates a new UserFactory backed by the given registry. + * + * @param registry the registry to register new users to + */ public UserFactory(UserRegistry registry) { this.registry = registry; } + /** + * Creates and registers a new user with the given name and session. If the name is already + * taken, a numeric suffix is appended and incremented until a free name is found (e.g. + * Lars_001, Lars_002, ...). + * + * @param desiredName the preferred display name + * @param sessionId the session to associate with the new user + * @return the newly created and registered user + */ public User create(String desiredName, SessionId sessionId) { var result = registry.registerIfAvailable(desiredName, sessionId); if (result.isPresent()) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java index 061298d..6a60877 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java @@ -5,11 +5,20 @@ import java.util.Collection; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +/** Manages all active users on the server. */ public class UserRegistry { private final ConcurrentHashMap byId = new ConcurrentHashMap<>(); private final ConcurrentHashMap byName = new ConcurrentHashMap<>(); private final ConcurrentHashMap bySessionId = new ConcurrentHashMap<>(); + /** + * Attempts to register a user under the given name atomically. Returns the registered user, or + * empty if the name is already taken. + * + * @param name the desired display name + * @param sessionId the session to associate with the new user + * @return an Optional containing the new user, or empty if the name was taken + */ public synchronized Optional registerIfAvailable(String name, SessionId sessionId) { if (byName.containsKey(name)) { return Optional.empty(); @@ -22,6 +31,12 @@ public class UserRegistry { return Optional.of(user); } + /** + * Removes the user with the given ID, but only if they are still disconnected. This prevents + * removing a user who has reconnected between the cleanup job's check and its removal call. + * + * @param userId the ID of the user to remove + */ public synchronized boolean removeIfStillDisconnected(UserId userId) { User user = byId.get(userId); if (user == null) { @@ -37,6 +52,12 @@ public class UserRegistry { return true; } + /** + * Marks the user associated with the given session as disconnected, clearing the session + * association and recording the disconnect timestamp. + * + * @param sessionId the session ID of the disconnected client + */ // TODO: Add to EventRegistry with DisconnectEvent public synchronized void onDisconnect(SessionId sessionId) { User user = bySessionId.remove(sessionId); @@ -47,6 +68,13 @@ public class UserRegistry { user.markDisconnected(); } + /** + * Reassociates a user with a new session, effectively restoring them after a reconnect. + * + * @param userId the ID of the user to reconnect + * @param sessionId the new session ID + * @return an Optional containing the user, or empty if the user was not found + */ public synchronized Optional reassignSession(UserId userId, SessionId sessionId) { User user = byId.get(userId); if (user == null) { @@ -58,10 +86,21 @@ public class UserRegistry { return Optional.of(user); } + /** + * Looks up a user by their session ID. + * + * @param sessionId the session ID to look up + * @return an Optional containing the user, or empty if no user is associated with this session + */ public Optional findBySessionId(SessionId sessionId) { return Optional.ofNullable(bySessionId.get(sessionId)); } + /** + * Returns all currently registered users. + * + * @return a collection of all users + */ public Collection getAllUsers() { return byId.values(); }