Merge branch 'feat/serverside-response' into 'main'
Create components to respond to client request Closes #8 See merge request cs108-fs26/Gruppe-13!44
This commit was merged in pull request #200.
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
|||||||
|
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 {
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
*
|
||||||
|
* <p>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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+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;
|
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) {}
|
public record PrimitiveResponse(SessionId sessionId, int requestId, String payload) {}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
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 {}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||||
|
|
||||||
|
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);
|
||||||
|
nodes.add(new ResponseBlock(tag, childBuilder.build().nodes()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an immutable {@link ResponseBody} from the accumulated nodes.
|
||||||
|
*
|
||||||
|
* @return a new {@link ResponseBody}
|
||||||
|
*/
|
||||||
|
public ResponseBody build() {
|
||||||
|
return new ResponseBody(nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
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());
|
||||||
|
session.getResponseQueue().put(primitiveResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
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());
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
} else if (node instanceof ResponseBlock block) {
|
||||||
|
encodeBlock(block, sb, depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
sb.append("=");
|
||||||
|
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());
|
||||||
|
|
||||||
|
for (ResponseNode child : block.children()) {
|
||||||
|
sb.append(NEWLINE);
|
||||||
|
encodeNode(child, sb, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(NEWLINE);
|
||||||
|
sb.append(INDENT.repeat(depth));
|
||||||
|
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("'", "\\'");
|
||||||
|
return "'" + escaped + "'";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +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 {}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
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