Saturday, December 28, 2013

Mixer2の最近のリリースでのエンハンス(特にSpringMVC関連)

Mixer2-1.2.22をリリースしました。

実はここ数週間で何回かリリースしましたが、まともにアナウンスしていませんでした。最近のリリースではどこをどういじったか、ざっくりまとめておきます。

1. Spring MVCとの組み合わせ方をドキュメントに書きました

Mixer2とSpringMVCと組み合わせたサンプルアプリケーションはもともとあったのですが、 javadocはともかく、文章の形では基本的な説明をまとめて書いてはいなかったので、 Use Mixer2 with Spring MVCというページを公式サイトにつくりました。 なお、もともとほぼ同じことがjavadocにも書いてありましたが、それも少し手直ししました。

※誤解の無いように念のため書いておきますが、Mixer2それ自体は他のライブラリ等への依存性が非常に少ない、独立性の高いxhtmlテンプレートエンジンです。 Webアプリケーションフレームワークと組み合わせて使うのが便利なことは確かですが、 必ずSpringMVCやSAStrutsと組み合わせないと何もできないということは全くありません。

2. Spring MVCでMixer2もJSPも両方使いたくてもうまく動かないのを直しました

公式サイトの説明のように


    
        
        
        
        
        
    
    
        
        
        
    

と書けば、コントローラから返されたview名を使ってMixer2ViewResolverがhtmlテンプレートファイルを見つけられなかった場合には、 そのままInternalResourceViewResolverへ処理が移って、view名に該当するjspが使われる...はずだったのですが、 実際にはMixer2VeiwResolverがhtmlテンプレートを見つけられなかった段階でいきなりFileNotFoundExceptionになってしまっていました。

それではまずいので、ちゃんと次のリゾルバーに処理が移るように直しました。このへんは、SpringMVCのマニュアルにも書かれている通りにしてみた、という感じです。

17.5.2 Chaining ViewResolvers

"The contract of a view resolver specifies that a view resolver can return null to indicate the view could not be found. Not all view resolvers do this ..." (ビューリゾルバーの実装ルールとしては、ビューを見つけられない場合にはnullを返すことができますよ。ただ全部のビューリゾルバーがそうしてるわけじゃなくケースバイケース...)
Spring MVC reference 3.2.xより

ただし、従来の挙動(見つからなければ即座にFileNotFoundException)でよいという人のために、 この機能をオフにする設定方法も用意しておきました。細かいことはMixer2XhtmlViewResolverのjavadocに書いてあります。

3. headタグの中ではremoveById()等が動かないことがあるのを直しました

単純にバグです。

4. 恥ずかしいミススペルを直しました

PathAjusterクラスはクラス名自体の綴りが間違っていることが今頃発覚w。 機能はそのままでPathAdjusterクラスとして作り直しました。 古い方のクラスは@Deprecatedにしておきましたが、こういうので後方互換性を失うのはまずいのでたぶんずっと消さずに残しておくと思います。

5. HTML5でAタグの中のDIVタグ等が消えるのを直しました

Mixer2-jaメーリングリストのほうでもらったバグ報告の対応です。

htmlテンプレートとしてmixer2Engineがロードするとき、テンプレート上のaタグの中にインライン要素(spanとかimgとか)が入ってるのはokだが、 ブロック要素(divとかpとか)が入っているとそれが消えてしまうという現象です。

これはちょっと盲点でした。というのは、html4.xまでの規格では、aタグの中にブロック要素を入れるのはNGなのです。 Mixer2はJAXBを使ってhtml(xhtml)を読み込むので、そういったところも厳格にNG扱いで、該当要素を無かったことにします。 ところが、html5ではaタグの中にブロック要素も入れられるようになりました。 HTML5 の “ブロック・レベル“ リンクというやつです。 Mixer2のhtml5に対する対応の不足、というバグとして対応しました。

ここ数週間のMixer2のエンハンス、ざっくり解説してみました。安心して最新版をご利用ください。今後ともごひいきに!

Monday, December 23, 2013

JAX-RSのビューとしてMixer2を使ってみる

