Skip to content

Commit

Permalink
GH-39 Support lifecycle event handlers in annotated routing (Resolve #39
Browse files Browse the repository at this point in the history
)
  • Loading branch information
dzikoysk committed Feb 16, 2024
1 parent 0669eea commit f189af5
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -18,6 +19,10 @@ annotation class Endpoints(val value: String = "")
@Target(FUNCTION)
annotation class ExceptionHandler(val value: KClass<out Exception>)

@Retention(RUNTIME)
@Target(FUNCTION)
annotation class LifecycleEventHandler(val lifecycleEvent: JavalinLifecycleEvent)

@Retention(RUNTIME)
@Target(FUNCTION)
annotation class Get(val value: String = "", val async: Boolean = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -52,17 +59,32 @@ object AnnotatedRouting : RoutingApiInitializer<AnnotatedRoutingConfig> {
setup.invokeAsSamWithReceiver(configuration)

val loader = ReflectiveEndpointLoader(configuration.resultHandlers)
val registeredEventListeners = mutableMapOf<JavalinLifecycleEvent, AnnotatedEvent>()
val registeredRoutes = mutableListOf<AnnotatedRoute>()
val registeredExceptionHandlers = mutableListOf<AnnotatedException>()

configuration.registeredRoutes.forEach {
val detectedEventListeners = loader.loadEventHandlers(it)
registeredEventListeners.putAll(detectedEventListeners)

val detectedRoutes = loader.loadRoutesFromEndpoint(it)
registeredRoutes.addAll(detectedRoutes)

val detectedExceptionHandlers = loader.loadExceptionHandlers(it)
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) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,6 +15,7 @@ import kotlin.reflect.KClass

typealias AnnotatedRoute = DefaultDslRoute<Context, Unit>
typealias AnnotatedException = DefaultDslException<Context, Exception, Unit>
typealias AnnotatedEvent = () -> Unit

internal class ReflectiveEndpointLoader(
private val resultHandlers: Map<Class<*>, HandlerResultConsumer<*>>
Expand Down Expand Up @@ -122,6 +124,27 @@ internal class ReflectiveEndpointLoader(
return dslExceptions
}

fun loadEventHandlers(endpoint: Any): Map<JavalinLifecycleEvent, AnnotatedEvent> {
val endpointClass = endpoint::class.java
val dslEvents = mutableMapOf<JavalinLifecycleEvent, AnnotatedEvent>()

endpointClass.declaredMethods.forEach { method ->
val lifecycleEventHandler = method.getAnnotation<LifecycleEventHandler>() ?: 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<Any?> =
resultHandlers.asSequence()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>()

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 {
Expand Down

0 comments on commit f189af5

Please sign in to comment.