Repository: tipsy/javalin
Pull request #1034 (2020-07-07 20:36:37)

Initial insert of jte integration
Hi @tipsy, this is a first draft of a jte integration for Javalin!

I went with solution 1, and added a "jte" key to the model map, which is a bit hacky.

Changing the model parameter to `Any`, would be nicer for jte, but require a change to the interface. Not sure if you would want to do that.

Maybe there's an even better solution I can't think of right now?

tipsy: Thanks, that was fast. I'll have a look in the weekend !
tipsy: Great work @casid. I agree that `ctx.render("test.jte", mapOf("jte" to JteTestPage("hello", "world")))` looks a bit hacky, and I don't think we should recommend that approach. We could change the interface, but that would make the experience worse for all the other template engines. What do you think about doing somehting like:

```kotlin
ctx.render("test.jte", Jte.page(JteTestPage("hello", "world")))
```

Where `Jte.page` is a static method that returns a map `{"jte": page}`?

Alternatively we could create `ctx.render(Page)` which does the same thing:

```kotlin
fun render(filePath: String, page: JtePage = Jte.emptyPage): Context {
    return html(JavalinRenderer.renderBasedOnExtension(filePath, mapOf("jte", page), this))
} 
```

In which case you would be able to do `ctx.render("test.jte", JteTestPage("hello", "world"))`. That is a tighter coupling that I would ideally want though 🤔

What do you think? Any other ideas?

Patch:
diff --git a/javalin/pom.xml b/javalin/pom.xml
index 6f429cb9..4b723b5a 100644
--- a/javalin/pom.xml
+++ b/javalin/pom.xml
@@ -81,6 +81,11 @@
             <artifactId>pebble</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.jusecase</groupId>
+            <artifactId>jte</artifactId>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>com.atlassian.commonmark</groupId>
             <artifactId>commonmark</artifactId>
diff --git a/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt b/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt
index 5d9a6f09..810826fd 100644
--- a/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt
+++ b/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt
@@ -26,4 +26,5 @@ enum class OptionalDependency(val displayName: String, val testClass: String, va
     SWAGGER_CORE("Swagger Core", "io.swagger.v3.oas.models.OpenAPI", "io.swagger.core.v3", "swagger-models", "2.1.2"),
     CLASS_GRAPH("ClassGraph", "io.github.classgraph.ClassGraph", "io.github.classgraph", "classgraph", "4.8.66"),
     JVMBROTLI("Jvm-Brotli", "com.nixxcode.jvmbrotli.common.BrotliLoader", "com.nixxcode.jvmbrotli", "jvmbrotli", "0.2.0"),
+    JTE("jte", "org.jusecase.jte.TemplateEngine", "org.jusecase", "jte", "0.6.0"),
 }
diff --git a/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt b/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt
index 447c1674..e1691b20 100644
--- a/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt
+++ b/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt
@@ -11,6 +11,7 @@ import io.javalin.core.util.Header
 import io.javalin.http.Context
 import io.javalin.http.HandlerEntry
 import io.javalin.http.HandlerType
+import java.net.URL
 import java.net.URLDecoder
 import java.util.*
 import javax.servlet.http.HttpServletRequest
@@ -74,7 +75,11 @@ object ContextUtil {
         this.handlerType = handlerType
     }
 
