Class RemotePropertiesHandler

java.lang.Object
com.ultikits.ultitools.manager.RemotePropertiesHandler
All Implemented Interfaces:
CloudRequestHandler

final class RemotePropertiesHandler extends Object implements CloudRequestHandler
Answers the UltiCloud protocol 2.0.0 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.

  • Field Details

  • Constructor Details

  • Method Details

    • handle

      public void handle(CloudRequest request, CloudChannel channel)
      Description copied from interface: CloudRequestHandler
      Validates 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:
      handle in interface CloudRequestHandler
      Parameters:
      request - the parsed envelope with a defensively copied payload
      channel - the reply channel, which may close during processing
    • get

      private void get(CloudRequest request, CloudChannel channel)
      Answers properties.get. The payload has no fields, so any field at all is invalid_message; the reply carries the typed values, the writable key set (which is where the panel learns that online-mode and white-list are read-only) and the version of the bytes the values were read from.
    • update

      private void update(CloudRequest request, CloudChannel channel)
      Answers properties.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

      private static Object typedValue(RemotePropertiesHandler.KeySpec spec, String text)
      Converts one raw server.properties value to the JSON type its key carries.
      Parameters:
      spec - the key's contract type
      text - the value as the file holds it
      Returns:
      the typed value, or null when 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 through PayloadReader, so a mismatch leaves as the router's invalid_message rather than reaching the file.
      Parameters:
      id - the request id for the validation error
      spec - the key's contract type
      key - the key being changed
      value - the JSON value the panel sent
      Returns:
      the value in the form server.properties stores
    • addTyped

      private static void addTyped(com.google.gson.JsonObject values, String key, Object typed)
    • readableSpecs

      private static Map<String,RemotePropertiesHandler.KeySpec> 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 vendored properties.get and properties.update schemas.
      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 refused properties.update and records the same text in the action log, so the log says what the operator was told.
    • refuseGet

      private void refuseGet(CloudChannel channel, String replyTo, String code, String message)
      Answers a refused properties.get and records the same text, as refuseUpdate(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

      private void auditAllowed(String action, String target)
    • record

      private void record(RemoteActionLog.Entry entry)
      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

      private static String keyNames(com.google.gson.JsonObject changes)
      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

      private static String capped(String message)
      The refusal text as the panel receives it: never empty and never above the contract's cap.