package dev.relism.models; import dev.relism.extension.FlashContext; import java.util.Optional; /** * Base class for class-based route handlers. * *

Annotate the subclass with {@link dev.relism.routing.Route @Route} and register it * via {@link dev.relism.extension.FlashApp#register} or {@link dev.relism.extension.FlashApp#scan}. * For one-off routes, prefer the lambda DSL ({@code app.get(path, handler)}). * *

Lifecycle

*
    *
  1. Instantiation — no-arg constructor (for scan) or manual {@code new Handler(...)}
  2. *
  3. {@link #bind} — called once by the framework at {@code start()}, injects the * {@link FlashContext} and invokes {@link #onInit()}
  4. *
  5. {@link #handle} — called on every matching request (hot-path, zero-alloc)
  6. *
* *

Service access

* Override {@link #onInit()} to cache services from the {@link FlashContext} * into private fields. This keeps the hot-path ({@code handle}) free of map lookups. * *
{@code
 * @Route(method = HttpMethod.GET, path = "/users")
 * public class UserHandler extends RequestHandler {
 *     private UserService users;
 *
 *     @Override protected void onInit() {
 *         users = require(UserService.class);
 *     }
 *
 *     @Override public Object handle(Request req, Response res) {
 *         return users.findAll();
 *     }
 * }
 * }
*/ public abstract class RequestHandler { private FlashContext ctx; /** * Called once by the framework after instantiation, before the first request. * Injects the {@link FlashContext} and triggers {@link #onInit()}. * *

Infrastructure method — do not call from user code. * Use {@link dev.relism.extension.FlashApp#register} or * {@link dev.relism.extension.FlashApp#scan} instead. */ public final void bind(FlashContext ctx) { this.ctx = ctx; onInit(); } /** * Override to cache services at boot time. Called once after {@link #bind}, * before any request reaches this handler. * *

Use {@link #require} and {@link #find} to retrieve services from the * {@link FlashContext}. Cache them in private fields so the hot-path * ({@link #handle}) has zero lookup overhead. * *

Important: if your class extends another handler base (e.g. * {@code JacksonHandler}), call {@code super.onInit()} first so the parent * can initialise its own services. * *

{@code
     * @Override protected void onInit() {
     *     super.onInit();
     *     myService = require(MyService.class);
     * }
     * }
*/ protected void onInit() {} /** * Retrieves a required service from the {@link FlashContext}. * Throws {@link IllegalStateException} if the service is not registered or * this handler has not been bound yet. * *

Typically called inside {@link #onInit()} to cache the result. * * @param type the service class * @param the service type * @return the service instance, never null */ protected T require(Class type) { checkBound(); return ctx.require(type); } /** * Looks up an optional service from the {@link FlashContext}. * * @param type the service class * @param the service type * @return the service, or empty if not registered */ protected Optional find(Class type) { checkBound(); return ctx.find(type); } /** * Looks up an optional service from the {@link FlashContext}. * Identical to {@link #find} — prefer this name for expressive call sites * ({@code optional(ViewEngine.class).ifPresent(...)}). * * @param type the service class * @param the service type * @return the service, or empty if not registered */ protected Optional optional(Class type) { checkBound(); return ctx.optional(type); } private void checkBound() { if (ctx == null) throw new IllegalStateException( getClass().getSimpleName() + " has not been bound to a FlashContext — " + "register via FlashApp.register() or FlashApp.scan(), not directly on the router"); } /** * Handles an incoming request. The return value determines the response body: * return a {@link Response} to replace the whole response, any other non-null value * to set it as the body, or {@code null} to leave the response as-is. */ public abstract Object handle(Request request, Response response) throws Exception; }