-    fun Context.isLocalhost() = this.host()?.contains("localhost") == true || this.host()?.contains("127.0.0.1") == true
+    fun Context.isLocalhost() = try {
+        URL(this.url()).host.let { it == "localhost" || it == "127.0.0.1" }
+    } catch (e: Exception) {
+        false
+    }
 
     fun changeBaseRequest(ctx: Context, req: HttpServletRequest) = Context(req, ctx.res).apply {
         this.pathParamMap = ctx.pathParamMap
diff --git a/javalin/src/main/java/io/javalin/plugin/rendering/JavalinRenderer.kt b/javalin/src/main/java/io/javalin/plugin/rendering/JavalinRenderer.kt
index 4d5689e4..2839ed46 100644
--- a/javalin/src/main/java/io/javalin/plugin/rendering/JavalinRenderer.kt
+++ b/javalin/src/main/java/io/javalin/plugin/rendering/JavalinRenderer.kt
@@ -9,12 +9,7 @@ package io.javalin.plugin.rendering
 import io.javalin.Javalin
 import io.javalin.http.Context
 import io.javalin.plugin.rendering.markdown.JavalinCommonmark
-import io.javalin.plugin.rendering.template.JavalinFreemarker
-import io.javalin.plugin.rendering.template.JavalinJtwig
-import io.javalin.plugin.rendering.template.JavalinMustache
-import io.javalin.plugin.rendering.template.JavalinPebble
-import io.javalin.plugin.rendering.template.JavalinThymeleaf
-import io.javalin.plugin.rendering.template.JavalinVelocity
+import io.javalin.plugin.rendering.template.*
 
 object JavalinRenderer {
 
@@ -28,6 +23,7 @@ object JavalinRenderer {
         register(JavalinPebble, ".peb", ".pebble")
         register(JavalinThymeleaf, ".html", ".tl", ".thyme", ".thymeleaf")
         register(JavalinCommonmark, ".md", ".markdown")
+        register(JavalinJte, ".jte")
     }
 
     fun renderBasedOnExtension(filePath: String, model: Map<String, Any?>, ctx: Context): String {
diff --git a/javalin/src/main/java/io/javalin/plugin/rendering/template/JavalinJte.kt b/javalin/src/main/java/io/javalin/plugin/rendering/template/JavalinJte.kt
new file mode 100644
index 00000000..de964904
--- /dev/null
+++ b/javalin/src/main/java/io/javalin/plugin/rendering/template/JavalinJte.kt
@@ -0,0 +1,38 @@
+/*
+ * Javalin - https://javalin.io
+ * Copyright 2017 David Åse
+ * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
+ */
+
+package io.javalin.plugin.rendering.template
+
+import io.javalin.core.util.OptionalDependency
+import io.javalin.core.util.Util
+import io.javalin.http.Context
+import io.javalin.plugin.rendering.FileRenderer
+import org.jusecase.jte.TemplateEngine
+import org.jusecase.jte.output.StringOutput
+import org.jusecase.jte.resolve.DirectoryCodeResolver
+import java.io.File
+
+object JavalinJte : FileRenderer {
+
+    private var templateEngine: TemplateEngine? = null
+    private val defaultTemplateEngine: TemplateEngine by lazy { defaultJteEngine() }
+
+    @JvmStatic
+    fun configure(staticTemplateEngine: TemplateEngine) {
+        templateEngine = staticTemplateEngine
+    }
+
+    override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
+        Util.ensureDependencyPresent(OptionalDependency.JTE)
+
+        val stringOutput = StringOutput()
+        (templateEngine ?: defaultTemplateEngine).render(filePath, model, stringOutput)
+        return stringOutput.toString()
+    }
+
+    private fun defaultJteEngine() = TemplateEngine.create(DirectoryCodeResolver(File("src/main/jte").toPath()))
+
+}
diff --git a/pom.xml b/pom.xml
index 223bd328..a420a061 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,7 @@
         <swagger.ui.version>3.25.2</swagger.ui.version>
         <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
         <velocity.version>2.2</velocity.version>
+        <jte.version>0.6.0</jte.version>
 
         <!-- TEST DEPENDENCIES -->
         <assertj.version>3.15.0</assertj.version>
@@ -198,6 +199,11 @@
                 <artifactId>pebble</artifactId>
                 <version>${pebble.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.jusecase</groupId>
+                <artifactId>jte</artifactId>
+                <version>${jte.version}</version>
+            </dependency>
             <dependency>
                 <groupId>com.atlassian.commonmark</groupId>
                 <artifactId>commonmark</artifactId>


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestTemplates.kt b/javalin/src/test/java/io/javalin/TestTemplates.kt
index dd73283f..201ecbdd 100644
--- a/javalin/src/test/java/io/javalin/TestTemplates.kt
+++ b/javalin/src/test/java/io/javalin/TestTemplates.kt
@@ -11,6 +11,7 @@ import com.mitchellbosecke.pebble.PebbleEngine
 import com.mitchellbosecke.pebble.loader.ClasspathLoader
 import io.javalin.plugin.rendering.FileRenderer
 import io.javalin.plugin.rendering.JavalinRenderer
+import io.javalin.plugin.rendering.template.JavalinJte
 import io.javalin.plugin.rendering.template.JavalinJtwig
 import io.javalin.plugin.rendering.template.JavalinPebble
 import io.javalin.plugin.rendering.template.JavalinVelocity
@@ -23,6 +24,9 @@ import org.jtwig.functions.FunctionRequest
 import org.jtwig.functions.SimpleJtwigFunction
 import org.jtwig.util.FunctionValueUtils
 import org.junit.Test
+import org.jusecase.jte.TemplateEngine
+import org.jusecase.jte.resolve.ResourceCodeResolver
+import java.io.File
 
 class TestTemplates {
 
@@ -137,6 +141,28 @@ class TestTemplates {
         assertThat(http.getBody("/quiz")).isEqualTo("<h1>Javalin is the best framework you will ever get</h1>")
     }
 
+    @Test
+    fun `jte works`() = TestUtil.test { app, http ->
+        try {
+            JavalinJte.configure(TemplateEngine.create(ResourceCodeResolver("templates/jte"), File("target/jte").toPath()))
+            app.get("/hello") { ctx -> ctx.render("test.jte", model("page", JteTestPage("hello", "world"))) }
+            assertThat(http.getBody("/hello")).isEqualToIgnoringNewLines("<h1>hello world!</h1>")
+        } catch (e:UnsupportedClassVersionError) {
+            // jte does require JDK 11+ to work
+        }
+    }
+
+    @Test
+    fun `jte multiple params work`() = TestUtil.test { app, http ->
+        try {
+            JavalinJte.configure(TemplateEngine.create(ResourceCodeResolver("templates/jte"), File("target/jte").toPath()))
+            app.get("/hello") { ctx -> ctx.render("multiple-params.jte", model("one", "hello", "two", "world")) }
+            assertThat(http.getBody("/hello")).isEqualToIgnoringNewLines("<h1>hello world!</h1>")
+        } catch (e:UnsupportedClassVersionError) {
+            // jte does require JDK 11+ to work
+        }
+    }
+
     @Test
     fun `markdown works`() = TestUtil.test { app, http ->
         app.get("/hello") { ctx -> ctx.render("/markdown/test.md") }
@@ -167,4 +193,6 @@ class TestTemplates {
         app.get("/hello") { ctx -> ctx.render("/templates/jtwig/multiple.dots.twig", model("message", "Hello jTwig!")) }
         assertThat(http.getBody("/hello")).isEqualTo("<h1>Hello jTwig!</h1>")
     }
+
+    data class JteTestPage(val hello:String, val world:String)
 }
diff --git a/javalin/src/test/resources/templates/jte/multiple-params.jte b/javalin/src/test/resources/templates/jte/multiple-params.jte
new file mode 100644
index 00000000..49620e8a
--- /dev/null
+++ b/javalin/src/test/resources/templates/jte/multiple-params.jte
@@ -0,0 +1,3 @@
+@param String one
+@param String two
+<h1>${one} ${two}!</h1>
diff --git a/javalin/src/test/resources/templates/jte/test.jte b/javalin/src/test/resources/templates/jte/test.jte
new file mode 100644
index 00000000..82ad6b50
--- /dev/null
+++ b/javalin/src/test/resources/templates/jte/test.jte
@@ -0,0 +1,3 @@
+@import io.javalin.TestTemplates.JteTestPage
+@param JteTestPage page
+<h1>${page.getHello()} ${page.getWorld()}!</h1>