From f189af5749b775bb669758561837daca7468ee05 Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Sat, 17 Feb 2024 00:15:00 +0100 Subject: [PATCH] GH-39 Support lifecycle event handlers in annotated routing (Resolve #39) --- .../routing/annotations/RoutingAnnotations.kt | 5 ++++ .../routing/annotations/AnnotatedRouting.kt | 22 ++++++++++++++++++ .../annotations/ReflectiveEndpointLoader.kt | 23 +++++++++++++++++++ .../annotations/AnnotatedRoutingTest.kt | 21 +++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/routing-annotations/routing-annotated-specification/src/main/kotlin/io/javalin/community/routing/annotations/RoutingAnnotations.kt b/routing-annotations/routing-annotated-specification/src/main/kotlin/io/javalin/community/routing/annotations/RoutingAnnotations.kt index 8951d9a..7b47417 100644 --- a/routing-annotations/routing-annotated-specification/src/main/kotlin/io/javalin/community/routing/annotations/RoutingAnnotations.kt +++ b/routing-annotations/routing-annotated-specification/src/main/kotlin/io/javalin/community/routing/annotations/RoutingAnnotations.kt @@ -1,5 +1,6 @@ package io.javalin.community.routing.annotations +import io.javalin.event.JavalinLifecycleEvent import io.javalin.http.HttpStatus import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationTarget.CLASS @@ -18,6 +19,10 @@ annotation class Endpoints(val value: String = "") @Target(FUNCTION) annotation class ExceptionHandler(val value: KClass) +@Retention(RUNTIME) +@Target(FUNCTION) +annotation class LifecycleEventHandler(val lifecycleEvent: JavalinLifecycleEvent) + @Retention(RUNTIME) @Target(FUNCTION) annotation class Get(val value: String = "", val async: Boolean = false) diff --git a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt index 7f7f9a9..34e7201 100644 --- a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt +++ b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt @@ -6,6 +6,13 @@ import io.javalin.community.routing.invokeAsSamWithReceiver import io.javalin.community.routing.registerRoute import io.javalin.community.routing.sortRoutes import io.javalin.config.JavalinConfig +import io.javalin.event.JavalinLifecycleEvent +import io.javalin.event.JavalinLifecycleEvent.SERVER_STARTED +import io.javalin.event.JavalinLifecycleEvent.SERVER_STARTING +import io.javalin.event.JavalinLifecycleEvent.SERVER_START_FAILED +import io.javalin.event.JavalinLifecycleEvent.SERVER_STOPPED +import io.javalin.event.JavalinLifecycleEvent.SERVER_STOPPING +import io.javalin.event.JavalinLifecycleEvent.SERVER_STOP_FAILED import io.javalin.http.BadRequestResponse import io.javalin.http.Context import io.javalin.http.Handler @@ -52,10 +59,14 @@ object AnnotatedRouting : RoutingApiInitializer { setup.invokeAsSamWithReceiver(configuration) val loader = ReflectiveEndpointLoader(configuration.resultHandlers) + val registeredEventListeners = mutableMapOf() val registeredRoutes = mutableListOf() val registeredExceptionHandlers = mutableListOf() configuration.registeredRoutes.forEach { + val detectedEventListeners = loader.loadEventHandlers(it) + registeredEventListeners.putAll(detectedEventListeners) + val detectedRoutes = loader.loadRoutesFromEndpoint(it) registeredRoutes.addAll(detectedRoutes) @@ -63,6 +74,17 @@ object AnnotatedRouting : RoutingApiInitializer { registeredExceptionHandlers.addAll(detectedExceptionHandlers) } + registeredEventListeners.forEach { (key, event) -> + when (key) { + SERVER_STARTING -> cfg.events.serverStarting { event.invoke() } + SERVER_STARTED -> cfg.events.serverStarted { event.invoke() } + SERVER_START_FAILED -> cfg.events.serverStartFailed { event.invoke() } + SERVER_STOP_FAILED -> cfg.events.serverStopFailed { event.invoke() } + SERVER_STOPPING -> cfg.events.serverStopping { event.invoke() } + SERVER_STOPPED -> cfg.events.serverStopped { event.invoke() } + } + } + registeredRoutes .sortRoutes() .groupBy { RouteIdentifier(it.method, it.path) } diff --git a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt index e9f1eb6..e4d0e9a 100644 --- a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt +++ b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt @@ -4,6 +4,7 @@ import io.javalin.community.routing.Route import io.javalin.community.routing.Route.BEFORE_MATCHED import io.javalin.community.routing.dsl.DefaultDslException import io.javalin.community.routing.dsl.DefaultDslRoute +import io.javalin.event.JavalinLifecycleEvent import io.javalin.http.Context import io.javalin.http.HttpStatus import io.javalin.validation.Validation @@ -14,6 +15,7 @@ import kotlin.reflect.KClass typealias AnnotatedRoute = DefaultDslRoute typealias AnnotatedException = DefaultDslException +typealias AnnotatedEvent = () -> Unit internal class ReflectiveEndpointLoader( private val resultHandlers: Map, HandlerResultConsumer<*>> @@ -122,6 +124,27 @@ internal class ReflectiveEndpointLoader( return dslExceptions } + fun loadEventHandlers(endpoint: Any): Map { + val endpointClass = endpoint::class.java + val dslEvents = mutableMapOf() + + endpointClass.declaredMethods.forEach { method -> + val lifecycleEventHandler = method.getAnnotation() ?: return@forEach + + require(method.trySetAccessible()) { + "Unable to access method $method in class $endpointClass" + } + + dslEvents[lifecycleEventHandler.lifecycleEvent] = object : AnnotatedEvent { + override fun invoke() { + method.invoke(endpoint) + } + } + } + + return dslEvents + } + @Suppress("UNCHECKED_CAST") private fun findResultHandler(method: Method): HandlerResultConsumer = resultHandlers.asSequence() diff --git a/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt b/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt index 719e611..c46d412 100644 --- a/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt +++ b/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt @@ -3,6 +3,7 @@ package io.javalin.community.routing.annotations import io.javalin.Javalin import io.javalin.community.routing.Route import io.javalin.community.routing.annotations.AnnotatedRouting.Annotated +import io.javalin.event.JavalinLifecycleEvent.SERVER_STARTED import io.javalin.http.Context import io.javalin.http.HandlerType import io.javalin.http.HttpStatus @@ -294,6 +295,26 @@ class AnnotatedRoutingTest { assertThat(Unirest.get("${client.origin}/throwing").asString().body).isEqualTo("java.lang.IllegalStateException") } + @Test + fun `should handle lifecycle events`() { + val log = mutableListOf() + + JavalinTest.test( + Javalin.create { + it.router.mount(Annotated) { cfg -> + cfg.registerEndpoints(object { + @LifecycleEventHandler(SERVER_STARTED) + fun onStart() { + log.add("Started") + } + }) + } + } + ) { _, _ -> + assertThat(log).containsExactly("Started") + } + } + @Test fun `should throw for unsupported return types`() { assertThatThrownBy {