Qute リファレンスガイド
Qute は、Quarkus のニーズを満たすために特別に設計されたテンプレートエンジンです。ネイティブイメージのサイズを小さくするために、リフレクションの使用は最小限に抑えられています。API は、命令型とノンブロッキングリアクティブ型の両方のスタイルのコーディングを組み合わせています。開発モードでは、 src/main/resources/templates
フォルダーにあるすべてのファイルが変更があるかどうか監視され、変更はアプリケーションにすぐに表示されます。さらに、ビルド時にテンプレートの問題のほとんどの検出を試み、即座に失敗します。
このガイドでは、 導入例 、 コア機能 の説明、 Quarkusとの統合 の詳細が記載されています。
Quteは、主にQuarkusのエクステンションとして設計されています。スタンドアロンライブラリとして使用することも可能です。しかし、その場合、いくつかの機能は使用できません。一般的に、Quarkusとの 統合 のセクションで言及されている機能は、全て無くなります。制限と可能性についての詳細は、 Quteをスタンドアロンライブラリとして使用 のセクションを参照してください。 |
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 | テンプレートには、任意の ビルディングブロック を使用することができます。この場合、 Ifセクション は、入力データに基づいてメッセージの適切な部分をレンダリングするために使用されます。 |
Quarkus では、メッセージをフォーマットするために使用されるエンジンは、 @Inject Engine によって注入されるものと同じです。したがって、 テンプレート 拡張メソッド、 テンプレートへの直接注入 、 タイプセーフメッセージバンドル など、Quarkus固有の統合機能を利用することができます。
|
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
詳細は io.quarkus.qute.Qute クラスの javadoc をお読みください。
|
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();
Quarkusでは、注入用にあらかじめ設定された Engine が用意されています( Quarkus統合 を参照)。
|
Engine
のインスタンスがあれば、テンプレートの内容を解析することができます。
Template hello = engine.parse(helloHtmlContent);
Quarkusでは、テンプレート定義を単純に注入することが出来ます。テンプレートは自動的に解析され、キャッシュされます - Quarkus Integration を参照してください。 |
最後に テンプレートインスタンス を作成し、データをセットして出力をレンダリングします。
// 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
) -
出力をレンダリングする
Engine はテンプレート定義をキャッシュすることができるので、何度も内容をパースする必要がありません。Quarkus では、キャッシュは自動的に行われます。
|
3. コア機能
3.1. 基本的なビルディングブロック
テンプレートの動的なパーツには、コメント、式、セクション、パースされていない文字データなどがあります。
- コメント
-
コメントは
{!
and ends with the sequence!}
というシーケンスで始まります (例:{! This is a comment !}
)。複数行にすることができ、式やセクションを含むことができます。{! {#if true} !}
。 コメントの内容は出力のレンダリングの際に完全に無視されます。 - 式
-
式 は、評価された値を出力するものです。1つまたは複数のパーツから構成されます。パーツは、単純なプロパティ:
{foo}
{item.name}
や、仮想メソッドを表します:{item.get(name)}
{name ?: 'John'}
。また、式は名前空間で始まることもあります:{inject:colors}
。 - セクション
-
A section may contain static text, expressions and nested sections:
{#if foo.active}{foo.name}{/if}
. The name in the closing tag is optional:{#if active}ACTIVE!{/}
. A section can be empty:{#myTag image=true /}
. Some sections support optional end tags, i.e. if the end tag is missing then the section ends where the parent section ends. A section may also declare nested section blocks:{#if item.valid} Valid. {#else} Invalid. {/if}
and decide which block to render. - パース対象外文字データ
-
レンダリングは行うが パースは行わない という内容をマークするために使用されます。 これは
{|
and ends with the sequence|}
:{| <script>if(true){alert('Qute is cute!')};</script> |}
というシーケンスで始まり、複数行になる可能性もあります。以前はパース対象外文字データを
{[
and end with]}
から始めることができました。この構文は、他の言語の構文とよく衝突するため、現在では削除されています。
3.2. 識別子とタグ
識別子は、式やセクションタグで使用されます。有効な識別子は、空白文字以外の文字列です。ただし、式の中では有効な Java 識別子のみを使用することが推奨されます。
ドットを含む識別子を指定する必要がある場合は、ブラケット表記を使用することができます (例: {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 | 無視対象: " で開始 |
エスケープシーケンス \{ and \} を使ってテキストにデリミタを挿入することも可能です。 実際には、エスケープシーケンスは通常、開始デリミタのためにのみ必要とされます。すなわち、\{foo} は {foo} として描画されます(パース、評価は行われません)。
|
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>
Quarkusでは、プロパティー( quarkus.qute.remove-standalone-lines )を false に設定することで、デフォルトの動作を無効にすることができます。この場合、スタンドアロン行のすべての空白文字が出力されます。
|
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. サポートされているリテラル
リテラル | 例 |
---|---|
ブーリアン |
|
null |
|
string |
|
integer |
|
長 |
|
double |
|
float |
|
3.4.2. 解決
式の最初のパーツは、常に 現在のコンテキスト・オブジェクト に対して解決されます。最初のパーツに対して結果が見つからない場合は、親コンテキストオブジェクトに対して解決されます(利用可能な場合)。名前空間で始まる式の場合、現在のコンテキストオブジェクトは、利用可能なすべての NamespaceResolver
を使用して見つけられます。名前空間で始まらない式の場合、現在のコンテキストオブジェクトは、タグの 位置から導き出されます 。式の他のすべての部分は、前の解決の結果に対して、すべての ValueResolver
s を使用して解決されます。
たとえば、式 {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 | セクション内の現在のコンテキストオブジェクトは、解決されたパラメーターのマップです。 |
現在のコンテキストは、暗黙のバインディング this を通してアクセスできます。
|
3.4.4. 組込リゾルバ
Name | Description | 例 |
---|---|---|
エルビス演算子 |
前のパーツが解決できなかったり、 |
|
orEmpty |
前のパーツが解決できなかったり、 |
|
Ternary Operator |
if-then-else文の省略形。 Ifセクション と異なり、入れ子の演算子はサポートされていません。 |
|
論理 AND 演算子 |
If セクション で説明したように、両方のパートが |
|
論理 OR 演算子 |
If セクション で説明したように、いずれかのパーツが |
|
三項演算子の条件は、 Ifセクション で説明したように、値が falsy とみなされない場合、 true と評価されます。
|
実際、オペレーターは 1 つのパラメーターを消費する "仮想メソッド" として実装されており、infix 記法で使用することができます。例えば、{person.name or 'John'} は {person.name.or('John')} に、{item.isActive ? item.name : 'Inactive item'} は {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 テンプレートの場合、'
、"
、<
, >
、&
の文字は、テンプレートバリアントが設定されている場合はデフォルトでエスケープされます。
Quarkusでは、 src/main/resources/templates にあるテンプレートに対してバリアントが自動的に設定されます。デフォルトでは、テンプレートファイルのコンテンツタイプを決定するために java.net.URLConnection#getFileNameMap() が使用されます。コンテンツタイプへのサフィックスの追加マップは、 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> としてレンダリングされます。 |
デフォルトでは、text/html 、text/xml 、application/xml および application/xhtml+xml コンテンツタイプのいずれかを持つテンプレートがエスケープされます。しかし、quarkus.qute.escape-content-types 設定プロパティーで、このリストを拡張することができます。
|
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 クラスに対して生成された値リゾルバによって評価されます。
|
仮想メソッドは通常、 @TemplateExtensionメソッド 、 @TemplateData または パラメータ宣言 で使用されるクラス用に生成された値リゾルバーによって評価されます。ただし、Javaのクラス/メソッドに依存しないカスタム値リゾルバーも登録可能です。 |
単一のパラメーターを持つ仮想メソッドは 中置記法を使用して呼び出すことができます。
<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. CompletionStage
と Uni
オブジェクトの評価
java.util.concurrent.CompletionStage
および io.smallrye.mutiny.Uni
を実装したオブジェクトは、特別な方法で評価されます。式の一部が CompletionStage
に解決された場合、このステージが完了すると解決が継続され、式の次のパーツ (もしあれば) が完了したステージの結果に対して評価されます。たとえば、式 {foo.size}
があり、foo
が CompletionStage<List<String>>
に解決される場合、size
は完了した結果、すなわち List<String>
に対して解決されます。式の一部が Uni
に解決される場合、CompletionStage
が Uni#subscribeAsCompletionStage()
を使用して Uni
から最初に作成され、その後上記のように評価されます。
各 Uni#subscribeAsCompletionStage() は新しいサブスクリプションになることに注目してください。 Uni の項目や障害がテンプレートデータとして使用される前に、メモ化を設定する必要があるかもしれません。つまり、 myUni.memoize().indefinitely() .
|
CompletionStage
が完了しなかったり、あるいは Uni
がアイテム/失敗を発しないということが起こり得ます。この場合、レンダリングメソッド (TemplateInstance#render()
および TemplateInstance#createUni()
) は、特定のタイムアウトを過ぎると失敗します。このタイムアウトは、テンプレートインスタンスの timeout
属性として指定することができます。timeout
属性が設定されていない場合、グローバルレンダリングタイムアウトが使用されます。
Quarkus では、デフォルトのタイムアウトは io.quarkus.qute.timeout 設定プロパティーで設定することができます。Qute をスタンドアロンで使用する場合は、EngineBuilder#timeout() メソッドを使用することができます。
|
以前のバージョンでは、TemplateInstance#render() メソッドだけがタイムアウト属性を尊重していました。たとえば、templateInstance.createUtni().ifNoItem().after(Duration.ofMillis(500)).fail() のように、io.quarkus.qute.useAsyncTimeout=false config プロパティーを使って、古い動作を保持しタイムアウトを自分で処理することが可能です。
|
3.4.8.1. テンプレートの問題個所の特定方法
タイムアウトが発生したときに、テンプレートの問題のあるパーツを見つけるのは簡単ではありません。ロガー io.quarkus.qute.nodeResolve
の TRACE
レベルを設定し、その後ログ出力の解析を試みることができます。
application.properties
の例quarkus.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. 不明なプロパティ
It can happen that an expression may not be evaluated at runtime. For example, if there is an expression {person.age}
and there is no property age
declared on the Person
class. The behavior differs based on whether the Strict Rendering is enabled or not.
これを有効にすると、プロパティーが見つからない場合、常に TemplateException
が発生し、レンダリングが中断されます。このエラーを抑制するために、default values や safe expressions を使用することができます。
無効な場合、デフォルトで特別な定数 NOT_FOUND
が出力に書き込まれます。
Quarkusでは、 設定リファレンス で説明されているように、 quarkus.qute.property-not-found-strategy でデフォルトのストラテジーを変更することが可能です。
|
タイプセーフ式 や タイプセーフテンプレート を使用した場合、ビルド時に同様のエラーが検出されます。 |
3.5. セクション
A section has a start tag that starts with #
, followed by the name of the section such as {#if}
and {#each}
. It may be empty, i.e. the start tag ends with /
: {#myEmptySection /}
. Sections usually contain nested expressions and other sections. The end tag starts with /
and contains the name of the section (optional): {#if foo}Foo!{/if}
or {#if foo}Foo!{/}
. Some sections support optional end tags, i.e. if the end tag is missing then the section ends where the parent section ends.
#let
Optional End Tag Example{#if item.isActive}
{#let price = item.price} (1)
{price}
// synthetic {/let} added here automatically
{/if}
// {price} cannot be used here!
1 | Defines the local variable that can be used inside the parent {#if} section. |
<1> Built-in section | <1> Supports Optional End Tag |
---|---|
<1> |
<1> ❌ |
<1> |
<1> ❌ |
<1> |
<1> ❌ |
<1> |
<1> ✅ |
<1> |
<1> ❌ |
<1> |
<1> ✅ |
<1> ユーザー定義タグ |
<1> ❌ |
<1> |
<1> ❌ |
<1> |
<1> ❌ |
3.5.1. Parameters
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 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.2. ループセクション
ループセクションでは、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
-true
ifhasNext == false
-
isFirst
-true
ifcount == 1
-
odd
- 要素のカウントが奇数である場合true
-
hasNext
- イテレーションにさらに要素がある場合にtrue
-
indexParity
- は、カウント値に応じてodd
またはeven
を出力します
しかし、キーを直接使うことはできません。その代わり、外部スコープの変数と競合する可能性を避けるために、接頭辞が使用されます。デフォルトでは、反復処理された要素のエイリアスの後にアンダースコアを付けたものが接頭辞として使用されます。たとえば、{#each}
セクション ({it_hasNext}
) の中では、hasNext
キーは it_
を接頭辞として使用する必要があります。
each
イテレーションのメタデータの例{#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
イテレーションのメタデータの例{#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.3. If セクション
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.4. When セクション
このセクションは、Java の switch
や Kotlin の when
に似ています。条件が満たされるまで、 テストされた値 をすべてのブロックに対して順次マッチさせます。最初にマッチしたブロックが実行されます。他のすべてのブロックは無視されます(この動作は、 break
文が必要な Java switch
とは異なります)。
when
/ is
の名前のエイリアスを使用した例{#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 | 演算子 を用いてマッチングロジックを指定することが可能です。 Ifセクション とは異なり、入れ子の演算子はサポートされていません。 |
3 | else は、他のブロックが値に一致しない場合に実行されます。 |
switch
/ case
の名前エイリアスを使用した例{#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") の場合に実行されます |
enum 定数は、テストされた値が利用可能な型情報を持ち、列挙型に解決された場合に検証されます。 |
is
/ case
ブロック条件では、以下の演算子がサポートされています。
オペレーター | エイリアス | 例 |
---|---|---|
not equal |
|
|
greater than |
|
|
greater than or equal to |
|
|
less than |
|
|
less than or equal to |
|
|
in |
|
|
not in |
|
|
3.5.5. Let セクション
このセクションでは、名前付きローカル変数を定義することができます。
{#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 | ローカル変数は、 リテラル を表すこともできる式、すなわち 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.6. With セクション
このセクションは、現在のコンテキストオブジェクトを設定するために使用することができます。これはテンプレート構造を単純化するのに便利かもしれません。
{#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.7. Include セクション
このセクションは、別のテンプレートをインクルードし、場合によってはテンプレートの一部をオーバーライドするために使用することができます (下記の テンプレートの継承 を参照)。
<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} セクションに使用されます。 |
セクションブロックは、オプションのエンドタグ ({/title} ) を定義することもできます。
|
3.5.8. ユーザー定義タグ
ユーザー定義タグは、タグテンプレート をインクルードし、オプションでいくつかの引数を渡し、場合によってはテンプレートの一部をオーバーライドするために使用することができます。たとえば、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}
セクションと同じように、テンプレートの継承を利用することができます。
myTag
This is {#insert title}my title{/title}! (1)
1 | insert セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできるパーツを指定するために使われます。 |
<p>
{#myTag}
{#title}my custom title{/title} (1)
{/myTag}
</p>
1 | 結果は、<p>これは私のカスタムタイトル!</p> のようになります。 |
3.5.9. フラグメント
フラグメントは、別個のテンプレートとして扱える、つまり別々にレンダリングできるテンプレートの一部分を表します。この機能を導入した主な動機の1つは、 htmxフラグメント のようなユースケースをサポートするためでした。
フラグメントは {#fragment}
セクションで定義することができます。 各フラグメントは、英数字とアンダースコアのみからなる識別子を持ちます。
フラグメント識別子は、テンプレート内で一意でなければならないことに注意してください。 |
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 を持つフラグメントを定義します。識別子には、英数字とアンダースコアしか使用できないことに注意してください。 |
io.quarkus.qute.Template.getFragment(String)
メソッドにより、プログラム的にフラグメントを取得することができます。
@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>
Quarkusでは、 タイプセーフフラグメント を定義することも可能です。 |
また、 {#include}
セクションを持つフラグメントを、別のテンプレートやフラグメントを定義するテンプレートの中に含めることもできます。
<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 を使用します。 |
同じテンプレートからフラグメントを参照する場合は、 $ の前の部分、つまり {#include $item_aliases /} のような部分をスキップしてください。
|
この機能を無効にするために、 {#include item$item_aliases _ignoreFragments=true /} を指定することができます。つまり、テンプレート識別子にドル記号 $ を指定しても、フラグメント検索は行われません。
|
3.5.9.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 | Defines a hidden fragment with identifier strong . In this particular case, we use the false boolean literal as the value of the rendered parameter. However, it’s possible to use any expression there. |
2 | フラグメント strong をインクルードし、値を渡します。構文 $strong に注意してください。これは、現在のテンプレートからフラグメント strong をインクルードするように処理されます。 |
3 | フラグメント strong をインクルードし、値を渡す。 |
上のスニペットは、次のようにレンダリングされます:
<h1>My page</h1>
<p>This document
<strong>contains</strong>
a lot of
<strong>information</strong>
!</p>
3.5.10. 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 is not possible to cache the parsed value to conserve resources and optimize performance. |
3.5.11. キャッシュされたセクション
めったに変更されないテンプレートの一部をキャッシュすることが実用的な場合もあります。キャッシュ機能を使うには、組み込みの io.quarkus.qute.CacheSectionHelper.Factory
を登録・設定します:
// A simple map-based cache
ConcurrentMap<String, CompletionStage<ResultNode>> map = new ConcurrentHashMap<>();
engineBuilder
.addSectionHelper(new CacheSectionHelper.Factory(new Cache() {
@Override
public CompletionStage<ResultNode> getValue(String key,
Function<String, CompletionStage<ResultNode>> loader) {
return map.computeIfAbsent(key, k -> loader.apply(k));
}
})).build();
If the quarkus-cache extension is present in a Quarkus application then the CacheSectionHelper is registered and configured automatically. The name of the cache is qute-cache . It can be configured in a standard way and even managed programmatically via @Inject @CacheName("qute-cache") Cache .
|
Then, the {#cached}
section can be used in a template:
{#cached} (1)
Result: {service.findResult} (2)
{/cached}
1 | If the key param is not used then all clients of the template share the same cached value. |
2 | This part of the template will be cached and the {service.findResult} expression is only evaluated when a cache entry is missing/invalidated. |
{#cached key=currentUser.username} (1)
User-specific result: {service.findResult(currentUser)}
{/cached}
1 | The key param is set and so a different cached value is used for each result of the {currentUser.username} expression. |
When using cache it’s very often important to have the option to invalidate a cache entry by the specific key. In Qute the key of a cache entry is a String that consist of the template name, line and column of the starting {#cached} tag and the optional key parameter: {TEMPLATE}:{LINE}:{COLUMN}_{KEY} . For example, foo.html:10:1_alpha is a key for the cached section in a template foo.html , the {#cached} tag is placed on the line 10, column 1. And the optional key parameter resolves to alpha .
|
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 | レンダリング終了後に実行されるコールバックを登録します。 |
また、 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);
テンプレートのレンダリングは2つのフェーズに分かれています。最初のフェーズ(非同期)では、テンプレート内のすべての式が解決され、 結果ツリー が構築されます。同期的な第2フェーズでは、結果ツリーが マテリアライズされ 、結果ノードが1つずつ、特定のコンシューマによって消費/バッファリングされるチャンクを出力します。 |
3.7. エンジン設定
3.7.1. 値リゾルバ
式を評価する際に値リゾルバを使用します。カスタム io.quarkus.qute.ValueResolver
は EngineBuilder.addValueResolver()
からプログラムで登録することができます。
ValueResolver
ビルダー例engineBuilder.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 のテンプレートがない場合に使用されます。ロケーターは、テンプレートの内容を読み取る際に、正しい文字エンコーディングを使用する責任があります。
Quarkusでは、 src/main/resources/templates のすべてのテンプレートが自動的に配置され、 quarkus.qute.default-charset を介して設定されたエンコーディング(デフォルトではUTF-8)が使用されます。カスタムロケーターは、 @Locate のアノテーションを使用して 登録 できます。
|
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 は有効な値ですが、 If セクション に記載されているように falsy とみなされ、出力はされません。
|
strict レンダリングはデフォルトで有効になっています。しかし、io.quarkus.qute.EngineBuilder.strictRendering(boolean)
によって、この機能を無効にすることができます。
Quarkus では、専用の config プロパティーである 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 インスタンスを注入します。 |
4.1. エンジンのカスタマイズ
Additional components can be registered manually via EngineBuilder
methods in a CDI observer method at runtime:
import io.quarkus.qute.EngineBuilder;
class MyBean {
void configureEngine(@Observes EngineBuilder builder) {
// Add a custom section helper
builder.addSectionHelper(new CustomSectionFactory());
// Add a custom value resolver
builder.addValueResolver(ValueResolver.builder()
.appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
.resolveSync(ctx -> (Long) ec.getBase() * 10)
.build());
}
}
However, in this particular case the section helper factory is ignored during validation at build time. If you want to register a section that participates in validation of templates at build time then use the convenient @EngineConfiguration
annotation:
import io.quarkus.qute.EngineConfiguration;
import io.quarkus.qute.SectionHelper;
import io.quarkus.qute.SectionHelperFactory;
@EngineConfiguration (1)
public class CustomSectionFactory implements SectionHelperFactory<CustomSectionFactory.CustomSectionHelper> {
@Inject
Service service; (2)
@Override
public List<String> getDefaultAliases() {
return List.of("custom");
}
@Override
public ParametersInfo getParameters() {
// Param "foo" is required
return ParametersInfo.builder().addParameter("foo").build(); (3)
}
@Override
public Scope initializeBlock(Scope outerScope, BlockInfo block) {
block.addExpression("foo", block.getParameter("foo"));
return outerScope;
}
@Override
public CustomSectionHelper initialize(SectionInitContext context) {
return new CustomSectionHelper();
}
class CustomSectionHelper implements SectionHelper {
private final Expression foo;
public CustomSectionHelper(Expression foo) {
this.foo = foo;
}
@Override
public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
return context.evaluate(foo).thenApply(fooVal -> new SingleResultNode(service.getValueForFoo(fooVal))); (4)
}
}
}
1 | A SectionHelperFactory annotated with @EngineConfiguration is used during validation of templates at build time and automatically registered at runtime (a) as a section factory and (b) as a CDI bean. |
2 | CDI Beanインスタンスは実行時に使用されます - これはファクトリーが注入ポイントを定義できることを意味します |
3 | Validate that foo parameter is always present; e.g. {#custom foo='bar' /} is ok but {#custom /} results in a build failure. |
4 | レンダリング時に注入された Service を使用します。 |
The @EngineConfiguration
annotation can be also used to register ValueResolver
s and NamespaceResolver
s.
4.1.1. テンプレートロケーターの登録
テンプレートロケータ を登録する最も簡単な方法は、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();
}
}
quarkus-resteasy-qute を使用する場合、コンテンツネゴシエーションは自動的に実行されます。詳しくは、 RESTEasy Integration の項をご覧ください。
|
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 Bean は、1 つのレンダリング操作用にテンプレート内のすべての式で共有され、レンダリングが終了した後に破棄されます。
|
cdi
および inject
の名前空間を持つすべての式は、ビルド時に検証されます。
For the expression cdi:personService.findPerson(10).name
, the implementation class of the injected bean must either declare the findPerson
method or a matching template extension method must exist.
表現 inject:foo.price
については,注入されたBeanの実装クラスは, price
プロパティ(例えば, getPrice()
メソッド)をもつか,一致する テンプレート拡張メソッド が存在する必要があります。
@Named でアノテーションされたすべてのBeanに対して ValueResolver も生成され、そのプロパティーにリフレクションなしでアクセスできるようになります。
|
アプリケーションが HTTP リクエスト を提供する場合、inject 名前空間を介して現在の io.vertx.core.http.HttpServerRequest を注入することもできます (例: {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 を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。 |
パラメーター宣言で使用されるすべての型に対して値リゾルバが自動的に生成され、そのプロパティーにリフレクションなしでアクセスできるようになります。 |
タイプセーフテンプレート のメソッドパラメータは、自動的にパラメータ宣言になります。 |
セクションは、パラメーター宣言にマッチする名前をオーバーライドできることに注意してください。
{@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
となります。
The type of a default value must be assignable to the type of the parameter declaration. For example, see the incorrect parameter declaration that results in a build failure: {@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)} .
|
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. タイプセーフテンプレート
You can define type-safe templates in your Java code. Parameters of type-safe templates are automatically turned into parameter declarations that are used to bind Type-safe Expressions. The type-safe expressions are then validated at build time.
There are two ways to define a type-safe template:
-
Annotate a class with
@io.quarkus.qute.CheckedTemplate
and all itsstatic native
methods will be used to define type-safe templates and the list of parameters they require. -
Use a Java record that implements
io.quarkus.qute.TemplateInstance
; the record components represent the template parameters and@io.quarkus.qute.CheckedTemplate
can be optionally used to configure the template.
4.5.1. Nested Type-safe Templates
If using templates in Jakarta REST resources, you can rely on the following convention:
-
テンプレートファイルを
/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 jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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 パラメータは自動的に パラメータ宣言 となるため、この名前を参照するすべての式が検証されます。 |
3 | テンプレート内で Item オブジェクトにアクセスできるようにします。 |
デフォルトでは、@CheckedTemplate でアノテーションされたクラスで定義されたテンプレートは、タイプセーフな式、つまり、ビルド時に検証可能な式のみを含むことができます。この要件を緩和するために、@CheckedTemplate(requireTypeSafeExpressions = false) を使用することができます。
|
4.5.2. トップレベルタイプセーフテンプレート
また、 @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 jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
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.3. Template Records
A Java record that implements io.quarkus.qute.TemplateInstance
denotes a type-safe template. The record components represent the template parameters and @io.quarkus.qute.CheckedTemplate
can be optionally used to configure the template.
package org.acme.quarkus.sample;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import io.quarkus.qute.TemplateInstance;
@Path("hello")
public class HelloResource {
record Hello(String name) implements TemplateInstance {} (1)
@GET
@Produces(MediaType.TEXT_PLAIN)
public TemplateInstance get(@QueryParam("name") String name) {
return new Hello(name); (2)
}
}
1 | Declares a type-safe template with the Java record. |
2 | Instantiate the record and use it as an ordinary TemplateInstance . |
4.5.4. カスタマイズされたテンプレート・パス
@CheckedTemplate
メソッドのテンプレートパスは、 ベースパス と デフォルトの名前 から構成されます。 ベースパス は @CheckedTemplate#basePath()
で指定されます. デフォルトでは,ネストした静的クラスの場合は宣言クラスのsimple name,トップレベルクラスの場合は空文字列が使用されます. デフォルトの名前 は, @CheckedTemplate#defaultName()
で指定された戦略によって導出されます。 デフォルトでは、 @CheckedTemplate
のメソッドの名前がそのまま使われます。
package org.acme.quarkus.sample;
import jakarta.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.5. タイプセーフフラグメント
You can also define a type-safe fragment in your Java code. A native static method with the name that contains a dollar sign $
denotes a method that represents a fragment of a type-safe template. The name of the fragment is derived from the annotated method name. The part before the last occurence of a dollar sign $
is the method name of the related type-safe template. The part after the last occurence of a dollar sign is the fragment identifier. The strategy defined by the relevant CheckedTemplate#defaultName()
is honored when constructing the defaulted names.
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();
}
}
この機能を無効にするために、 @CheckedTemplate#ignoreFragments=true を指定することができます。つまり、メソッド名にドル記号 $ を指定しても、チェックされたフラグメントメソッドになることはありません。
|
4.6. テンプレート拡張メソッド
Extension methods can be used to extend the data classes with new functionality (to extend the set of accessible properties and methods) or to resolve expressions for a specific namespace. For example, it is possible to add computed properties and virtual methods.
値リゾルバは、 @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()
でマッチング名を指定することは可能です。
TemplateInstance.createUni()
の例@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
の例@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()
で指定された正規表現と名前を照合することも可能です。
TemplateInstance.createUni()
の例@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()
を使用して、一致する名前のコレクションを指定することができます。追加の文字列メソッド・パラメータも必須です。
TemplateInstance.createUni()
の例@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"
}
余分な一致条件は無視されます。優先順位の高い順に並べると、以下のようになります。 matchRegex() , matchNames() および matchName() 。
|
4.6.2. メソッドパラメーター
An extension method may declare parameters. If no namespace is specified then the first parameter that is not annotated with @TemplateAttribute
is used to pass the base object, i.e. org.acme.Item
in the first example. If matching any name or using a regular expression, then a string method parameter needs to be used to pass the property name. Parameters annotated with @TemplateAttribute
are obtained via TemplateInstance#getAttribute()
. All other parameters are resolved when rendering the template and passed to the extension method.
@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. 名前空間拡張メソッド
If TemplateExtension#namespace()
is specified then the extension method is used to resolve expressions with the given namespace. Template extension methods that share the same namespace are grouped in one resolver ordered by TemplateExtension#priority()
. The first matching extension method is used to resolve an expression.
@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')}
-
マップ値は、直接アクセスすることもできます ({map.myKey} )。正規の識別子ではないキーにはブラケット表記を使用します ({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}
-
リストの要素にはインデックス ({list.10} または {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では、実行時にデータにアクセスするためのリフレクションの使用を避けることができます。
非publicのメンバー、コンストラクタ、静的イニシャライザ、静的メソッド、合成メソッド、ボイドメソッドは常に無視されます。 |
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)}
を介してアクセスすることができます。
名前空間は、英数字とアンダースコアだけで構成されます。 |
@TemplateData
でアノテーションされたクラスpackage 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 の単純名が名前空間として使用されます。
@TemplateEnum
でアノテーションされた enumpackage model;
@TemplateEnum (1)
public enum Status {
ON,
OFF
}
1 | 名前空間 Status を持つネームリゾルバーが自動的に生成される。 |
enum 以外のクラスで宣言された @TemplateEnum は無視されます。また、enum が @TemplateData アノテーションを宣言する場合は、@TemplateEnum アノテーションは無視されます。
|
{#if machine.status == Status:ON}
The machine is ON!
{/if}
Quarkus は、名前空間の競合の可能性を検出し、特定の名前空間が複数の @TemplateData や @TemplateEnum アノテーションによって定義されている場合、ビルドを失敗させます。
|
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.txt
User 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
といったテンプレート例外に遭遇するかも知れません。
この問題を解決するには、いくつかの方法があります。
-
タイプセーフテンプレート や タイプセーフ式 の使用
-
この場合、最適化されたバリューリゾルバーが自動的に生成され、実行時に使用されます。
-
これが望ましい解決策です
-
-
Annotate the model class with
@TemplateData
- a specialized value resolver is generated and used at runtime -
モデルクラスに
@io.quarkus.runtime.annotations.RegisterForReflection
のアノテーションを付けて、リフレクションベースの値リゾルバーを動作させます。
4.10. RESTEasy 統合
If you want to use Qute in your Jakarta REST application, then depending on which Jakarta REST stack you are using, you’ll need to register the proper extension first. If you are using the traditional quakus-resteasy
extension, then in your pom.xml
file, add:
<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
実装を登録しています。そのため、ユーザーは必要なすべての内部ステップを処理する必要がなくなります。
The end result is that a using Qute within a Jakarta REST resource may look as simple as:
package org.acme.quarkus.sample;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
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 | If there is no @Location qualifier provided, the field name is used to locate the template. In this particular case, we’re injecting a template with path templates/hello.txt . |
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. |
3 | レンダリングをトリガーしないことに注意してください - これは特別な ContainerResponseFilter の実装によって自動的に行われます。 |
Users are encouraged to use Type-safe templates that help to organize the templates for a specific Jakarta REST 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 クラスを使用すると、Jakarta REST リソースメソッドのボディからテンプレートインスタンスを取得することができます:
@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 です。 |
@Inject とは異なり、 RestTemplate 経由で取得したテンプレートは検証されません。即ち、テンプレートが存在しなくてもビルドは失敗しません。
|
4.12. タイプセーフメッセージバンドル
4.12.1. 基本概念
The basic idea is that every message is potentially a very simple template. In order to prevent type errors, a message is defined as an annotated method of a message bundle interface. Quarkus generates the message bundle implementation at build time.
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 | Each method must be annotated with @Message . The value is a qute template. If no value is provided, then a corresponding value from a localized file is taken. If no such file exists, an exception is thrown and the build fails. |
3 | メソッドのパラメーターはテンプレートで使用することができます。 |
メッセージバンドルは、実行時に使用することができます:
-
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
を使用して、実行時に解決されたキーのローカライズされたメッセージを取得することも可能である。この場合、検証はスキップされます。
4.12.2. デフォルトのバンドル名
The bundle name is defaulted unless it’s specified with @MessageBundle#value()
. For a top-level class the msg
value is used by default. For a nested class the name consists of the simple names of all enclosing classes in the hierarchy (top-level class goes first), followed by the simple name of the message bundle interface. Names are separated by underscores.
例えば、以下のメッセージバンドルの名前は、デフォルトで Controller_index
になります:
class Controller {
@MessageBundle
interface index {
@Message("Hello {name}!")
String hello(String name); (1)
}
}
1 | このメッセージは、 {Controller_index:hello(name)} を介してテンプレートで使用することができます。 |
バンドル名は、ローカライズされたファイル名の一部としても使用されます。例えば、 Controller_index は、 Controller_index_de.properties となります。
|
4.12.3. バンドル名とメッセージキー
Message keys are used directly in templates. The bundle name is used as a namespace in template expressions. The @MessageBundle
can be used to define the default strategy used to generate message keys from method names. However, the @Message
can override this strategy and even define a custom key. By default, the annotated element’s name is used as-is. Other possibilities are:
-
キャメルケースを止め、ハイフン化したもの。 例:
helloName()
→hello-name
-
キャメルケースを止め、パーツをアンダースコア区切りとしたもの。例:
helloName()
→hello_name
4.12.4. バリデーション
-
すべてのメッセージバンドルテンプレートは検証されます。
-
名前空間を持たないすべての式は、パラメーターにマッピングする必要があります。たとえば、
Hello {foo}
→ の場合、メソッドにはfoo
という名前のパラメーターが必要です。 -
すべての式は、パラメーターのタイプに対して検証されます。たとえば、パラメーター
foo
のタイプがorg.acme.Foo
→org.acme.Foo
であるHello {foo.bar}
場合、bar
という名前のプロパティーを持つ必要があります。未使用の パラメーターごとに警告メッセージが記録されます。
-
-
{msg:hello(item.name)}
のようなメッセージバンドルメソッドを参照する式も検証されます。
4.12.5. ローカライゼーション
デフォルトでは、@MessageBundle
インターフェイスには quarkus.default-locale
設定プロパティーで指定したデフォルトロケールが使用されます。しかし、io.quarkus.qute.i18n.MessageBundle#locale()
を使用して、カスタムのロケールを指定することができます。さらに、ローカライズされたバンドルを定義するには、以下の 2 つの方法があります。
-
@Localized
でアノテーションされたデフォルトのインターフェイスを拡張するインターフェイスを作成します。 -
アプリケーションアーカイブの
src/main/resources/messages
ディレクトリーにある UTF-8 エンコードファイルを作成します (例:msg_de.properties
)。
ローカライズされたインターフェイスはリファクタリングを容易にしますが、外部ファイルの方が便利な場面も多いかもしれません。 |
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 | 値はローカライズされたテンプレートです。 |
Message bundle files must be encoded in UTF-8. The file name consists of the relevant bundle name (e.g. msg
) and underscore followed by a language tag (IETF; e.g. en-US
). The language tag may be omitted, in which case the language tag of the default bundle locale is used. For example, if bundle msg
has default locale en
, then msg.properties
is going to be treated as msg_en.properties
. If both msg.properties
and msg_en.properties
are detected, an exception is thrown and build fails. The file format is very simple: each line represents either a key/value pair with the equals sign used as a separator or a comment (line starts with #
). Blank lines are ignored. Keys are mapped to method names from the corresponding message bundle interface. Values represent the templates normally defined by io.quarkus.qute.i18n.Message#value()
. A value may be spread out across several adjacent normal lines. In such case, the line terminator must be escaped with a backslash character \
. The behavior is very similar to the behavior of the java.util.Properties.load(Reader)
method.
msg_de.properties
# This comment is ignored
hello_name=Hallo {name}! (1) (2)
1 | ローカライズされたファイルの各行は、キーと値のペアを表します。キーは、メッセージバンドルインターフェイスで宣言されたメソッドに対応しなければなりません。値はメッセージテンプレートになります。 |
2 | キーと値は等号で区切られています。 |
この例では .properties という接尾辞を使用していますが、これはほとんどの IDE やテキストエディターが .properties ファイルのシンタックスハイライトをサポートしているからです。しかし、実際には接尾辞は何でもよいため、単に無視されます。
|
メッセージバンドルインターフェイスごとに、ターゲットディレクトリーにサンプルのプロパティーファイルが自動的に生成されます。たとえば、デフォルトで @MessageBundle に名前が指定されない場合、mvn clean package を用いてアプリケーションをビルドすると、target/qute-i18n-examples/msg.properties というファイルが生成されます。このファイルは、特定のロケールのベースとして使用することができます。単にファイル名を変更 (例: msg_fr.properties ) して メッセージのテンプレートを変更し、これを src/main/resources/messages ディレクトリーに移動します。
|
hello=Hello \
{name} and \
good morning!
行の終端はバックスラッシュ文字 \
でエスケープされ、次の行の先頭の空白は無視されることに注意してください。つまり、{msg:hello('Edgar')}
は Hello Edgar and good morning!
とレンダリングされます。
Once we have the localized bundles defined, we need a way to select the correct bundle for a specific template instance, i.e. to specify the locale for all message bundle expressions in the template. By default, the locale specified via the quarkus.default-locale
configuration property is used to select the bundle. Alternatively, you can specify the locale
attribute of a template instance.
locale
属性の例@Singleton
public class MyBean {
@Inject
Template hello;
String render() {
return hello.instance().setAttribute("locale", Locale.forLanguageTag("cs")).render(); (1)
}
}
1 | Locale インスタンスかロケールタグ文字列 (IETF) をセットすることが出来ます。 |
quarkus-resteasy-qute を使用する場合、ユーザーによって設定されていない場合、 locale 属性は Accept-Language ヘッダーから導出されます。
|
@Localized
修飾子を使用して、ローカライズされたMessage Bundleインタフェースを注入することができます。
@Singleton
public class MyBean {
@Localized("cs") (1)
AppMessages msg;
String render() {
return msg.hello_name("Jachym");
}
}
1 | アノテーションの値はロケールタグ文字列(IETF)です。 |
4.12.6. メッセージテンプレート
メッセージバンドルインターフェースの全てのメソッドは、メッセージテンプレートを定義しなければなりません。通常は 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. 設定リファレンス
ビルド時に固定される設定プロパティ - その他の設定プロパティは実行時にオーバーライド可能です。
型 |
デフォルト |
|
---|---|---|
The list of suffixes used when attempting to locate a template file. By default, Environment variable: Show more |
list of string |
|
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 Examples:
Environment variable: Show more |
list of string |
|
This regular expression is used to exclude template files from the The matched input is the file path relative from the By default, the hidden files are excluded. The name of a hidden file starts with a dot. 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 |
list of string |
|
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 This strategy is never used when evaluating section parameters, e.g. By default, 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 Note that the Environment variable: Show more |
boolean |
|
The global rendering timeout in milliseconds. It is used if no Environment variable: Show more |
長 |
|
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)
で結果をキャッシュに格納することができます。
-
- Template initializers
-
-
No
TemplateInstance.Initializer
is registered by default, therefore@TemplateGlobal
annotations are ignored. -
A custom
TemplateInstance.Initializer
can be registered withEngineBuilder#addTemplateInstanceInitializer()
and initialize a template instance with any data and attributes.
-
- セクション
-
-
デフォルトでは、セクションヘルパーは登録されていません。
-
デフォルトの値リゾルバーのセットは、便利な
EngineBuilder.addDefaultSectionHelpers()
メソッドとEngineBuilder.addDefaults()
メソッドを介して、それぞれ登録することができます。
-
- 値リゾルバー
-
-
ValueResolver が自動生成されることはありません。
-
@TemplateExtension
のメソッド は動作しません。 -
@TemplateData
と@TemplateEnum
アノテーションは無視されます。
-
-
デフォルトの値リゾルバーのセットは、便利な
EngineBuilder.addDefaultValueResolvers()
メソッドとEngineBuilder.addDefaults()
メソッドを介して、それぞれ登録することができます。組み込みの拡張メソッドによって提供されるすべての機能が、デフォルトの値リゾルバーによってカバーされているわけではありません。ただし、カスタムの値リゾルバーは ValueResolver.builder()
を使って簡単に構築することができます。 -
Qute がオブジェクトのプロパティーにアクセスし、パブリックメソッドを呼び出せるように、
Engine.addValueResolver (new ReflectionValueResolver())
を介してReflectionValueResolver
インスタンスを登録することが推奨されます。リフレクションは、一部の制限された環境では正しく動作しないか、GraalVM ネイティブイメージの場合の登録など、追加の設定が必要な場合があることに留意してください。
-
- ユーザー定義タグ
-
-
ユーザー定義のタグは自動的に登録されません。
-
タグは、
Engine.builder().addSectionHelper(new UserTagSectionHelper.Factory("tagName","tagTemplate.html")).build()
を介して手動で登録することができます。
-
- タイプセーフ
-
-
タイプセーフ式 はバリデーションされません。
-
タイプセーフメッセージバンドル には対応していません。
-
- インジェクション
-
Template
インスタンスを注入することはできませんし、その逆もできません。テンプレートは、inject:
およびcdi:
の名前空間を介して@Named
CDI Bean を注入することはできません。