こんにちは。nabedgeこと渡辺です。 ひとつ前の記事はJava Advent Calendar2013のやつでしたが、 今日はJava EE Advent Calendar 2013の23日目なんです。 JavaじゃなくてJavaEEです。ここでSpringMVCがどうのとか言っちゃうといろんな人が(コンコン、おや誰かきたようだ

そんなわけで、JavaEE規格の中でもWeb系エンジニアにとって最も身近な、JAX-RSのお話です。

今年の3月頃、 JAX-RSはHTML Webアプリケーションを開発するのに充分なフレームワークであるか? - AOEの日記 というエントリが結構話題になりまして、そこではこんな話もありました。

テンプレート処理パートの実装について

(中略)
ただ、普通のJSPプログラミングであるため、エラー時にメッセージやハイライトをしたり、条件に応じた表示変更と言った処理もゴリゴリ実装する必要があります。これが地味にめんどかったりします。少し大きな規模の開発案件になった場合はある程度プレゼンテーションレイヤで必要となるライブラリを整備してあげる必要があると思います。

そのとおりです。javaのmvcフレームワークは、案外どれも「ビューはjsp(jsf)で」と一言書いてあるだけです。 しかし現実世界に置けるM,V,CのViewは、それほど単純ではありません。むしろ、ビューは十分に複雑です。

JAX-RSの設計思想として、httpレスポンスをhtmlではなくjsonやxmlで返すことに主眼を置いているように思えます。 素直に考えると、クライアントサイドはJavaScript等で実装しサーバーから受け取ったjsonやxmlを料理せよ、ということでしょうし、 実際そうしようとしている開発現場も増えてきているかもしれません。 しかしそのためにはBackbone.jsやAngular.jsといったクライアントサイドMVCフレームワークの使い方も覚えなければなりません。 Webデザイナーの仕事とうまく歩調を合わせることまで考えると、道のりは険しそうです。

話をサーバーサイドに戻しましょう。 Viewを実装するために、jsp、jsfあるいはVelocityやThymeleafのようなテンプレートエンジンの特殊文法を覚えたり、 それでも足りなくてカスタムタグやプラグインを書き、気がつけばWebデザイナーさんの仕事は置いてけぼりになっている。 そんなことになるくらいなら、はじめからViewをjavaで普通に書けばいいじゃない! テンプレートは普通のhtml/cssで!それがMixer2の存在理由です。

前置きが長くなりましたがとにかく、html主体のwebアプリケーションを、JAX-RSとMixer2で作る、簡単なサンプルを作ってみました。 ソースは https://github.com/nabedge/mixer2-sample/tree/master/mixer2-jaxrs-sample です。

jax-rs実装にはjersey、サーバはtomcatを想定しています。(あっ、なんでglassfishじゃねーんだって言われそう^^;)

ポイントは二つ。まず一つ目が、リソースクラスのメソッドの戻り値がorg.mixer2.jaxb.xhtml.Html型であることです。 IntexResource.javaをご覧ください。

    @GET
    @Produces(MediaType.TEXT_HTML)
    public Html index(@QueryParam(value = "message") String message)
            throws IOException, TagTypeUnmatchException {

        // loading template
        String template = "m2mockup/m2template/index.html";
        InputStream is = this.getClass().getClassLoader()
                .getResourceAsStream(template);
        Html html = mixer2Engine.loadHtmlTemplate(is);
        
        // replace content in <div id="message"> tag
        if (message != null) {
            Div div = html.getBody().getById("message", Div.class);
            div.unsetContent();
            div.getContent().add(message);
        }
        
        // replace static file path
        Pattern pattern = Pattern.compile("^\\.+/.*m2static/(.*)$");
        PathAjuster.replacePath(html, pattern, "m2static/$1");
        
        // return html object
        return html;
    }

Html型を返されたjerseyは、それを処理しうるMessageBodyWriterを探して引き継いでくれます。 それがMixer2Writer.java です。

@Provider
@Produces(MediaType.TEXT_HTML)
public class Mixer2Writer implements MessageBodyWriter {

    // -- snip --

    @Override
    public void writeTo(Html html, Class type, Type genericType, Annotation[] annotations,
            MediaType mediaType, MultivaluedMap multivaluedMap,
            OutputStream outputStream) throws IOException, WebApplicationException {
        // marshalling html object to string.
        String str = mixer2Engine.saveToString(html);
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
        writer.write(str);
        writer.flush();
    }
}

Html型のインスタンスをMixer2Engineに渡して文字列化し、そのまま出力しているだけです。

いろいろ急いでしまったので、突っ込みどころだらけです。リソースクラス内でビューの処理もやってしまっている点はイケてなくて、 本来ならViewableViewableContext をうまく実装して切り分けるべきでしょう。そもそもJavaEEだっつってんのになんでGlassFishじゃなくtomcatで試しているんだとか。 Mixer2Engineの呼び出しもCDIを使うほうがJavaEEらしくなるでしょう。

このサンプル、どなたか試していただいて、もちょっとカッコよくしていただけませんか? マサカリも歓迎ですが、Pull Requestはもっと歓迎です。だってほら、クリスマスだし。

Sunday, December 8, 2013

Mixer2のSpringMVC連携機能がver 1.2.17でさらに進化!

こんばんは。nabedgeこと渡辺です。 この記事はJava Advent Calendar 2013の8日目です。

さて、私が作っているMixer2というテンプレートエンジンですが、 秋口に秀逸な pull request をいただいたので、ありがたくマージしてversion1.2.17をリリースしました。

本当は先週リリースした1.2.14で既にマージ済みだったのですが、 JavaDocに書いたサンプルコードのとおりに書くと動かない(笑)というヤバいのがあったのでリリースしなおしました。 そのJavaDocを書いたのはプルリクをくれた人ではなく私です。 さらに言うと、リリース作業で凡ミスして1.2.15と1.2.16を欠番にしてしまったのも私です。 ^^;)

