Class RemoteConfigHandler
Why this cannot overwrite operator content. Nothing here edits a file by rendering a new
whole document. A write names the paths it owns (OwnedPaths) and goes through
OperatorFileWriter, which re-reads the file at write time, applies only those settings,
renders the document from its own node tree and then refuses the write unless every line outside
the owned settings - comments, order, quoting, other values - comes out byte-identical. The panel's
baseVersion is the SHA-256 of the raw bytes it read; the same value is handed to the gate as
the expected fingerprint, so an operator's edit made after the read answers file_changed and
nothing is written. A file the operator marked read-only is refused before anything is staged.
The only write path here is an explicit panel edit of the named keys, guarded by that version;
nothing in this class writes at start-up, at shutdown, on a reload or on a timer. The values are
checked per key before the write - the entry's declared type and its @Range bounds here, and
the entity's own validators (@Range, @NotEmpty, @Size, @Pattern and
its declared-type converters) through AbstractConfigEntity.validateProposedProperties(com.google.gson.JsonObject) - so a
refused key never reaches the file and the accepted ones are written in one verified replace.
Every edit is recorded. As the 6.3.0 configuration edit did, each config.update that
reached a decision writes one action-log entry (capability FILE_WRITE, action config.update,
the request's path as target, panel as actor): ALLOWED when at least one key was written, DENIED with
the text the panel received when nothing was. Values are never recorded. config.tree and
config.entries record nothing; a request the switch gate refused is recorded by the gate.
All file work runs on the thread the caller injects, which must be the server thread: the
configuration layer refuses registry access anywhere else
(ConfigManager.permitsConfigThread(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.String)). Payload validation runs on the dispatching thread so a
malformed request still becomes the router's correlated invalid_message.
Known limitation (niche case, documented rather than defended): a writer outside the framework that replaces the file between the gate's last read and its atomic rename is not detected; the gate's own re-read immediately before publishing is what narrows that window.
- Since:
- 6.3.1
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOne change accepted for the write, with the entry that types it.private static final classOne panel change, already shape-checked.private static final classOne row ofconfig.update.result's per-key array.private static final classprivate final classThe one action-log entry of oneconfig.updaterequest (6.3.0 recorded every panel configuration edit): ALLOWED when at least one key was written to the file, DENIED with the text the panel received when nothing was written. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Consumer<RemoteActionLog.Entry> private static final intconfig.updatebounds.private static final Stringprivate final ConfigManagerprivate final Executorprivate static final intconfig.entries.resultcarries at most this many entries.private static final StringOne monitor per configuration file, so a read-hash-compare-write of a file never interleaves with another.private static final Stringprivate static final intprivate static final intprivate static final intprivate static final Loggerprivate static final intprivate final Supplier<List<UltiToolsPlugin>> private static final Stringprivate static final Stringprivate static final StringThe action-log actor for this surface: protocol v2 carries no per-operator identity.private static final Stringprivate final Fileprivate static final intprivate static final intprivate static final intconfig.tree.resultbounds.private static final Stringprivate static final String -
Constructor Summary
ConstructorsConstructorDescriptionRemoteConfigHandler(Supplier<List<UltiToolsPlugin>> modules, ConfigManager configs, Executor configThread) Creates the handler that publishes paths relative to the process working directory, the root the remote file surface already uses.RemoteConfigHandler(Supplier<List<UltiToolsPlugin>> modules, ConfigManager configs, Executor configThread, File serverRoot) Creates the handler with an explicit server root; tests only, since the panel's paths are the paths a server operator would see under the working directory.RemoteConfigHandler(Supplier<List<UltiToolsPlugin>> modules, ConfigManager configs, Executor configThread, File serverRoot, Consumer<RemoteActionLog.Entry> actionLog) Creates the handler with an explicit server root and the action log that receives one entry perconfig.updaterequest. -
Method Summary
Modifier and TypeMethodDescriptionprivate static Stringprivate static List<RemoteConfigHandler.Change> Validates every change item against the frozenconfig.updateschema shapes and copies it.private StringentityRefusal(AbstractConfigEntity entity, RemoteConfigHandler.Change change, RemoteConfigEntries.Entry entry) The entity's own check of one change: it applies the value to the declared field in memory, runs the same validators and converters a load runs, and restores every field before returning.private voidentries(CloudChannel channel, String replyTo, String path) private voidfailed(CloudChannel channel, String replyTo, String type, RuntimeException failure) private voidhandleEntries(CloudRequest request, CloudChannel channel) Answers one file's typed entries and the SHA-256 of its current bytes.private voidhandleTree(CloudRequest request, CloudChannel channel) Answers the configuration file tree: each loaded module with the files it registered.private voidhandleUpdate(CloudRequest request, CloudChannel channel) Writes exactly the keys a panel edit names, and only while the file still holds the bytes it read.private static InvalidFrameExceptionprivate static com.google.gson.JsonObjectkeysData(List<RemoteConfigHandler.KeyResult> results, String version) private static Objectprivate RemoteConfigHandler.Registeredprivate Map<String, UltiToolsPlugin> private static StringnothingWritten(List<RemoteConfigHandler.KeyResult> results) The action-log reason of an update whose every key was refused: the key names with the per-key messages the panel received.private voidonConfigThread(CloudChannel channel, String replyTo, String type, Runnable work) private static OwnedPathsowned(List<RemoteConfigHandler.Accepted> accepted) private StringpublishedPath(UltiToolsPlugin module, String configFilePath) The panel's path of one registered configuration file: the file's location relative to the server root, with/separators, as the contract's path primitive requires.private static StringrefusalText(ConfigurationException refusal) private static voidrefuseUpdate(CloudChannel channel, String replyTo, RemoteConfigHandler.UpdateAudit audit, String code, String message) Answers a refusedconfig.updateand records the same text as its one DENIED action-log entry, so the log says what the operator was told.(package private) voidregister(AgentMessageRouter router) Registers the three request types on the protocol router.private Stringreload(RemoteConfigHandler.Registered registered) Makes the module take the values just written, by reloading the one entity that owns the written file.private com.google.gson.JsonElementtree()private voidupdate(CloudChannel channel, String replyTo, String path, String baseVersion, List<RemoteConfigHandler.Change> changes, RemoteConfigHandler.UpdateAudit audit) private static com.google.gson.JsonElementprivate voidwrite(CloudChannel channel, String replyTo, RemoteConfigHandler.Registered registered, ConfigLoadResult loaded, List<RemoteConfigHandler.Change> changes, RemoteConfigHandler.UpdateAudit audit) private static StringwriteDenied(String reason) The panel's explanation of a write the gate refused: the gate's own reason, which names the line an operator has to fix and never a value.
-
Field Details
-
LOGGER
-
FILE_LOCKS
One monitor per configuration file, so a read-hash-compare-write of a file never interleaves with another. -
ENTRIES_MAX
private static final int ENTRIES_MAXconfig.entries.resultcarries at most this many entries.- See Also:
-
TREE_PLUGINS_MAX
private static final int TREE_PLUGINS_MAXconfig.tree.resultbounds.- See Also:
-
TREE_FILES_MAX
private static final int TREE_FILES_MAX- See Also:
-
CHANGES_MAX
private static final int CHANGES_MAXconfig.updatebounds.- See Also:
-
KEY_MAX
private static final int KEY_MAX- See Also:
-
TEXT_MAX
private static final int TEXT_MAX- See Also:
-
LIST_MAX
private static final int LIST_MAX- See Also:
-
LIST_ITEM_MAX
private static final int LIST_ITEM_MAX- See Also:
-
MESSAGE_MAX
private static final int MESSAGE_MAX- See Also:
-
PATH_NOT_ALLOWED
- See Also:
-
NOT_FOUND
- See Also:
-
CONFIG_INVALID
- See Also:
-
UNREADABLE
- See Also:
-
FILE_CHANGED
- See Also:
-
WRITE_DENIED
- See Also:
-
NOT_RELOADED
- See Also:
-
INTERNAL
- See Also:
-
PANEL_ACTOR
The action-log actor for this surface: protocol v2 carries no per-operator identity.- See Also:
-
modules
-
configs
-
configThread
-
serverRoot
-
actionLog
-
-
Constructor Details
-
RemoteConfigHandler
RemoteConfigHandler(Supplier<List<UltiToolsPlugin>> modules, ConfigManager configs, Executor configThread) Creates the handler that publishes paths relative to the process working directory, the root the remote file surface already uses.- Parameters:
modules- the loaded modules whose registered configuration files may be shownconfigs- the configuration registry, for listing registered files and reloading a moduleconfigThread- the executor that must run the server thread
-
RemoteConfigHandler
RemoteConfigHandler(Supplier<List<UltiToolsPlugin>> modules, ConfigManager configs, Executor configThread, File serverRoot) Creates the handler with an explicit server root; tests only, since the panel's paths are the paths a server operator would see under the working directory.- Parameters:
modules- the loaded modules whose registered configuration files may be shownconfigs- the configuration registryconfigThread- the executor that must run the server threadserverRoot- the directory every published path is relative to
-
RemoteConfigHandler
RemoteConfigHandler(Supplier<List<UltiToolsPlugin>> modules, ConfigManager configs, Executor configThread, File serverRoot, Consumer<RemoteActionLog.Entry> actionLog) Creates the handler with an explicit server root and the action log that receives one entry perconfig.updaterequest.- Parameters:
modules- the loaded modules whose registered configuration files may be shownconfigs- the configuration registryconfigThread- the executor that must run the server threadserverRoot- the directory every published path is relative toactionLog- receives one entry perconfig.updaterequest that reached a decision, ornullwhen the caller has no action log to write to (tests)
-
-
Method Details
-
register
Registers the three request types on the protocol router.- Parameters:
router- the router that dispatches inbound frames
-
handleTree
Answers the configuration file tree: each loaded module with the files it registered. -
handleEntries
Answers one file's typed entries and the SHA-256 of its current bytes. -
handleUpdate
Writes exactly the keys a panel edit names, and only while the file still holds the bytes it read. -
tree
private com.google.gson.JsonElement tree() -
modulesByName
-
entries
-
update
private void update(CloudChannel channel, String replyTo, String path, String baseVersion, List<RemoteConfigHandler.Change> changes, RemoteConfigHandler.UpdateAudit audit) -
write
private void write(CloudChannel channel, String replyTo, RemoteConfigHandler.Registered registered, ConfigLoadResult loaded, List<RemoteConfigHandler.Change> changes, RemoteConfigHandler.UpdateAudit audit) -
writeDenied
The panel's explanation of a write the gate refused: the gate's own reason, which names the line an operator has to fix and never a value. The fixed sentence it replaces read like a permission problem and left the actionable part in the server log only.- Parameters:
reason- the gate's value-free reason, empty when it wrote or found nothing to write- Returns:
- the message for the
write_deniederror
-
refuseUpdate
private static void refuseUpdate(CloudChannel channel, String replyTo, RemoteConfigHandler.UpdateAudit audit, String code, String message) Answers a refusedconfig.updateand records the same text as its one DENIED action-log entry, so the log says what the operator was told. -
nothingWritten
The action-log reason of an update whose every key was refused: the key names with the per-key messages the panel received. Both are value-free: a key is a setting's name, and every per-key message names a rule.- Parameters:
results- the per-key results, none of them ok- Returns:
- the bounded reason
-
entityRefusal
private String entityRefusal(AbstractConfigEntity entity, RemoteConfigHandler.Change change, RemoteConfigEntries.Entry entry) The entity's own check of one change: it applies the value to the declared field in memory, runs the same validators and converters a load runs, and restores every field before returning.The text handed to the panel names the rule, never the value and never the load-time wording: the configuration layer's own message echoes the offending value and speaks of a module that refused to load, which is not what happened to a panel edit. Only the error code - the class of rule that refused - is forwarded.
-
refusalText
-
owned
-
reload
Makes the module take the values just written, by reloading the one entity that owns the written file. Only that entity is reloaded: reloading every file of the module would let a broken file this write never touched block the reload of the edited one (ConfigManager.reloadConfigs(com.ultikits.ultitools.abstracts.UltiToolsPlugin)stops at the first failure), and the panel would be told the edit failed when the file it edited was written and readable.- Parameters:
registered- the file that was written and its owning entity- Returns:
- the panel's explanation when the module did not take the new values, or null when it did
-
keysData
private static com.google.gson.JsonObject keysData(List<RemoteConfigHandler.KeyResult> results, String version) -
lookup
-
publishedPath
The panel's path of one registered configuration file: the file's location relative to the server root, with/separators, as the contract's path primitive requires. A file outside the root, or a name the primitive rejects, is not published at all - the panel never sees a path it could not name back. -
lockFor
-
onConfigThread
-
failed
-
bounded
-
changes
Validates every change item against the frozenconfig.updateschema shapes and copies it. A malformed item is anInvalidFrameException, so the router answersinvalid_message. -
value
-
invalid
-