Class ServerPropertiesManager
Why a panel edit cannot overwrite operator content (maintainer decisions of 2026-10-04, "operator-written
configuration is never overwritten" and "what code may write, by file type": a panel edit writes only the named
key; UltiKits/UltiTools-Reborn#607). The file is never re-emitted: an edit replaces the value text of the one
physical line that defines the key and nothing else - the header and operator comments, key order, the key text
and separator as written, line terminators and every other value keep their bytes. The file is decoded the way
the running server reads it (measured from the Paper 1.21.11 jar, Settings#loadFromFile: strict UTF-8,
falling back to ISO-8859-1 when the file is not valid UTF-8), the new value is escaped with the
Properties value grammar in a form that reader returns exactly, and the result is re-encoded in the
charset it was decoded with. Before publishing, every other line is checked byte for byte and a fresh parse must
return the new value and every other key unchanged; a key defined on more than one line, or over continuation
lines, is refused with a reason. Publication is atomic through AtomicConfigWriter, and only while the file
still holds the bytes the edit was made against. A refusal writes nothing and logs one WARNING naming the file,
the key and the reason, never a value.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOne physical line that defines a key, located with thePropertiesgrammar.static final classOne typed read ofserver.propertiesfor the protocol 2.0.0 panel, as returned byreadVersioned(int).private static final classThe file's bytes and its text as the server decodes them: strict UTF-8, or ISO-8859-1 when the bytes are not valid UTF-8 (Paper 1.21.11Settings#loadFromFile, measured from the server jar).static final classThe per-key result ofupdateWithBaseVersion(String, Map).static final classWhat happened to one key of a protocol 2.0.0 properties update.static final classOutcome of oneset_allbatch.private static enumWhat actually happened to one key.private static final classAn outcome, and for a refusal the reason (no value). -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Stringprivate static final Loggerprivate final FileThe keys the frozen protocol 2.0.0 contract lets a panel read, in contract order.The keys a remote update may change: the contract's twelve.private UltiPanelWebSocketClientprivate static final ObjectSerializes panel edits of the file within this server run. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionapplySetAll(com.google.gson.JsonObject values) Apply a batch of properties, report it over the socket, and hand the outcome back.private com.google.gson.JsonObjectprivate static List<byte[]> byteLines(byte[] bytes) Splits bytes into lines, each with its terminator (LF, CR or CRLF).private static StringOne line naming why a single-key write did not happen, ornullwhen itdid.private static booleanprivate static StringProperties#saveConvertfor a value: a leading space,\,= : # !and the four control escapes are escaped; any other control character, a lone surrogate, and - in a file the server reads as ISO-8859-1 - every character above~is written as\uXXXX, which the reader decodes in both charsets.The whitelisted keys the file defines, decoded the way the server reads them (see the class description).private voidvoidhandleServerProperties(com.google.gson.JsonObject data) Deprecated, for removal: This API element is subject to removal in a future version.Use readVersioned(int) and updateWithBaseVersion(String, Map), which the protocol 2.0.0 session calls; this v1 handler replies only over the client handed to setWebSocketClient.private voidhandleSet(com.google.gson.JsonObject data) private voidhandleSetAll(com.google.gson.JsonObject data) private static booleanisEditableContent(byte[] bytes, ServerPropertiesManager.PropertiesText text, int maxContentBytes) The contract's RUNTIME_CHECKSfile-contentrule overserver.properties: within the connection's limit, valid UTF-8 (the manager falls back to ISO-8859-1 for the server's own reading, but the panel only ever writes UTF-8) and no C0 control character other than TAB, LF and CR, and no DEL.private static booleanisWhitespace(char c) private static StringlineNumbers(List<ServerPropertiesManager.Definition> definitions) readVersioned(int maxContentBytes) Readsserver.propertiesfor the protocol 2.0.0 panel: the fourteen readable keys that are present and parse to their contract type, the writable key set, and the version a later update must send back as itsbaseVersion.private static ServerPropertiesManager.WriteResultprivate voidsendResponse(com.google.gson.JsonObject response) booleansetProperty(String key, String value) voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework never calls it, and its own UltiCloud session (protocol 2.0.0) carries the panel requests.private static int[]splitKey(CharSequence line) Where the key ends and the value starts in one logical line, exactly asProperties#load0decides: the key ends at the first unescaped=,:or whitespace; then whitespace, at most one=or:, and whitespace are skipped.private static com.google.gson.JsonArraytoJsonArray(List<String> keys) private static StringProperties#loadConvert:\uXXXX,\t \n \r \f, and\xfor any other x.updateWithBaseVersion(String baseVersion, Map<String, String> changes) Applies a protocol 2.0.0 properties update: for each requested key, replaces the value text of the physical line that defines it and nothing else, and only whileserver.propertiesstill hashes tobaseVersion.private static StringversionOf(byte[] bytes) private voidThe response above is what the panel reads; this is what a server operator reads.The keys a remote update may change: the contract's twelve.writeProperty(String key, String value) Why this exists next tosetProperty(String, String): the boolean is lossy.
-
Field Details
-
LOGGER
-
WRITE_LOCK
Serializes panel edits of the file within this server run. -
FILE_NAME
- See Also:
-
serverRoot
-
webSocketClient
-
SAFE_KEYS
-
V2_READABLE_KEYS
The keys the frozen protocol 2.0.0 contract lets a panel read, in contract order.SAFE_KEYSstays its own set: this list decides whatreadVersioned(int)reports, not what the v1 write path accepts. -
V2_WRITABLE_KEYS
The keys a remote update may change: the contract's twelve. It is deliberately independent ofSAFE_KEYS, which still contains the v1-onlyspawn-animalsandspawn-npcs.online-modeandwhite-listmust never join it: turning server authentication or the whitelist off from the panel is an elevation of privilege the operator has not granted (maintainer decision, user decision 15), so those two keys are readable and never writable.
-
-
Constructor Details
-
ServerPropertiesManager
-
-
Method Details
-
setWebSocketClient
@Deprecated(since="6.3.1", forRemoval=true) public void setWebSocketClient(UltiPanelWebSocketClient client) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework never calls it, and its own UltiCloud session (protocol 2.0.0) carries the panel requests. Removed in 6.4.0.Sets the protocol v1 client the v1 senders of this manager reply over.- Scheduled removal version:
- 6.4.0
-
getSafeProperties
The whitelisted keys the file defines, decoded the way the server reads them (see the class description).- Returns:
- key to value; empty when the file is absent, unreadable or unparseable
-
setProperty
-
writableKeys
The keys a remote update may change: the contract's twelve.- Returns:
- an unmodifiable list of the writable keys, in contract order
-
readVersioned
Readsserver.propertiesfor the protocol 2.0.0 panel: the fourteen readable keys that are present and parse to their contract type, the writable key set, and the version a later update must send back as itsbaseVersion.The version is the SHA-256 of exactly the bytes this call read, so the values and the version always describe one file state; a key whose value does not parse to its type is left out rather than reported with a wrong type. The contract's RUNTIME_CHECKS
file-contentrule applies here too — the file must be valid UTF-8, within the connection's limit and free of C0 control characters other than TAB, LF and CR — because the panel reads this file to write it back.- Parameters:
maxContentBytes- the connection's negotiatedfileMaxBytes- Returns:
- the snapshot, or which refusal prevented one
-
updateWithBaseVersion
public ServerPropertiesManager.PropertiesUpdateResult updateWithBaseVersion(String baseVersion, Map<String, String> changes) Applies a protocol 2.0.0 properties update: for each requested key, replaces the value text of the physical line that defines it and nothing else, and only whileserver.propertiesstill hashes tobaseVersion.Why this path cannot overwrite operator content (configuration-layer rule): the version is computed from the bytes read inside
WRITE_LOCK, the comparison happens there too, and publication goes throughAtomicConfigWriter, which re-reads the file under its own lock and replaces it only while it still holds those bytes. A panel edit therefore lands only when the operator is writing back the very content the panel read; any other state answersServerPropertiesManager.PropertiesUpdateResult.Outcome.FILE_CHANGEDand leaves every byte in place. Keys outside the contract's twelve are refused per key even when a caller reaches this method directly — that refusal is the defence in depth behind the strict payload schema, soonline-modeandwhite-listcannot be changed remotely by any route.- Parameters:
baseVersion- the SHA-256 the panel read, or null to have the update refused as changedchanges- the requested keys and their new values, already in their text form- Returns:
- the per-key outcome, the version now on disk and, for a refusal, its reason
-
isEditableContent
private static boolean isEditableContent(byte[] bytes, ServerPropertiesManager.PropertiesText text, int maxContentBytes) The contract's RUNTIME_CHECKSfile-contentrule overserver.properties: within the connection's limit, valid UTF-8 (the manager falls back to ISO-8859-1 for the server's own reading, but the panel only ever writes UTF-8) and no C0 control character other than TAB, LF and CR, and no DEL. -
versionOf
-
writeProperty
Why this exists next tosetProperty(String, String): the boolean is lossy. Afalsecould mean "the key is not on the whitelist", "the running server server's server.properties does not have the key", "there is no server.properties to write to", or "the write itself failed" — four situations with four different fixes, collapsed into one value. The batch path has to tell the caller which one happened, so the real outcome is produced here andsetPropertykeeps its original contract by narrowing it.The write changes only the line defining
key(see the class description for why nothing else can change); a refusal carries its reason. -
refuse
-
lineNumbers
-
isWhitespace
private static boolean isWhitespace(char c) -
endsWithOddBackslashes
-
splitKey
Where the key ends and the value starts in one logical line, exactly asProperties#load0decides: the key ends at the first unescaped=,:or whitespace; then whitespace, at most one=or:, and whitespace are skipped.- Returns:
[keyLength, valueStart]
-
unescape
Properties#loadConvert:\uXXXX,\t \n \r \f, and\xfor any other x. -
escape
Properties#saveConvertfor a value: a leading space,\,= : # !and the four control escapes are escaped; any other control character, a lone surrogate, and - in a file the server reads as ISO-8859-1 - every character above~is written as\uXXXX, which the reader decodes in both charsets. In a UTF-8 file other characters are written as they are, as the server itself writes them. -
byteLines
Splits bytes into lines, each with its terminator (LF, CR or CRLF). -
describeOutcome
One line naming why a single-key write did not happen, ornullwhen itdid. Exposed throughhandleSet(JsonObject)'s response so a panel operator is told the reason, not justsuccess: false. -
handleServerProperties
@Deprecated(since="6.3.1", forRemoval=true) public void handleServerProperties(com.google.gson.JsonObject data) Deprecated, for removal: This API element is subject to removal in a future version.Use readVersioned(int) and updateWithBaseVersion(String, Map), which the protocol 2.0.0 session calls; this v1 handler replies only over the client handed to setWebSocketClient. Removed in 6.4.0.Handle WebSocket message for server properties operations.- Parameters:
data- the message data- Scheduled removal version:
- 6.4.0
-
handleGet
private void handleGet() -
handleSet
private void handleSet(com.google.gson.JsonObject data) -
handleSetAll
private void handleSetAll(com.google.gson.JsonObject data) -
applySetAll
Apply a batch of properties, report it over the socket, and hand the outcome back.The return value is the point.
handleSetAlldoes not need it — it only ever builds a message — butupdate_configwithfileName: server_propertiesroutes through here too, and itsconfig_update_responseis supposed to distinguish "delivered" from "took effect". Before this returned anything, that path had no way to learn the answer and reported success unconditionally.- Parameters:
values- key → value, an explicit JSON null meaning "leave this key alone"- Returns:
- what happened to each key
-
buildSetAllResponse
-
toJsonArray
-
warnIfIncomplete
The response above is what the panel reads; this is what a server operator reads. Both are needed: someone changing a setting that silently does not apply otherwise finds nothing on either side. -
sendResponse
private void sendResponse(com.google.gson.JsonObject response)
-