Slim3 に Scenic3 と PirkaEngineを使う方法

ここの所、随分とAppEngineへの関心が高まってきているようです。Slim3の周辺も活発になってきている事もあり、Scenic3に注目していただく方もチラホラと出てきました。注目されるということは非常にモチベーションが高まります。
今回は前回に予告した通り、Scenic3にPirkaEngineを組み合わせる方法の紹介です。Slim3はGAE上で動作するフルスタックのMVCフレームワークですが、その大部分の機能はModelに相当するDatastore周りです。View層はJSPに幾つかのヘルパが用意されており、Controller部分はシンプルな1コントローラクラス=1URLのパターンになっています。この組み合わせでも、十分に機能するのですが、JSPを使いたくない場合や1クラスに複数のアクションメソッドを記述したいスタイルとは相性が悪くなります。後者についてはScenic3が解決する領域ですが、どうせならばJSPも使わない方法を提案します。

PirkaEngineとは?

PirkaEngineはJava6を用いたXHTMLテンプレートエンジンです。プレビューを崩さないように考慮されたXML属性を用いてループや条件分岐等を扱うことができます。また、依存ライブラリがないため、GAEとは相性が良くなっています。

ゴール

このエントリーでのゴールはPirkaEngineをViewに採用し、HTMLに近いテンプレートのメリットを伝えることです。Scenic3とPirkaEngineを組み合わせれば、ViewとControllerに不安はなくなるでしょう。

事前知識

Google App EngineSlim3の制約によりテンプレートエンジンのテンプレートとしてhtmlファイルを使うにはクセがあるので最初に説明します。
まず、GAEの特徴で、静的ファイル(static-files)はAppEngineの動くサーバではなく静的ファイル専用のサーバで処理されます。どのファイルを静的ファイルとして設定するかはappengine-web.xmlに記述しますが、デフォルトで.htmlは静的ファイルとして扱われる為、.htmlのリクエストをAppEngineで処理するにはstatic-filesの設定を修正する必要があります。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <static-files>
    <include path="favicon.ico" />
  </static-files>
</appengine-web-app>

設定はなんでも良いのですが、何でも良いので設定を明示的に記述し、デフォルト設定を上書きする必要があります。

また、Slim3では拡張子付きのURLは動的リクエストとして扱いません*1。したがって、AppRouterでisStaticをオーバーライドする必要があります。

public class AppRouter extends RouterImpl {
    @Override
    public boolean isStatic(String path) throws NullPointerException {
        return false;
    }
}

ここでは全てのリクエストをstaticとして扱っています。Scenic3の設定でまとめて静的ファイルのパスを設定する、もしくはAppRouterで適切に静的ファイルを判定してください。順序としては,先にAppRouterのisStaticで判定され、その後にScenic3のAppUrlsで判定されます。

準備

用意するのはscenic3-x.x.x.jar, pirka-y.y.y.jar, pirka-slim3.z.z.z.jarの3つのライブラリです。それぞれのjarをwar/WEB-INF/libに配置し、APTの設定を行ってください。pirkaengineの設定はweb.xmlに記述します。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <listener>
    <listener-class>org.pirkaengine.slim3.PirkaEngineInitalizer</listener-class>
  </listener>
</web-app>

listerで初期化を行うように指定すれば準備完了です。

テンプレートファイル

テンプレートファイルは拡張子を.htmlとして、XHTMLで記述します。

<html xmlns:prk="http://pirkaengine.org/" >
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>Scenic3/PirkaEngine Sample</title>
</head>
<body>
  <h1>Scenic3/PirkaEngine Sample</h1>
  <p>${greeting}</p>
</body>
</html>

大きな違いと言えば、xml名前空間の指定があることだけです。埋め込み変数は${}の中に記述できます。
このファイルをindex.htmlとして保存します。そうすることで、テンプレートの表示確認がローカル環境で簡単に行え、リンクなどをテンプレートに設定しても違和感なく確認が可能です。

Pageクラス

PageクラスはScenic3のScenicPageのサブクラスとして実装もできますが、PirkaPageのサブクラスとした方が便利です。

@Page("/")
public class FrontPage extends PirkaPage {
    /**
     * PirkaEngineを使いindex.htmlを表示します
     * @return
     * @throws IOException
     * @throws PirkaException
     */
    @Default
    public Navigation index() throws PirkaException, IOException {
        viewModel("greeting", "こんにちは、PirkaEngineの世界へ!");
        return render("index.html");
    }
}

viewModelメソッドでは特別な事はなにもやっていません。viewModelメソッドを使わない場合は次のようになります。

Map<String, Object> viewModel = new HashMap<String, Object>();
viewModel.put("greeting", "こんにちは、PirkaEngineの世界へ!");
return PirkaRenderer.render(request, response, templateName, viewModel);

PirkaEngineでは単純にKey-ValueのMapをテンプレートエンジンに渡してレンダリングしています。

リストを表示する

リストを表示するには次のように書きます。

@Page("/")
public class FrontPage extends PirkaPage {
    @ActionPath("list.html")
    public Navigation hello() throws PirkaException, IOException {
        List<String> list = new ArrayList<String>();
        list.add("Java");
        list.add("Python");
        viewModel("list", list);
        return render("list.html");
    }
}

list.html

<html xmlns:prk="http://pirkaengine.org/" >
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>List - Scenic3/PirkaEngine Sample</title>
</head>
<body>
  <h1>Google App Engine Support Languages</h1>
  <ul prk:for="elm in list">
    <li>${elm}</li>
  </ul>
  <p>${list.size} languages supported.</p>
</body>
</html>

prk:for="elm in list"」でviewModelに指定したlistをループさせています。

まとめ

PirkaEngineを使うと非常に直感的に動的なhtmlを生成でき、ローカルのブラウザで崩れることなく確認できます。デザイナーと協業が必要なウェブサービスであればこの価値は高くなるでしょう。ユーザとのモックによる打ち合わせでも重宝します。他にも様々な構文やレイアウト機能をサポートしています(ドキュメント)。
次回はサポートしている構文を幾つか紹介したいと思います。サンプルプロジェクトは[http://code.google.com/p/scenic3/source/browse/#svn%2Fsamples%2Fscenic3-with-pirkaengine
:title=こちら]からどうぞ。

*1:.s3を除く