Class RemotePropertiesHandler
- All Implemented Interfaces:
CloudRequestHandler
properties.get and properties.update
requests for one connection; internal, not for module use.
This class carries the contract's type knowledge — which of the fourteen readable keys is a
string, an integer within which bounds, a boolean, or one of the difficulty and gamemode
spellings — and ServerPropertiesManager carries the file knowledge: the line-preserving
rewrite, the write lock and the version comparison. Splitting it that way keeps the frozen
contract's vocabulary out of the manager and the file's grammar out of the handler.
Authentication and the whitelist are never remotely writable. The strict payload shape
rejects any key outside the contract's twelve with invalid_message before the manager is
reached, and the manager refuses the same keys again per key if a future caller bypasses the
payload check. Both paths are pinned by tests (maintainer decision: user decision 15).
Every accepted and refused update is recorded. One RemoteActionLog entry per
properties.update request that reached a decision: ALLOWED when the update was applied
(fully or partially), DENIED with exactly the text the panel received otherwise. The entry's
target is the sorted list of key names the request named — never a value — because the audit
trail must say which settings were touched without copying server configuration into a log. The
log is injected; plan 22-21 binds it to the live RemoteActionLog.
Every read is recorded too, as 6.3.0 recorded its server_properties read: one
entry per properties.get, ALLOWED with the target server.properties when the
values were sent, DENIED with the text the panel received otherwise. Values are never
recorded. From the first step of the disable a get is still answered but no longer recorded:
the disable closes the action log, and a read changes nothing.
Per-key results follow the contract: ok when every requested key was written,
partial when at least one was refused or is absent from the file while the others were
written, and error with file_changed, not_found or write_denied
when nothing could be written.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOne key's contract type: its kind and, for numbers and enumerations, its bounds.private static enumThe JSON shape one property key's value takes. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Consumer<RemoteActionLog.Entry> private static final StringThe action-log target of aproperties.get: the file it reads, since the request names no key.private static final Loggerprivate static final intThe contract's upper bound on a result message, in code points.private static final intThe contract's upper bound onmotd, and on every other string-typed property value.private static final StringThe action-log actor for this surface: protocol v2 carries no per-operator identity.private final ServerPropertiesManagerprivate static final Stringprivate static final Stringprivate static final Stringprivate static final Stringprivate static final Map<String, RemotePropertiesHandler.KeySpec> What each key's value looks like in the frozen contract'sproperties.getschema.private final BooleanSupplier -
Constructor Summary
ConstructorsConstructorDescriptionRemotePropertiesHandler(ServerPropertiesManager properties, Consumer<RemoteActionLog.Entry> actionLog) Creates the handler over the manager that owns the file.RemotePropertiesHandler(ServerPropertiesManager properties, Consumer<RemoteActionLog.Entry> actionLog, BooleanSupplier readsAudited) Creates the handler with an explicit answer to "are reads recorded now". -
Method Summary
Modifier and TypeMethodDescriptionprivate static voidprivate voidauditAllowed(String action, String target) private static StringThe refusal text as the panel receives it: never empty and never above the contract's cap.private static voidSends a correlated error for one result type, capped at the contract's 500 code points.private voidget(CloudRequest request, CloudChannel channel) Answersproperties.get.voidhandle(CloudRequest request, CloudChannel channel) Validates the payload and handles it on the dispatching thread.private static StringkeyNames(com.google.gson.JsonObject changes) The audit target of one update: the key names it named, sorted, joined by commas.private static Map<String, RemotePropertiesHandler.KeySpec> The contract's type for each of the fourteen readable keys, in contract order.private voidrecord(RemoteActionLog.Entry entry) Hands one entry to the action log.private voidrefuseGet(CloudChannel channel, String replyTo, String code, String message) Answers a refusedproperties.getand records the same text, asrefuseUpdate(com.ultikits.ultitools.websocket.CloudChannel, java.lang.String, java.lang.String, java.lang.String, java.lang.String)does.private voidrefuseUpdate(CloudChannel channel, String replyTo, String target, String code, String message) Answers a refusedproperties.updateand records the same text in the action log, so the log says what the operator was told.private static StringtextValue(String id, RemotePropertiesHandler.KeySpec spec, String key, com.google.gson.JsonElement value) Validates one requested value against its key's contract type and returns the text to write.private static ObjecttypedValue(RemotePropertiesHandler.KeySpec spec, String text) Converts one rawserver.propertiesvalue to the JSON type its key carries.private voidupdate(CloudRequest request, CloudChannel channel) Answersproperties.update.
-
Field Details
-
LOGGER
-
PROPERTIES_GET
- See Also:
-
PROPERTIES_GET_RESULT
- See Also:
-
PROPERTIES_UPDATE
- See Also:
-
PROPERTIES_UPDATE_RESULT
- See Also:
-
MAX_MESSAGE_LENGTH
private static final int MAX_MESSAGE_LENGTHThe contract's upper bound on a result message, in code points.- See Also:
-
PANEL_ACTOR
The action-log actor for this surface: protocol v2 carries no per-operator identity.- See Also:
-
GET_TARGET
The action-log target of aproperties.get: the file it reads, since the request names no key.- See Also:
-
MAX_TEXT_LENGTH
private static final int MAX_TEXT_LENGTHThe contract's upper bound onmotd, and on every other string-typed property value.- See Also:
-
READABLE
What each key's value looks like in the frozen contract'sproperties.getschema. -
properties
-
actionLog
-
readsAudited
-
-
Constructor Details
-
RemotePropertiesHandler
RemotePropertiesHandler(ServerPropertiesManager properties, Consumer<RemoteActionLog.Entry> actionLog) Creates the handler over the manager that owns the file.- Parameters:
properties- the manager whose read and update operations this handler exposesactionLog- receives one entry per decided request, ornullwhen the caller has no action log to write to (tests and headless harnesses)
-
RemotePropertiesHandler
RemotePropertiesHandler(ServerPropertiesManager properties, Consumer<RemoteActionLog.Entry> actionLog, BooleanSupplier readsAudited) Creates the handler with an explicit answer to "are reads recorded now".- Parameters:
properties- the manager whose read and update operations this handler exposesactionLog- as forRemotePropertiesHandler(ServerPropertiesManager, Consumer)readsAudited- whether aproperties.getis recorded; production asksPluginInitiationUtils.isAcceptingPanelMessages(), so from the first step of the disable a get is still answered but writes no line to the closing action log
-
-
Method Details
-
handle
Description copied from interface:CloudRequestHandlerValidates the payload and handles it on the dispatching thread. No Bukkit main-thread scheduling is implied; handlers must schedule such work themselves. Synchronous payload validation failures become correlated invalid_message replies; other runtime failures become internal_error without exposing exception text. Asynchronous failures are the handler's responsibility.- Specified by:
handlein interfaceCloudRequestHandler- Parameters:
request- the parsed envelope with a defensively copied payloadchannel- the reply channel, which may close during processing
-
get
Answersproperties.get. The payload has no fields, so any field at all isinvalid_message; the reply carries the typed values, the writable key set (which is where the panel learns thatonline-modeandwhite-listare read-only) and the version of the bytes the values were read from. -
update
Answersproperties.update. The payload is validated against the frozen schema first — only the contract's twelve keys, each with its own type and bounds — so an unwritable key or a string where a number belongs never reaches the file. -
typedValue
Converts one rawserver.propertiesvalue to the JSON type its key carries.- Parameters:
spec- the key's contract typetext- the value as the file holds it- Returns:
- the typed value, or
nullwhen the text does not parse to that type
-
textValue
private static String textValue(String id, RemotePropertiesHandler.KeySpec spec, String key, com.google.gson.JsonElement value) Validates one requested value against its key's contract type and returns the text to write. Validation goes throughPayloadReader, so a mismatch leaves as the router'sinvalid_messagerather than reaching the file.- Parameters:
id- the request id for the validation errorspec- the key's contract typekey- the key being changedvalue- the JSON value the panel sent- Returns:
- the value in the form
server.propertiesstores
-
addTyped
-
readableSpecs
The contract's type for each of the fourteen readable keys, in contract order. The bounds and the two enumerations are copied from the vendoredproperties.getandproperties.updateschemas.- Returns:
- an unmodifiable key to type map
-
error
private static void error(CloudChannel channel, String replyTo, String type, String code, String message) Sends a correlated error for one result type, capped at the contract's 500 code points. -
refuseUpdate
private void refuseUpdate(CloudChannel channel, String replyTo, String target, String code, String message) Answers a refusedproperties.updateand records the same text in the action log, so the log says what the operator was told. -
refuseGet
Answers a refusedproperties.getand records the same text, asrefuseUpdate(com.ultikits.ultitools.websocket.CloudChannel, java.lang.String, java.lang.String, java.lang.String, java.lang.String)does. The entry is written before the reply, so whoever sees the answer also finds the line. -
auditAllowed
-
record
Hands one entry to the action log. A missing sink is a silent no-op, and a sink that throws must not cost the panel its reply — the update itself has already been decided. -
keyNames
The audit target of one update: the key names it named, sorted, joined by commas. Sorted so two requests naming the same keys produce the same target regardless of payload order, and names only because a property value must never be copied into the action log.- Parameters:
changes- the requested changes- Returns:
- the comma-joined key names
-
capped
The refusal text as the panel receives it: never empty and never above the contract's cap.
-