Skip to content

Commit

Permalink
jte: model support
Browse files Browse the repository at this point in the history
- fix #3602
- ref #3599
  • Loading branch information
jknack committed Dec 22, 2024
1 parent af81aea commit 11c6378
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 10 deletions.
127 changes: 119 additions & 8 deletions docs/asciidoc/modules/jte.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ https://jte.gg[Jte] Secure and speedy templates for Java and Kotlin.
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
<version>${jte.version}</version>
<version>{jte_version}</version>
<configuration>
<sourceDirectory>${basedir}/src/main/jte</sourceDirectory> <!-- This is the directory where your .jte files are located. -->
<contentType>Html</contentType>
Expand All @@ -38,11 +38,11 @@ https://jte.gg[Jte] Secure and speedy templates for Java and Kotlin.
----
plugins {
id 'java'
id 'gg.jte.gradle' version '${jte.version}'
id 'gg.jte.gradle' version '{jte_version}'
}
dependencies {
implementation('gg.jte:jte:${jte.version}')
implementation('gg.jte:jte:{jte_version}')
}
jte {
Expand Down Expand Up @@ -70,10 +70,10 @@ NOTE: Complete code generator options are https://github.com/casid/jte/blob/main
import io.jooby.jte.JteModule;
{
install(new JteModule(Paths.of("src", "main", "jte"))); <1>
install(new JteModule(Paths.get("src", "main", "jte"))); <1>
get("/", ctx -> {
return new ModelAndView("hello.jte", Map.of("name", "Jte")); <2>
return new MapModelAndView("hello.jte", Map.of("name", "Jte")); <2>
});
}
----
Expand All @@ -84,10 +84,10 @@ import io.jooby.jte.JteModule;
import io.jooby.jte.JteModule
{
install(JteModule(Paths.of("src", "main", "jte"))) <1>
install(JteModule(Paths.get("src", "main", "jte"))) <1>
get("/") {
ModelAndView("hello.jte", Map.of("name", "Jte")) <2>
MapModelAndView("hello.jte", Map.of("name", "Jte")) <2>
}
}
----
Expand All @@ -100,13 +100,121 @@ will put all the generated classes in `src/main/jte/jte-classes`.

In production will read the classes from classpath.

=== Models

jte-models is a generator extension for jte that creates a typesafe facade for rendering templates.

1) Add the dependency:

[dependency, groupId="gg.jte", artifactId="jte-models", version="jte.version"]
.

2) Configure code generator

.Maven
[source,xml,role="primary",subs="verbatim,attributes"]
----
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
<version>{jte_version}</version>
<configuration>
<sourceDirectory>${project.basedir}/src/main/jte</sourceDirectory>
<contentType>Html</contentType>
<extensions>
<extension>
<className>gg.jte.models.generator.ModelExtension</className>
</extension>
</extensions>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-models</artifactId>
<version>{jte_version}</version>
</dependency>
</dependencies>
</plugin>
----

.Gradle
[source,groovy,role="secondary",subs="verbatim,attributes"]
----
plugins {
id 'gg.jte.gradle' version '{jte_version}'
}
dependencies {
implementation 'gg.jte:jte-runtime:{jte_version}'
jteGenerate 'gg.jte:jte-models:{jte_version}'
}
jte {
generate()
binaryStaticContent = true
jteExtension 'gg.jte.models.generator.ModelExtension'
}
----

==== Usage

.Java
[source,java,role="primary"]
----
import io.jooby.jte.JteModule;
{
install(new JteModule(Paths.get("src", "main", "jte")));
get("/static", ctx -> {
var templates = new StaticTemplates();
return templates.helloWorld("Hi!");
});
get("/dynamic", ctx -> {
var templates = new DynamicTemplates(require(TemplateEngine.class));
return templates.helloWorld("Hi!");
});
}
----

.Kotlin
[source, kt, role="secondary"]
----
import io.jooby.jte.JteModule
{
install(JteModule(Paths.get("src", "main", "jte")))
get("/static") {
val templates = StaticTemplates()
templates.helloWorld("Hi!")
}
get("/dynamic") {
val templates = DynamicTemplates(require(TemplateEngine::class))
templates.helloWorld("Hi!")
}
}
----

More at https://jte.gg/jte-models/[jte-models].

=== Options

==== Custom class directory

If you prefer a custom directory for compiled templates you need to do use:

install(new JteModule(Paths.of("src", "main", "jte"), Paths.of("compiled-templates")));
install(new JteModule(Paths.get("src", "main", "jte"), Paths.get("compiled-templates")));

Also, you need to configure Maven or Gradle to generate templates classes:

Expand Down Expand Up @@ -156,5 +264,8 @@ You need to make sure to copy the `compiled-templates` folder as part of your de

It is possible to provide your own/custom template engine:

