|
| 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] |
0 commit comments