Create components to respond to client request #200
+22
-6
@@ -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}
|
||||
*
|
||||
* <p>This implementation returns the fixed {@code -ERR} prefix.
|
||||
*
|
||||
* @return the {@code -ERR} prefix
|
||||
*/
|
||||
@Override
|
||||
public String prefix() {
|
||||
return "-ERR";
|
||||
|
||||
+7
@@ -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) {}
|
||||
|
||||
+30
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -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<ResponseNode> children) implements ResponseNode {}
|
||||
|
||||
+21
@@ -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}.
|
||||
*
|
||||
* <p>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<ResponseNode> nodes;
|
||||
|
||||
/**
|
||||
* Package-private constructor used by {@link ResponseBodyBuilder}.
|
||||
*
|
||||
* @param nodes the list of response nodes to include in this body
|
||||
*/
|
||||
ResponseBody(List<ResponseNode> 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<ResponseNode> nodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
+26
@@ -4,14 +4,35 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Builder for {@link ResponseBody} instances.
|
||||
*
|
||||
* <p>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<ResponseNode> 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<ResponseBodyBuilder> 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);
|
||||
}
|
||||
|
||||
+18
@@ -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());
|
||||
|
||||
+40
@@ -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("'", "\\'");
|
||||
|
||||
+5
@@ -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}.
|
||||
*
|
||||
* <p>Implementations include {@link ResponseParameter} and {@link ResponseBlock}.
|
||||
*/
|
||||
public interface ResponseNode {}
|
||||
|
||||
+16
@@ -1,6 +1,22 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
/**
|
||||
* A parameter node stored in a {@link ResponseBody}.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
||||
|
||||
+20
@@ -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.
|
||||
*
|
||||
* <p>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}
|
||||
*
|
||||
* <p>This implementation returns the fixed {@code +OK} prefix.
|
||||
*
|
||||
* @return the {@code +OK} prefix
|
||||
*/
|
||||
@Override
|
||||
public final String prefix() {
|
||||
return "+OK";
|
||||
|
||||
Reference in New Issue
Block a user