ちょっとした小ネタです。
ひっそりと、Apache Velocity 2.0がリリースされていました。
リリース日は、2017年8月6日。4ヶ月近く、気付いてませんでした(笑)。
Apache Velocityの更新の様子は時々見ているのですが、2.0タグが振られたと思ったらRCに戻ったり、2.0タグがまた更新
されたりしてだいぶ落ち着きがない感じがしていたのですが、いつの間にかリリースされていたようです…。
Apache Velocity 1.7が、2010年11月29日のリリースであることを考えると、実に7年ぶりくらいのリリースとなります。
で、せっかくなのでちょっと見てみましょうと。
お断り
世間的には、Apache Velocityはすでに役目を終えた印象かと思います。Spring Frameworkのサポートも外れていますし、
一般的に使うのであればVelocity Toolsも合わせて使うことになるとは思いますが、最新のVelocity Toolsはまだリリース
されていません。
[SPR-13795] Remove Velocity support - Spring JIRA
今回リリースされているのは、Velocity Engineのみとなります。
Note for Velocity Tools users: Velocity Tools 3.0 shall soon be released. Meanwhile, you are encouraged to use the Velocity Tools 3.x last snapshot (see Velocity Tools 3.x Upgrading notes).
http://velocity.apache.org/news.html#engine20
また、Apache Velocity 1.7から2.0になってものすごく劇的に変わったというわけでもないので、2.0のリリースで
なにか状況が変わるようなこともないと思います。
Apache Velocity自体の更新頻度も、とても低いですしね(Velocity Toolsはいつ出ることやら…)。
そんなわけで、ここから先はそれでもApache Velocityに興味のある方だけお読みいただければよいと思います。
Apache Velocity 2.0
正確には、Velocity Engine 2.0です。
リリース内容はこちら。
Velocity Engine 2.0 released
詳細なRelease Notesは、こちら。
Apache Velocity Engine - Changes Report
ざっくりとリリース内容を書くと
- LoggingがSLF4Jへ
- 空白の設定ができるようになったよ
- メソッド引数と配列の添字に、式が使えるよ
- 型変換ハンドラが設定可能だよ
- メモリ消費量を削減したよ
- JSR-223のサポート
という感じです。JSR-223のサポートってなにさ?という感じですけどね…。
あと、ビルドや依存関係の視点でいくと…
- ビルドシステムがApache AntからApache Mavenへ
- 必要な依存関係がSLF4JとCommons Lang 3のみに(前はCommons Collectionも必要だったり、オプションでOROとかJDOMとかいろいろあった)
- アーティファクトのIDが「velocity」から「velocity-engine-core」へ
というような変化もあります。
Commons CollectionsのExtendedPropertiesというクラスに依存していたのですが、これをApache Velocity側に取り込んだようです…。
https://github.com/apache/velocity-engine/blob/2.0/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java
* This class is a clone of org.apache.commons.collections.ExtendedProperties
https://github.com/apache/velocity-engine/blob/2.0/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java#L131-L132
* (which has been removed from commons-collections-4.x)
まあ、依存関係がスッキリしたのは良いですよね。
地味な改善では、入出力のデフォルトがUTF-8になったり、JSR-223のサポート(!)とかどういう動機なのか不明なものもありますが、
ざっくり使ってみましょうか。
Getting Started
では、使ってみましょうというところですが、まともにドキュメントが揃っていないのでUser Guideで構文を、あとはサンプルを見て書くことに
なるでしょう。
https://github.com/apache/velocity-engine/tree/2.0/velocity-engine-examples
Velocity Engineを利用するのに、必要な依存関係はこちら。
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> </dependency>
ロギングについてはSLF4Jですが、今回はslf4j-simpleを入れておくことにします。
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> </dependency>
実行はテストで。
<dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.8.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.19</version> <dependencies> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-surefire-provider</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.0.2</version> </dependency> </dependencies> </plugin> </plugins> </build>
では、コードの雛形を…。
src/test/java/org/littlewings/velocity/VelocityCoreTest.java
package org.littlewings.velocity; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.util.Arrays; import java.util.List; import java.util.Properties; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.app.VelocityEngine; import org.junit.jupiter.api.Test; public class VelocityCoreTest { static { System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug"); } // ここに、テストを書く! }
なんとなく、slf4j-simpleをデバッグモードに。
velocity.propertiesは、こんな感じで用意。
src/test/resources/velocity.properties
resource.loader=classpath classpath.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
設定ファイルを、クラスパスからロードできてもいいんではないかと…。
テンプレートは、こんな感じで用意。
src/test/resources/simple.vm
こんにちは $!{name}!! Fruits: #foreach ($fruit in $!fruits) #if ($fruit.price > 500) $fruit.name #end #end
使い方は、これまでのApache Velocityの使い方と変わりません。
@Test public void simpleUsage() throws IOException { Properties properties = new Properties(); try (InputStream is = getClass().getClassLoader().getResourceAsStream("velocity.properties")) { properties.load(is); } Velocity.init(properties); VelocityContext context = new VelocityContext(); context.put("name", "Velocity"); List<Fruit> fruits = Arrays.asList( Fruit.create("apple", 600), Fruit.create("orange", 300), Fruit.create("banana", 800) ); context.put("fruits", fruits); Template template = Velocity.getTemplate("simple.vm"); StringWriter writer = new StringWriter(); template.merge(context, writer); System.out.println(writer.toString()); }
Fruitクラスの定義は、こんな感じです。
public static class Fruit { String name; int price; static Fruit create(String name, int price) { Fruit fruit = new Fruit(); fruit.setName(name); fruit.setPrice(price); return fruit; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } }
結果。
こんにちは Velocity!! Fruits: apple banana
VelocityEngineのインスタンスを個別に作成する場合。
@Test public void instance() throws IOException { Properties properties = new Properties(); try (InputStream is = getClass().getClassLoader().getResourceAsStream("velocity.properties")) { properties.load(is); } VelocityEngine velocity = new VelocityEngine(); velocity.init(properties); VelocityContext context = new VelocityContext(); context.put("name", "Velocity"); List<Fruit> fruits = Arrays.asList( Fruit.create("apple", 600), Fruit.create("orange", 300), Fruit.create("banana", 800) ); context.put("fruits", fruits); Template template = velocity.getTemplate("simple.vm"); StringWriter writer = new StringWriter(); template.merge(context, writer); System.out.println(writer.toString()); }
まあ、ふつうに使えそうですね?
JSR-223サポート
最初、これがなにを言っているのかわかりませんでしたが、Apache VelocityのテンプレートをスクリプトとしてJSR-223のAPIで扱うことができます。
要するに、Apache VelocityがJSR-223のScriptEngineの実装を提供します。
これを使うには、「velocity-engine-scripting」を依存関係に追加します。
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-scripting</artifactId> <version>2.0</version> </dependency>
「velocity-engine-core」は、scripting側の依存関係に含まれます。
コードの雛形。
src/test/java/org/littlewings/velocity/VelocityScriptingTest.java
package org.littlewings.velocity; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.Properties; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.script.SimpleScriptContext; import org.apache.velocity.script.VelocityScriptEngine; import org.junit.jupiter.api.Test; public class VelocityScriptingTest { static { System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug"); } // ここに、テストを書く! }
単純に使う場合には、こんな使い方になります。
@Test public void simpleScripting() throws ScriptException { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("velocity"); Bindings bindings = scriptEngine.createBindings(); bindings.put("name", "Velocity"); PrintWriter writer = (PrintWriter) scriptEngine.eval("Hello ${name}!!", bindings); writer.println(); }
なんと、ScriptEngineManagerから、「velocity」でScriptEngineを取得できます。
ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("velocity");
テンプレートに渡す値は、Bindingsで設定します。
Bindings bindings = scriptEngine.createBindings(); bindings.put("name", "Velocity");
ScriptEngine#evalに、テンプレートをStringとして渡したり、Readerとして渡したりするとテンプレートを評価することができます。
PrintWriter writer = (PrintWriter) scriptEngine.eval("Hello ${name}!!", bindings);
writer.println();
ScriptEngine#evalの結果は、デフォルトではPrintWriterとして戻ってきます。
PrintWriter writer = (PrintWriter) scriptEngine.eval("Hello ${name}!!", bindings);
writer.println();
Writerが戻ってくるという動作自体は、変更できないようです。
結果。
Hello Velocity!!
動いてますね…。
ちなみに、この時のApache Velocityの設定はデフォルトのもので動作します。
[main] DEBUG org.apache.velocity - Default Properties resource: org/apache/velocity/runtime/defaults/velocity.properties [main] DEBUG org.apache.velocity - ResourceLoader instantiated: org.apache.velocity.runtime.resource.loader.FileResourceLoader
このあたりをカスタマイズしたい場合は、こんな感じになります。
@Test public void complex() throws IOException, ScriptException { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("velocity"); Properties properties = new Properties(); try (InputStream is = getClass().getClassLoader().getResourceAsStream("velocity.properties")) { properties.load(is); } ScriptContext context = new SimpleScriptContext(); Bindings bindings = scriptEngine.createBindings(); context.setBindings(bindings, ScriptContext.ENGINE_SCOPE); context.setWriter(new StringWriter()); bindings.put(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, properties); bindings.put(VelocityScriptEngine.FILENAME, "simple.vm"); bindings.put("name", "Velocity"); List<Fruit> fruits = Arrays.asList( Fruit.create("apple", 600), Fruit.create("orange", 300), Fruit.create("banana", 800) ); bindings.put("fruits", fruits); try (BufferedReader reader = new BufferedReader(new InputStreamReader( getClass().getClassLoader().getResourceAsStream("simple.vm"), StandardCharsets.UTF_8) )) { StringWriter writer = (StringWriter) scriptEngine.eval(reader, context); System.out.println(writer.toString()); } }
velocity.propertiesをPropertiesとして取り込んでおいて
Properties properties = new Properties(); try (InputStream is = getClass().getClassLoader().getResourceAsStream("velocity.properties")) { properties.load(is); }
設定自体は、Bindingsにキー「VelocityScriptEngine.VELOCITY_PROPERTIES_KEY」で登録します。
ScriptContext context = new SimpleScriptContext(); Bindings bindings = scriptEngine.createBindings(); context.setBindings(bindings, ScriptContext.ENGINE_SCOPE); context.setWriter(new StringWriter()); bindings.put(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, properties); bindings.put(VelocityScriptEngine.FILENAME, "simple.vm"); bindings.put("name", "Velocity"); List<Fruit> fruits = Arrays.asList( Fruit.create("apple", 600), Fruit.create("orange", 300), Fruit.create("banana", 800) );
また、ScriptContext#setWriterでWriterは差し換えることが可能です。
context.setWriter(new StringWriter());
ファイル名も指定できるのですが、これは実はロギングのタグとして扱われるだけです。
bindings.put(VelocityScriptEngine.FILENAME, "simple.vm");
ScriptEngine#evalには、この場合はScriptContextを渡します。
try (BufferedReader reader = new BufferedReader(new InputStreamReader( getClass().getClassLoader().getResourceAsStream("simple.vm"), StandardCharsets.UTF_8) )) { StringWriter writer = (StringWriter) scriptEngine.eval(reader, context); System.out.println(writer.toString()); }
結果。
こんにちは Velocity!! Fruits: apple banana
動きましたね?
ここまでの動作についてのドキュメント?申し訳程度に、このくらいが…。
Velocity Scripting
結局、スクリプト(というかテンプレート)はStringかReaderとして渡す必要があり、ResourceLoaderを設定しようが
関係なかったりします。どうにも中途半端感が…。Propertiesを指定できることはできますが、それ以外を設定するのには
有効、という程度です。
まとめ
2017年夏にひっそりとリリースされていた、Apache Velocity 2.0を試してみました。
Velocity Toolsはまだ出ていませんし、今更Velocity感もあるとは思いますが、興味があればどうぞ的な。
個人的には、汎用のテンプレートエンジンとしては扱いやすくて割と好きなんですけどね…。