From 0f58b4e9981d38f6059fd039c395adb3c2811838 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Mon, 23 Mar 2026 15:44:58 +0100 Subject: [PATCH 01/11] Add: Encodable interface as lowest instance --- .../dbis/cs108/casono/server/network/response/Encodable.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java new file mode 100644 index 0000000..af4d231 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java @@ -0,0 +1,5 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +public interface Encodable { + String encode(); +} -- 2.52.0 From eb63f6d39b91b4170e38aacabafde5e732e9dd07 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Mon, 23 Mar 2026 15:45:44 +0100 Subject: [PATCH 02/11] Add: Base classes for all responses --- .../network/response/ErrorResponse.java | 32 +++++++++++++++++++ .../server/network/response/Response.java | 16 ++++++++++ .../network/response/SuccessResponse.java | 23 +++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java new file mode 100644 index 0000000..801667e --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java @@ -0,0 +1,32 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; + +public class ErrorResponse extends Response { + private final String code; + private final String message; + + public ErrorResponse( + SessionId sessionId, int requestId, String errorCode, String errorMessage) { + this.sessionId = sessionId; + this.requestId = requestId; + this.code = errorCode; + this.message = errorMessage; + } + + public ErrorResponse(SessionId sessionId, int requestId, String errorCode) { + this.sessionId = sessionId; + this.requestId = requestId; + this.code = errorCode; + this.message = null; + } + + @Override + public String encode() { + if (message != null && !message.isEmpty()) { + return "-ERR CODE=" + code + " MSG=" + message; + } else { + return "-ERR CODE=" + code; + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java new file mode 100644 index 0000000..f9c0f20 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java @@ -0,0 +1,16 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; + +public abstract class Response implements Encodable { + protected SessionId sessionId; + protected int requestId; + + public SessionId getSessionId() { + return sessionId; + } + + public int getRequestId() { + return requestId; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java new file mode 100644 index 0000000..e9f9f2d --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java @@ -0,0 +1,23 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; + +public abstract class SuccessResponse extends Response { + public SuccessResponse(SessionId sessionId, int requestId) { + this.sessionId = sessionId; + this.requestId = requestId; + } + + public final String encode() { + String payload = payload(); + if (payload.isBlank()) { + return "+OK"; + } else { + return "+OK " + payload; + } + } + + protected String payload() { + return ""; + } +} -- 2.52.0 From bcfbe065321f7861f8f19ee84784469bc3387d1d Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 12:19:25 +0100 Subject: [PATCH 03/11] Add: ResponseNode and implementations to store body of response --- .../casono/server/network/response/ResponseBlock.java | 5 +++++ .../cs108/casono/server/network/response/ResponseNode.java | 3 +++ .../casono/server/network/response/ResponseParameter.java | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java new file mode 100644 index 0000000..868b7ee --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java @@ -0,0 +1,5 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import java.util.List; + +public record ResponseBlock(String tag, List children) implements ResponseNode {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java new file mode 100644 index 0000000..ea22d0a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java @@ -0,0 +1,3 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +public interface ResponseNode {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java new file mode 100644 index 0000000..4d04293 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java @@ -0,0 +1,7 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +public record ResponseParameter(String key, Object value) implements ResponseNode { + public String rawValue() { + return value.toString(); + } +} -- 2.52.0 From e7c9063a5d74523f39722e8d6972462371b13222 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 12:33:36 +0100 Subject: [PATCH 04/11] Remove: Encodable as encode-method is no longer used --- .../dbis/cs108/casono/server/network/response/Encodable.java | 5 ----- .../dbis/cs108/casono/server/network/response/Response.java | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java deleted file mode 100644 index af4d231..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Encodable.java +++ /dev/null @@ -1,5 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network.response; - -public interface Encodable { - String encode(); -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java index f9c0f20..c2274ad 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java @@ -2,7 +2,7 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; -public abstract class Response implements Encodable { +public abstract class Response { protected SessionId sessionId; protected int requestId; -- 2.52.0 From 796827cbb4e57860d167ebffce3176affa92f0a7 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 12:52:35 +0100 Subject: [PATCH 05/11] Add: ResponseBody and ResponseBodyBuilder classes for building response body --- .../server/network/response/ResponseBody.java | 19 ++++++++++++++ .../network/response/ResponseBodyBuilder.java | 25 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java new file mode 100644 index 0000000..7e75f59 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java @@ -0,0 +1,19 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import java.util.List; + +public class ResponseBody { + private final List nodes; + + ResponseBody(List nodes) { + this.nodes = List.copyOf(nodes); + } + + public static ResponseBodyBuilder builder() { + return new ResponseBodyBuilder(); + } + + public List nodes() { + return nodes; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java new file mode 100644 index 0000000..e8a8329 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java @@ -0,0 +1,25 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class ResponseBodyBuilder { + private final List nodes = new ArrayList<>(); + + public ResponseBodyBuilder param(String key, Object value) { + nodes.add(new ResponseParameter(key, value)); + return this; + } + + public ResponseBodyBuilder block(String tag, Consumer content) { + ResponseBodyBuilder childBuilder = new ResponseBodyBuilder(); + content.accept(childBuilder); + nodes.add(new ResponseBlock(tag, childBuilder.build().nodes())); + return this; + } + + public ResponseBody build() { + return new ResponseBody(nodes); + } +} -- 2.52.0 From 02c82c4e953fb59084065613a11f84325fe8802f Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 13:11:25 +0100 Subject: [PATCH 06/11] Refactor: Responses to use new ResponseBody --- .../network/response/ErrorResponse.java | 30 +++++-------------- .../server/network/response/Response.java | 17 +++++++++-- .../network/response/SuccessResponse.java | 19 ++++-------- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java index 801667e..a26244b 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java @@ -3,30 +3,16 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; public class ErrorResponse extends Response { - private final String code; - private final String message; - - public ErrorResponse( - SessionId sessionId, int requestId, String errorCode, String errorMessage) { - this.sessionId = sessionId; - this.requestId = requestId; - this.code = errorCode; - this.message = errorMessage; - } - - public ErrorResponse(SessionId sessionId, int requestId, String errorCode) { - this.sessionId = sessionId; - this.requestId = requestId; - this.code = errorCode; - this.message = null; + public ErrorResponse(SessionId sessionId, int requestId, String errorCode, String errorMessage) { + super(sessionId, requestId, ResponseBody.builder() + .param("CODE", errorCode) + .param("MSG", errorMessage) + .build() + ); } @Override - public String encode() { - if (message != null && !message.isEmpty()) { - return "-ERR CODE=" + code + " MSG=" + message; - } else { - return "-ERR CODE=" + code; - } + public String prefix() { + return "-ERR"; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java index c2274ad..b42e7af 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java @@ -3,8 +3,17 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; public abstract class Response { - protected SessionId sessionId; - protected int requestId; + private final SessionId sessionId; + private final int requestId; + private final ResponseBody body; + + protected Response(SessionId sessionId, int requestId, ResponseBody body) { + this.sessionId = sessionId; + this.requestId = requestId; + this.body = body; + } + + public abstract String prefix(); public SessionId getSessionId() { return sessionId; @@ -13,4 +22,8 @@ public abstract class Response { public int getRequestId() { return requestId; } + + public ResponseBody getBody() { + return body; + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java index e9f9f2d..dae5308 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java @@ -3,21 +3,12 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; public abstract class SuccessResponse extends Response { - public SuccessResponse(SessionId sessionId, int requestId) { - this.sessionId = sessionId; - this.requestId = requestId; + protected SuccessResponse(SessionId sessionId, int requestId, ResponseBody body) { + super(sessionId, requestId, body); } - public final String encode() { - String payload = payload(); - if (payload.isBlank()) { - return "+OK"; - } else { - return "+OK " + payload; - } - } - - protected String payload() { - return ""; + @Override + public final String prefix() { + return "+OK"; } } -- 2.52.0 From a281f747895311a4aba34ce86e96a1c8444911ad Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 13:39:06 +0100 Subject: [PATCH 07/11] Add: ResponseEncoder to encode Response to PrimitiveResponse --- .../network/response/ResponseEncoder.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java new file mode 100644 index 0000000..873ee66 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java @@ -0,0 +1,57 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +public class ResponseEncoder { + private static final String INDENT = "\t"; + private static final String NEWLINE = "\n"; + + public static PrimitiveResponse encode(Response response) { + StringBuilder sb = new StringBuilder(); + sb.append(response.prefix()); + + for (ResponseNode node : response.getBody().nodes()) { + sb.append(NEWLINE); + encodeNode(node, sb, 1); + } + + sb.append(NEWLINE).append("END"); + + return new PrimitiveResponse( + response.getSessionId(), response.getRequestId(), sb.toString()); + } + + private static void encodeNode(ResponseNode node, StringBuilder sb, int depth) { + if (node instanceof ResponseParameter param) { + encodeParameter(param, sb, depth); + } else if (node instanceof ResponseBlock block) { + encodeBlock(block, sb, depth); + } + } + + private static void encodeParameter(ResponseParameter param, StringBuilder sb, int depth) { + sb.append(INDENT.repeat(depth)); + sb.append(param.key()); + sb.append("="); + sb.append(maskIfNeeded(param.value().toString())); + } + + private static void encodeBlock(ResponseBlock block, StringBuilder sb, int depth) { + sb.append(INDENT.repeat(depth)); + sb.append(block.tag()); + + for (ResponseNode child : block.children()) { + sb.append(NEWLINE); + encodeNode(child, sb, depth + 1); + } + + sb.append(NEWLINE); + sb.append(INDENT.repeat(depth)); + sb.append("END"); + } + + private static String maskIfNeeded(String value) { + if (value.contains(" ")) { + return "'" + value + "'"; + } + return value; + } +} -- 2.52.0 From d0b431b65022f849dd7ecb7272ebafed1b7f6dae Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 13:40:34 +0100 Subject: [PATCH 08/11] Fix: Escape single quote when masking string --- .../casono/server/network/response/ResponseEncoder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java index 873ee66..bc66aad 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java @@ -49,8 +49,9 @@ public class ResponseEncoder { } private static String maskIfNeeded(String value) { - if (value.contains(" ")) { - return "'" + value + "'"; + if (value.contains(" ") || value.contains("'")) { + String escaped = value.replace("'", "\\'"); + return "'" + escaped + "'"; } return value; } -- 2.52.0 From 676216d291ce1e113d4195a5613bb100bef4f1e2 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 13:49:24 +0100 Subject: [PATCH 09/11] Add: ResponseDispatcher for handling response dispatching --- .../network/response/ResponseDispatcher.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java new file mode 100644 index 0000000..184cd78 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java @@ -0,0 +1,18 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; + +public class ResponseDispatcher { + private final SessionManager sessionManager; + + public ResponseDispatcher(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public void dispatch(Response response) throws InterruptedException { + PrimitiveResponse primitiveResponse = ResponseEncoder.encode(response); + Session session = sessionManager.getSessionById(response.getSessionId()); + session.getResponseQueue().put(primitiveResponse); + } +} -- 2.52.0 From 696542003fa9535f883c396fab064e5ae3f68d13 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 14:33:46 +0100 Subject: [PATCH 10/11] Add: Detailed JavaDoc documentation for response related components Disclaimer: The docstrings have been written with Copilot but fact-checked by me. --- .../network/response/ErrorResponse.java | 28 ++++++++++--- .../network/response/PrimitiveResponse.java | 7 ++++ .../server/network/response/Response.java | 31 +++++++++++++- .../network/response/ResponseBlock.java | 7 ++++ .../server/network/response/ResponseBody.java | 21 ++++++++++ .../network/response/ResponseBodyBuilder.java | 26 ++++++++++++ .../network/response/ResponseDispatcher.java | 18 +++++++++ .../network/response/ResponseEncoder.java | 40 +++++++++++++++++++ .../server/network/response/ResponseNode.java | 5 +++ .../network/response/ResponseParameter.java | 16 ++++++++ .../network/response/SuccessResponse.java | 20 ++++++++++ 11 files changed, 212 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java index a26244b..602181b 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ErrorResponse.java @@ -2,15 +2,31 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; 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 { - public ErrorResponse(SessionId sessionId, int requestId, String errorCode, String errorMessage) { - super(sessionId, requestId, ResponseBody.builder() - .param("CODE", errorCode) - .param("MSG", errorMessage) - .build() - ); + /** + * Construct an error response with a code and message. + * + * @param sessionId the target session id + * @param requestId the originating request id + * @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) { + super( + sessionId, + requestId, + ResponseBody.builder().param("CODE", errorCode).param("MSG", errorMessage).build()); } + /** + * {@inheritDoc} + * + *

This implementation returns the fixed {@code -ERR} prefix. + * + * @return the {@code -ERR} prefix + */ @Override public String prefix() { return "-ERR"; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/PrimitiveResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/PrimitiveResponse.java index 38a7422..1ef7a6a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/PrimitiveResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/PrimitiveResponse.java @@ -2,4 +2,11 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; +/** + * Immutable transport record representing an encoded response ready for delivery to a session. + * + * @param sessionId the target session id + * @param requestId the originating request id + * @param payload the serialized response payload + */ public record PrimitiveResponse(SessionId sessionId, int requestId, String payload) {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java index b42e7af..8a3cf96 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/Response.java @@ -2,28 +2,57 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; 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 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 body the structured response body + */ protected Response(SessionId sessionId, int requestId, ResponseBody body) { this.sessionId = sessionId; this.requestId = requestId; this.body = body; } + /** + * Returns the protocol prefix for this response (for example {@code "+OK"} or {@code "-ERR"}). + * + * @return the response prefix string used by the encoder + */ public abstract String prefix(); + /** + * Returns the session id that should receive this response. + * + * @return the target {@link SessionId} + */ public SessionId getSessionId() { return sessionId; } + /** + * Returns the request identifier associated with this response. + * + * @return the numeric request id + */ public int getRequestId() { return requestId; } + /** + * Returns the immutable {@link ResponseBody} that carries the structured payload for this + * response. + * + * @return the response body + */ public ResponseBody getBody() { - return body; + return body; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java index 868b7ee..075abaf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBlock.java @@ -2,4 +2,11 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import java.util.List; +/** + * A block node that contains a tag and a list of child {@link ResponseNode} elements. Blocks can be + * nested to build hierarchical response bodies. + * + * @param tag the block tag + * @param children the child nodes contained in this block + */ public record ResponseBlock(String tag, List children) implements ResponseNode {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java index 7e75f59..8c52ffc 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBody.java @@ -2,17 +2,38 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import java.util.List; +/** + * Immutable container for the structured content of a {@link Response}. + * + *

A {@code ResponseBody} holds an ordered list of {@link ResponseNode} items (parameters and + * blocks). Use {@link #builder()} to construct instances. + */ public class ResponseBody { private final List nodes; + /** + * Package-private constructor used by {@link ResponseBodyBuilder}. + * + * @param nodes the list of response nodes to include in this body + */ ResponseBody(List nodes) { this.nodes = List.copyOf(nodes); } + /** + * Create a new {@link ResponseBodyBuilder} for assembling a response body. + * + * @return a fresh builder instance + */ public static ResponseBodyBuilder builder() { return new ResponseBodyBuilder(); } + /** + * Returns the ordered list of {@link ResponseNode} elements contained in this body. + * + * @return an immutable list of nodes + */ public List nodes() { return nodes; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java index e8a8329..f6491b0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseBodyBuilder.java @@ -4,14 +4,35 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +/** + * Builder for {@link ResponseBody} instances. + * + *

Provides methods to append parameter nodes and nested blocks and to produce an immutable + * {@link ResponseBody} via {@link #build()}. + */ public class ResponseBodyBuilder { private final List nodes = new ArrayList<>(); + /** + * Add a key/value parameter to the response body under construction. + * + * @param key the parameter name + * @param value the parameter value (will be converted to string when encoded) + * @return this builder for fluent chaining + */ public ResponseBodyBuilder param(String key, Object value) { nodes.add(new ResponseParameter(key, value)); return this; } + /** + * Add a nested block with the given tag. The provided consumer receives a child builder to + * populate the block content. + * + * @param tag the block tag + * @param content consumer that appends child nodes to the block + * @return this builder for fluent chaining + */ public ResponseBodyBuilder block(String tag, Consumer content) { ResponseBodyBuilder childBuilder = new ResponseBodyBuilder(); content.accept(childBuilder); @@ -19,6 +40,11 @@ public class ResponseBodyBuilder { return this; } + /** + * Build an immutable {@link ResponseBody} from the accumulated nodes. + * + * @return a new {@link ResponseBody} + */ public ResponseBody build() { return new ResponseBody(nodes); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java index 184cd78..57b9181 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseDispatcher.java @@ -3,13 +3,31 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; +/** + * Helper that dispatches {@link Response} instances to the corresponding {@link Session} by + * encoding them and enqueuing the resulting {@link PrimitiveResponse} into the session's response + * queue. + */ public class ResponseDispatcher { private final SessionManager sessionManager; + /** + * Create a dispatcher bound to a {@link SessionManager}. + * + * @param sessionManager manager used to resolve sessions + */ public ResponseDispatcher(SessionManager sessionManager) { this.sessionManager = sessionManager; } + /** + * Encode the given {@link Response} and enqueue the resulting {@link PrimitiveResponse} into + * 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 + */ public void dispatch(Response response) throws InterruptedException { PrimitiveResponse primitiveResponse = ResponseEncoder.encode(response); Session session = sessionManager.getSessionById(response.getSessionId()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java index bc66aad..dbe8d12 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseEncoder.java @@ -1,9 +1,20 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; +/** + * Utility responsible for encoding a {@link Response} into a protocol payload string and wrapping + * it into a {@link PrimitiveResponse} suitable for transmission. + */ public class ResponseEncoder { private static final String INDENT = "\t"; private static final String NEWLINE = "\n"; + /** + * Encode a {@link Response} into a {@link PrimitiveResponse} containing the serialized payload + * string. + * + * @param response the response to encode + * @return a {@link PrimitiveResponse} with encoded payload + */ public static PrimitiveResponse encode(Response response) { StringBuilder sb = new StringBuilder(); sb.append(response.prefix()); @@ -19,6 +30,13 @@ public class ResponseEncoder { response.getSessionId(), response.getRequestId(), sb.toString()); } + /** + * Internal helper to encode any {@link ResponseNode}. + * + * @param node node to encode + * @param sb string builder to append to + * @param depth current indentation depth + */ private static void encodeNode(ResponseNode node, StringBuilder sb, int depth) { if (node instanceof ResponseParameter param) { encodeParameter(param, sb, depth); @@ -27,6 +45,13 @@ public class ResponseEncoder { } } + /** + * Encode a {@link ResponseParameter} into the string builder. + * + * @param param the parameter to encode + * @param sb the output builder + * @param depth indentation depth + */ private static void encodeParameter(ResponseParameter param, StringBuilder sb, int depth) { sb.append(INDENT.repeat(depth)); sb.append(param.key()); @@ -34,6 +59,14 @@ public class ResponseEncoder { sb.append(maskIfNeeded(param.value().toString())); } + /** + * Encode a {@link ResponseBlock}, including its children and terminating with an {@code END} + * marker. + * + * @param block the block to encode + * @param sb the output builder + * @param depth current indentation depth + */ private static void encodeBlock(ResponseBlock block, StringBuilder sb, int depth) { sb.append(INDENT.repeat(depth)); sb.append(block.tag()); @@ -48,6 +81,13 @@ public class ResponseEncoder { sb.append("END"); } + /** + * Quote or escape the provided value if it contains whitespace or single quotes so the encoded + * payload remains parseable. + * + * @param value the raw string value + * @return quoted/escaped value + */ private static String maskIfNeeded(String value) { if (value.contains(" ") || value.contains("'")) { String escaped = value.replace("'", "\\'"); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java index ea22d0a..c3af815 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseNode.java @@ -1,3 +1,8 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; +/** + * Marker interface for elements that may appear in a {@link ResponseBody}. + * + *

Implementations include {@link ResponseParameter} and {@link ResponseBlock}. + */ public interface ResponseNode {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java index 4d04293..87f599a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/ResponseParameter.java @@ -1,6 +1,22 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; +/** + * A parameter node stored in a {@link ResponseBody}. + * + *

Represents a simple key/value pair. Callers can use {@link #rawValue()} to obtain the string + * representation of the stored value. + * + * @param key the parameter name + * @param value the parameter value + */ public record ResponseParameter(String key, Object value) implements ResponseNode { + /** + * Returns the raw string representation of the stored value. This is a convenience wrapper + * around {@code Object#toString()} and may throw {@link NullPointerException} if the stored + * value is {@code null}. + * + * @return the value as string + */ public String rawValue() { return value.toString(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java index dae5308..e88913f 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/SuccessResponse.java @@ -2,11 +2,31 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.response; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; +/** + * Abstract {@link Response} specialization indicating a successful outcome. + * + *

Implementations of this class use the {@code +OK} prefix. It provides a protected constructor + * so subclasses can supply the response body content. + */ 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 body the response body + */ protected SuccessResponse(SessionId sessionId, int requestId, ResponseBody body) { super(sessionId, requestId, body); } + /** + * {@inheritDoc} + * + *

This implementation returns the fixed {@code +OK} prefix. + * + * @return the {@code +OK} prefix + */ @Override public final String prefix() { return "+OK"; -- 2.52.0 From 9766fe52c5e429042a27d1979843af3a25ed6e03 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 24 Mar 2026 14:34:59 +0100 Subject: [PATCH 11/11] Add: OkResponse class as default for successful response with no body --- .../server/network/response/OkResponse.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/OkResponse.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/OkResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/OkResponse.java new file mode 100644 index 0000000..a3f26c5 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/response/OkResponse.java @@ -0,0 +1,20 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.response; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; + +/** + * A simple success response with an empty body. + * + *

Use this to acknowledge successful requests that do not carry additional payload data. + */ +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 + */ + public OkResponse(SessionId sessionId, int requestId) { + super(sessionId, requestId, ResponseBody.builder().build()); + } +} -- 2.52.0