Skip to content

Commit 848d202

Browse files
committed
Introduce Qute extensions
- qute core as an independent project - qute core extension - qute resteasy extension - first version of guide
1 parent 73e72c4 commit 848d202

File tree

135 files changed

+10591
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+10591
-0
lines changed

bom/deployment/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,17 @@
508508
<artifactId>quarkus-scala-deployment</artifactId>
509509
<version>${project.version}</version>
510510
</dependency>
511+
512+
<dependency>
513+
<groupId>io.quarkus</groupId>
514+
<artifactId>quarkus-qute-deployment</artifactId>
515+
<version>${project.version}</version>
516+
</dependency>
517+
<dependency>
518+
<groupId>io.quarkus</groupId>
519+
<artifactId>quarkus-qute-resteasy-deployment</artifactId>
520+
<version>${project.version}</version>
521+
</dependency>
511522

512523
<!-- Quarkus test dependencies -->
513524

bom/runtime/pom.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,16 @@
739739
<artifactId>quarkus-test-kubernetes-client</artifactId>
740740
<version>${project.version}</version>
741741
</dependency>
742+
<dependency>
743+
<groupId>io.quarkus</groupId>
744+
<artifactId>quarkus-qute</artifactId>
745+
<version>${project.version}</version>
746+
</dependency>
747+
<dependency>
748+
<groupId>io.quarkus</groupId>
749+
<artifactId>quarkus-qute-resteasy</artifactId>
750+
<version>${project.version}</version>
751+
</dependency>
742752

743753
<!-- External dependencies -->
744754

@@ -2460,6 +2470,23 @@
24602470
<artifactId>quarkus-vertx-graphql</artifactId>
24612471
<version>${project.version}</version>
24622472
</dependency>
2473+
2474+
<!-- Qute -->
2475+
<dependency>
2476+
<groupId>io.quarkus.qute</groupId>
2477+
<artifactId>qute-core</artifactId>
2478+
<version>${project.version}</version>
2479+
</dependency>
2480+
<dependency>
2481+
<groupId>io.quarkus.qute</groupId>
2482+
<artifactId>qute-generator</artifactId>
2483+
<version>${project.version}</version>
2484+
</dependency>
2485+
<dependency>
2486+
<groupId>io.quarkus.qute</groupId>
2487+
<artifactId>qute-rxjava</artifactId>
2488+
<version>${project.version}</version>
2489+
</dependency>
24632490
</dependencies>
24642491
</dependencyManagement>
24652492
</project>

core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
4646
public static final String REACTIVE_MYSQL_CLIENT = "reactive-mysql-client";
4747
public static final String NEO4J = "neo4j";
4848
public static final String OIDC = "oidc";
49+
public static final String QUTE = "qute";
4950
public static final String RESTEASY = "resteasy";
5051
public static final String RESTEASY_JACKSON = "resteasy-jackson";
5152
public static final String RESTEASY_JAXB = "resteasy-jaxb";

