Repository: tipsy/javalin
Pull request #1504 (2022-04-06 14:55:57)

[config] Add future callback to context resolvers
closes 1495


Patch:
diff --git a/javalin/src/main/java/io/javalin/http/Context.kt b/javalin/src/main/java/io/javalin/http/Context.kt
index f4f6a01e..5cbb5dde 100644
--- a/javalin/src/main/java/io/javalin/http/Context.kt
+++ b/javalin/src/main/java/io/javalin/http/Context.kt
@@ -354,6 +354,7 @@ open class Context(@JvmField val req: HttpServletRequest, @JvmField val res: Htt
         result.future.takeIf { it.isDone }?.get() as InputStream? ?: result.previous
     }
 
+    /** The default callback (used if no callback is provided) can be configured through [ContextResolver.defaultFutureCallback] */
     @JvmOverloads
     fun future(future: CompletableFuture<*>, callback: Consumer<Any?>? = null): Context {
         resultReference.updateAndGet { oldResult ->
diff --git a/javalin/src/main/java/io/javalin/http/ContextResolver.kt b/javalin/src/main/java/io/javalin/http/ContextResolver.kt
index 5162b3e6..51362ef3 100644
--- a/javalin/src/main/java/io/javalin/http/ContextResolver.kt
+++ b/javalin/src/main/java/io/javalin/http/ContextResolver.kt
@@ -1,7 +1,7 @@
 package io.javalin.http
 
 import io.javalin.core.util.Header
-import javax.servlet.http.HttpServletRequest
+import java.io.InputStream
 
 
 const val CONTEXT_RESOLVER_KEY = "contextResolver"
@@ -15,5 +15,13 @@ class ContextResolver {
     @JvmField var scheme: (Context) -> String = { it.req.scheme }
     @JvmField var url: (Context) -> String = { it.req.requestURL.toString() }
     @JvmField var fullUrl: (Context) -> String = { it.url() + if (it.queryString() != null) "?" + it.queryString() else "" }
+    @JvmField var defaultFutureCallback: (ctx: Context, result: Any?) -> Unit = { ctx, result ->
+        when (result) {
+            is Unit -> {}
+            is InputStream -> ctx.result(result)
+            is String -> ctx.result(result)
+            is Any -> ctx.json(result)
+        }
+    }
     // @formatter:on
 }
diff --git a/javalin/src/main/java/io/javalin/http/JavalinServletHandler.kt b/javalin/src/main/java/io/javalin/http/JavalinServletHandler.kt
index c40b9438..5093b7c9 100644
--- a/javalin/src/main/java/io/javalin/http/JavalinServletHandler.kt
+++ b/javalin/src/main/java/io/javalin/http/JavalinServletHandler.kt
@@ -106,7 +106,7 @@ class JavalinServletHandler(
             .also { result -> if (ctx.isAsync()) ctx.req.asyncContext.addTimeoutListener { result.future.cancel(true) } }
             .let { result ->
                 result.future
-                    .thenAccept { any -> (result.callback ?: defaultFutureCallback()).accept(any) } // callback after future resolves - modifies ctx result, status, etc
+                    .thenAccept { any -> (result.callback?.accept(any) ?: ctx.contextResolver().defaultFutureCallback(ctx, any)) } // callback after future resolves - modifies ctx result, status, etc
                     .thenApply { ctx.resultStream() ?: previousResult } // set value of future to be resultStream (or previous stream)
                     .exceptionally { throwable -> exceptionMapper.handleFutureException(ctx, throwable) } // standard exception handler
                     .thenApply { inputStream -> inputStream.also { queueNextTaskOrFinish() } } // we have to attach the "also" to the input stream to avoid mapping a void
@@ -135,16 +135,6 @@ class JavalinServletHandler(
         }
     }
 
-    /** Runs after a future is resolved, if not user defined callback exists */
-    private fun defaultFutureCallback(): Consumer<Any?> = Consumer { result ->
-        when (result) {
-            is Unit -> {}
-            is InputStream -> ctx.result(result)
-            is String -> ctx.result(result)
-            is Any -> ctx.json(result)
-        }
-    }
-
 }
 
 /** Checks if request is executed asynchronously */


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestFuture.kt b/javalin/src/test/java/io/javalin/TestFuture.kt
index fe6789c0..a3d39d6b 100644
--- a/javalin/src/test/java/io/javalin/TestFuture.kt
+++ b/javalin/src/test/java/io/javalin/TestFuture.kt
@@ -183,6 +183,17 @@ class TestFuture {
         assertThat(future.isCancelled).isTrue()
     }
 
+    @Test
+    fun `can set default callback via context resolvers`() {
+        val ignoringServer = Javalin.create {
+            it.contextResolvers { it.defaultFutureCallback = { ctx, _ -> ctx.result("Ignore result") } }
+        }
+        TestUtil.test(ignoringServer) { app, http ->
+            app.get("/") {  it.future(CompletableFuture.completedFuture("Success")) }
+            assertThat(http.get("/").body).isEqualTo("Ignore result")
+        }
+    }
+
     private fun getFuture(result: String?, delay: Long = 10): CompletableFuture<String> {
         val future = CompletableFuture<String>()
         Executors.newSingleThreadScheduledExecutor().schedule({