Skip to content

Commit 2aca661

Browse files
committed
GH-53 Bind matched Endpoint in before matched and after matched handlers (Resolve #53)
1 parent e6f40cb commit 2aca661

File tree

4 files changed

+62
-25
lines changed

4 files changed

+62
-25
lines changed

routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ object AnnotatedRouting : RoutingApiInitializer<AnnotatedRoutingConfig> {
5858
val configuration = AnnotatedRoutingConfig()
5959
setup.invokeAsSamWithReceiver(configuration)
6060

61-
val loader = ReflectiveEndpointLoader(configuration.resultHandlers)
61+
val loader = ReflectiveEndpointLoader(
62+
internalRouter = internalRouter,
63+
resultHandlers = configuration.resultHandlers
64+
)
65+
6266
val registeredEventListeners = mutableMapOf<JavalinLifecycleEvent, AnnotatedEvent>()
6367
val registeredRoutes = mutableListOf<AnnotatedRoute>()
6468
val registeredExceptionHandlers = mutableListOf<AnnotatedException>()

routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import io.javalin.community.routing.dsl.DefaultDslRoute
77
import io.javalin.event.JavalinLifecycleEvent
88
import io.javalin.http.Context
99
import io.javalin.http.HttpStatus
10+
import io.javalin.router.Endpoint
11+
import io.javalin.router.InternalRouter
12+
import io.javalin.util.Util.firstOrNull
1013
import io.javalin.validation.Validation
1114
import io.javalin.validation.Validator
1215
import java.lang.reflect.AnnotatedElement
@@ -22,21 +25,12 @@ typealias AnnotatedException = DefaultDslException<Context, Exception, Unit>
2225
typealias AnnotatedEvent = () -> Unit
2326

2427
internal class ReflectiveEndpointLoader(
28+
private val internalRouter: InternalRouter,
2529
private val resultHandlers: Map<Class<*>, HandlerResultConsumer<*>>
2630
) {
2731

2832
private val repeatedPathSeparatorRegex = Regex("/+")
2933

30-
private fun getAllDeclaredMethods(clazz: Class<*>): Collection<Method> {
31-
val methods = mutableListOf<Method>()
32-
var currentClass: Class<*>? = clazz
33-
while (currentClass?.name != "java.lang.Object") {
34-
methods.addAll(currentClass?.declaredMethods ?: emptyArray())
35-
currentClass = currentClass?.superclass
36-
}
37-
return methods
38-
}
39-
4034
fun loadRoutesFromEndpoint(endpoint: Any): List<AnnotatedRoute> {
4135
val endpointClass = endpoint::class.java
4236

@@ -160,7 +154,7 @@ internal class ReflectiveEndpointLoader(
160154
"Unable to access method $method in class $endpointClass"
161155
}
162156

163-
dslEvents[lifecycleEventHandler.lifecycleEvent] = object : AnnotatedEvent {
157+
dslEvents[lifecycleEventHandler.lifecycleEvent] = object : AnnotatedEvent {
164158
override fun invoke() {
165159
method.invoke(endpoint)
166160
}
@@ -231,6 +225,12 @@ internal class ReflectiveEndpointLoader(
231225
expectedTypeAsClass.isAssignableFrom(Context::class.java) -> { ctx, _ ->
232226
ctx
233227
}
228+
expectedTypeAsClass.isAssignableFrom(Endpoint::class.java) -> { ctx, _ ->
229+
internalRouter
230+
.findHttpHandlerEntries(ctx.method(), ctx.path().removePrefix(ctx.contextPath()))
231+
.firstOrNull()
232+
?.endpoint
233+
}
234234
isAnnotationPresent<Param>() -> { ctx, _ ->
235235
getAnnotationOrThrow<Param>()
236236
.value
@@ -289,4 +289,14 @@ internal class ReflectiveEndpointLoader(
289289
private inline fun <reified A : Annotation> AnnotatedElement.getAnnotation(): A? =
290290
getAnnotation(A::class.java)
291291

292+
private fun getAllDeclaredMethods(clazz: Class<*>): Collection<Method> {
293+
val methods = mutableListOf<Method>()
294+
var currentClass: Class<*>? = clazz
295+
while (currentClass?.name != "java.lang.Object") {
296+
methods.addAll(currentClass?.declaredMethods ?: emptyArray())
297+
currentClass = currentClass?.superclass
298+
}
299+
return methods
300+
}
301+
292302
}

routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import io.javalin.event.JavalinLifecycleEvent.SERVER_STARTED
77
import io.javalin.http.Context
88
import io.javalin.http.HandlerType
99
import io.javalin.http.HttpStatus
10+
import io.javalin.router.Endpoint
1011
import io.javalin.testtools.HttpClient
1112
import io.javalin.testtools.JavalinTest
1213
import kong.unirest.Unirest
@@ -36,11 +37,25 @@ class AnnotatedRoutingTest {
3637
object {
3738
// formatter:off
3839
@Before fun beforeEach(ctx: Context) { ctx.header("before", "true") }
39-
@Before("/specific") fun beforeSpecific(ctx: Context) { ctx.header("before", "specific") }
40-
@BeforeMatched fun beforeEachMatched(ctx: Context) { ctx.header("before-matched", "true") }
41-
@AfterMatched fun afterEachMatched(ctx: Context) { ctx.header("after-matched", "true") }
42-
@After("/specific") fun afterSpecific(ctx: Context) { ctx.header("after", "specific") }
4340
@After fun afterEach(ctx: Context) { ctx.header("after", "true") }
41+
42+
@Before("/specific") fun beforeSpecific(ctx: Context, endpoint: Endpoint?) {
43+
ctx.header("before", "specific")
44+
ctx.header("before-endpoint", endpoint?.path ?: "not-matched")
45+
}
46+
@BeforeMatched fun beforeEachMatched(ctx: Context, endpoint: Endpoint) {
47+
ctx.header("before-matched", "true")
48+
ctx.header("before-matched-endpoint", "${endpoint.method.name} ${endpoint.path}")
49+
}
50+
@AfterMatched fun afterEachMatched(ctx: Context, endpoint: Endpoint) {
51+
ctx.header("after-matched", "true")
52+
ctx.header("after-matched-endpoint", "${endpoint.method.name} ${endpoint.path}")
53+
}
54+
@After("/specific") fun afterSpecific(ctx: Context, endpoint: Endpoint?) {
55+
ctx.header("after", "specific")
56+
ctx.header("after-endpoint", endpoint?.path ?: "not-matched")
57+
}
58+
4459
@Get("/get") fun testGet(ctx: Context) { ctx.header("get", "true") }
4560
@Post("/post") fun testPost(ctx: Context) { ctx.header("post", "true") }
4661
@Put("/put") fun testPut(ctx: Context) { ctx.header("put", "true") }
@@ -64,10 +79,16 @@ class AnnotatedRoutingTest {
6479
.forEach {
6580
val response = request(it.name, "${client.origin}/test/${it.name.lowercase()}").asEmpty()
6681
assertThat(response.headers.getFirst(it.name.lowercase())).isEqualTo("true")
82+
6783
assertThat(response.headers.getFirst("before")).isEqualTo("true")
84+
assertThat(response.headers.getFirst("before-endpoint")).isEmpty()
6885
assertThat(response.headers.getFirst("before-matched")).isEqualTo("true")
86+
assertThat(response.headers.getFirst("before-matched-endpoint")).isEqualTo("${it.name} /test/${it.name.lowercase()}")
87+
6988
assertThat(response.headers.getFirst("after")).isEqualTo("true")
89+
assertThat(response.headers.getFirst("after-endpoint")).isEmpty()
7090
assertThat(response.headers.getFirst("after-matched")).isEqualTo("true")
91+
assertThat(response.headers.getFirst("after-matched-endpoint")).isEqualTo("${it.name} /test/${it.name.lowercase()}")
7192
}
7293
}
7394
}
@@ -91,9 +112,11 @@ class AnnotatedRoutingTest {
91112
withinSharedScenario { client ->
92113
val beforeSpecific = request("GET", "${client.origin}/test/specific").asEmpty()
93114
assertThat(beforeSpecific.headers.getFirst("before")).isEqualTo("specific")
115+
assertThat(beforeSpecific.headers.getFirst("before-endpoint")).isEqualTo("not-matched")
94116

95117
val beforeTooSpecific = request("GET", "${client.origin}/test/specific/too-specific").asEmpty()
96118
assertThat(beforeTooSpecific.headers.getFirst("before")).isNotEqualTo("specific")
119+
assertThat(beforeTooSpecific.headers.getFirst("before-endpoint")).isEmpty()
97120
}
98121
}
99122

@@ -102,9 +125,11 @@ class AnnotatedRoutingTest {
102125
withinSharedScenario { client ->
103126
val afterSpecific = request("GET", "${client.origin}/test/specific").asEmpty()
104127
assertThat(afterSpecific.headers.getFirst("after")).isEqualTo("specific")
128+
assertThat(afterSpecific.headers.getFirst("after-endpoint")).isEqualTo("not-matched")
105129

106130
val afterTooSpecific = request("GET", "${client.origin}/test/specific/too-specific").asEmpty()
107131
assertThat(afterTooSpecific.headers.getFirst("after")).isNotEqualTo("specific")
132+
assertThat(afterTooSpecific.headers.getFirst("after-endpoint")).isEmpty()
108133
}
109134
}
110135

routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/example/AnnotatedRoutingExample.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
11
package io.javalin.community.routing.annotations.example;
22

33
import io.javalin.Javalin;
4-
import io.javalin.community.routing.annotations.Before;
5-
import io.javalin.community.routing.annotations.Body;
6-
import io.javalin.community.routing.annotations.Endpoints;
7-
import io.javalin.community.routing.annotations.ExceptionHandler;
8-
import io.javalin.community.routing.annotations.Get;
9-
import io.javalin.community.routing.annotations.Header;
10-
import io.javalin.community.routing.annotations.Param;
11-
import io.javalin.community.routing.annotations.Post;
12-
import io.javalin.community.routing.annotations.Version;
4+
import io.javalin.community.routing.annotations.*;
135
import io.javalin.http.Context;
146
import io.javalin.openapi.OpenApi;
157
import io.javalin.openapi.OpenApiContent;
168
import io.javalin.openapi.OpenApiParam;
179
import io.javalin.openapi.OpenApiResponse;
10+
import io.javalin.router.Endpoint;
1811
import kong.unirest.HttpResponse;
1912
import kong.unirest.Unirest;
2013
import org.jetbrains.annotations.Nullable;
@@ -64,6 +57,11 @@ void beforeEach(Context ctx) {
6457
System.out.println("Before each request: " + ctx.method() + " " + ctx.path());
6558
}
6659

60+
@BeforeMatched
61+
void beforeMatched(Context ctx, Endpoint endpoint) {
62+
System.out.println("Before matched request to " + endpoint.getMethod() + " " + endpoint.getPath());
63+
}
64+
6765
// describe http method and path with annotation
6866
@Post("/hello")
6967
// use parameters to extract data from request

0 commit comments

Comments
 (0)