[source,java]
----
var templateEngine = TemplateEngine.create(...) or TemplateEngine.createPrecompiled(..)
install(new JteModule(templateEngine));
----
13 changes: 13 additions & 0 deletions docs/src/main/java/io/jooby/adoc/Dependencies.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.jsoup.Jsoup;
Expand All @@ -38,13 +40,16 @@ public String toString() {

private Map<String, Dependency> dependencyMap = new TreeMap<>();

private Map<String, String> properties = new TreeMap<>();

private static final Dependencies instance = new Dependencies();

private Dependencies() {
try {
for (Document pom : pomList()) {
collectDependencies(pom, pom.select("dependencyManagement").select("dependencies"));
collectDependencies(pom, pom.select("dependencies"));
properties(pom, properties::putIfAbsent);
}
} catch (IOException x) {
throw SneakyThrows.propagate(x);
Expand All @@ -70,6 +75,10 @@ public static Dependencies.Dependency get(String artifactId) {
return dep;
}

public static String version(String property) {
return instance.properties.getOrDefault(property, property);
}

private List<Document> pomList() throws IOException {
List<Document> poms = new ArrayList<>();
Document jooby =
Expand Down Expand Up @@ -122,4 +131,8 @@ private static String findVersion(Document pom, String artifactId, String versio
}
return versionRef;
}

private static void properties(Document pom, BiConsumer<String, String> properties) {
pom.select("properties > *").forEach(e -> properties.accept(e.tagName(), e.text()));
}
}
2 changes: 2 additions & 0 deletions docs/src/main/java/io/jooby/adoc/DependencyProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public Object process(StructuralNode parent, Reader reader, Map<String, Object>
throw new IllegalArgumentException("Dependency without version: " + groupId + ":" + Arrays.toString(artifactId));
}
}
} else {
version = Dependencies.version(version);
}
maven(
groupId,
Expand Down
9 changes: 8 additions & 1 deletion modules/jooby-jte/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte</artifactId>
<version>3.1.15</version>
<version>${jte.version}</version>
</dependency>

<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-models</artifactId>
<version>${jte.version}</version>
<optional>true</optional>
</dependency>

<!-- Test dependencies -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.internal.jte;

import java.nio.charset.StandardCharsets;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import gg.jte.models.runtime.JteModel;
import io.jooby.Context;
import io.jooby.buffer.DataBuffer;

public class JteModelEncoder implements io.jooby.MessageEncoder {
@Nullable @Override
public DataBuffer encode(@NonNull Context ctx, @NonNull Object value) throws Exception {
if (value instanceof JteModel jte) {
var buffer = ctx.getBufferFactory().allocateBuffer();
var output = new DataBufferOutput(buffer, StandardCharsets.UTF_8);
jte.render(output);
return buffer;
}
return null;
}
}
6 changes: 5 additions & 1 deletion modules/jooby-jte/src/main/java/io/jooby/jte/JteModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
import io.jooby.Jooby;
import io.jooby.MediaType;
import io.jooby.ServiceRegistry;
import io.jooby.internal.jte.JteModelEncoder;

/**
* Jte templates: https://jte.gg.
* Jte templates: https://jooby.io/modules/jte
*
* <pre>
* </pre>
Expand Down Expand Up @@ -85,7 +86,10 @@ public void install(@NonNull Jooby application) {

ServiceRegistry services = application.getServices();
services.put(TemplateEngine.class, templateEngine);
// model and view
application.encoder(MediaType.html, new JteTemplateEngine(templateEngine));
// jte models
application.encoder(new JteModelEncoder());
}

/**
Expand Down
1 change: 1 addition & 0 deletions modules/jooby-jte/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
requires static com.github.spotbugs.annotations;
requires gg.jte;
requires gg.jte.runtime;
requires static gg.jte.models;
}
42 changes: 42 additions & 0 deletions modules/jooby-jte/src/test/java/io/jooby/jte/Issue3602.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.jte;

import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.*;

import java.util.Map;

import org.junit.jupiter.api.Test;

import gg.jte.TemplateOutput;
import gg.jte.models.runtime.JteModel;
import io.jooby.Context;
import io.jooby.buffer.DataBuffer;
import io.jooby.buffer.DataBufferFactory;
import io.jooby.internal.jte.JteModelEncoder;

public class Issue3602 {

@Test
public void shouldRenderJteModel() throws Exception {
var bufferFactory = mock(DataBufferFactory.class);
var buffer = mock(DataBuffer.class);
when(bufferFactory.allocateBuffer()).thenReturn(buffer);

var attributes = Map.<String, Object>of("foo", 1);
var ctx = mock(Context.class);
when(ctx.getBufferFactory()).thenReturn(bufferFactory);
when(ctx.getAttributes()).thenReturn(attributes);

var model = mock(JteModel.class);

var engine = new JteModelEncoder();
engine.encode(ctx, model);

verify(model, times(1)).render(isA(TemplateOutput.class));
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<yasson.version>3.0.4</yasson.version>
<rocker.version>2.1.0</rocker.version>
<thymeleaf.version>3.1.3.RELEASE</thymeleaf.version>
<jte.version>3.1.15</jte.version>

<!-- data -->
<HikariCP.version>6.2.1</HikariCP.version>
Expand Down

0 comments on commit 11c6378

Please sign in to comment.