Class ModuleRequestHandler

java.lang.Object
com.ultikits.ultitools.utils.ModuleRequestHandler
All Implemented Interfaces:
CloudRequestHandler

@Internal final class ModuleRequestHandler extends Object implements CloudRequestHandler
Bridges UltiCloud protocol v2 module.request to the released 6.3.0 PanelResponderRegistry; internal, not for module use.

One request names a module, an action, that action's parameters and the actor the gateway asserts (D-25). The payload is read strictly against the frozen module.request schema, so a missing, extra or wrongly typed field becomes a correlated invalid_message before any module code runs.

The payload's module is the module's UltiCloud identifier -- its identify-string, lower-case (ModuleIds), never its plugin.yml name. The responder is the one PanelResponderRegistry.moduleRequestType(String, String) names: registered under <module>.<action> or, as in 6.3.0, under <owner name lower-cased>.<action>, and owned by the module with that identifier. A request can never reach another module's responder, and one that would is answered module_not_found without dispatching (T-22-99). The responder receives a fresh object holding a copy of params plus the actor object exactly as received; the plugin neither derives nor overrides the actor, which the authenticated gateway channel vouches for (T-22-98).

This handler does not consult the capabilities the gateway accepted: the router admits module.request through its fromPayload:module sentinel, and the only module check here is the responder's owner. Whether a request for a module whose declared capability the gateway did not accept is filtered is decided in 22-21.

Each module-owned request is also republished on the module-facing event bus as one PanelMessageEvent, built on the dispatching thread and delivered on the main thread, with the fields the v1 inbound path gives it: the type the responder is registered under, the responder's input and the request envelope as received. A request answered module_not_found reached no module's responder and publishes nothing.

Exactly one module.response is produced per request, on whichever thread settles the registry's future, and the dispatching thread is never blocked:

  • the responder's result becomes status: ok with that result as data, whatever its shape: an object, an array, a primitive, or null when the responder's future completed with null or JsonNull ("answered, no content");
  • no responder, or one owned by another module, becomes module_not_found;
  • the registry's deadline passing becomes deadline_exceeded;
  • every other failure — a responder that throws, completes exceptionally, returns no future (null instead of a future), or is refused because the registry's queue is full or the registry shut down — becomes module_error carrying only the root cause's message, cut to the schema's limit;
  • a result that cannot be written as strict JSON — a NaN or infinite number, which Gson writes as a bare token no strict parser accepts — becomes module_error with a fixed message, because the gateway would drop such a frame and the panel would wait for its own deadline;
  • a reply that cannot be built at all becomes one internal_error, so the single reply is never lost to a channel or serialiser failure inside the completion callback.
Since:
6.3.1
  • Field Details

    • ACTION

      private static final Pattern ACTION
      The contract's action pattern: lowercase letters, digits, dot, underscore and hyphen.
    • MODULE_IDENTIFIER_MAX_LENGTH

      private static final int MODULE_IDENTIFIER_MAX_LENGTH
      The contract's bound on a module identifier.
      See Also:
    • MAX_USER_ID

      private static final long MAX_USER_ID
      The largest actor user id the contract accepts.
      See Also:
    • MESSAGE_MAX_LENGTH

      private static final int MESSAGE_MAX_LENGTH
      The contract's bound on a response message.
      See Also:
    • RESPONSE_TYPE

      private static final String RESPONSE_TYPE
      See Also:
    • NO_RESPONDER_MESSAGE

      private static final String NO_RESPONDER_MESSAGE
      See Also:
    • DEADLINE_MESSAGE

      private static final String DEADLINE_MESSAGE
      See Also:
    • NON_JSON_MESSAGE

      private static final String NON_JSON_MESSAGE
      See Also:
    • INTERNAL_ERROR_MESSAGE

      private static final String INTERNAL_ERROR_MESSAGE
      See Also:
    • RESULT_GSON

      private static final com.google.gson.Gson RESULT_GSON
      Shared Gson used only to hand a result to a non-lenient JsonWriter, which is the one check that both walks the whole value and refuses a non-finite number: Gson#toJson(JsonElement) always writes leniently, and Gson's strict reader accepts a bare NaN as the string "NaN", so serialising and reparsing would prove nothing.
    • registry

      private final PanelResponderRegistry registry
    • shutdown

      private final ShutdownGate.Signal shutdown
  • Constructor Details

    • ModuleRequestHandler

      ModuleRequestHandler(PanelResponderRegistry registry)
      Creates the bridge over the registry the framework already owns, never refused for a shutdown.
      Parameters:
      registry - the registry holding module responders; never null
    • ModuleRequestHandler

      ModuleRequestHandler(PanelResponderRegistry registry, ShutdownGate.Signal shutdown)
      Parameters:
      registry - the registry holding module responders; never null
      shutdown - the shutdown signal (ShutdownGate): a request is handed to its responder only with a permit, which is returned when the responder's work ends (22-23 second verification), so UltiTools#onDisable waits for a responder that started before it, bounded by PluginInitiationUtils.PANEL_CHANGE_DRAIN_MS
  • Method Details

    • handle

      public void handle(CloudRequest request, CloudChannel channel)
      Validates one module.request and hands it to its owning module's responder.
      Specified by:
      handle in interface CloudRequestHandler
      Parameters:
      request - the parsed request whose payload names module, action, params and actor
      channel - the reply channel; a reply may be dropped when the connection has closed
    • publishPanelMessageEvent

      private static void publishPanelMessageEvent(CloudRequest request, String messageType, com.google.gson.JsonObject data)
      Republishes the request for modules that subscribe to PanelMessageEvent, exactly as the v1 inbound path does: built here on the dispatching thread, delivered on the main thread, carrying the type the responder is registered under, the responder's input (the request's params plus the gateway-asserted actor) and the v2 envelope as received. Best effort: no failure here may affect the request's single reply.
    • validateActor

      private static void validateActor(String id, com.google.gson.JsonObject actor)
      Checks the gateway-asserted actor against the contract's actor object, exactly as sent.
    • reply

      private static void reply(CloudChannel channel, String replyTo, com.google.gson.JsonElement result, Throwable failure)
      Produces the request's single reply from the registry's outcome.
    • sendInternalError

      private static void sendInternalError(CloudChannel channel, String replyTo)
      Answers internal_error once, and gives up quietly if the channel refuses that too.
    • isStrictJson

      private static boolean isStrictJson(com.google.gson.JsonElement result)
      Whether result can be written as strict JSON. A non-lenient JsonWriter refuses a non-finite double or float with IllegalArgumentException, which is exactly the value the gateway's parser rejects and Gson would otherwise write as the bare token NaN or Infinity.
    • isDeadline

      private static boolean isDeadline(Throwable failure)
      Whether the registry's own deadline failed this request, rather than a responder.
    • rootCauseMessage

      private static String rootCauseMessage(Throwable failure)
      The deepest cause's message, which is the contract's module_error message; the framework's own wrapper text around it is not sent. A blank message falls back to the root cause's class name, and the result is cut to the schema's limit.
    • truncate

      private static String truncate(String text)