The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Qute テンプレートエンジン

Quteは、Quarkus のニーズを満たすために特別に設計されたテンプレートエンジンです。ネイティブイメージのサイズを小さくするために、リフレクションの使用は最小限に抑えられています。API は、命令型とノンブロッキングリアクティブ型の両方のスタイルのコーディングを組み合わせています。開発モードでは、 src/main/resources/templates にあるすべてのファイルが変更のために監視され、変更はすぐに表示されます。さらに、ビルド時にテンプレートの問題のほとんどを検出するようにしています。このガイドでは、アプリケーションでテンプレートを簡単にレンダリングする方法を学びます。

ソリューション

次のセクションの指示に従って、段階的にアプリケーションを作成していくことをお勧めします。ただし、完成した例にすぐにアクセスすることもできます。

Gitリポジトリをクローンする: git clone https://github.com/quarkusio/quarkus-quickstarts.git 、または archive をダウンロードする。

このソリューションは qute-quickstart directory にあります。

JAX-RS で Hello World

JAX-RS アプリケーションで Qute を使用する場合は、まずエクステンションを追加する必要があります:

  • RESTEasy Reactive を使用している場合は quarkus-resteasy-reactive-qute:

    pom.xml
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-reactive-qute</artifactId>
    </dependency>
    build.gradle
    implementation("io.quarkus:quarkus-resteasy-reactive-qute")
  • RESTEasy Classicを使用している場合は、 quarkus-resteasy-qute :

    pom.xml
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-qute</artifactId>
    </dependency>
    build.gradle
    implementation("io.quarkus:quarkus-resteasy-qute")

まずはとてもシンプルなテンプレートから:

hello.text
Hello {name}! (1)
1 {name} は、テンプレートがレンダリングされたときに評価される値の式です。
デフォルトでは、 src/main/resources/templates ディレクトリーとそのサブディレクトリーにあるすべてのファイルがテンプレートとして登録されます。開発モードではテンプレートは起動時に検証され、変更が監視されます。

では、リソースクラスに「コンパイル済み」テンプレートを注入してみましょう。

HelloResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello; (1)

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return hello.data("name", name); (2) (3)
    }
}
1 @Location 修飾子が指定されていない場合は、フィールド名がテンプレートの場所を特定するために使用されます。このケースでは、パスが templates/hello.txt であるテンプレートを注入しています。
2 Template.data() は、実際のレンダリングがトリガーされる前にカスタマイズできる新しいテンプレートインスタンスを返します。この場合、名前の値をキー name の下に置きます。データマップはレンダリング中にアクセス可能です。
3 レンダリングをトリガーしないことに注意してください - これは特別な ContainerResponseFilter の実装によって自動的に行われます。

アプリケーションが動作している場合は、エンドポイントを要求することができます:

$ curl -w "\n" http://localhost:8080/hello?name=Martin
Hello Martin!

タイプセーフテンプレート

Java コードでテンプレートを宣言する別の方法もあり、以下の規約に依存しています。

  • テンプレートファイルを /src/main/resources/templates ディレクトリーに整理し、リソースクラスごとに 1 つのディレクトリーにグループ化します。つまり、 ItemResource クラスが hellogoodbye の 2 つのテンプレートを参照している場合は、 /src/main/resources/templates/ItemResource/hello.txt/src/main/resources/templates/ItemResource/goodbye.txt に配置します。リソースクラスごとにテンプレートをグループ化することで、テンプレートへのナビゲートが容易になります。

  • 各リソースクラスで、リソースクラス内で @CheckedTemplate static class Template {} クラスを宣言します。

  • リ ソ ース のテ ン プ レー ト フ ァ イ ルごとに public static native TemplateInstance method(); を 1 つずつ宣言 し ます。

  • テンプレートインスタンスを構築するには、これらの静的メソッドを使用します。

先ほどの例を、このスタイルで書き換えてみました:

まずはとてもシンプルなテンプレートから:

HelloResource/hello.txt
Hello {name}! (1)
1 {name} は、テンプレートがレンダリングされたときに評価される値の式です。

では、これらのテンプレートをリソースクラスで宣言して使ってみましょう。

HelloResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.CheckedTemplate;

@Path("hello")
public class HelloResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance hello(String name); (1)
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return Templates.hello(name); (2)
    }
}
1 これは、パス templates/HelloResource/hello.txt でテンプレートを宣言します。
2 Templates.hello() は、リソースメソッドから返される新しいテンプレートインスタンスを返します。 レンダリングをトリガーしないことに注意してください。これは、特別な `ContainerResponseFilter`実装によって自動的に行われます。
@CheckedTemplate クラスを宣言したら、すべてのメソッドが既存のテンプレートを指しているかどうかをチェックしますので、もし Java コードからテンプレートを使おうとして追加するのを忘れてしまった場合は、ビルド時にお知らせします :)

このスタイルの宣言では、他のリソースで宣言されたテンプレートも参照できることを覚えておいてください。

HelloResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import io.quarkus.qute.TemplateInstance;

@Path("goodbye")
public class GoodbyeResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return HelloResource.Templates.hello(name);
    }
}

トップレベルのタイプセーフテンプレート

Naturally, if you want to declare templates at the top-level, directly in /src/main/resources/templates/hello.txt, for example, you can declare them in a top-level (non-nested) Templates class:

HelloResource.java
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@CheckedTemplate
public class Templates {
    public static native TemplateInstance hello(String name); (1)
}
1 これは、パス templates/hello.txt でテンプレートを宣言します。

テンプレートパラメーター宣言

テンプレートで パラメーター宣言 を宣言した場合、Quteはこのパラメーターを参照するすべての式の検証を試み、間違った式が見つかった場合はビルドが失敗します。

このようなシンプルなクラスがあったとしましょう:

Item.java
public class Item {
    public String name;
    public BigDecimal price;
}

そして、商品名と価格が記載されたシンプルな HTML ページをレンダリングしたいと思います。

改めてテンプレートから始めてみましょう。

ItemResource/item.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title> (1)
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div> (2)
</body>
</html>
1 この式は検証済みです。式を {item.nonSense} に変更するとビルドに失敗します。
2 これも検証されています。

最後に、タイプセーフなテンプレートを使ってリソースクラスを作ってみましょう。

ItemResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@Path("item")
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance item(Item item); (1)
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(Integer id) {
        return Templates.item(service.findItem(id)); (2)
    }
}
1 templates/ItemResource/item.html に対して TemplateInstance を与えるメソッドを宣言し、その Item item パラメーターを宣言することで、テンプレートを検証することができます。
2 テンプレート内で Item オブジェクトにアクセスできるようにします。

テンプレート内部のテンプレートパラメーター宣言

あるいは、テンプレートファイル自体でテンプレートパラメーターを宣言することもできます。

改めてテンプレートから始めてみましょう。