そんなことはさておき、何が新しくなったのかというとSpringMVCとMixer2を連携させるためのViewとViewResolverです。 とりあえずフルーツショップサンプルアプリケーションの商品配送先住所の入力画面用のコントローラで説明してみます。

@RequestMapping(value = "/shipping")
public String shipping(Model model,
    @RequestParam(value = "redirected", required = false) boolean redirected,
    @Valid Shipping shipping, Errors errors) {

    model.addAttribute("shipping", shipping);
    model.addAttribute("redirected", redirected);
    model.addAttribute("errors", errors);
    return "checkout/shipping";
    // 以前のMixer2ではここで return "shippingView" のように、
    // springコンテナ上にDI済みのviewクラスの名称を
    // 与える必要があったが、新しいMixer2では
    // テンプレートファイルのパスでよい。
    // そしてテンプレートファイルのパスは
    // Viewクラスのパッケージ名/クラス名とそのまま紐づくルール。
}

コントローラの戻り値はテンプレートファイルのパスそのものです。 つまり戻り値の書き方はInternalResourceViewResolver + JSPを使う場合と同じでいいところがポイントです。

次に、配送先住所の入力画面のViewクラスを見てみましょう。

// Viewクラスに@ComponentのようなDIアノテーションは不要。
// 新しいViewResolverが代わりにやってくれる。
public class ItemView extends AbstractMixer2XhtmlView {

    @Autowired
    protected FooBar fooBar; 
    // もちろんSpringコンテナから任意の
    // オブジェクトをDIで受け取れる

    @Override
    protected Html renderHtml(Html html, 
            Map<String, Object> model, 
            HttpServletRequest request,
            HttpServletResponse response) 
            throws TagTypeUnmatchException {

        // Html html = mixer2Engine.loadHtmlTemplate(filePath);
        // のようなコードは不要。
        // 新しくなったAbstractMixer2XhtmlViewが、
        // コントローラのメソッドの戻り値で指定された
        // パスを使ってあらかじめテンプレートをロードして
        // Htmlオブジェクトに格納済み。

        @SuppressWarnings("unchecked")
        List<Category> categoryList = (List<Category>) model.get("categoryList");
        Item item = (Item) model.get("item");

        // embed item box
        replaceItemBox(html, item);

        // 途中は省略

        return html;
    }

ここでSpringの設定のほうを見てみます。



    
    
    
    
    

Mixer2XhtmlViewResolverのprefix,suffix,basePackageのプロパティがポイントです。 つまり、コントローラのメソッドの戻り値が "foo" の場合、
org.mixer2.sample.web.view.FooView がビュークラスで、
クラスパス上の m2mockup/m2template/foo.html がテンプレートファイルです。

コントローラのメソッドの戻り値が "foo/bar" の場合、
org.mixer2.sample.web.view.foo.BarView がビュークラスで、
m2mockup/m2template/foo/bar.html がテンプレートファイルということになります。

もちろんビュークラスはAbstractMixer2XhtmlViewを継承していなければなりません。 なお、複数のテンプレートを組み合わせてhtmlを生成したい場合は、ビュークラス内で getMixer2Engine()でいつでもエンジンを使えますので、好きなテンプレートをいくらでもロード可能です。

クレバーなプルリクをくださったkazuki43zooさんに感謝!

Java Advent Calendar 2013、 次はJavaFXの大御所、skrbさんです。