docs/src/main/asciidoc/qute.adoc

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
////
2+
This guide is maintained in the main Quarkus repository
3+
and pull requests should be submitted there:
4+
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
5+
////
6+
= Qute Templating Engine
7+
8+
include::./attributes.adoc[]
9+
10+
Qute is a templating engine designed specifically to meet the Quarkus needs.
11+
The usage of reflection is minimized to reduce the size of native images.
12+
The API combines both the imperative and the non-blocking reactive style of coding.
13+
In the development mode, all files located in `META-INF/resources/templates` are watched for changes and modifications are immediately visible.
14+
Furthermore, we try to detect most of the template problems at build time.
15+
In this guide, you will learn how to easily render templates in your application.
16+
17+
[NOTE]
18+
====
19+
This extension is considered `preview`.
20+
API or configuration properties might change as the extension matures.
21+
Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker].
22+
====
23+
24+
== Hello World with JAX-RS
25+
26+
If you want to use Qute in your JAX-RS application, you need to add the `quarkus-qute-resteasy` extension first.
27+
In your `pom.xml` file, add:
28+
29+
[source,xml]
30+
----
31+
<dependency>
32+
<groupId>io.quarkus</groupId>
33+
<artifactId>quarkus-qute-resteasy</artifactId>
34+
</dependency>
35+
----
36+
37+
We'll start with a very simple template:
38+
39+
.hello.txt
40+
----
41+
Hello {name}! <1>
42+
----
43+
<1> `{name}` is a value expression that is evaluated when the template is rendered.
44+
45+
NOTE: By default, all files located in the `META-INF/resources/templates` directory and its subdirectories are registered as templates. Templates are validated during startup and watched for changes in the development mode.
46+
47+
Now let's inject the "compiled" template in the resource class.
48+
49+
.HelloResource.java
50+
[source,java]
51+
----
52+
package org.acme.quarkus.sample;
53+
54+
import javax.inject.Inject;
55+
import javax.ws.rs.GET;
56+
import javax.ws.rs.Path;
57+
import javax.ws.rs.QueryParam;
58+
59+
import io.quarkus.qute.TemplateInstance;
60+
import io.quarkus.qute.Template;
61+
62+
@Path("hello")
63+
public class HelloResource {
64+
65+
@Inject
66+
Template hello; <1>
67+
68+
@GET
69+
@Produces(MediaType.TEXT_PLAIN)
70+
public TemplateInstance get(@QueryParam("name") String name) {
71+
return hello.data("name", name); <2> <3>
72+
}
73+
}
74+
----
75+
<1> If there is no `@ResourcePath` qualifier provided the field name is used to locate the template. In this particular case, we're injecting a template with path `META-INF/resources/templates/hello.txt`.
76+
<2> `Template.data()` returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key `name`. The data map is accessible during rendering.
77+
<3> Note that we don't trigger the rendering - this is done automatically by a special `ContainerResponseFilter` implementation.
78+
79+
If running your application, you can request the endpoint:
80+
81+
```
82+
$ curl -w "\n" http://localhost:8080/hello?name=Martin
83+
Hello Martin!
84+
```
85+
86+
== Parameter Declarations and Template Extension Methods
87+
88+
Qute has many useful features.
89+
In this example, we'll demonstrate two of them.
90+
If you declare a *parameter declaration* in a template then Qute attempts to validate all expressions that reference this parameter and if an incorrect expression is found the build fails.
91+
*Template extension methods* are used to extend the set of accessible properties of data objects.
92+
93+
Let's suppose we have a simple class like this:
94+
95+
.Item.java
96+
[source,java]
97+
----
98+
public class Item {
99+
public String name;
100+
public BigDecimal price;
101+
}
102+
----
103+
104+
And we'd like to render a simple HTML page that contains the item name, price and also a discounted price.
105+
The discounted price is sometimes called a "computed property".
106+
We will implement a template extension method to render this property easily.
107+
Let's start again with the template:
108+
109+
.item.html
110+
[source,html]
111+
----
112+
{@org.acme.Item item} <1>
113+
<!DOCTYPE html>
114+
<html>
115+
<head>
116+
<meta charset="UTF-8">
117+
<title>{item.name}</title> <2>
118+
</head>
119+
<body>
120+
<h1>{item.name}</h1>
121+
<div>Price: {item.price}</div>
122+
{#if item.price > 100} <3>
123+
<div>Discounted Price: {item.discountedPrice}</div> <4>
124+
{/if}
125+
</body>
126+
</html>
127+
----
128+
<1> Optional parameter declaration. Qute attempts to validate all expressions that reference the parameter `item`.
129+
<2> This expression is validated. Try to change the expression to `{item.nonSense}` and the build should fail.
130+
<3> `if` is a basic control flow section.
131+
<4> This expression is also validated against the `Item` class and obviously there is no such property declared. However, there is a template extension method declared on the `ItemResource` class - see below.
132+
133+
Finally, let's create a resource class.
134+
135+
.ItemResource.java
136+
[source,java]
137+
----
138+
package org.acme.quarkus.sample;
139+
140+
import javax.inject.Inject;
141+
import javax.ws.rs.GET;
142+
import javax.ws.rs.Path;
143+
import javax.ws.rs.PathParam;
144+
import javax.ws.rs.Produces;
145+
import javax.ws.rs.core.MediaType;
146+
147+
import io.quarkus.qute.TemplateInstance;
148+
import io.quarkus.qute.Template;
149+
150+
@Path("item")
151+
public class ItemResource {
152+
153+
@Inject
154+
ItemService service;
155+
156+
@Inject
157+
Template item; <1>
158+
159+
@GET
160+
@Path("{id}")
161+
@Produces(MediaType.TEXT_HTML)
162+
public TemplateInstance get(@PathParam("id") Integer id) {
163+
return item.data("item", service.findItem(id)); <2>
164+
}
165+
166+
@TemplateExtension <3>
167+
static BigDecimal discountedPrice(Item item) {
168+
return item.price.multiply(new BigDecimal("0.9"));
169+
}
170+
}
171+
----
172+
<1> Inject the template with path `META-INF/resources/templates/item.html`.
173+
<2> Make the `Item` object accessible in the template.
174+
<3> A static template extension method can be used to add "computed properties" to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name.
175+
176+
== Rendering Periodic Reports
177+
178+
Templating engine could be also very useful when rendering periodic reports.
179+
You'll need to add `quarkus-scheduler` and `quarkus-qute` extensions first.
180+
In your `pom.xml` file, add:
181+
182+
[source,xml]
183+
----
184+
<dependency>
185+
<groupId>io.quarkus</groupId>
186+
<artifactId>quarkus-qute</artifactId>
187+
</dependency>
188+
<dependency>
189+
<groupId>io.quarkus</groupId>
190+
<artifactId>quarkus-scheduler</artifactId>
191+
</dependency>
192+
----
193+
194+
Let's suppose the have a `SampleService` bean whose `get()` method returns a list of samples.
195+
196+
.Sample.java
197+
[source,java]
198+
----
199+
public class Sample {
200+
public boolean valid;
201+
public String name;
202+
public String data;
203+
}
204+
----
205+
206+
The template is simple:
207+
208+
.report.html
209+
[source,html]
210+
----
211+
<!DOCTYPE html>
212+
<html>
213+
<head>
214+
<meta charset="UTF-8">
215+
<title>Report {now}</title>
216+
</head>
217+
<body>
218+
<h1>Report {now}</h1>
219+
{#for sample in samples} <1>
220+
<h2>{sample.name ?: 'Unknown'}</h2> <2>
221+
<p>
222+
{#if sample.valid}
223+
{sample.data}
224+
{#else}
225+
<strong>Invalid sample found</strong>.
226+
{/if}
227+
</p>
228+
{/for}
229+
</body>
230+
</html>
231+
----
232+
<1> The loop section makes it possible to iterate over iterables, maps and streams.
233+
<2> This value expression is using https://en.wikipedia.org/wiki/Elvis_operator[elvis operator] - if the name is null the default value is used.
234+
235+
[source,java]
236+
.ReportGenerator.java
237+
----
238+
package org.acme.quarkus.sample;
239+
240+
import javax.inject.Inject;
241+
242+
import io.quarkus.qute.Template;
243+
import io.quarkus.qute.api.ResourcePath;
244+
import io.quarkus.scheduler.Scheduled;
245+
246+
public class ReportGenerator {
247+
248+
@Inject
249+
SampleService service;
250+
251+
@ResourcePath("reports/v1/report_01") <1>
252+
Template report;
253+
254+
@Scheduled(cron="0 30 * * * ?") <2>
255+
void generate() {
256+
String result = report
257+
.data("samples", service.get())
258+
.data("now", java.time.LocalDateTime.now())
259+
.render(); <3>
260+
// Write the result somewhere...
261+
}
262+
}
263+
----
264+
<1> In this case, we use the `@ResourcePath` qualifier to specify the template path: `META-INF/resources/templates/reports/v1/report_01.html`.
265+
<2> Use the `@Scheduled` annotation to instruct Quarkus to execute this method on the half hour. For more information see the link:scheduler[Scheduler] guide.
266+
<3> The `TemplateInstance.render()` method triggers rendering. Note that this method blocks the current thread.
267+
268+
[[qute-configuration-reference]]
269+
== Qute Configuration Reference
270+
271+
include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional]

extensions/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@
132132

133133
<!-- Logging -->
134134
<module>logging-json</module>
135+
136+
<!-- Templating -->
137+
<module>qute</module>
138+
<module>qute-resteasy</module>
135139
</modules>
136140

137141
<!-- Unfortunately the config below introduces a build dependency on the maven plugin

0 commit comments

Comments
 (0)