Qute Reference Guide
Qute は、Quarkus のニーズを満たすために特別に設計されたテンプレートエンジンです。ネイティブイメージのサイズを小さくするために、リフレクションの使用は最小限に抑えられています。API は、命令型とノンブロッキングリアクティブ型の両方のスタイルのコーディングを組み合わせています。開発モードでは、src/main/resources/templates フォルダーにあるすべてのファイルが変更があるかどうか監視され、変更はアプリケーションにすぐに表示されます。さらに、ビルド時にテンプレートの問題のほとんどの検出を試み、即座に失敗します。
このガイドでは、introductory example、core features の説明、Quarkus integration の詳細について説明しています。
| Qute is primarily designed as a Quarkus extension. It is possible to use it as a "standalone" library too. However, in such case some features are not available. In general, any feature mentioned under the Quarkus の統合 section is missing. You can find more information about the limitations and possibilities in the スタンドアローンライブラリーとして使用する場合のQute section. |
1. 最も簡単な例
Qute を試す最も簡単な方法は、便利な io.quarkus.qute.Qute クラスを使用し、簡単なメッセージをフォーマットするために使用できる fmt() 静的メソッドの 1 つを呼び出すことです。
import io.quarkus.qute.Qute;
Qute.fmt("Hello {}!", "Lucy"); (1)
// => Hello Lucy!
Qute.fmt("Hello {name} {surname ?: 'Default'}!", Map.of("name", "Andy")); (2)
// => Hello Andy Default!
Qute.fmt("<html>{header}</html>").contentType("text/html").data("header", "<h1>My header</h1>").render(); (3)
// <html><h1>Header</h1></html> (4)
Qute.fmt("I am {#if ok}happy{#else}sad{/if}!", Map.of("ok", true)); (5)
// => I am happy!
| 1 | 空の式 {} はプレースホルダーであり、インデックスベースの配列アクセスサー (例えば {data[0]}) に置き換えられます。 |
| 2 | 代わりにデータマップを提供することができます。 |
| 3 | より複雑なフォーマットを必要とする場合には、ビルダーライクな API が利用可能です。 |
| 4 | "text/html" テンプレートでは、デフォルトで特殊な char が html エンティティーに置き換えられることに注意してください。 |
| 5 | テンプレートには、任意の ビルディングブロック を使用することができます。この場合、セクションの場合 は、入力データに基づいてメッセージの適切な部分をレンダリングするために使用されます。 |
In Quarkus, the engine used to format the messages is the same as the one injected by @Inject Engine. Therefore, you can make use of any Quarkus-specific integration feature such as テンプレート拡張メソッド, テンプレートに直接Beansを注入 or even タイプセーフメッセージバンドル.
|
Qute.fmt(String) メソッドが返すフォーマットオブジェクトは、遅延評価され、たとえば、ログメッセージとして使用することができます。
LOG.info(Qute.fmt("Hello {name}!").data("name", "Foo"));
// => Hello Foo! and the message template is only evaluated if the log level INFO is used for the specific logger
Please read the javadoc of the io.quarkus.qute.Qute class for more details.
|
2. ハローワールドの例
この例では、Quteのテンプレートを扱う際の基本的なワークフローをデモしたいと思います。まず、簡単なhello worldの例から始めましょう。 テンプレートのコンテンツ は必ず必要になります。
<html>
<p>Hello {name}! (1)
</html>
| 1 | {name} は、テンプレートがレンダリングされたときに評価される値の式です。 |
そして、その内容を テンプレート定義 Javaオブジェクトにパースする必要があります。テンプレート定義は、 io.quarkus.qute.Template のインスタンスです。
Quteを"スタンドアロン"で使用する場合は、まず io.quarkus.qute.Engine のインスタンスを作成する必要があります。 Engine は、専用の設定でテンプレートを管理するための一元的なポイントを表しています。便利なビルダーを使ってみましょう。
Engine engine = Engine.builder().addDefaults().build();
In Quarkus, there is a preconfigured Engine available for injection - see Quarkus の統合.
|
Engine のインスタンスがあれば、テンプレートの内容を解析することができます。
Template hello = engine.parse(helloHtmlContent);
| In Quarkus, you can simply inject the template definition. The template is automatically parsed and cached - see Quarkus の統合. |
最後に テンプレートインスタンス を作成し、データをセットして出力をレンダリングします。
// Renders <html><p>Hello Jim!</p></html>
hello.data("name", "Jim").render(); (1) (2)
| 1 | Template.data(String, Object) は、ワンステップでテンプレートのインスタンスを作成してデータを設定する便利なメソッドです。 |
| 2 | TemplateInstance.render() は同期レンダリングをトリガーします。つまり、レンダリングが終了するまで現在のスレッドはブロックされます。しかし、レンダリングをトリガーして結果を消費する非同期のメソッドもあります。 例えば、 CompletionStage<String> を返す TemplateInstance.renderAsync() メソッドや、Mutiny の Multi<String> を返す TemplateInstance.createMulti() です。 |
なので、ワークフローはシンプルです。
-
テンプレートコンテンツを作成する (
hello.html) -
テンプレートの定義を解析する (
io.quarkus.qute.Template) -
テンプレートインスタンスを作成する (
io.quarkus.qute.TemplateInstance) -
出力をレンダリングする
The Engine is able to cache the template definitions so that it’s not necessary to parse the contents again and again. In Quarkus, the caching is done automatically.
|
3. コア機能
3.1. 基本的なビルディングブロック
テンプレートの動的なパーツには、コメント、式、セクション、パースされていない文字データなどがあります。
- Comments
-
コメントは
{!and ends with the sequence!}というシーケンスで終わり、例えば{! This is a comment !}のようになります。 複数行にすることができ、{! {#if true} !}のように式やセクションを含むことができます。コメントの内容は、出力をレンダリングする際に完全に無視されます。 - 式
-
expression は評価値を出力します。 これは 1つ以上のパーツから構成されます。 パーツは
{foo}、{item.name}のような単純なプロパティーや、{item.get(name)}、{name ?: 'John'}のような仮想メソッドを表わすことができます。 また、式は名前空間{inject:colors}で始まることもあります。 - セクション
-
セクション は静的テキスト、式、ネストされたセクションを含むことができます(
{#if foo.active}{foo.name}{/if})。クロージングタグの名前はオプションです({#if active}ACTIVE!{/})。セクションは空にすることができます({#myTag image=true /})。セクションはネストしたセクションブロックを宣言することもでき ({#if item.valid} Valid. {#else} Invalid. {/if})、どのブロックをレンダリングするかを決定します。 - パース対象外文字データ
-
It is used to mark the content that should be rendered but not parsed. It starts with the sequence
\{|and ends with the sequence|\}:\{| <script>if(true){alert('Qute is cute!')};</script> |\}, and could be multi-line.以前はパース対象外文字データを
{[and end with]}から始めることができました。この構文は、他の言語の構文とよく衝突するため、現在では削除されています。
3.2. 識別子とタグ
識別子は、式やセクションタグで使用されます。有効な識別子は、空白文字以外の文字列です。ただし、式の中では有効な Java 識別子のみを使用することが推奨されます。
You can use bracket notation if you need to specify an identifier that contains a dot, e.g. {map['my.key']}.
|
テンプレート文書を解析する際、パーサーはすべての tag を識別します。
タグは中括弧で始まり、中括弧で終わります (例: {foo})。
タグの内容は以下で始まる必要があります。
-
数字、または
-
アルファベット文字、または
-
アンダースコア、または
-
組み込みコマンド (
#、!、@、/)
上記のいずれかで始まらない場合は、パーサによって無視されます。
<html>
<body>
{_foo.bar} (1)
{! comment !}(2)
{ foo} (3)
{{foo}} (4)
{"foo":true} (5)
</body>
</html>
| 1 | パース対象: アンダースコアで始まる式 |
| 2 | パース対象: コメント |
| 3 | 無視対象: 空白で開始 |
| 4 | 無視対象: { で開始 |
| 5 | 無視対象: " で開始 |
It is also possible to use escape sequences \{ and \} to insert delimiters in the text. In fact, an escape sequence is usually only needed for the start delimiter, i.e. \{foo} will be rendered as {foo} (no parsing/evaluation will happen).
|
3.3. テンプレートから独立した行を削除する
デフォルトでは、パーサーはテンプレート出力からスタンドアロン行を削除します。
スタンドアロン行 とは、少なくとも 1 つのセクションタグ (例: {#each} や {/each})、パラメーター宣言 (例: {@org.acme.Foo foo})、またはコメントを含むけれど、式を含まず、空白以外の文字を含まない行のことになります。
言い換えれば、セクションタグやパラメーター宣言を含まない行は、スタンドアロン行ではありません。
同様に、式 または 非空白文字列 を含む行も スタンドアロン行ではありません。
<html>
<body>
<ul>
{#for item in items} (1)
<li>{item.name} {#if item.active}{item.price}{/if}</li> (2)
(3)
{/for} (4)
</ul>
<body>
</html>
| 1 | これはスタンドアロン行なので削除されます。 |
| 2 | スタンドアロン行ではない - 式と非空白文字を含む |
| 3 | スタンドアロン行ではない - セクションタグ/パラメーター宣言を含まない |
| 4 | これはスタンドアロン行です。 |
<html>
<body>
<ul>
<li>Foo 100</li>
</ul>
<body>
</html>
In Quarkus, the default behavior can be disabled by setting the property quarkus.qute.remove-standalone-lines to false.
In this case, all whitespace characters from a standalone line will be printed to the output.
|
quarkus.qute.remove-standalone-lines=false<html>
<body>
<ul>
<li>Foo 100</li>
</ul>
<body>
</html>
3.4. 式
式が評価され、値が出力されます。これには 1 つ以上のパーツがあり、各パーツはプロパティーアクセサー (別名 Field Access Expression) または仮想メソッド呼び出し (別名 Method Invocation Expression) のいずれかを表します。
プロパティーにアクセスする際には、ドット表記とブラケット表記のどちらかを使うことができます。 object.property (ドット表記) 構文では、 property は 有効な識別子 でなければなりません。 object[property_name] (ブラケット表記) 構文では、 property_name は null ではない リテラル 値でなければなりません。
式は、名前空間の後にコロン (:) を付けて開始することができます。有効な名前空間は英数字とアンダースコアで構成されます。名前空間の式は別の方法で解決されます。解決 も参照してください。
{name} (1)
{item.name} (2)
{item['name']} (3)
{global:colors} (4)
| 1 | 名前空間がなし、1 つの部分: name |
| 2 | 名前空間なし、2つの部分: item . name |
| 3 | {item.name} と同じですが、ブラケット表記を使います。 |
| 4 | 名前空間 global , 1つのパーツ: colors |
式の一部を 仮想メソッド とすることができ、その場合、名前の後にコンマで区切られたパラメーターのリストを括弧で囲むことができます。仮想メソッドのパラメーターは、ネストされた式か、リテラル 値のどちらかにすることができます。これらのメソッドを "仮想" と呼ぶのは、実際の Java メソッドによってバックアップされる必要がないためです。仮想メソッドの詳細については、次のセクション の仮想メソッドを参照してください。
{item.getLabels(1)} (1)
{name or 'John'} (2)
| 1 | 名前空間なし、2つのパーツ - item , getLabels(1) 、2番目のパーツは、名前 getLabels と パラメーター 1 を持つ仮想メソッドです。 |
| 2 | name.or('John') に翻訳される、単一のパラメーターを持つ仮想メソッドに使用できる 中置 (infix) 記法。名前空間なし、2 つのパーツ - name . or('John') |
3.4.1. サポートされているリテラル
| リテラル | 例 |
|---|---|
boolean |
|
null |
|
string |
|
integer |
|
long |
|
double |
|
float |
|
3.4.2. 解決
式の最初の部分は、常に カレントコンテキストオブジェクト に対して解決されます。最初の部分の結果が見つからない場合は、(利用可能な場合) 親コンテキストオブジェクトに対して解決されます。名前空間で始まる式の場合、カレントコンテキストオブジェクトは、利用可能なすべての NamespaceResolver を使用して検出されます。名前空間で始まらない式の場合、現在のコンテキストオブジェクトはタグの 位置から派生し ます。式の他のすべての部分は、前の解決結果に対してすべての ValueResolver を使用して解決されます。
たとえば、式 {name} には名前空間がなく、単一のパーツ - name があります。
"name" は、現在のコンテキストオブジェクトに対して利用可能なすべての値リゾルバーを使用して解決されます。
しかし、式 {global:colors} には、名前空間 global と単一パーツ - colors があります。
まず、現在のコンテキストオブジェクトを見つけるために、利用可能なすべての NamespaceResolver が使用されます。
その後、見つかったコンテキストオブジェクトに対して "colors" を解決するために値リゾルバーが使用されます。
|
テンプレート・インスタンスに渡されたデータは、常に
|
3.4.3. カレントコンテキスト
式が名前空間を指定しない場合、カレントコンテキストオブジェクト はタグの位置から派生します。デフォルトでは、現在のコンテキストオブジェクトは、テンプレートインスタンスに渡されたデータを表します。しかし、セクションは現在のコンテキストオブジェクトを変更することがあります。典型的な例は、let セクションで、名前付きローカル変数を定義するために使用することができます。
{#let myParent=order.item.parent myPrice=order.price} (1)
<h1>{myParent.name}</h1>
<p>Price: {myPrice}</p>
{/let}
| 1 | セクション内の現在のコンテキストオブジェクトは、解決されたパラメーターのマップです。 |
The current context can be accessed via the implicit binding this.
|
3.4.4. 組込リゾルバ
| 名前 | 説明 | 例 |
|---|---|---|
エルビス演算子 |
前のパーツが解決できなかったり、 |
|
orEmpty |
前のパーツが解決できなかったり、 |
|
Ternary Operator |
if-then-else 文の省略形。セクションの場合 とは異なり、入れ子式の演算子はサポートされていません。 |
|
論理 AND 演算子 |
セクションの場合 で説明したように、両方のパーツが |
|
論理 OR 演算子 |
if_section] で説明したように、いずれかのパーツが |
|
The condition in a ternary operator evaluates to true if the value is not considered falsy as described in the セクションの場合.
|
In fact, the operators are implemented as "virtual methods" that consume one parameter and can be used with infix notation. For example {person.name or 'John'} is translated to {person.name.or('John')} and {item.isActive ? item.name : 'Inactive item'} is translated to {item.isActive.ifTruthy(item.name).or('Inactive item')}
|
3.4.5. 配列
ループセクション を使用して、配列の要素を反復処理することができます。また、指定した配列の長さを取得し、インデックス値を用いて直接要素にアクセスすることも可能です。さらに、 take(n)/takeLast(n) メソッドを用いると、最初と最後の n 要素にアクセスすることができます。
<h1>Array of length: {myArray.length}</h1> (1)
<ul>
<li>First: {myArray.0}</li> (2)
<li>Second: {myArray[1]}</li> (3)
<li>Third: {myArray.get(2)}</li> (4)
</ul>
<ol>
{#for element in myArray}
<li>{element}</li>
{/for}
</ol>
First two elements: {#each myArray.take(2)}{it}{/each} (5)
| 1 | 配列の長さを出力します。 |
| 2 | 配列の最初の要素を出力します。 |
| 3 | ブラケット表記で配列の 2 つ目の要素を出力します。 |
| 4 | 配列の 3 番目の要素を仮想メソッド get() で出力します。 |
| 5 | 配列の最初の 2 要素を出力します。 |
3.4.6. 文字エスケープ
HTML と XML テンプレートの場合、 ' , " , < , > , & の文字は、テンプレートバリアントが設定されている場合はデフォルトでエスケープされます。
In Quarkus, a variant is set automatically for templates located in the src/main/resources/templates. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content-type of a template file. The additional map of suffixes to content types can be set via quarkus.qute.content-types.
|
エスケープされていない値をレンダリングする必要がある場合:
-
java.lang.Objectの拡張メソッドとして実装されているrawまたはsafeプロパティーを使用します。 -
Stringの値をio.quarkus.qute.RawStringでラップします。
<html>
<h1>{title}</h1> (1)
{paragraph.raw} (2)
</html>
| 1 | title が Expressions & Escapes に解決した場合は、 Expressions & Escapes としてレンダリングされます。 |
| 2 | <p>My text!</p> に解決する paragraph は、<p>My text!</p> としてレンダリングされます。 |
By default, a template with one of the following content types is escaped: text/html, text/xml, application/xml and application/xhtml+xml. However, it’s possible to extend this list via the quarkus.qute.escape-content-types configuration property.
|
3.4.7. 仮想メソッド
仮想メソッドとは、通常のJavaメソッド呼び出しのように見える 式のパーツ です。Javaクラスの実際のメソッドと一致する必要がないため、「仮想」と呼ばれています。実際には、通常のプロパティーと同様に、仮想メソッドも値リゾルバによって処理されます。唯一の違いは、仮想メソッドの場合、値リゾルバが式でもあるパラメーターを消費することです。
<html>
<h1>{item.buildName(item.name,5)}</h1> (1)
</html>
| 1 | buildName(item.name,5) は、名前 buildName と 2 つのパラメーター item.name と 5 を持つ仮想メソッドを表します。この仮想メソッドは、以下の Java クラスに対して生成された値リゾルバによって評価されます。
|
| Virtual methods are usually evaluated by value resolvers generated for @TemplateExtension methods, @TemplateData or classes used in parameter declarations. However, a custom value resolver that is not backed by any Java class/method can be registered as well. |
単一のパラメーターを持つ仮想メソッドは 中置記法を使用して呼び出すことができます。
<html>
<p>{item.price or 5}</p> (1)
</html>
| 1 | item.price or 5 は item.price.or(5) に翻訳されます。 |
仮想メソッドのパラメーターは、「入れ子」仮想メソッドの呼び出しにすることができます。
<html>
<p>{item.subtractPrice(item.calculateDiscount(10))}</p> (1)
</html>
| 1 | item.calculateDiscount(10) が最初に評価され、その後 item.subtractPrice() への引数として渡されます。 |
3.4.8. Evaluation of CompletionStage and Uni Objects
java.util.concurrent.CompletionStage および io.smallrye.mutiny.Uni を実装したオブジェクトは、特別な方法で評価されます。
式の一部が CompletionStage に解決された場合、このステージが完了すると解決が継続され、式の次のパーツ (もしあれば) が完了したステージの結果に対して評価されます。
たとえば、式 {foo.size} があり、foo が CompletionStage<List<String>> に解決される場合、`size は完了した結果、すなわち List<String> に対して解決されます。
式の一部が Uni に解決される場合、CompletionStage が Uni#subscribeAsCompletionStage() を使用して Uni から最初に作成され、その後上記のように評価されます。
Note that each Uni#subscribeAsCompletionStage() results in a new subscription. You might need to configure memoization of the Uni item or failure before it’s used as template data, i.e. myUni.memoize().indefinitely().
|
CompletionStage が完了しなかったり、あるいは Uni がアイテム/失敗を発しないということが起こり得ます。この場合、レンダリングメソッド (TemplateInstance#render() および TemplateInstance#createUni()) は、特定のタイムアウトを過ぎると失敗します。このタイムアウトは、テンプレートインスタンスの timeout 属性として指定することができます。timeout 属性が設定されていない場合、グローバルレンダリングタイムアウトが使用されます。
In Quarkus, the default timeout can be set via the io.quarkus.qute.timeout configuration property. If using Qute standalone then the EngineBuilder#timeout() method can be used.
|
In previous versions, only the TemplateInstance#render() method honored the timeout attribute. You can use the io.quarkus.qute.useAsyncTimeout=false config property to preserve the old behavior and take care of the timeout yourself, for example templateInstance.createUtni().ifNoItem().after(Duration.ofMillis(500)).fail().
|
3.4.8.1. テンプレートの問題個所の特定方法
タイムアウトが発生したときに、テンプレートの問題のあるパーツを見つけるのは簡単ではありません。ロガー io.quarkus.qute.nodeResolve の TRACE レベルを設定し、その後ログ出力の解析を試みることができます。
application.properties Examplequarkus.log.category."io.quarkus.qute.nodeResolve".min-level=TRACE
quarkus.log.category."io.quarkus.qute.nodeResolve".level=TRACE
テンプレートで使用されるすべての式とセクションについて、次のような一対のログメッセージが表示されるはずです。
TRACE [io.qua.qut.nodeResolve] Resolve {name} started: Template hello.html at line 8
TRACE [io.qua.qut.nodeResolve] Resolve {name} completed: Template hello.html at line 8
もし、 completed のログメッセージがない場合は、調査すべき良い候補があります。
3.4.9. 不明なプロパティ
式が実行時に評価されない場合があります。
たとえば、{person.age} という式があり、Person クラスに age というプロパティーが宣言されていない場合です。
strict レンダリング が有効かどうかによって、動作が異なってきます。
これを有効にすると、プロパティーが見つからない場合、常に TemplateException が発生し、レンダリングが中断されます。このエラーを抑制するために、default values や safe expressions を使用することができます。
無効な場合、デフォルトで特別な定数 NOT_FOUND が出力に書き込まれます。
In Quarkus, it’s possible to change the default strategy via the quarkus.qute.property-not-found-strategy as described in the 設定リファレンス.
|
| Similar errors are detected at build time if タイプセーフな式 and タイプセーフテンプレート are used. |
3.5. セクション
セクションは # で始まる開始タグを持ち、その後に {#if} や {#each} のようなセクション名が続きます。また、空であっても構いません。つまり、開始タグは / で終了します: {#myEmptySection /}。セクションは通常、ネストされた式や他のセクションを含んでいます。終了タグは / で始まり、セクションの名前 (オプション) を含みます。 {#if foo}Foo!{/if}または `{#if foo}Foo!{/}
A start tag can define parameters with optional names, e.g. {#if item.isActive} and {#let foo=1 bar=false}.
Parameters are separated by one or more spaces.
Names are separated from the values by the equals sign.
Names and values can be prefixed and suffixed with any number of spaces, e.g. {#let id='Foo'} and {#let id = 'Foo'} are equivalents where the name of the parameter is id and the value is Foo.
Values can be grouped using parentheses, e.g. {#let id=(item.id ?: 42)} where the name is id and the value is item.id ?: 42.
Sections can interpret parameter values in any way, e.g. take the value as is.
However, in most cases the parameter value is registered as an expression and evaluated before use.
1つのセクションには、いくつかのコンテンツ ブロック を含めることができます。
"main" ブロックは常に存在します。
追加/入れ子になったブロックも # で始まり、パラメーターを持つことができます ({#else if item.isActive})。
セクションのロジックを定義するセクションヘルパーは、任意のブロックを「実行」し、パラメーターを評価することができます。
#if Section Example{#if item.name is 'sword'}
It's a sword! (1)
{#else if item.name is 'shield'}
It's a shield! (2)
{#else}
Item is neither a sword nor a shield. (3)
{/if}
| 1 | これがメインブロックです。 |
| 2 | 追加ブロック。 |
| 3 | 追加ブロック。 |
3.5.1. ループセクション
ループセクションでは、 Iterable、 Iterator、array、 Map (要素は Map.Entry)、 Stream、 Integer、 int (プリミティブ値) のインスタンスに対して反復処理することができます。パラメーター値に null を指定すると、no-op となります。
このセクションには 2 つのフレーバーがあります。最初のものは each という名前を使い、it は反復処理要素の暗黙のエイリアスです。
{#each items}
{it.name} (1)
{/each}
| 1 | name は現在の反復要素に対して解決されます。 |
もう一つの形式は、for という名前を使い、反復要素を参照するために使われるエイリアスを指定します。
{#for item in items} (1)
{item.name}
{/for}
| 1 | item は、反復処理要素に使用されるエイリアスです。 |
また、ループ内の反復処理メタデータには、以下のキーでアクセスすることが可能です。
-
count- 1 を基準としたインデックス -
index- ゼロ基準のインデックス -
hasNext- イテレーションにさらに要素がある場合はtrueとります。 -
isLast-trueifhasNext == false -
isFirst-trueifcount == 1 -
odd- 要素のカウントが奇数である場合true -
hasNext- イテレーションにさらに要素がある場合にtrue -
indexParity- は、カウント値に応じてoddまたはevenを出力します
しかし、キーを直接使うことはできません。
その代わり、外部スコープの変数と競合する可能性を避けるために、接頭辞が使用されます。
デフォルトでは、反復処理された要素のエイリアスの後にアンダースコアを付けたものが接頭辞として使用されます。
たとえば、{#each} セクション ({it_hasNext}) の中では、hasNext キーは it_ を接頭辞として使用する必要があります。
each Iteration Metadata Example{#each items}
{it_count}. {it.name} (1)
{#if it_hasNext}<br>{/if} (2)
{/each}
| 1 | it_count は 1 を基準としたインデックスを表します。 |
| 2 | <br> は、反復処理がより多くの要素を持っている場合にのみレンダリングされます。 |
また、{#for} セクションの中で {item_hasNext} の形式で、item 要素のエイリアスと使用する必要があります。
for Iteration Metadata Example{#for item in items}
{item_count}. {item.name} (1)
{#if item_hasNext}<br>{/if} (2)
{/for}
| 1 | item_count は 1 を基準としたインデックスを表します。 |
| 2 | <br> は、反復処理がより多くの要素を持っている場合にのみレンダリングされます。 |
|
反復処理メタデータの接頭辞は、スタンドアロンの Qute では
|
for 文は、1 から始まる整数でも動作します。 以下の例では、 total = 3 です。
{#for i in total}
{i}: ({i_count} {i_indexParity} {i_even})<br>
{/for}
出力は、以下のようになります。
1: (1 odd false)
2: (2 even true)
3: (3 odd false)
ループセクションは、反復する項目がないときに実行される {#else} ブロックも定義することができます。
{#for item in items}
{item.name}
{#else}
No items.
{/for}
3.5.2. セクションの場合
if セクションは、基本的な制御フローセクションを表しています。最も単純なバージョンでは、単一のパラメーターを受け取り、条件が true と評価された場合にコンテンツをレンダリングします。演算子なしの条件は値が falsy (つまり、 null 、 false 、空のコレクション、空のマップ、空の配列、空の文字列/文字列シーケンス、またはゼロに等しい数) ではない場合、 true と評価されます。
{#if item.active}
This item is active.
{/if}
条件では、以下の演算子を使うこともできます:
| オペレーター | エイリアス | 優先順位 (大優先) |
|---|---|---|
logical complement |
|
4 |
greater than |
|
3 |
greater than or equal to |
|
3 |
less than |
|
3 |
less than or equal to |
|
3 |
equals |
|
2 |
not equals |
|
2 |
logical AND (short-circuiting) |
|
1 |
logical OR (short-circuiting) |
|
1 |
{#if item.age > 10}
This item is very old.
{/if}
複数の条件にも対応しています。
{#if item.age > 10 && item.price > 500}
This item is very old and expensive.
{/if}
優先順位の規則は、カッコで上書きすることができます。
{#if (item.age > 10 || item.price > 500) && user.loggedIn}
User must be logged in and item age must be > 10 or price must be > 500.
{/if}
また、 else ブロックを何個でも追加することができます。
{#if item.age > 10}
This item is very old.
{#else if item.age > 5}
This item is quite old.
{#else if item.age > 2}
This item is old.
{#else}
This item is not old at all!
{/if}
3.5.3. セクションの際
このセクションは、Java の switch や Kotlin の when に似ています。条件が満たされるまで、 テストされた値 をすべてのブロックに対して順次マッチさせます。最初にマッチしたブロックが実行されます。他のすべてのブロックは無視されます(この動作は、 break 文が必要な Java switch とは異なります)。
when/is name aliases{#when items.size}
{#is 1} (1)
There is exactly one item!
{#is > 10} (2)
There are more than 10 items!
{#else} (3)
There are 2 -10 items!
{/when}
| 1 | パラメーターが1つだけの場合、それは等価かがテストされます。 |
| 2 | 演算子 を使ってマッチングロジックを指定することができます。 セクションの場合 とは異なり、入れ子になった演算子はサポートされていません。 |
| 3 | else は、他のブロックが値に一致しない場合に実行されます。 |
switch/case name aliases{#switch person.name}
{#case 'John'} (1)
Hey John!
{#case 'Mary'}
Hey Mary!
{/switch}
| 1 | case は is の別名です。 |
enum に解決されるテスト値は特別な扱いを受けます。 is / case ブロックのパラメーターは式として評価されるのではなく、テストされた値に対して toString() を呼び出した結果と比較されます。
{#when machine.status}
{#is ON}
It's running. (1)
{#is in OFF BROKEN}
It's broken or OFF. (2)
{/when}
| 1 | このブロックは machine.status.toString().equals("ON") の場合に実行されます |
| 2 | このブロックは、 machine.status.toString().equals("OFF") または machine.status.toString().equals("BROKEN") の場合に実行されます |
| An enum constant is validated if the tested value has a type information available and resolves to an enum type. |
is / case ブロック条件では、以下の演算子がサポートされています。
| オペレーター | エイリアス | 例 |
|---|---|---|
not equal |
|
|
greater than |
|
|
greater than or equal to |
|
|
less than |
|
|
less than or equal to |
|
|
in |
|
|
not in |
|
|
3.5.4. セクションを以下のようにする
このセクションでは、名前付きローカル変数を定義することができます。
{#let myParent=order.item.parent isActive=false age=10 price=(order.price + 10)} (1)(2)
<h1>{myParent.name}</h1>
Is active: {isActive}
Age: {age}
{/let} (3)
| 1 | ローカル変数は、literal を表すことができる式 (つまり isActive=false と age=10) で初期化されます。 |
| 2 | 中置記法は、括弧を使用してグループ化する場合にのみサポートされます。例えば、 price=(order.price + 10) は price=order.price.plus(10) と同等です。 |
| 3 | この変数は、 let セクションを定義している外では利用できないことに留意してください。 |
セクションパラメーターのキー (ローカル変数の名前) が ? で終わる場合、ローカル変数は、 ? サフィックスのないキーが null または "not found" に解決された場合にのみ設定されます。
{#let enabled?=true} (1) (2)
{#if enabled}ON{/if}
{/let}
| 1 | true は事実上 default value で、親スコープが enabled をすでに定義していない場合にのみ使用されます。 |
| 2 | enabled?=true は enabled=enabled.or(true) の短縮版です。 |
このセクションタグも set のエイリアスで登録されています。
{#set myParent=item.parent price=item.price}
<h1>{myParent.name}</h1>
<p>Price: {price}
{/set}
3.5.5. セクションあり
このセクションは、現在のコンテキストオブジェクトを設定するために使用することができます。これはテンプレート構造を単純化するのに便利かもしれません。
{#with item.parent}
<h1>{name}</h1> (1)
<p>{description}</p> (2)
{/with}
| 1 | name は item.parent に対して解決されます。 |
| 2 | description も item.parent に対して解決されます。 |
|
タイプセーフテンプレート や タイプセーフな式 を定義するテンプレートでは、
|
このセクションは、複数の高価な呼び出しを避けたいときにも便利かもしれません。
{#with item.callExpensiveLogicToGetTheValue(1,'foo',bazinga)}
{#if this is "fun"} (1)
<h1>Yay!</h1>
{#else}
<h1>{this} is not fun at all!</h1>
{/if}
{/with}
| 1 | this は item.callExpensiveLogicToGetTheValue(1,'foo',bazinga) の結果です。結果が複数の式で使用されても、このメソッドは一度しか呼び出されません。 |
3.5.6. セクションを含む
このセクションは、別のテンプレートをインクルードし、場合によってはテンプレートの一部をオーバーライドするために使用することができます (下記の テンプレートの継承 を参照)。
<html>
<head>
<meta charset="UTF-8">
<title>Simple Include</title>
</head>
<body>
{#include foo limit=10 /} (1)(2)
</body>
</html>
| 1 | id foo でテンプレートをインクルードします。インクルードされたテンプレートは、現在のコンテキストからデータを参照することができます。 |
| 2 | また、インクルードされたテンプレートで使用できるオプションのパラメーターを定義することも可能です。 |
テンプレートの継承 により、テンプレートレイアウトの再利用が可能になります。
<html>
<head>
<meta charset="UTF-8">
<title>{#insert title}Default Title{/}</title> (1)
</head>
<body>
{#insert}No body!{/} (2)
</body>
</html>
| 1 | insert セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできるパーツを指定するために使われます。 |
| 2 | insert セクションは、オーバーライドされない場合にレンダリングされるデフォルトのコンテンツを定義することができます。もし名前が与えられていない場合は、関連する {#include} セクションのメインブロックが使用されます。 |
{#include base} (1)
{#title}My Title{/title} (2)
<div> (3)
My body.
</div>
{/include}
| 1 | include セクションは、拡張テンプレートを指定するために使用されます。 |
| 2 | 入れ子になったブロックは、オーバーライドするパーツを指定するために使用されます。 |
| 3 | メインブロックのコンテンツは、name パラメーターが指定されていない {#insert} セクションに使用されます。 |
Section blocks can also define an optional end tag - {/title}.
|
3.5.7. ユーザー定義タグ
ユーザー定義タグは、タグテンプレート をインクルードし、オプションでいくつかの引数を渡し、場合によってはテンプレートの一部をオーバーライドするために使用することができます。たとえば、itemDetail.html というタグテンプレートがあるとします。
{#if showImage} (1)
{it.image} (2)
{nested-content} (3)
{/if}
| 1 | showImage は名前付きパラメーターです。 |
| 2 | it は、タグの最初の名前のないパラメーターに置き換えられる特別なキーです。 |
| 3 | (オプション) nested-content は、タグの内容に置き換えられる特別なキーです。 |
Quarkus では、src/main/resources/templates/tags にあるすべてのファイルが、自動的に登録および監視されます。Qute スタンドアロンでは、解析されたテンプレートを itemDetail.html という名前で置き、関連する UserTagSectionHelper をエンジンに登録する必要があります。
Engine engine = Engine.builder()
.addSectionHelper(new UserTagSectionHelper.Factory("itemDetail","itemDetail.html"))
.build();
engine.putTemplate("itemDetail.html", engine.parse("..."));
そして、以下のようなタグを呼び出すことができます。
<ul>
{#for item in items}
<li>
{#itemDetail item showImage=true} (1)
= <b>{item.name}</b> (2)
{/itemDetail}
</li>
{/for}
</ul>
| 1 | item は反復要素に解決され、タグテンプレートで it キーを使用して参照することができます。 |
| 2 | タグテンプレートで nested-content キーを使用して注入されたタグコンテンツ。 |
デフォルトでは、タグテンプレートは親コンテキストからのデータを参照することができます。たとえば、上記のタグは {items.size} の式を使用することができます。しかし、時にはこの動作を無効にして、タグを isolated テンプレートとして、つまり、タグを呼び出すテンプレートのコンテキストにアクセスせずに実行すると便利なことがあります。このような場合には、 _isolated または _isolated=true の引数を、たとえば {#itemDetail item showImage=true _isolated /} のように呼び出しサイトに追加してください。
ユーザータグも、通常の {#include} セクションと同じように、テンプレートの継承を利用することができます。
myTagThis is {#insert title}my title{/title}! (1)
| 1 | insert セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできるパーツを指定するために使われます。 |
<p>
{#myTag}
{#title}my custom title{/title} (1)
{/myTag}
</p>
| 1 | 結果は、<p>これは私のカスタムタイトル!</p> のようになります。 |
3.5.8. フラグメント
フラグメントは、別個のテンプレートとして扱える、つまり別々にレンダリングできるテンプレートの一部分を表します。この機能を導入した主な動機の1つは、 htmxフラグメント のようなユースケースをサポートするためでした。
フラグメントは {#fragment} セクションで定義することができます。
各フラグメントは、英数字とアンダースコアのみからなる識別子を持ちます。
| Note that a fragment identifier must be unique in a template. |
item.html{@org.acme.Item item}
{@java.util.List<String> aliases}
<h1>Item - {item.name}</h1>
<p>This document contains a detailed info about an item.</p>
{#fragment id=item_aliases} (1)
<h2>Aliases</h2>
<ol>
{#for alias in aliases}
<li>{alias}</li>
{/for}
</ol>
{/fragment}
| 1 | 識別子 item_aliases を持つフラグメントを定義します。識別子には、英数字とアンダースコアしか使用できないことに注意してください。 |
You can obtain a fragment programatically via the io.quarkus.qute.Template.getFragment(String) method.
@Inject
Template item;
String useTheFragment() {
return item.getFragment("item_aliases") (1)
.data("aliases", List.of("Foo","Bar")) (2)
.render();
}
| 1 | 識別子 item_aliases を持つテンプレートフラグメントを取得します。 |
| 2 | データが正しく設定されているようにします。 |
上記のスニペットは、次のようにレンダリングされるはずです:
<h2>Aliases</h2>
<ol>
<li>Foo</li>
<li>Bar</li>
</ol>
| In Quarkus, it is also possible to define a type-safe fragment. |
また、 {#include} セクションを持つフラグメントを、別のテンプレートやフラグメントを定義するテンプレートの中に含めることもできます。
user.html<h1>User - {user.name}</h1>
<p>This document contains a detailed info about a user.</p>
{#include item$item_aliases aliases=user.aliases /} (1)(2)
| 1 | ドル記号 $ を含むテンプレート識別子は、フラグメントを表します。 item$item_aliases の値は、次のように訳されます。テンプレート item からフラグメント item_aliases を使用します。 |
| 2 | aliases パラメーターは、関連するデータを渡すために使用されます。データが正しく設定されていることを確認する必要があります。この特定のケースでは、フラグメントは {#for alias in aliases} セクションの aliases の値として式 user.aliases を使用します。 |
If you want to reference a fragment from the same template you can skip the part before $, i.e. something like {#include $item_aliases /}.
|
You can specify {#include item$item_aliases _ignoreFragments=true /} in order to disable this feature, i.e. a dollar sign $ in the template identifier does not result in a fragment lookup.
|
3.5.8.1. 非表示 (hidden) フラグメント
デフォルトでは、フラグメントは通常、オリジナルテンプレートの一部としてレンダリングされます。しかし、 rendered=false を使って、フラグメントを 非表示 にするのが便利な場合もあります。興味深い使用例として、フラグメントを定義したテンプレートの中で複数回使用できるフラグメントがあります。
item.html{#fragment id=strong rendered=false} (1)
<strong>{val}</strong>
{/fragment}
<h1>My page</h1>
<p>This document
{#include [strong] val='contains' /} (2)
a lot of
{#include [strong] val='information' /} (3)
!</p>
| 1 | 識別子 strong を持つ隠しフラグメントを定義します。
この特定のケースでは、 rendered パラメーターの値として false ブール値リテラルを使用します。
ただし、任意の式を使用することも可能です。 |
| 2 | Include the fragment strong and pass the value. Note the syntax [strong] which is translated to include the fragment strong from the current template. |
| 3 | フラグメント strong をインクルードし、値を渡す。 |
上のスニペットは、次のようにレンダリングされます:
<h1>My page</h1>
<p>This document
<strong>contains</strong>
a lot of
<strong>information</strong>
!</p>
3.5.9. Eval セクション
このセクションは、テンプレートを動的に解析および評価するために使用することができます。動作は セクションを含む に非常に似ています。ただし、
-
テンプレートの内容は、
io.quarkus.qute.TemplateLocatorを介して取得されるのではなく、直接渡されます。 -
評価済みテンプレートの一部をオーバーライドすることはできません。
{#eval myData.template name='Mia' /} (1)(2)(3)
| 1 | myData.template の結果がテンプレートとして使用されます。テンプレートは カレントコンテキスト で実行されます。つまり、インクルードされているテンプレートのデータを参照することができます。 |
| 2 | また、評価済みテンプレートで使用可能なオプションのパラメーターを定義することも可能です。 |
| 3 | セクションの内容は常に無視されます。 |
| The evaluated template is parsed and evaluated every time the section is executed. In other words, it’s not possible to cache the parsed value to conserve resources and optimize the performance. |
3.6. レンダリング出力
TemplateInstance は、レンダリングをトリガーして結果を消費するためのいくつかの方法を提供しています。
最も簡単な方法は TemplateInstance.render() です。このメソッドは同期レンダリングをトリガーします。
つまり、レンダリングが終了するまで現在のスレッドをブロックして出力を返します。
対照的に、TemplateInstance.renderAsync() は CompletionStage<String> を返し、レンダリングが終了すると完了します。
TemplateInstance.renderAsync() Exampletemplate.data(foo).renderAsync().whenComplete((result, failure) -> { (1)
if (failure == null) {
// consume the output...
} else {
// process failure...
}
};
| 1 | レンダリング終了後に実行されるコールバックを登録します。 |
また、https://smallrye.io/smallrye-mutiny/[Mutiny] タイプを返す 2 つのメソッドがあります。
TemplateInstance.createUni() は新しい Uni<String> オブジェクトを返します。
createUni() を呼び出しても、テンプレートはすぐにレンダリングされません。
その代わり、Uni.subscribe() が呼び出されるたびに、テンプレートの新しいレンダリングがトリガーされます。
TemplateInstance.createUni() Exampletemplate.data(foo).createUni().subscribe().with(System.out::println);
TemplateInstance.createMulti() は新しい Multi<String> オブジェクトを返します。各アイテムはレンダリングされたテンプレートの一部/チャンクを表します。繰り返しになりますが、 createMulti() はレンダリングをトリガしません。代わりに、サブスクライバによって実行がトリガされるたびに、テンプレートが再びレンダリングされます。
TemplateInstance.createMulti() Exampletemplate.data(foo).createMulti().subscribe().with(buffer:append,buffer::flush);
| The template rendering is divided in two phases. During the first phase, which is asynchronous, all expressions in the template are resolved and a result tree is built. In the second phase, which is synchronous, the result tree is materialized, i.e. one by one the result nodes emit chunks that are consumed/buffered by the specific consumer. |
3.7. エンジン設定
3.7.1. 値リゾルバ
式を評価する際に値リゾルバを使用します。カスタム io.quarkus.qute.ValueResolver は EngineBuilder.addValueResolver() からプログラムで登録することができます。
ValueResolver Builder ExampleengineBuilder.addValueResolver(ValueResolver.builder()
.appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
.resolveSync(ctx -> (Long) ctx.getBase() * 10)
.build());
3.7.2. テンプレートロケーター
テンプレートは手動で登録することも、テンプレートロケーターを介して自動的に登録することもできます。ロケーターは Engine.getTemplate() メソッドが呼び出され、エンジンに特定の ID のテンプレートがキャッシュに保存されていない場合に使用されます。ロケーターはテンプレートの内容を読み込む際に、正しい文字エンコーディングを使用する必要があります。
In Quarkus, all templates from the src/main/resources/templates are located automatically and the encoding set via quarkus.qute.default-charset (UTF-8 by default) is used. Custom locators can be registered via the @Locate annotation.
|
3.7.3. コンテンツフィルタ
コンテンツフィルタを使用して、解析前にテンプレートコンテンツを変更することができます。
engineBuilder.addParserHook(new ParserHook() {
@Override
public void beforeParsing(ParserHelper parserHelper) {
parserHelper.addContentFilter(contents -> contents.replace("${", "$\\{")); (1)
}
});
| 1 | すべての ${ との一致をエスケープします。 |
3.7.4. strict レンダリング
strict レンダリングにより、開発者は、タイプミスや無効な式によって引き起こされるわかりにくいエラーをキャッチすることができます。この機能を有効にすると、解決できない式、つまり io.quarkus.qute.Results.NotFound のインスタンスとして評価される式は、常に TemplateException となり、レンダリングは中断されます。NotFound 値は基本的に、値リゾルバーが式を正しく解決できなかったことを意味するため、エラーとみなされます。
null is a valid value though. It is considered falsy as described in the セクションの場合 and does not produce any output.
|
strict レンダリングはデフォルトで有効になっています。しかし、io.quarkus.qute.EngineBuilder.strictRendering(boolean) によって、この機能を無効にすることができます。
In Quarkus, a dedicated config property can be used instead: quarkus.qute.strict-rendering.
|
"not found" エラーを引き起こす可能性のある式をどうしても使いたい場合は、デフォルト値 および 安全な式 を使ってエラーを抑制することができます。
デフォルト値は、式の前のパーツが解決できないか、null に解決される場合に使用されます。
エルビス演算子を使ってデフォルト値 {foo.bar ?: 'baz'} を出力することができます。
これは事実上、仮想メソッド {foo.bar.or('baz')} と同じになります。安全な式は ?? という接尾辞で終わり、式が解決できない場合は null という結果になります。
これは、{#if valueNotFound??}Only rendered if valueNotFound is truthy!{/if} セクションの {#if} などで非常に便利です。
実際のところ、?? は .or(null) の簡単な表記法に過ぎず、たとえば {#if valueNotFound??} は {#if valueNotFound.or(null)} となります。
4. Quarkus の統合
QuarkusアプリケーションでQuteを使用する場合は、以下の依存関係をプロジェクトに追加してください。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
Quarkusでは、事前に設定されたエンジンインスタンスが提供され、インジェクションに利用できます。@ApplicationScoped スコープのBean、 io.quarkus.qute.Engine 型のBean、 @Default 修飾子が自動的に登録されます。さらに、 src/main/resources/templates ディレクトリーにあるすべてのテンプレートが検証され、簡単にインジェクションすることができます。
import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import io.quarkus.qute.Location;
class MyBean {
@Inject
Template items; (1)
@Location("detail/items2_v1.html") (2)
Template items2;
@Inject
Engine engine; (3)
}
| 1 | Location 修飾子が与えられていない場合は、フィールド名がテンプレートを特定するために使われます。この特定のケースでは、コンテナーはパス src/main/resources/templates/items.html でテンプレートを見つけようとします。 |
| 2 | Location 修飾子は、 src/main/resources/templates からの相対パスからテンプレートを注入するようにコンテナーに指示します。この場合のフルパスは src/main/resources/templates/detail/items2_v1.html です。 |
| 3 | 設定された Engine インスタンスを注入します。 |
CDI observerメソッドを通じてエンジン設定に貢献することも可能です。
EngineBuilder Observer Exampleimport io.quarkus.qute.EngineBuilder;
class MyBean {
void configureEngine(@Observes EngineBuilder builder) {
builder.addValueResolver(ValueResolver.builder()
.appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
.resolveSync(ctx -> (Long) ec.getBase() * 10)
.build());
}
}
4.1. テンプレートロケーターの登録
template locators を登録する最も簡単な方法は、これらを CDI Bean にすることです。
テンプレート検証を行うビルド時にはカスタムロケーターは利用できないので、@Locate アノテーションで検証を無効化する必要があります。
@Locate("bar.html") (1)
@Locate("foo.*") (2)
public class CustomLocator implements TemplateLocator {
@Inject (3)
MyLocationService myLocationService;
@Override
public Optional<TemplateLocation> locate(String templateId) {
return myLocationService.getTemplateLocation(templateId);
}
}
| 1 | 実行時に、カスタムロケーターによって bar.html という名前のテンプレートが配置されます。 |
| 2 | 正規表現 foo.* は、名前が foo で始まるテンプレートのバリデーションを無効にします。 |
| 3 | 注入フィールドは @Locate でアノテーションされたテンプレートロケーターとして解決され、シングルトンセッション Bean として登録されます。 |
4.2. テンプレートバリアント
コンテンツネゴシエーションに基づいてテンプレートの特定のバリアントをレンダリングすることが有用な場合があります。これは TemplateInstance.setAttribute() を通じて特別な属性を設定することで行うことができます。
class MyService {
@Inject
Template items; (1)
@Inject
ItemManager manager;
String renderItems() {
return items.data("items",manager.findItems()).setAttribute(TemplateInstance.SELECTED_VARIANT, new Variant(Locale.getDefault(),"text/html","UTF-8")).render();
}
}
When using quarkus-resteasy-qute the content negotiation is performed automatically. See RESTEasy 統合.
|
4.3. テンプレートに直接Beansを注入
@Named でアノテーションされた CDI Bean は、cdi や inject 名前空間を通して、任意のテンプレートで参照することができます。
{cdi:personService.findPerson(10).name} (1)
{inject:foo.price} (2)
| 1 | まず、personService という名前の Bean が見つかり、それをベースオブジェクトとして使用します。 |
| 2 | まず、 foo という名前のBeanを見つけ、それをベースオブジェクトとして使用します。 |
@Named @Dependent beans are shared across all expressions in a template for a single rendering operation, and destroyed after the rendering finished.
|
名前空間 cdi と inject を含むすべての式は、ビルド時に検証されます。 cdi:personService.findPerson(10).name という式では、注入される Bean の実装クラスは findPerson メソッドを宣言しているか、マッチするテンプレート拡張メソッド が存在しなければなりません。 inject:foo.price という式では、注入される Bean の実装クラスは price プロパティー (例: getPrice() メソッド) を持つか、マッチする テンプレート拡張メソッド が存在しなければなりません。
A ValueResolver is also generated for all beans annotated with @Named so that it’s possible to access its properties without reflection.
|
If your application serves HTTP requests you can also inject the current io.vertx.core.http.HttpServerRequest via the inject namespace, e.g. {inject:vertxRequest.getParam('foo')}.
|
4.4. タイプセーフな式
テンプレート式は、オプションでタイプセーフにすることができます。これは、式が既存のJavaの型とテンプレート拡張メソッドに対して検証されることを意味します。無効な/不正確な式が見つかった場合、ビルドは失敗します。
例えば、 item.name という式があり、 item が org.acme.Item にマップされている場合、 Item には name というプロパティーがあるか、一致するテンプレート拡張メソッドが存在しなければなりません。
オプションの パラメーター宣言 は、最初のパートがパラメーター名と一致する式に Java 型をバインドするために使用されます。パラメーター宣言はテンプレートで直接指定します。
Java タイプは、java.lang パッケージの JDK タイプでない限り、常に fully qualified name で識別される必要があります。この場合、パッケージ名はオプションです。
パラメーター化されたタイプもサポートされていますが、ワイルドカードは常に無視され、上限と下限のみが考慮されます。
たとえば、パラメーター宣言 {@java.util.List<? extends org.acme.Foo> list} は {@java.util.List<org.acme.Foo> list} として認識されます。
タイプ変数は特別な方法で扱われることはないため、決して使用しないでください。
{@org.acme.Foo foo} (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
<h1>{title}</h1> (2)
Hello {foo.message.toLowerCase}! (3) (4)
</body>
</html>
| 1 | パラメーター宣言 - foo を org.acme.Foo にマップします。 |
| 2 | 検証されていません - パラメーター宣言にマッチしません。 |
| 3 | この式は検証されています。org.acme.Foo はプロパティー message を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。 |
| 4 | 同様に、 foo.message から解決されたオブジェクトの Java 型は、プロパティー toLowerCase を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。 |
| A value resolver is automatically generated for all types used in parameter declarations so that it’s possible to access its properties without reflection. |
| Method parameters of type-safe templates are automatically turned into parameter declarations. |
セクションは、パラメーター宣言にマッチする名前をオーバーライドできることに注意してください。
{@org.acme.Foo foo}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
<h1>{foo.message}</h1> (1)
{#for foo in baz.foos}
<p>Hello {foo.message}!</p> (2)
{/for}
</body>
</html>
| 1 | org.acme.Foo に対して検証されています。 |
| 2 | 検証されていません - foo はループセクションでオーバーライドされています。 |
パラメーター宣言では、キーの後に default value を指定することができます。
キーとデフォルト値は等号で区切られます ({@int age=10})。
デフォルト値は、パラメーターのキーが null に解決されるか、見つからない場合に、テンプレート内で使用されます。
たとえば、パラメーター宣言 {@String foo="Ping"} があり、foo が見つからない場合、{foo} を使用すると Ping が出力されます。
一方、(たとえば TemplateInstance.data("foo", "Pong") を介して) 値が設定されている場合は、{foo} の出力は Pong となります。
デフォルト値のタイプは,パラメーター宣言のタイプに割り当て可能でなければなりません。すなわち、パラメーター宣言 ({@org.acme.Foo foo=1}) は不正確なため、ビルドに失敗します。
The default value is actually an expression. So the default value does not have to be a literal (such as 42 or true). For example, you can leverage the @TemplateEnum and specify an enum constant as a default value of a parameter declaration: {@org.acme.MyEnum myEnum=MyEnum:FOO}. However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. {@org.acme.Foo foo=(foo1 ?: foo2)}`.
|
| The type of a default value is not validated in Qute standalone. |
{@int pages} (1)
{@java.util.List<String> strings} (2)
{@java.util.Map<String,? extends Number> numbers} (3)
{@java.util.Optional<?> param} (4)
{@String name="Quarkus"} (5)
| 1 | Java プリミティブタイプ。 |
| 2 | String` は java.lang.String ( {@java.util.List<java.lang.String> strings}) に置き換えることができます。 |
| 3 | ワイルドカードは無視され、代わりに上限値が使用されます: {@java.util.Map<String,Number>} |
| 4 | ワイルドカードは無視され、代わりに上限値が使用されます: {@java.util.Optional<java.lang.Object>} |
| 5 | タイプは java.lang.String で、キーは name、デフォルト値は Quarkus です。 |
4.5. タイプセーフテンプレート
また、Java コードの中で、タイプセーフのテンプレートを定義することもできます。JAX-RS リソース内でテンプレート を使う場合は、以下の規約に従うことができます。
-
テンプレートファイルを
/src/main/resources/templatesディレクトリーに整理し、リソースクラスごとに 1 つのディレクトリーにグループ化します。つまり、ItemResourceクラスがhelloとgoodbyeの 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 つずつ宣言 し ます。 -
テンプレートインスタンスを構築するには、これらの静的メソッドを使用します。
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) (2)
}
@GET
@Path("{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance get(Integer id) {
return Templates.item(service.findItem(id)); (3)
}
}
| 1 | templates/ItemResource/item.html に対して TemplateInstance を与えるメソッドを宣言し、その Item item パラメーターを宣言することで、テンプレートを検証することができます。 |
| 2 | `item`パラメーターは自動的に parameter declaration に変換されるので、この名前を参照するすべての式が検証されます。 |
| 3 | テンプレート内で Item オブジェクトにアクセスできるようにします。 |
By default, the templates defined in a class annotated with @CheckedTemplate can only contain type-safe expressions, i.e. expressions that can be validated at build time. You can use @CheckedTemplate(requireTypeSafeExpressions = false) to relax this requirement.
|
4.5.1. トップレベルタイプセーフテンプレート
また、 @CheckedTemplate でアノテーションされたトップレベルの 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 を持つテンプレートを宣言します。 name パラメーターは自動的に パラメーター宣言 に変換されるので、この名前を参照するすべての式が検証されます。 |
そして、テンプレートファイルごとに public static native TemplateInstance method(); を宣言します。これらの静的メソッドを使用してテンプレートインスタンスを構築します。
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("hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public TemplateInstance get(@QueryParam("name") String name) {
return Templates.hello(name);
}
}
4.5.2. カスタマイズされたテンプレート・パス
@CheckedTemplate メソッドのテンプレートパスは、 ベースパス と デフォルトの名前 から構成されます。 ベースパス は @CheckedTemplate#basePath() で指定されます. デフォルトでは,ネストした静的クラスの場合は宣言クラスのsimple name,トップレベルクラスの場合は空文字列が使用されます. デフォルトの名前 は, @CheckedTemplate#defaultName() で指定された戦略によって導出されます。 デフォルトでは、 @CheckedTemplate のメソッドの名前がそのまま使われます。
package org.acme.quarkus.sample;
import javax.ws.rs.Path;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.CheckedTemplate;
@Path("item")
public class ItemResource {
@CheckedTemplate(basePath = "items", defaultName = CheckedTemplate.HYPHENATED_ELEMENT_NAME)
static class Templates {
static native TemplateInstance itemAndOrder(Item item); (1)
}
}
| 1 | このメソッドのテンプレートパスは、 items/item-and-order になります。 |
4.5.3. タイプセーフフラグメント
Java コード内で、タイプセーフな fragment も定義できます。
名前にドル記号 $ が含まれる native static メソッドは、タイプセーフなテンプレートのフラグメントを表すメソッドを示します。
フラグメントの名前は、アノテーションが付けられたメソッド名から導出されます。
最後に現れるドル記号 $ の前にある部分は、関連するタイプセーフなテンプレートのメソッド名です。
最後に現れるドル記号の後にある部分は、フラグメント識別子です。
関連する CheckedTemplate#defaultName() で定義されたストラテジーは、デフォルト名を構築する際に適用されます。
import io.quarkus.qute.CheckedTemplate;
import org.acme.Item;
@CheckedTemplate
class Templates {
// defines a type-safe template
static native TemplateInstance items(List<Item> items);
// defines a fragment of Templates#items() with identifier "item"
static native TemplateInstance items$item(Item item); (1)
}
| 1 | Quarkusでは、ビルド時に、 Templates#items() に対応する各テンプレートに、識別子 item を持つフラグメントが含まれているかどうかが検証されます。さらに、フラグメントメソッドのパラメーターも検証されます。一般に、フラグメントに含まれ、オリジナル/アウターテンプレートのデータを参照するすべてのタイプセーフ 式には、特定のパラメータが必要です。 |
items.html<h1>Items</h1>
<ol>
{#for item in items}
{#fragment id=item} (1)
<li>{item.name}</li> (2)
{/fragment}
{/for}
</ol>
| 1 | 識別子 item を持つフラグメントを定義します |
| 2 | {item.name} `式では、`Templates#items$item() メソッドが、名前 item および型 org.acme.Item のパラメーターを宣言する必要があります。 |
class ItemService {
String renderItem(Item item) {
// this would return something like "<li>Foo</li>"
return Templates.items$item(item).render();
}
}
You can specify @CheckedTemplate#ignoreFragments=true in order to disable this feature, i.e. a dollar sign $ in the method name will not result in a checked fragment method.
|
4.6. テンプレート拡張メソッド
エクステンションメソッドは、データクラスを新しい機能で拡張するため (アクセス可能なプロパティーとメソッドのセットを拡張するため)、または特定の namespace のための式を解決するために使用できます。 たとえば、計算済みプロパティ や 仮想メソッド を追加できます。
値リゾルバは、 @TemplateExtension でアノテーションされたメソッドに対して自動的に生成されます。クラスが @TemplateExtension でアノテーションされている場合、そのクラスで宣言されている プライベートではないスタティック・メソッド ごとに値リゾルバが生成されます。メソッドレベルのアノテーションは、クラスで定義された動作をオーバーライドします。以下の要件を満たさないメソッドは無視されます。
テンプレート拡張メソッド:
-
privateであってはなりません -
staticでなければなりません -
voidを返してはいけません
名前空間が定義されていない限り、@TemplateAttribute でアノテーションされた最初のパラメーターのクラスがベース・オブジェクトにマッチするように使用されます。そうでなければ、名前空間が式をマッチするのに使用されます。
4.6.1. 名前によるマッチング
メソッド名は、デフォルトでプロパティ名と一致するように使用されます。
package org.acme;
class Item {
public final BigDecimal price;
public Item(BigDecimal price) {
this.price = price;
}
}
@TemplateExtension
class MyExtensions {
static BigDecimal discountedPrice(Item item) { (1)
return item.getPrice().multiply(new BigDecimal("0.9"));
}
}
| 1 | このメソッドは、 Item.class 型のベース・オブジェクトと discountedPrice プロパティー名を持つ式にマッチします。 |
このテンプレート拡張メソッドを使うと、以下のテンプレートをレンダリングすることができます。
{item.discountedPrice} (1)
| 1 | item は org.acme.Item のインスタンスに解決されます。 |
ただし、 matchName() でマッチング名を指定することは可能です。
TemplateExtension#matchName() Example@TemplateExtension(matchName = "discounted")
static BigDecimal discountedPrice(Item item) {
// this method matches {item.discounted} if "item" resolves to an object assignable to "Item"
return item.getPrice().multiply(new BigDecimal("0.9"));
}
特別な定数 - TemplateExtension#ANY / * - を使用すると、拡張メソッドが任意の名前にマッチするように指定することができます。
TemplateExtension#ANY Example@TemplateExtension(matchName = "*")
static String itemProperty(Item item, String name) { (1)
// this method matches {item.foo} if "item" resolves to an object assignable to "Item"
// the value of the "name" argument is "foo"
}
| 1 | 追加の文字列メソッドパラメータは、実際のプロパティ名を渡すために使用されます。 |
また、 matchRegex() で指定された正規表現と名前を照合することも可能です。
TemplateExtension#matchRegex() Example@TemplateExtension(matchRegex = "foo|bar")
static String itemProperty(Item item, String name) { (1)
// this method matches {item.foo} and {item.bar} if "item" resolves to an object assignable to "Item"
// the value of the "name" argument is "foo" or "bar"
}
| 1 | 追加の文字列メソッドパラメータは、実際のプロパティ名を渡すために使用されます。 |
最後に、 matchNames() を使用して、一致する名前のコレクションを指定することができます。追加の文字列メソッド・パラメータも必須です。
TemplateExtension#matchNames() Example@TemplateExtension(matchNames = {"foo", "bar"})
static String itemProperty(Item item, String name) {
// this method matches {item.foo} and {item.bar} if "item" resolves to an object assignable to "Item"
// the value of the "name" argument is "foo" or "bar"
}
Superfluous matching conditions are ignored. The conditions sorted by priority in descending order are:
matchRegex(), matchNames() and matchName().
|
4.6.2. メソッドパラメーター
拡張メソッドはパラメーターを宣言することができます。名前空間が指定されていない場合、 @TemplateAttribute でアノテーションされていない最初のパラメーターが基底オブジェクトを渡すために使用されます。例:最初の例における org.acme.Item 。任意の名前に一致する場合、または正規表現を使用する場合は、文字列メソッドパラメータを使用してプロパティ名を渡す必要があります。 @ TemplateAttribute でアノテーションされたパラメーターは、 TemplateInstance#getAttribute() を介して取得されます。 他のすべてのパラメーターは、テンプレートをレンダリングするときに解決され、拡張メソッドに渡されます。
@TemplateExtension
class BigDecimalExtensions {
static BigDecimal scale(BigDecimal val, int scale, RoundingMode mode) { (1)
return val.setScale(scale, mode);
}
}
| 1 | このメソッドは、 BigDecimal.class 型の基底オブジェクトと、 scale の仮想メソッド名と 2 つの仮想メソッドパラメーターを持つ式にマッチします。 |
{item.discountedPrice.scale(2,mode)} (1)
| 1 | item.discountedPrice は BigDecimal のインスタンスに解決されます。 |
4.6.3. 名前空間拡張メソッド
TemplateExtension#namespace() が指定されている場合、拡張メソッドが、指定された namespace を持つ式を解決するために使用されます。
同じ名前空間を共有するテンプレートのエクステンションメソッドは、TemplateExtension#priority() で順に 1 つのリゾルバーにグループ化されます。
最初にマッチするエクステンションメソッドが式の解決に使用されます。
@TemplateExtension(namespace = "str")
public class StringExtensions {
static String format(String fmt, Object... args) {
return String.format(fmt, args);
}
static String reverse(String val) {
return new StringBuilder(val).reverse().toString();
}
}
これらの拡張メソッドは、以下のように使用することができます。
{str:format('%s %s!','Hello', 'world')} (1)
{str:reverse('hello')} (2)
| 1 | 出力は Hello world! です |
| 2 | 出力は olleh です |
4.6.4. 組み込みテンプレートエクステンション
Quarkusは、組み込みの拡張メソッドのセットを提供しています。
4.6.4.1. マップ
-
keysまたはkeySet: マップに含まれるキーのセットビューを返します。-
{#for key in map.keySet}
-
-
values: マップに含まれる値のコレクションビューを返します。-
{#for value in map.values}
-
-
size: マップ内のキーと値の対応付けの個数を返します。-
{map.size}
-
-
isEmpty: マップにキーと値のマッピングが含まれていない場合に true を返します。-
{#if map.isEmpty}
-
-
get(key): 指定したキーがマッピングされた値を返します。-
{map.get('foo')}
-
A map value can be also accessed directly: {map.myKey}. Use the bracket notation for keys that are not legal identifiers: {map['my key']}.
|
4.6.5. リスト
-
get(index): リストの指定した位置の要素を返す-
{list.get(0)}
-
-
reversed: リストを逆にしたイテレータを返す-
{#for r in recordsList.reversed}
-
-
take: 与えられたリストから最初のn要素を返します。nが範囲外の場合はIndexOutOfBoundsExceptionを投げます。-
{#for r in recordsList.take(3)}
-
-
takeLast: 与えられたリストから最後のn要素を返します。nが範囲外の場合はIndexOutOfBoundsExceptionを投げます。-
{#for r in recordsList.takeLast(3)}
-
-
first: 指定されたリストの最初の要素を返します。リストが空の場合、NoSuchElementExceptionをスローします。-
{recordsList.first}
-
-
last: 指定されたリストの最後の要素を返します。リストが空の場合、NoSuchElementExceptionをスローします。-
{recordsList.last}
-
A list element can be accessed directly via an index: {list.10} or even {list[10]}.
|
4.6.5.1. 整数値
-
mod: モデューロ演算-
{#if counter.mod(5) == 0}
-
-
plusまたは+: 加算-
{counter + 1} -
{age plus 10} -
{age.plus(10)}
-
-
minusまたは-: 引き算-
{counter - 1} -
{age minus 10} -
{age.minus(10)}
-
4.6.5.2. Strings
-
fmtまたはformat:java.lang.String.format()によって文字列のインスタンスをフォーマットします。-
{myStr.fmt("arg1","arg2")} -
{myStr.format(locale,arg1)}
-
-
str:fmtまたはstr:format: 与えられた文字列値をjava.lang.String.format()でフォーマットします。-
{str:format("Hello %s!",name)} -
{str:fmt(locale,'%tA',now)}
-
-
+: 連結-
{item.name + '_' + mySuffix} -
{name + 10}
-
4.6.5.3. 設定
-
config:<name>またはconfig:[<name>]: 与えられたプロパティー名に対する config 値を返します。-
{config:foo}または{config:['property.with.dot.in.name']}
-
-
config:property(name): 指定したプロパティー名の設定値を返します。名前は式で動的に取得することが可能です。-
{config:property('quarkus.foo')} -
{config:property(foo.getPropertyName())}
-
-
config:boolean(name): 指定したプロパティー名の設定値をbooleanとして返します。名前は式で動的に取得することが可能です。-
{config:boolean('quarkus.foo.boolean') ?: 'Not Found'} -
{config:boolean(foo.getPropertyName()) ?: 'property is false'}
-
-
config:integer(name): 指定したプロパティー名の設定値をintegerで返します。名前は式で動的に取得することが可能です。-
{config:integer('quarkus.foo')} -
{config:integer(foo.getPropertyName())}
-
4.6.5.4. 時間
-
format(pattern):java.timeパッケージの一時オブジェクトをフォーマットします。-
{dateTime.format('d MMM uuuu')}
-
-
format(pattern,locale):java.timeパッケージの一時オブジェクトをフォーマットします。-
{dateTime.format('d MMM uuuu',myLocale)}
-
-
format(pattern,locale,timeZone):java.timeパッケージの一時オブジェクトをフォーマットします。-
{dateTime.format('d MMM uuuu',myLocale,myTimeZoneId)}
-
-
time:format(dateTime,pattern):java.timeパッケージ、java.util.Date、java.util.Calendar、java.lang.Numberの一時オブジェクトをフォーマットします。-
{time:format(myDate,'d MMM uuuu')}
-
-
time:format(dateTime,pattern,locale):java.timeパッケージ、java.util.Date、java.util.Calendar、java.lang.Numberの一時オブジェクトをフォーマットします。-
{time:format(myDate,'d MMM uuuu', myLocale)}
-
-
time:format(dateTime,pattern,locale,timeZone):java.timeパッケージ、java.util.Date、java.util.Calendar、java.lang.Numberの一時オブジェクトをフォーマットします。-
{time:format(myDate,'d MMM uuuu',myLocale,myTimeZoneId)}
-
4.7. @TemplateData
値リゾルバは、 @TemplateData でアノテーションされた型に対して自動的に生成されます。これにより、Quarkusでは、実行時にデータにアクセスするためのリフレクションの使用を避けることができます。
| Non-public members, constructors, static initializers, static, synthetic and void methods are always ignored. |
package org.acme;
@TemplateData
class Item {
public final BigDecimal price;
public Item(BigDecimal price) {
this.price = price;
}
public BigDecimal getDiscountedPrice() {
return price.multiply(new BigDecimal("0.9"));
}
}
Item の任意のインスタンスをテンプレート内で直接使用することができます。
{#each items} (1)
{it.price} / {it.discountedPrice}
{/each}
| 1 | items は org.acme.Item インスタンスのリストに解決されます。 |
さらに、 @TemplateData.properties() と @TemplateData.ignore() を使用して、生成されるリゾルバを微調整することができます。最後に、アノテーションの「ターゲット」を指定することも可能です。これは、アプリケーションによって管理されないサードパーティーのクラスに便利です。
@TemplateData(target = BigDecimal.class)
@TemplateData
class Item {
public final BigDecimal price;
public Item(BigDecimal price) {
this.price = price;
}
}
{#each items}
{it.price.setScale(2, rounding)} (1)
{/each}
| 1 | 生成された値リゾルバは、 BigDecimal.setScale() メソッドを呼び出す方法を知っています。 |
4.7.1. 静的なフィールドとメソッドへのアクセス
@TemplateData#namespace() が空でない値に設定されている場合、ターゲットクラスのパブリック静的フィールドとメソッドにアクセスする名前空間リゾルバーが自動的に生成されます。
デフォルトでは、名前空間はターゲットクラスの FQCN で、ドットやドル記号はアンダースコアに置き換えられます。
たとえば、名前 org.acme.Foo を持つクラスの名前空間は org_acme_Foo です。
静的フィールド Foo.AGE には、{org_acme_Foo:AGE} からアクセスすることができます。
静的メソッド Foo.computeValue(int number) には、{org_acme_Foo:computeValue(10)} を介してアクセスできます。
| A namespace can only consist of alphanumeric characters and underscores. |
@TemplateDatapackage model;
@TemplateData (1)
public class Statuses {
public static final String ON = "on";
public static final String OFF = "off";
}
| 1 | 名前空間 model_Status を持つネームリゾルバーが自動的に生成されます。 |
{#if machine.status == model_Status:ON}
The machine is ON!
{/if}
4.7.2. enum の便利なアノテーション
また、enum 定数にアクセスするための便利なアノテーションとして、@io.quarkus.qute.TemplateEnum があります。このアノテーションは、機能的には @TemplateData(namespace = TemplateData.SIMPLENAME) と同等です。つまり、対象の enum に対して自動的に名前空間リゾルバーが生成され、対象の enum の単純名が名前空間として使用されます。
@TemplateEnumpackage model;
@TemplateEnum (1)
public enum Status {
ON,
OFF
}
| 1 | 名前空間 Status を持つネームリゾルバーが自動的に生成される。 |
@TemplateEnum declared on a non-enum class is ignored. Also, if an enum also declares the @TemplateData annotation, then the @TemplateEnum annotation is ignored.
|
{#if machine.status == Status:ON}
The machine is ON!
{/if}
Quarkus detects possible namespace collisions and fails the build if a specific namespace is defined by multiple @TemplateData and/or @TemplateEnum annotations.
|
4.8. グローバル変数
io.quarkus.qute.TemplateGlobal アノテーションは、任意のテンプレートでアクセス可能な グローバル変数 を提供する静的フィールドとメソッドを示すために使用されます。内部的には、各グローバル変数は TemplateInstance#data(String, Object) メソッドを介して、任意の TemplateInstance のデータマップに追加されます。
enum Color { RED, GREEN, BLUE }
@TemplateGlobal (1)
public class Globals {
static int age = 40;
static Color[] myColors() {
return new Color[] { Color.RED, Color.BLUE };
}
@TemplateGlobal(name = "currentUser") (2)
static String user() {
return "Mia";
}
}
| 1 | クラスが @TemplateGlobal でアノテーションが付けられている場合、パラメーターを宣言しないすべての非 void 非プライベート静的メソッドとすべての非プライベート静的フィールドは、グローバル変数と見なされます。名前はデフォルトです。つまり、フィールド/メソッドの名前が使用されます。 |
| 2 | メソッドレベルのアノテーションは、クラスレベルのアノテーションをオーバーライドします。この特別なケースでは、名前はデフォルトではなく、明示的に選択されます。 |
User: {currentUser} (1)
Age: {age} (2)
Colors: {#each myColors}{it}{#if it_hasNext}, {/if}{/each} (3)
| 1 | currentUser resolves to Globals#user(). |
| 2 | age は Globals#age に解決されます。 |
| 3 | myColors resolves to Globals#myColors(). |
| Note that global variables implicitly add parameter declarations to all templates and so any expression that references a global variable is validated during build. |
User: Mia
Age: 40
Colors: RED, BLUE
4.8.1. 競合の解消
グローバル変数は、通常のデータオブジェクトと競合する場合があります。タイプセーフテンプレート は、グローバル変数を自動的にオーバーライドします。たとえば、以下の定義は Globals#user() メソッドが提供するグローバル変数をオーバーライドします。
import org.acme.User;
@CheckedTemplate
public class Templates {
static native TemplateInstance hello(User currentUser); (1)
}
| 1 | currentUser は Globals#user() が提供するグローバル変数と競合しています。 |
そのため、Globals#user() メソッドが java.lang.String を返し、それが name プロパティーを持たないにもかかわらず、対応するテンプレートはバリデーションエラーにはなりません。
templates/hello.txtUser name: {currentUser.name} (1)
| 1 | org.acme.User には name プロパティーがあります。 |
その他のテンプレートでは、明示的なパラメーター宣言が必要です。
{@org.acme.User currentUser} (1)
User name: {currentUser.name}
| 1 | このパラメーター宣言は、Globals#user() メソッドが提供するグローバル変数によって追加される宣言を上書きします。 |
4.9. ネイティブ実行可能ファイル
JVM モードでは、リフレクションベースの値リゾルバーが、プロパティーにアクセスし、モデルクラスのメソッドを呼び出すために使われる場合があります。
しかし、これは ネイティブ実行可能ファイル に対しては、そのままでは正しく機能しません。
その結果、Foo クラスが関連するゲッターメソッドを宣言していたとしても、Property "name" not found on the base object "org.acme.Foo" in expression {foo.name} in template hello.html などのテンプレート例外に遭遇する可能性があります。
この問題を解決するには、いくつかの方法があります。
-
type-safe templates または type-safe expressions をご利用ください。
-
この場合、最適化されたバリューリゾルバーが自動的に生成され、実行時に使用されます。
-
これが望ましい解決策です
-
-
モデルクラスに
@TemplateDataのアノテーションを付ける - 実行時に専用の値リゾルバーが生成され、使用されます。 -
モデルクラスに
@io.quarkus.runtime.annotations.RegisterForReflectionのアノテーションを付けて、リフレクションベースの値リゾルバーを動作させます。
4.10. RESTEasy 統合
JAX-RSアプリケーションでQuteを使用したい場合、使用しているJAX-RSスタックによっては、まず適切なエクステンションを登録する必要があります。トラディショナルな quakus-resteasy のエクステンションを使用している場合は、 以下を pom.xml ファイルに追加してください。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-qute</artifactId>
</dependency>
代わりに quarkus-resteasy-reactive エクステンションを介して RESTEasy Reactive を使用している場合は、 次を pom.xml ファイルに追加してください。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-qute</artifactId>
</dependency>
これらのエクステンションはどちらも、リソースメソッドが TemplateInstance を返すことを可能にする特別な ContainerResponseFilter 実装を登録しています。そのため、ユーザーは必要なすべての内部ステップを処理する必要がなくなります。
最終的には、JAX-RSリソース内でQuteを使用すると、次のようにシンプルに見えるかもしれません。
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 の実装によって自動的に行われます。 |
| Users are encouraged to use Type-safe templates that help to organize the templates for a specific JAX-RS resource and enable type-safe expressions automatically. |
コンテンツネゴシエーションは自動的に実行されます。結果の出力はクライアントから受け取った Accept ヘッダーに依存します。
@Path("/detail")
class DetailResource {
@Inject
Template item; (1)
@GET
@Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
public TemplateInstance item() {
return item.data("myItem", new Item("Alpha", 1000)); (2)
}
}
| 1 | 注入されたフィールドから派生したベースパスを持つバリアントテンプレートを注入します - src/main/resources/templates/item |
| 2 | text/plain では src/main/resources/templates/item.txt のテンプレートが使用されます。 text/html では META-INF/resources/templates/item.html のテンプレートが使用されます。 |
RestTemplate utilクラスは、JAX-RSリソースメソッドのボディからテンプレートインスタンスを取得するために使用することができます。
@Path("/detail")
class DetailResource {
@GET
@Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
public TemplateInstance item() {
return RestTemplate.data("myItem", new Item("Alpha", 1000)); (1)
}
}
| 1 | テンプレートの名前は、リソースクラスとメソッド名から派生しています。このケースでは、DetailResource/item です。 |
Unlike with @Inject the templates obtained via RestTemplate are not validated, i.e. the build does not fail if a template does not exist.
|
4.12. タイプセーフメッセージバンドル
4.12.1. 基本概念
基本的な考え方は、すべてのメッセージは潜在的に非常にシンプルなテンプレートであるということです。型エラーを防ぐために、メッセージは メッセージバンドルインターフェース のアノテーションメソッドとして定義されます。Quarkusは、ビルド時に メッセージバンドルの実装を 生成します。そのため、実行時にバンドルを使用することができます。
-
io.quarkus.qute.i18n.MessageBundles#get()を経由し、直接コードの中で使用。例:MessageBundles.get(AppMessages.class).hello_name("Lucie") -
@InjectであなたのBeanに注入されます。 例:@Inject AppMessages -
メッセージ・バンドル名前空間を介してテンプレート内で参照。
{msg:hello_name('Lucie')} (1) (2) (3) {msg:message(myKey,'Lu')} (4)1 msgがデフォルトの名前空間です。2 hello_nameがメッセージキーです。3 Lucieはメッセージバンドルインターフェースメソッドのパラメーターです。4 また、予約されたキー messageを使用して、実行時に解決されたキーのローカライズされたメッセージを取得することも可能である。この場合、検証はスキップされます。
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;
@MessageBundle (1)
public interface AppMessages {
@Message("Hello {name}!") (2)
String hello_name(String name); (3)
}
| 1 | メッセージバンドルインターフェイスを表します。 バンドル名のデフォルトは msg で、テンプレート式の名前空間として使用されます (例: {msg:hello_name})。 |
| 2 | 各メソッドには @Message のアノテーションが必要です。その値はquteテンプレートです。値が提供されない場合、ローカライズされたファイルから対応する値が取得されます。もし、そのようなファイルがなければ、例外がスローされ、ビルドは失敗します。 |
| 3 | メソッドのパラメーターはテンプレートで使用することができます。 |
4.12.2. バンドル名とメッセージキー
キーはテンプレート内で直接使用されます。バンドル名はテンプレート式の名前空間として使用されます。 @MessageBundle は、メソッド名からメッセージキーを生成するために使用されるデフォルトのストラテジーを定義するために使用することができます。しかし、 @Message はこのストラテジーをオーバーライドし、カスタム・キーを定義することもできます。デフォルトでは、アノテーションされた要素の名前がそのまま使用されます。他の可能性としては、以下のようなものがあります。
-
キャメルケースを止め、ハイフン化したもの。 例:
helloName()→hello-name -
キャメルケースを止め、パーツをアンダースコア区切りとしたもの。例:
helloName()→hello_name
4.12.3. バリデーション
-
すべてのメッセージバンドルテンプレートは検証されます。
-
名前空間を持たないすべての式は、パラメーターにマッピングする必要があります。たとえば、
Hello {foo}→ の場合、メソッドにはfooという名前のパラメーターが必要です。 -
すべての式は、パラメーターのタイプに対して検証されます。たとえば、パラメーター
fooのタイプがorg.acme.Foo→org.acme.FooであるHello {foo.bar}場合、barという名前のプロパティーを持つ必要があります。A warning message is logged for each unused parameter.
-
-
{msg:hello(item.name)}のようなメッセージバンドルメソッドを参照する式も検証されます。
4.12.4. ローカライゼーション
デフォルトでは、@MessageBundle インターフェイスには quarkus.default-locale 設定プロパティーで指定したデフォルトロケールが使用されます。しかし、io.quarkus.qute.i18n.MessageBundle#locale() を使用して、カスタムのロケールを指定することができます。さらに、ローカライズされたバンドルを定義するには、以下の 2 つの方法があります。
-
@Localizedでアノテーションされたデフォルトのインターフェイスを拡張するインターフェイスを作成します。 -
アプリケーションアーカイブの
src/main/resources/messagesディレクトリーにある UTF-8 エンコードファイルを作成します (例:msg_de.properties)。
| While a localized interface enables easy refactoring an external file might be more convenient in many situations. |
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.Message;
@Localized("de") (1)
public interface GermanAppMessages extends AppMessages {
@Override
@Message("Hallo {name}!") (2)
String hello_name(String name);
}
| 1 | 値はロケールタグ文字列(IETF)です。 |
| 2 | 値はローカライズされたテンプレートです。 |
メッセージバンドルファイルは、UTF-8 でエンコードされている必要があります。ファイル名は、関連するバンドル名 (例: msg ) とアンダースコア、続いてロケートタグ (IETF) で構成されます。ファイルフォーマットは非常にシンプルで、各行は、セパレーターとして等号が使われるキーと値のペアか、コメント (行頭は #) を表しています。空白行は無視されます。キーは対応するメッセージバンドルインターフェイスから mapped to method names されます。値は通常、 io.quarkus.qute.i18n.Message#value() によって定義されるテンプレートを表します。値は、隣接する複数の通常行にまたがっていることがあります。その場合、行の終端をバックスラッシュ文字 \ でエスケープしなければなりません。この挙動は java.util.Properties.load(Reader) メソッドの挙動に非常に似ています。
msg_de.properties# This comment is ignored
hello_name=Hallo {name}! (1) (2)
| 1 | ローカライズされたファイルの各行は、キーと値のペアを表します。キーは、メッセージバンドルインターフェイスで宣言されたメソッドに対応しなければなりません。値はメッセージテンプレートになります。 |
| 2 | キーと値は等号で区切られています。 |
We use the .properties suffix in our example because most IDEs and text editors support syntax highlighting of .properties files. But in fact, the suffix could be anything - it is just ignored.
|
An example properties file is generated into the target directory for each message bundle interface automatically. For example, by default if no name is specified for @MessageBundle the file target/qute-i18n-examples/msg.properties is generated when the application is build via mvn clean package. You can use this file as a base for a specific locale. Just rename the file - e.g. msg_fr.properties, change the message templates and move it in the src/main/resources/messages directory.
|
hello=Hello \
{name} and \
good morning!
行の終端はバックスラッシュ文字 \ でエスケープされ、次の行の先頭の空白は無視されることに注意してください。つまり、{msg:hello('Edgar')} は Hello Edgar and good morning! とレンダリングされます。
ローカライズされたバンドルを定義したら、特定のテンプレートインスタンスに対して正しいバンドルを select する方法、つまりテンプレート内のすべてのメッセージバンドル式に対してロケールを指定する方法が必要です。デフォルトでは、 quarkus.default-locale 設定プロパティーで指定したロケールが、バンドルの選択に使用されます。また、テンプレートインスタンスの locale 属性を指定することもできます。
locale Attribute Example@Singleton
public class MyBean {
@Inject
Template hello;
String render() {
return hello.instance().setAttribute("locale", Locale.forLanguageTag("cs")).render(); (1)
}
}
| 1 | Locale インスタンスかロケールタグ文字列 (IETF) をセットすることが出来ます。 |
When using quarkus-resteasy-qute the locale attribute is derived from the Accept-Language header if not set by a user.
|
@Localized 修飾子を使用して、ローカライズされたMessage Bundleインタフェースを注入することができます。
@Singleton
public class MyBean {
@Localized("cs") (1)
AppMessages msg;
String render() {
return msg.hello_name("Jachym");
}
}
| 1 | アノテーションの値はロケールタグ文字列(IETF)です。 |
4.12.5. メッセージテンプレート
メッセージバンドルインターフェースの全てのメソッドは、メッセージテンプレートを定義しなければなりません。通常は io.quarkus.qute.i18n.Message#value() で定義しますが、利便性のため、ローカライズしたファイルに値を定義するオプションも用意されています。
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;
@MessageBundle
public interface AppMessages {
@Message (1)
String hello_name(String name);
@Message("Goodbye {name}!") (2)
String goodbye(String name);
}
| 1 | アノテーションの値が定義されていない。この場合、ローカライズされた補足ファイルの値が使用される。 |
| 2 | アノテーションの値は、ローカライズされたファイルに定義された値を優先して定義される。 |
hello_name=Hello \
{name} and \
good morning!
goodbye=Best regards, {name} (1)
| 1 | io.quarkus.qute.i18n.Message#value() は常に優先されるため、この値は無視されます。 |
メッセージテンプレートはビルド時に検証されます。メッセージテンプレートの欠落が検出された場合、例外がスローされ、ビルドに失敗します。
4.13. 設定リファレンス
Configuration property fixed at build time - All other configuration properties are overridable at runtime
タイプ |
デフォルト |
|
|---|---|---|
The list of suffixes used when attempting to locate a template file. By default, Environment variable: Show more |
文字列のリスト |
|
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 Environment variable: Show more |
文字列のリスト |
|
This regular expression is used to exclude template files from the Environment variable: Show more |
|
|
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:
- Environment variable: Show more |
string |
|
The list of content types for which the Environment variable: Show more |
文字列のリスト |
|
The default charset of the templates files. Environment variable: Show more |
|
|
The strategy used when a standalone expression evaluates to a "not found" value at runtime and the Environment variable: Show more |
|
|
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: Show more |
boolean |
|
If set to Environment variable: Show more |
boolean |
|
The global rendering timeout in milliseconds. It is used if no Environment variable: Show more |
long |
|
If set to Environment variable: Show more |
boolean |
|
The additional map of suffixes to content types. This map is used when working with template variants. By default, the Environment variable: Show more |
|
5. スタンドアローンライブラリーとして使用する場合のQute
Quteは、主にQuarkusのエクステンションとして設計されています。しかし、「スタンドアロン」のライブラリとして使用することも可能です。この場合、いくつかの機能は利用できず、いくつかの追加設定が必要となります。
- エンジン
-
まず第一に、すぐに使用できるマネージド
Engineインスタンスはありません。新しいインスタンスをEngine.builder()で設定する必要があります。 - テンプレート
-
-
デフォルトでは、template locators は登録されていません。つまり、
Engine.getTemplate(String)は機能しません。 -
カスタムテンプレートロケーターは
EngineBuilder.addLocator()を使って登録するか、テンプレートを手動で解析して、Engine.putTemplate(String, Template)で結果をキャッシュに格納することができます。
-
- セクション
-
-
デフォルトでは、セクションヘルパーは登録されていません。
-
デフォルトの値リゾルバーのセットは、便利な
EngineBuilder.addDefaultSectionHelpers()メソッドとEngineBuilder.addDefaults()メソッドを介して、それぞれ登録することができます。
-
- 値リゾルバー
-
-
ValueResolvers は自動生成されません。-
@TemplateExtensionmethods は動作しません。 -
@TemplateDataと@TemplateEnumの注釈は無視されます。
-
-
デフォルトの値リゾルバーのセットは、便利な
EngineBuilder.addDefaultValueResolvers()メソッドとEngineBuilder.addDefaults()メソッドを介して、それぞれ登録することができます。Not all functionality provided by the built-in extension methods is covered by the default value resolvers. However, a custom value resolver can be easily built via the ValueResolver.builder(). -
Qute がオブジェクトのプロパティーにアクセスし、パブリックメソッドを呼び出せるように、
Engine.addValueResolver (new ReflectionValueResolver())を介してReflectionValueResolverインスタンスを登録することが推奨されます。Keep in mind that reflection may not work correctly in some restricted environments or may require additional configuration, e.g. registration in case of a GraalVM native image.
-
- ユーザー定義タグ
-
-
ユーザー定義のタグは自動的に登録されません。
-
タグは、
Engine.builder().addSectionHelper(new UserTagSectionHelper.Factory("tagName","tagTemplate.html")).build()を介して手動で登録することができます。
-
- タイプセーフ
-
-
タイプセーフな式 は検証されていません。
-
Type-safe message bundles はサポートされていません。
-
- インジェクション
-
Templateインスタンスを注入することはできませんし、その逆もできません。テンプレートは、inject:およびcdi:の名前空間を介して@NamedCDI Bean を注入することはできません。