item.html
{@org.acme.Item item} (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title> (2)
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div>
</body>
</html>
1 オプションのパラメーター宣言。Qute は、パラメーター item を参照するすべての式の検証を試みます。
2 この式は検証済みです。式を {item.nonSense} に変更するとビルドに失敗します。

最後に、リソースクラスを作成してみましょう。

ItemResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("item")
public class ItemResource {

    @Inject
    ItemService service;

    @Inject
    Template item; (1)

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(Integer id) {
        return item.data("item", service.findItem(id)); (2)
    }
}
1 パスが templates/item.html のテンプレートを注入します。
2 テンプレート内で Item オブジェクトにアクセスできるようにします。

テンプレート拡張メソッド

テンプレート拡張メソッド は 、データオブジェクトのアクセス可能なプロパティーのセットを拡張するために使用されます。

時には、テンプレートで使用したいクラスをコントロールできず、それらのクラスにメソッドを追加できないことがあります。テンプレート拡張メソッドを使うと、テンプレートから利用できるようになるクラスのメソッドを、あたかもターゲットクラスに属しているかのように宣言することができます。

アイテム名、価格、値引き価格を含むシンプルな HTML ページを拡張していきましょう。値引き価格は「計算プロパティー」と呼ばれることがあります。このプロパティーを簡単にレンダリングするためのテンプレート拡張メソッドを実装します。テンプレートを更新してみましょう。

HelloResource/item.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title>
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div>
    {#if item.price > 100} (1)
    <div>Discounted Price: {item.discountedPrice}</div> (2)
    {/if}
</body>
</html>
1 if は、基本的な制御フローのセクションです。
2 この式は Item クラスに対しても検証されており、明らかにそのようなプロパティーは宣言されていません。しかし、 TemplateExtensions クラスにはテンプレート拡張メソッドが宣言されています - 以下を参照してください。

最後に、すべての拡張メソッドを置くクラスを作りましょう。

TemplateExtensions.java
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateExtension;

@TemplateExtension
public class TemplateExtensions {

    public static BigDecimal discountedPrice(Item item) { (1)
        return item.price.multiply(new BigDecimal("0.9"));
    }
}
1 静的テンプレート拡張メソッドを使用して、データクラスに「計算プロパティー」を追加することができます。最初のパラメーターのクラスはベースオブジェクトと一致するように使用され、メソッド名はプロパティー名と一致するように使用されます。
テンプレート拡張メソッドは、 @TemplateExtension でアノテーションを付ければ、すべてのクラスに配置することができますが、規約上、ターゲットタイプでグループ化するか、 TemplateExtensions クラスにまとめておくことをお勧めします。

定期レポートのレンダリング

テンプレーティングエンジンは、定期的なレポートをレンダリングする際にも非常に便利かもしれません。最初に quarkus-schedulerquarkus-qute のエクステンションを追加する必要があります。 pom.xml ファイルに追加します。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-scheduler</artifactId>
</dependency>

Let’s suppose we have a SampleService bean whose get() method returns a list of samples.

Sample.java
public class Sample {
    public boolean valid;
    public String name;
    public String data;
}

テンプレートはシンプルです:

report.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Report {now}</title>
</head>
<body>
    <h1>Report {now}</h1>
    {#for sample in samples} (1)
      <h2>{sample.name ?: 'Unknown'}</h2> (2)
      <p>
      {#if sample.valid}
        {sample.data}
      {#else}
        <strong>Invalid sample found</strong>.
      {/if}
      </p>
    {/for}
</body>
</html>
1 ループセクションでは、イテレーション、マップ、ストリームの反復処理が可能になります。
2 この値式は elvis 演算子 を使用します - nameが NULL の場合はデフォルト値が使用されます。
ReportGenerator.java
package org.acme.quarkus.sample;

import javax.inject.Inject;

import io.quarkus.qute.Template;
import io.quarkus.qute.Location;
import io.quarkus.scheduler.Scheduled;

public class ReportGenerator {

    @Inject
    SampleService service;

    @Location("reports/v1/report_01") (1)
    Template report;

    @Scheduled(cron="0 30 * * * ?") (2)
    void generate() {
        String result = report
            .data("samples", service.get())
            .data("now", java.time.LocalDateTime.now())
            .render(); (3)
        // Write the result somewhere...
    }
}
1 この場合、 @Location 修飾子を使用してテンプレートのパスを指定します: templates/reports/v1/report_01.html .
2 @Scheduled アノテーションを使用して、Quarkusにこのメソッドを30分毎に実行するよう指示します。詳細については、 スケジューラーガイド を参照してください。
3 TemplateInstance.render() メソッドはレンダリングをトリガします。このメソッドは現在のスレッドをブロックすることに注意してください。

リアクティブと非同期API

テンプレートは、 CompletionStage<String> (非同期にレンダリングされた出力で完了)としてレンダリングすることも、レンダリングされたチャンクを含む Publisher<String> としてレンダリングすることもできます。

CompletionStage<String> async = template.data("name", "neo").renderAsync();
Publisher<String> publisher = template.data("name", "neo").publisher();

Publisher の場合、テンプレートはサブスクライバーからのリクエストに応じてチャンクごとにレンダリングされます。レンダリングは、サブスクライバーがリクエストするまで開始されません。返された Publisherio.smallrye.mutiny.Multi のインスタンスです。

以下のように、 io.smallrye.mutiny.Uni のインスタンスを作成することができます:

Uni<String> uni = Uni.createFrom().completionStage(() -> template.data("name", "neo").renderAsync());

この場合、サブスクライバーが要求した場合にのみレンダリングが開始されます。

Qute リファレンスガイド

Qute について詳しく知りたい方は、 Qute リファレンスガイド を参考にしてください。

Qute 設定リファレンス

ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能

Configuration property

タイプ

デフォルト

The list of suffixes used when attempting to locate a template file.

By default, engine.getTemplate("foo") would result in several lookups: foo, foo.html, foo.txt, etc.

Environment variable: QUARKUS_QUTE_SUFFIXES

list of string

qute.html,qute.txt,html,txt

The list of exclude rules used to intentionally ignore some parts of an expression when performing type-safe validation. An element value must have at least two parts separated by dot. The last part is used to match the property/method name. The prepended parts are used to match the class name. The value * can be used to match any name. Examples: - org.acme.Foo.name - exclude the property/method name on the org.acme.Foo class - org.acme.Foo.* - exclude any property/method on the org.acme.Foo class - *.age - exclude the property/method age on any class

Environment variable: QUARKUS_QUTE_TYPE_CHECK_EXCLUDES

list of string

This regular expression is used to exclude template files from the templates directory. Excluded templates are neither parsed nor validated during build and are not available at runtime. The matched input is the file path relative from the templates directory and the / is used as a path separator. By default, the hidden files are excluded. The name of a hidden file starts with a dot.

Environment variable: QUARKUS_QUTE_TEMPLATE_PATH_EXCLUDE

Pattern

^\..|.\/\..*$

The prefix is used to access the iteration metadata inside a loop section. A valid prefix consists of alphanumeric characters and underscores. Three special constants can be used: - <alias_> - the alias of an iterated element suffixed with an underscore is used, e.g. item_hasNext and it_count - <alias?> - the alias of an iterated element suffixed with a question mark is used, e.g. item?hasNext and it?count - <none> - no prefix is used, e.g. hasNext and count By default, the <alias_> constant is set.

Environment variable: QUARKUS_QUTE_ITERATION_METADATA_PREFIX

string

<alias_>

The list of content types for which the ', ", <, > and & characters are escaped if a template variant is set.

Environment variable: QUARKUS_QUTE_ESCAPE_CONTENT_TYPES

list of string

text/html,text/xml,application/xml,application/xhtml+xml

The default charset of the templates files.

Environment variable: QUARKUS_QUTE_DEFAULT_CHARSET

Charset

UTF-8

The strategy used when a standalone expression evaluates to a "not found" value at runtime and the io.quarkus.qute.strict-rendering config property is set to false This strategy is never used when evaluating section parameters, e.g. {#if foo.name}. In such case, it’s the responsibility of the section to handle this situation appropriately. By default, the NOT_FOUND constant is written to the output. However, in the development mode the PropertyNotFoundStrategy#THROW_EXCEPTION is used by default, i.e. when the strategy is not specified.

Environment variable: QUARKUS_QUTE_PROPERTY_NOT_FOUND_STRATEGY

defaultOutput the NOT_FOUND constant., noopNo operation - no output., throw-exceptionThrow a TemplateException., output-originalOutput the original expression string, e.g. {foo.name}.

Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at least one section tag, parameter declaration, or comment but no expression and no non-whitespace character.

Environment variable: QUARKUS_QUTE_REMOVE_STANDALONE_LINES

boolean

true

If set to true then any expression that is evaluated to a Results.NotFound value will always result in a TemplateException and the rendering is aborted. Note that the quarkus.qute.property-not-found-strategy config property is completely ignored if strict rendering is enabled.

Environment variable: QUARKUS_QUTE_STRICT_RENDERING

boolean

true

The global rendering timeout in milliseconds. It is used if no timeout template instance attribute is set.

Environment variable: QUARKUS_QUTE_TIMEOUT

long

10000

If set to true then the timeout should also be used for asynchronous rendering methods, such as TemplateInstance#createUni() and TemplateInstance#renderAsync().

Environment variable: QUARKUS_QUTE_USE_ASYNC_TIMEOUT

boolean

true

The additional map of suffixes to content types. This map is used when working with template variants. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content type of a template file.

Environment variable: QUARKUS_QUTE_CONTENT_TYPES

Map<String,String>