Sunday, December 16, 2012

mixer2とSpringMVCで作るマルチリンガルサイト

こんばんは。この記事は Spring Framework Advent Calendar 2012の??日目です。参加者少なくて途切れてしまってるみたいますが ^^; 遅ればせながら参戦。

さて、11月にJJUG CCC 2012 Fall 日本Javaユーザーグループっていうイベントがありました。twitter4jというライブラリを作っている山本さんの講演で、ライブラリの配布や使い方のマニュアルなどを載せているWebサイトを、日本語、英語両方で作っているという話がありました。 自作のタグライブラリやjspでちょっとしたCMS(コンテンツマネジメントシステム)を自作しているそうです。

それにからんで、twitterでこんなやりとりをさせていただきました。

実際、ドキュメントを日本語と英語の両方で書かなければならない場面は結構あって、mixer2もいちおう英語日本語の両方で最低限の情報を書いています。

mavenに限らず、Java言語の世界では、hogehoge.propertiesには英語、hogehoge_ja.propertiesには日本語で書いておくと、JavaのLocaleという標準的な多言語対応(i18n)が使えるようになりますよ、みたいな実装が普通です。

ところが、案外これが面倒です。mixer2の場合はmavenのsite機能を使っていますが、たとえば英語のページのソース日本語のページのソースを、別々のファイルに書かなければなりません。 結果、日本語には書いてあるけど英語のほうで翻訳漏れ!といったことが起きます。やったことある人ならわかると思いますが、翻訳の場合、同じファイルに英語と日本語を交互に書くようにすると結構便利です。

山本さんもそこを目指して、同じjsp(拡張子はhtmlにしてるみたいですが)に日本語と英語の両方を特殊なカスタムタグで書いているのでしょう。(実際そんなスライドが講演資料にありました)

そこで、mixer2とSpringMVCを使って、同じリソースに英語と日本語を交互に書けるが、結果は英語サイトと、日本語サイトのように別々に表示できる簡易CMSを作ってみました。

サンプルの説明に入る前に先にお詫びです。山本さんが言っていた「カスタムタグもまだ使い続けたい」という要望には下記のサンプルでは対応できていません。html全体をmxier2で出力する方式をとったためです。ただ、mixer2は部分マーシャル機能もありますので、画面の一部をmixer2に作らせて、それ以外を普通のjspとカスタムタグで作る、みたいなことで対応可能でしょう。

さて、サンプルのソースはここです。mixer2-sampleというリポジトリにまとめて入っています。

作業環境はmavenが動けばどんなIDEでもかまいませんが、ここでは「 SpringToolSuiteでSAStrutsとSpringMVCのHelloWorldを作るまで」で紹介したSTSを想定しています。

先日の「nabedge blog: mixer2でJSPレスなSpringMVCアプリケーションを作ってみた」と同じ方法でmixer2-multilingual-websiteプロジェクトをIDE上に持ちこんでください。そのままTomcat等で起動してみましょう。

シャーロックホームズの原著と日本語訳を載せるサイトです。適当にクリックしてみるとわかるとおり、en/index.htmlでは英語が、ja/index.htmlでは日本語が表示されます。ところが、index.htmlファイルはソースツリー上に一つしかありません。ここがポイントです。

index.html(トップページのテンプレート)はこんな感じです。

  

Sherlock Holmes

シャーロック ホームズ

Sherlock Holmes is a fictional detective created by author and physician Sir Arthur Conan Doyle.

シャーロック・ホームズ(Sherlock Holmes)は、アーサー・コナン・ドイルの推理小説 に登場する架空の探偵。

日本語のタグにはlang-jaというclass属性を、英語のタグにはlang-enをあてています。SpringMVCのコントローラクラスがmixer2実行エンジンを通じてこのテンプレートをロードし、URLの先頭が「en」の場合はlang-ja属性を持つタグを全て削除、「ja」の場合はその逆をやって、出力させるという方法です。

全てのhttpリクエストはPageControllerクラスが処理するようにします。 下の @RequestMapping(value = "/{langStr}/**/*.html") によって、この形式に当てはまるurlへのリクエストは全てこのメソッドに集まります。

    @RequestMapping(value = "/{langStr}/**/*.html")
    public ModelAndView show(@PathVariable String langStr,
            HttpServletRequest request) throws TagTypeUnmatchException,
            IOException {

        logger.debug("# request processing...");
        ModelAndView modelAndView = new ModelAndView();

        // set Lang
        Lang lang;
        try {
            lang = Lang.valueOf(langStr.toUpperCase());
        } catch (IllegalArgumentException e) {
            // set "en" if langStr unmatch Lang enum.
            modelAndView.setViewName("redirect:/"
                    + defaultLang.toString().toLowerCase() + "/index.html");
            return modelAndView;
        }
        logger.debug("# lang = " + lang.toString());

        // build template file path from URI
        String path = StringUtils.substringAfter(request.getRequestURI(),
                request.getContextPath() + "/" + lang.toString().toLowerCase());
        logger.debug("# path = " + path);
        String templatePath = "classpath:m2mockup/m2template" + path;
        logger.debug("# templatePath = " + templatePath);

        // load template
        Html html = mixer2Engine.loadHtmlTemplate(ResourceUtils
                .getFile(templatePath));

        // replace side menu (except for index.html)
        if (!templatePath.equals(indexTemplatePath)) {
            Html indexHtml = mixer2Engine.loadHtmlTemplate(ResourceUtils
                    .getFile(indexTemplatePath));
            Div sideBarDiv = indexHtml.getById("sidebar", Div.class);
            html.replaceById("sidebar", sideBarDiv);
        }

        // remove other language tags
        PageHelper.removeOtherLangTags(html, lang);

        // replace anchor to top page
        for (A a : html.getDescendants("topPageAnchor", A.class)) {
            a.setHref(request.getContextPath() + "/");
        }

        // remake lang list at page top
        PageHelper.remakeLangList(html, lang, path);

        // replace static file path
        M2staticHelper.replaceM2staticPath(html);

        modelAndView.setViewName("mixer2view");
        modelAndView.addObject("htmlString", mixer2Engine.saveToString(html));
        return modelAndView;
    }

たとえば、/en/foo/bar.htmlというURLへのリクエストから、まず言語が英語であることを認識します。次にclasspath:m2mockup/m2template/foo/bar.html というテンプレートファイルを探してロードします。 PageHelper.removeOtherLangTags(html, lang); によって、英語以外のタグを全て削除しています。

PageHelperクラスの説明をする前に、Langというenum型クラスについて説明します。

public enum Lang {
    EN("English")
    ,JA("日本語")
    ;

    private String name;

    private Lang(String name){
        this.setName(name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

コンストラクタつきのenumクラスです。HashMapよりも使い勝手がいいです。

さて、PageHelperクラスはこんな感じです。

public class PageHelper {

    public static void removeOtherLangTags(Html html, Lang lang) {
        for (Lang l : Lang.values()) {
            if (l.equals(lang)) {
                continue;
            }
            String target = "lang-" + l.toString().toLowerCase();
            html.removeDescendants(target);
        }
    }

html.removeDescendants(target); がポイントです。要するに、指定されたLang型引数以外の言語がclass属性で指定されているタグを、削除しています。要するに en/foo/bar.html であればLang型変数に英語が指定され、その情報をもとにhtml.removeDescendants("lang-ja") しているわけです。こうすることで、表示したい言語以外の言語で書かれたhtmlタグを全て削除しています。

最後にSpringMVCでのLocaleの使い方の話。このサンプルアプリでは、http://.../[contextPath]/ にアクセスすると、そのブラウザのlocaleにあわせて自動的に /en/index.html または /ja/index.html にリダイレクトされるようになっています。やり方は簡単です。

    @RequestMapping(value = "/")
    public String top(Locale locale) {
        Lang lang = defaultLang;
        try {
            lang = Lang.valueOf(locale.getLanguage().toUpperCase());
        } catch (IllegalArgumentException e) {
            return "redirect:/" + defaultLang.toString().toLowerCase()
                    + "/index.html";
        }
        String redirect = "redirect:/" + lang.toString().toLowerCase()
                + "/index.html";
        logger.debug("# " + redirect);
        return redirect;
    }

@RequestMapping(value = "/") というアノテーションがついているメソッドが http://.../[contextPath]/ へのアクセスを処理します。SpringMVCの便利なところは、この処理メソッドの引数にいろいろなものを指定できるところです。 jaav.util.Locale型引数をあてればそこにはブラウザからのリクエストに含まれるロケール情報が自動的にはいります。あとはその言語情報をこっちのLang型変数にあてるだけです。

いかがでしたでしょうか。mixer2はhtml専用のテンプレートエンジンにすぎませんが、他のフレームワークと組み合わせることで結構便利に使えます。それではHappy hacking !

Wednesday, December 5, 2012

mixer2でJSPレスなSpringMVCアプリケーションを作ってみた (Java Advent Calendar 2012)

このエントリはJava Advent Calendar 2012の5日目です。 ちなみに昨日のエントリはJavaEE Advent Calendar 2012の4日めで、こっちはEEのつかないほうのjavaです。しかも両方ともJavaかJavaEEかはどっちでもいい内容です。Adventカレンダの募集にサクサク応募してたら、うっかり連チャンになってしまってこういうややこしいことになってしまいました。w

しかもmixer2を使ったサンプルアプリケーションを、昨日のエントリではSAStrutsで、今日のエントリではSpringMVCで作るという二番煎じ一網打尽っぷり。 でも、簡単なサンプルとはいえまったく同じWebアプリを二つの代表的MVCフレームワークで作ってみると、いろいろな違いだったり共通点だったりが見えてきてなかなか面白いですよ。


目次
  1. mixer2とは?
  2. 環境の準備と、Fruit Shopアプリケーション(SpringMVC版)の起動
  3. まずテンプレートファイルを見てみよう
  4. item.htmlとItemControllerクラス
  5. 静的リソースを出力するSpringMVCの設定はたった1行


1. mixer2とは?

mixer2はJavaアプリケーション用テンプレートエンジンです。たとえばApache VelocityFreeMarkerMayaaといったソフトと同じジャンルです。

mixer2の最大の特徴は、テンプレートを100%pureなXHTMLで書けることです。

...そろそろ昨日のSAStruts編のコピペですませようとしているのがバレそうなので、mixer2の解説については昨日のエントリ本家サイトのほうをご覧ください

2. 環境の準備と、Fruit Shopアプリケーション(SpringMVC版)の起動

昨日のエントリと全く同じです。最終的にインポートするプロジェクトをmixer2-fruitshop-sastrutsではなくてmixer2-fruitshop-springmvcのほうをインポートしてください。

3. まずテンプレートファイルをみてみよう

テンプレートファイルが src/main/resources/m2mockup/m2templates にあります。 昨日のエントリではブラウザで開いてモックアップとしての使用感を体験してもらいました。そこで今日は、item.htmlをHTMLエディタで開いてみてください。

    

item name

item description here. item description here. item description here. item description here. item description here. item description here. item description here. item description here. item description here.
price amount
$99.99

<h1 id="itemName">item name</h1> や、 <span id="itemPrice">99.99</span> といった感じで、本来ならデータベース上の商品情報を埋め込むのであろう部分にダミーの値が埋め込まれています。と同時に、それらがid属性つきのH1タグやspanタグで明示的に指定されていることに注目してください。mixer2は、こうしたタグの種類やid属性をたどって値を差し替えてゆきます。(id属性じゃなくてclass属性でも置換可能)

続いてItemControllerクラスを見てみましょう。

    @RequestMapping(value = "/item/{itemId}", method = RequestMethod.GET)
    public ModelAndView showItem(@PathVariable long itemId) throws IOException, TagTypeUnmatchException {

        // load html template
        File file = ResourceUtils.getFile(mainTemplate);
        Html html = mixer2Engine.loadHtmlTemplate(file)

        // embed item box
        ItemHelper.replaceItemBox(html, itemService.getItem(itemId));

        // (途中を省略します

        ModelAndView modelAndView = new ModelAndView("mixer2view", "htmlString", mixer2Engine
                .saveToString(html));
        return modelAndView;        
    }

SpringMVCにもSAStrutsと同様に、http://.../item/999 の999の部分を自動的に変数(itemId)に割り当ててくれる、PathVariableアノテーションがあるのでそれを使っています。便利ですね。

mixer2Engine.loadHtmlTemplate(file) でテンプレートファイルを読み込んで、htmlタグの文字列をHtml型のオブジェクトに変換しています。 別につくっておいたヘルパークラスの力を借りてそのhtmlオブジェクトの内部を商品情報で差し替えて、 最後にModelAndViewクラス内にhtmlStringという文字列で格納しておきます。

mixer2view.jspがModelAndViewを受け取って、htmlStringをそのまま出力しているだけです。

ヘルパークラス、例えば ItemHelperでやっていることは、昨日のエントリの同名クラスと全く同じですので、説明は割愛させてください。

4. 静的リソースを出力するSpringMVCの設定はたった1行

さて、このサンプルアプリの静的リソース、例えばロゴ画像やstyle.cssファイルは、どうやって出力しているのでしょうか?

本来、Webアプリケーションでの静的リソースファイルは、Webアプリルート、つまりsrc/main/webapp (WEB-INF除く)に置かないと、クライアントはhttpアクセスできない、という点はSAStrutsもSpringMVCも同じです。昨日のSAStrutsでは専用のアクションクラスを作りましたが、実はSpringMVCには便利な機能があります。DispatherServletの設定ファイルをご覧ください。

  

たったこれだけです。これで、 http://.../[contextPath]/m2static/foo/bar.png へのアクセスに対して、クラスパス配下の m2mockup/m2static/foo/bar.png をDispatcherServletがレスポンスしてくれます。 cache-period="60" は、レスポンスヘッダに Cache-Control: max-age=60 を自動的に追加してくれます。便利ですね。

ただし、テンプレート上に、たとえば <img src="../m2static/img/fruitshop-logo.png" /> と書いてあると、そこは <img src="/[contextPath]/m2static/img/fruitshop-logo.png" /> に書き変える必要があります。M2staticHelperクラスでそれを一括でやっています。

そろそろお気づきの方もいるかもしれません。なぜ、HTMLモックアップを、

  • m2mockup/m2static/ の配下に静的ファイル
  • m2mockup/m2template/ の配下にテンプレートとなる*.htmlファイル
という形で格納しているのか? これは、<mvc:resources mapping="/m2static/**" location="classpath:/m2mockup/m2static/" > という1行の設定で済ませるために、わざと一番上のレベルで静的ファイルかそうでないかを分けているのです。いっしょにしてしまうと、DispatcherServletがうっかりテンプレートのhtmlファイルをそのままレスポンスしてしまう事故がありえます。そうしたことを防ぐことができます。

さて、最後はテストです。mixer2を使ってViewのレベルまでのテストをすることはもちろんなのですが、テストのフィクスチャ(事前条件となる入力値)をexcelファイルに書いておくことができる、DBUnitを使うテストケースもいくつか書いてみました。

それらについて説明を続けたいのですが、すいません、長くなりそうです。っていうかもう二日連続で濃いめのエントリを書いたのでもう限界です。^^;) このへんで、 Java Advent Calendar 2012の5日目を終えたいと思います。 次は@megascusさん!


参考書籍
Spring3入門 ――Javaフレームワーク・より良い設計とアーキテクチャ
長谷川 裕一 大野 渉 土岐 孝平
技術評論社
売り上げランキング: 8064

Tuesday, December 4, 2012

mixer2でJSPレスなSAStrutsアプリケーションを作ってみた (JavaEE Advent Calendar 2012)

サーバーサイドJavaが好きだ!(お約束)

こんにちは。このエントリはJavaEE Advent Calendar 2012の4日目です!

プロローグ

(……きこえますか…きこえますか…mixer2のコミッターさん… デザイナーに作ってもらったhtmlを… jspに書き換える作業の繰り返しが… つらくなってきた… プログラマです… 今… あなたの…心に…直接… 呼びかけています…mixer2の公式サイトにあるSAStrutsのサンプルは… しょぼすぎです… もっとまともな… サンプルアプリを作るのです… サンプルを… とにかくサンプルを…)

そんな啓示があったような気がしたので、とりあえずよくあるECサイトっぽい感じで作ってみました。題してFruit Shop(くだもの屋さん)サンプルアプリケーション です。

もちろんソースコードも公開しています。nabedge/mixer2-sampleです。

このエントリでは、mixer2というテンプレートエンジンとSAStruts(Super Agile Struts)というMVCフレームワークとでWebアプリを作るとしたらどんな勘所があるのか? そして何よりmixer2の便利さを、サンプルアプリを通じて紹介します。

Seasar2+SAStrutsではなく、SpringMVCフレームワークを使ったバージョンもあります。 詳しくはこちら!→mixer2でJSPレスなSpringMVCアプリケーションを作ってみた (Java Advent Calendar 2012)

目次
  1. mixer2について
  2. Fruit Shopアプリケーションのチェックアウト(クローン)と起動
  3. まずテンプレートファイルを見てみよう
  4. item.htmlとItemActionクラス
  5. 静的リソースを出力するM2StaticActionとM2StaticHelper
  6. 参考書籍


1. mixer2について

ではソースコードをgithubからチェックアウトして、、、とその前に、あらためてmixer2についてお話しておきます。

mixer2はJavaアプリケーション用テンプレートエンジンです。たとえばApache VelocityFreeMarkerMayaaといったソフトと同じジャンルです。

mixer2の最大の特徴は、テンプレートを100%pureなXHTMLで書けることです。 それはつまり、Webデザイナーが作ったモックアップHTMLファイルにプログラマが手を加えることはほとんどなしでWebアプリ化できるということです。 実際、サンプルアプリのトップページのテンプレートには<c:out value="${data}" />のようなカスタムタグは一切ありません。ごく一般的なhtmlマークアップだけです。逆にjspファイルはたったの1個しかありません。

その代償として、サーバサイドのJavaコードが少し膨れ上がります。そりゃそうです。JSPファイルに書いていたことを、後述するViewHelperクラスの形で書いているからです。しかし、JSPのカスタムタグやVelocityのVTLやmayaaの特殊XML記法ではなく、普通のJavaコードでMVCのView開発できるということは、普通のJavaの知識だけでいいということです。学習の容易さは段違いです。

カスタムタグとhtmlタグとがゴチャゴチャにスパゲティ化したJSPよりは、普通のJavaコードのほうがメンテナンスもデバッグも容易です。そして従来ではSelenium等を使うしかなかったViewに対する自動テストを、普段いつも使っているJUnitで書けます。このサンプルアプリには、ViewのレベルまでをもJUnitでテストするテストケースも入っています。

なお、jspと違って、mixer2のテンプレートはどこにおいてあってもかまいません。サンプルでは通常のリソースファイルとしてsrc/main/resources配下に置いてありますが、たとえばデータベースのテーブルの中に <html>...</html> という文字列でテンプレートが置いてあっても使えます。機能は同じでも多数のデザインを使い分けたいWebアプリ、たとえばマルチテナント型のECサイトや、ブログサービスサイト等では効果的なテンプレートエンジンです。

2. Fruit Shopアプリケーションのチェックアウト(クローン)と起動

ではそろそろ、サンプルアプリのソースをgithubからcloneして実際に動かしてみましょう。mavenとeclipseの環境を用意してください。maven3を使ってますが、maven2.2.xでも動くと思います。よくわからない方は、この前書いたSpringToolSuiteでSAStrutsとSpringMVCのHelloWorldを作るまでというエントリをごらんの上、PCに開発環境を構築しておいてください。

  1. eclipseのEGitでcloneする方法です。まず Window→Open Perspective→OtherでGitRepositoryエクスプローラを開き、新しいリポジトリのcloneボタンをクリックします。

  2. サンプルアプリのリポジトリURLは https://github.com/nabedge/mixer2-sample.git です。ダイアログボックスのURI欄に入力すればそれで他の欄にも入力されます。

  3. git cloneが完了するとこんな感じで見えるはずです。WorkingDirectory内のmixer2-fruitshop-sastrutsを右クリックして Import Projects してください。

  4. とりあえず Import as General Project(一般プロジェクトとしてインポートする)を選択してください。

  5. 表示をJavaパースペクティブに戻して、インポートしたmixer2-fruitshop-sastrutsプロジェクトを右クリック→Configure→Convert to Maven Projectしてください。これでプロジェクトがmavenプロジェクトとして認識され、ソースがコンパイルされます。

  6. あとはサーバで起動するだけです。

  7. トップページが見えたら起動成功です!

3. まずテンプレートファイルを見てみよう

テンプレートファイルが src/main/resources/m2mockup/m2templates にあります。m2staticは画像やcssなどの静的リソース置き場です。まずはindex.htmlをエディタではなくWebブラウザで開いてみてください。

トップページが見れましたね? 適当にリンクやボタンをクリックしてみてください。画面遷移できますね。ただしこれはただの静的ファイルであり、いわゆるHTMLモックアップと呼ばれるものです。買い物カゴに何を入れても入れたように見えるだけ、決済処理をしてもしたように見えるだけの、紙芝居です。Webアプリケーション開発では、まずワイヤーフレームと呼ばれる紙で画面を検討し、最終的にこうしたHTMLモックアップによる仕様の確認が行われます。

このサンプルアプリでは、紙芝居状態のこのHTMLモックアップをそのままテンプレートとして使います。JSPに書きかえる必要はありません。mixer2エンジンがHTML文字列をHTMLオブジェクトとしてインスタンス化し、すべてのタグはそれぞれの型のJavaオブジェクトに変換することで、MVCで言うViewを普通のjavaコードで処理します。

4. item.htmlとItemActionクラス

いちばん構造が単純な商品詳細画面を使って細かい解説に移りましょう。仕様は次のとおりです。

  • http://.../[contextPath]/item/999 というURLのアクセスを受けて、商品番号999の商品データをデータベースから取り出し、商品名(itemName),商品説明(itemDescription),価格(itemPrice)を画面に表示する。(画像を準備するのが面倒だったので商品画像の差し替えは省略 ^^;)
  • add cartボタンを押したらその商品をカートに追加しカート画面に遷移する。
  • 画面上部のロゴ画像を押したらトップページへ遷移する。
  • 画面左のカテゴリ画面へのリンク、カート画面へのリンクはそれぞれの画面へ遷移する。

SAStrutsでは、/item/ というURLに対するアクセスはItemActionクラスのitem()メソッドに割り当てられます。各メソッドはアクションフォームクラス(ここではItemForm)を経由してリクエストパラメータを受け取ってビジネスロジック(サービスクラス)を処理し、結果をアクションフォームクラスまたはActionクラス上のpublicなプロパティに格納しておきます。ViewたるJSPは、それらを使って画面を形成します。

ItemActionクラスのソースコードです。

public class ItemAction {

    @SuppressWarnings("unused")
    private static Logger logger = Logger.getLogger(ItemAction.class);

    public String htmlString;

    @Resource
    protected Mixer2Engine mixer2Engine;

    @Resource
    protected JdbcManager jdbcManager;

    @Resource
    protected CategoryService categoryService;

    @Resource
    protected ItemService itemService;

    @ActionForm
    @Resource
    protected ItemForm itemForm;

    private String mainTemplate = "m2mockup/m2template/item.html";

itemFormがアクションフォームのインスタンスで、ここではitemId(商品番号)をStringで持ってるだけです。URLパラメータとして受け取ったid値がこのitemIdプロパティに自動的に格納されます。

htmlStringが、最終的にmixer2view.jspに渡すためのプロパティです。文字通り<html>...</html>までの全ての画面情報をhtmlStringにつっこんでitem()メソッドは完了です。

では、テンプレートhtmlファイルをどうやって読み込んで、どうやって中身を差し替え、どうやってhtml文字列に戻してhtmlStringにつっこんでいるのでしょうか? まずはテンプレートの読み込みです。

        // load html template
        File file = ResourceUtil.getResourceAsFile(mainTemplate);
        Html html = mixer2Engine.loadHtmlTemplate(file);

これだけです。Seasar2のResourceUtilはクラスパス内の任意のリソースファイルを取得できます。これで得たテンプレートファイルをmixer2Engineに渡すと<html>から</html>までの文字列がHtml型のオブジェクトに変換されます。

        // get item data from database
        ItemDto item = itemService.getItem(Long
                .valueOf(itemForm.itemId));
        
        // embed item boxes
        ItemHelper.replaceItemBox(html, item);

http://.../contextPath/item/999 の999の部分を SAStrutsのリクエストプロセッサが自動的にitemFormのitemIdプロパティに格納してくれますので、あとはそれをItemServiceクラスのgetItemメソッドに渡すだけです。内部ではjdbcManagerを使ってSQLを発行し、SELECT文で得られた結果をItemDto型のオブジェクトに変換して返してくれます。詳しくはItemServiceクラスのソースを見てください。

さていよいよ、itemオブジェクトの内容をhtmlに埋め込んでいきます。公式サイトのHelloWorldのように、Actionクラスの中に埋め込み操作を直接書いてもかまいません。しかしよほど単純な画面でもない限り、それをやるとActionクラスが肥大化しています。

そこで、ItemHelperというヘルパークラスに切り出してそいつにhtmlとitemの両方を渡し、埋め込み操作をやらせます。

ItemHelperのソースをご覧ください。単純なオブジェクト操作だけのユーティリティ的なクラスです。

        // contet div
        Div itemBox = html.getBody().getById("itemBox", Div.class);

        // item information
        itemBox.getById("itemName", H1.class).getContent().clear();
        itemBox.getById("itemName", H1.class).getContent().add(item.name);

mixer2のHtml型オブジェクトは、内部にBody型、Head型、P型、Div型、Span型など、すべてのHTMLタグにあわせたオブジェクトを内包しています。と同時にそれらを操るユーティリティメソッドがついています。

Div itemBox = html.getBody().getById("itemBox", Div.class);
これはhtmlからbody要素を取り出し、さらにその中からitemBoxというid属性を持つdiv要素(<div id="itemBox">...</div>)を取得しています。JavaScriptで言うgetElementById()メソッドと似た感じだと思ってください。 なお、ここではgetBody()をはさんでいますが、
Div itemBox = html.getById("itemBox", Div.class);
でも結果は同じです。

こうして取得したタグ要素の中の商品名、説明、価格などの情報を、itemService経由でデータベースから取得した商品情報(itemオブジェクト)の値で置換していきます。

        itemBox.getById("itemName", H1.class).getContent().clear();
        itemBox.getById("itemName", H1.class).getContent().add(item.name);

商品名はH1タグ内に埋め込むのですが、読み込んだテンプレートには <h1 id="itemName">item name</h1> と言う形で、item nameというダミーの商品名が既に入っています。そこで、h1タグの中身をいったんすべて消してから、改めて商品名を追加しています。価格や説明もそれぞれspan要素やdiv要素の中身を同じ方法で置換しています。

なお、divやspanなどの要素は中に複数のタグや文字列を持つことが出来る要素です。そうした子要素はgetContent()メソッドによってjava.util.List型で返されますので、clear()で全要素の削除、add()で新要素の追加、となります。

次に、カートに追加するボタンまわりの操作です。

        // addCart form
        String ctx = RequestUtil.getRequest().getContextPath();
        Form addCartForm = itemBox.getById("addCartForm", Form.class);
        addCartForm.setAction(ctx + "/cart/add");

テンプレート上では <form id="addCartForm" action="cart.html"> のようにカート画面へ遷移するだけの紙芝居になっています。それを <form id="addCartForm" action="/コンテキストパス/cart/add"> に置換しています。

htmlタグはさまざまな属性を持ちますが、mixer2ではそれらを操作するメソッドも各タグ型に用意されています。Form型であればgetAction()でaction属性を取得、setAction()でaction属性をセットできます。

次に、カートに入れるときに、商品番号もhiddenで送信する必要があります。デザイン決めの段階では必要ないのでテンプレートには含まれていません。そこで、hiddenのinput要素をformに追加しておく必要があります。

        Input input = new Input();
        input.setType(InputType.HIDDEN);
        input.setName("itemId");
        input.setValue(Long.toString(item.id));
        addCartForm.getContent().add(input);

これで<input type="hidden" name="itemId" value="999">というタグがフォームの最後に追加されます。

最後に、いろいろ操作を加えたhtmlオブジェクトをhtml文字列に戻して、ActionクラスのhtmlStringプロパティに格納します。

        // output
        htmlString = mixer2Engine.saveToString(html);
        return "/mixer2view.jsp";

mixer2veiw.jspではこのhtmlStringをそのまま表示するだけです。

5. 静的リソースを出力するM2StaticActionとM2StaticHelper

ところで、各画面の左上のロゴ画像や、cssファイルは、どうやって出力しましょう?

実は意外に難しい問題です。htmlモックアップは*.htmlだけでなく*.css,*.pngでひとまとめで構成されます。今回はこれをJavaアプリのリソースファイルの一部としてm2mockupというフォルダの配下に置いています。しかし通常のServlet/JSP仕様のアプリでは、静的リソースファイルはWebアプリルートフォルダ、つまり今回で言えばsrc/main/webappの配下(WEB-INF除く)に置かないとクライアントからアクセスできません。

ところが、*.htmlは静的リソースではなくアプリケーションコードが使うテンプレートなので、リソース置き場(つまりsrc/main/resources)に置くほうが都合が良いです。だからといってテンプレートをsrc/main/resources, 画像をsrc/main/webappにそれぞれ分けて格納してしまうと、htmlモックアッップの一括管理が難しくなります。

そこで、画像やcssへのリクエストも専用のActionクラスでレスポンスするようにすれば、やはり全てまとめてリソース(src/main/resources配下)に置くことができます。

まず、htmlモックアップ上で <img src="../../m2static/img/logo.png"/> のようになっている画像のパスを、専用Actionへ誘導するヘルパーを書きます。それがM2StaticHelperです。

        for (Img img : ab.getDescendants(Img.class)) {
            if (img.isSetSrc()) {
                String src = img.getSrc();
                img.setSrc(convertPath(src));
            }
        }

上のコードは、与えられたタグ(ここではAbstractJaxb型にキャストされている)の中を走査して全てのimgタグを抜き取り、そのsrc属性をconvertPath()メソッドにあてて差し替えています。

convertPath()メソッドは何をするのかというと、

    private static String convertPath(String str) {
        String ctx = RequestUtil.getRequest().getContextPath();
        if (!str.startsWith("http") && str.matches(".*/m2static/.*")) {
            str = ctx + "/m2static/?path="
                    + URLUtil.encode(str.split("/m2static", 2)[1], encoding);
        }
        return str;
    }

要するに、 "../../m2static/img/logo.png" のような文字列を、"/コンテキストパス/m2static/?path=%2Fimg%2Flogo.png" に差し替えています。これによって、imgタグやstyleタグなどのsrc属性はM2Actionクラスを経由するURIに誘導されます。

M2StaticActionクラスでは、pathというパラメータで受け取った文字列でm2staticフォルダ配下のリソースファイルを探し、それに適切なContent-Typeヘッダ属性をつけてレスポンスします。

    @Execute(validator = false, urlPattern = "m2static")
    public String outputImage() throws IOException {

        // create static file path
        String path = "m2mockup/m2static/" + m2staticForm.path;
        log.debug("static file path = " + path);

        // set contentType
        HttpServletResponse response = ResponseUtil.getResponse();
        String contentType = MimeTypeUtil.guessContentType(path);
        if (contentType == null
                && "css".equals(ResourceUtil.getExtension(path))) {
            response.setContentType("text/css");
        } else {
            response.setContentType(contentType);
        }

        // set cache-control header
        response.addHeader("Cache-Control", "max-age=60");

        // output image
        InputStream inputStream = ResourceUtil
                .getResourceAsStreamNoException(path);
        OutputStream outputStream = response.getOutputStream();
        byte[] buf = new byte[1024];
        if (inputStream == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                    "File Not Found: " + path);
        } else {
            while (inputStream.read(buf) > 0) {
                outputStream.write(buf);
            }
            inputStream.close();
        }

        // need not view
        return null;
    }

静的リソースまでをもActionクラスで返すのはちょっと贅沢なので、Cache-Controlヘッダをつけて無駄な再リクエストを減らすようにしてあります。

さて次はいよいよActionクラスのテストケースです。MVCのVIEWをもJUnitでテストしてしまう方法...なんですが、長くなりそうです。テストの話は次の機会にしましょう。 引き続きJavaEE Advent Calendar 2012をお楽しみください。次は@sugarlifeさん!

参考書籍

Seasar 2 徹底入門 SAStruts/S2JDBC 対応
竹添 直樹
翔泳社
売り上げランキング: 15271

Saturday, December 1, 2012

SpringToolSuiteでSAStrutsとSpringMVCのHelloWorldを作るまで

Spring Tool Suite(以下STS)はeclipseをベースにしたIDEです。ライセンス料等はかかりません。

eclipseをベースとしているので見た目も操作感もeclipseそのものですが、ノーマルのeclipseと違ってサーバーサイドJava開発に必要なプラグインやmavenコマンドなどが初めから標準搭載なので、開発環境を手っ取り早く作るにはもってこいです。もちろんEclipseマーケットプレイスなども普通に使えるのでお好みのプラグインを追加することも可能です。

2012年10月に公開されたSTS3.1.0はeclipse3.8ベースのものとeclipse4.2ベースのものの両方が公開されています。両者ともmavenコマンド本体とm2eプラグイン、m2e-wtpプラグイン等が同梱されています。 公式ブログによると、eclipse3.xと4.xとでUIプラットフォームの違いからくるいくつかの問題があったようです。

eclipse3.8ベースのほうのSTSでSpringMVCとSAStrutsのプロジェクトを試作してみたところ、安定して開発ができそうな感じが得られました。今回はその手順を細かめに紹介します。

準備するもの

  • JDK6以上がインストール済みのPCが必要です。JREではなくJDKです。 mavenコマンドがコンパイル作業をすることがあるのでJDKが必須。
  • Tomcatは同梱されていませんのでこれもあらかじめインストールしておいてください。 Apache Tomcat - Apache Tomcat 7 Downloadsからzipをダウンロードして解凍するだけです。ここでは C:\apache-tomcat-7.0.33 に解凍済みだとして解説します。7ではなくてTomcat6でも大丈夫です。

STSのインストールと起動

  1. Tool Suites Download | SpringSource.orgからspring-tool-suite-3.1.0.RELEASE-e3.8-win32.zipをダウンロードします(64bitOSの人はwin64のほうを)
  2. お好みの位置でzipを解凍します。
  3. STSを起動します。解凍したフォルダのspringsource\sts-3.1.0.RELEASE\STS.exeを実行するだけです。緑色のスプラッシュが見慣れないかもしれませんが、起動したアプリをよく見るとそれはeclipseそのものであることがわかると思います。

mavenのWebプロキシ設定

これから作るHelloWorldアプリはmavenプロジェクトとして構成されます。 mavenコマンドはインターネット上のセントラルリポジトリサーバーから必要なjarを自動的にダウンロードしてきますが、このとき、Webプロキシを通過させる必要があるLAN環境の場合には、mavenの設定追加が必要です。

${user.home}/.m2/settings.xml というファイルを作成し、 http://maven.apache.org/settings.html#Proxiesで解説されている形で設定を書いておきます。 ちなみに ${user.home}/.m2/settings.xml というのはWindows7で言えば C:\Users\[OSユーザ名]\.m2\settings.xml にあたります。

もちろん、直接インターネットに出られるマシンの場合にはこの設定は不要です。

パースペクティブの変更

これは必須ではありませんが、この後の説明の画面キャプチャがJavaパースペクティブになっているので、それにあわせておいてください。

Tomcatの登録

STSの(というよりeclipseの)WTPプラグインを使ってeclipse内でTomcatと連携できるようにしておきます。

  1. Window→Preference→Server→RuntimeEnvironmentのダイアログを開きます

  2. Addボタンでサーバ追加用のダイアログが出ますので、ApacheTomcat7を選択してNextをクリックします

  3. あらかじめ展開しておいたTomcatのフォルダを指定して、Finishボタンを押せば完了です。

SpringMVCによるHelloWorld Webアプリの作り方

これで開発環境の準備が整いました。いよいよWebアプリを作ってみましょう。SpringMVCフレームワークを使う場合はプロジェクトの新規作成ウィザードで、SpringMVCテンプレートを選択するだけです。

  1. File→New→SpringTemplateProjectをクリック

  2. SpringMVCプロジェクトをクリック

  3. プロジェクト名とJavaパッケージ名を決めます。ここではmyspringmvc, パッケージはcom.example.myspringmvc としました。

  4. これでHelloWorldプロジェクトの完成です。ここでもしも赤いバツ印が出ていてもあわてる必要はありません。eclipse上で再ビルドすれば直ります。Project→Cleanしてください。(メニュー上のBuild Automaticallyのチェックはオンにしておいてください)

  5. いよいよTomcat上で起動してみましょう。プロジェクトを右クリックしてRun on Serverをクリックします。

  6. まだTomcatサーバがeclipseに無いので作ります。manually define a new serverのラジオボタンをオンにして、Tomcat7を選択し、finishをクリックします。

  7. tomcatが起動し、Hello World画面が表示されます!

SAStrutsによるHelloWorld Webアプリの作り方

私はSpringMVCも好きですが、SeasarプロジェクトのSAStruts(エスエーストラッツ/SuperAgileStruts)も、驚くほど使い勝手が良いので気に入っています。

STS(Spring Tool Suite)は文字通りSpringフレームワークをベースとしたアプリケーションを作るための開発環境ですが、元はeclipseなので、Srping以外であっても何でも開発できます。

STSにはmaven連携プラグインはもちろんmavenコマンド本体も初めから同梱されているのがポイントです。sastruts公式サイトでの説明にあるとおり、mavenコマンドをコマンドラインから実行してプロジェクトを作ってみましょう

  1. Windowsコマンドプロンプトを開きます。まずJAVA_HOME環境変数を設定しておきましょう。
    set JAVA_HOME=C:\jdk1.6.0_33
    
  2. 次に、mavenコマンドでアーキタイプからプロジェクトを新規生成します。 (下の例ではSTSがC:\springsourceの配下に展開されていると仮定しています)
    C:\springsource\apache-maven-3.0.4\bin\mvn.bat archetype:generate ^
     -DarchetypeRepository=https://www.seasar.org/maven/maven2/ ^
     -DarchetypeGroupId=org.seasar.sastruts ^
     -DarchetypeArtifactId=sa-struts-archetype ^
     -DarchetypeVersion=1.0.4-sp9.1 ^
     -DgroupId=com.example ^
     -DartifactId=mysastruts ^
     -Dversion=1.0-SNAPSHOT
    
  3. コマンドプロンプト上にBUILD SUCCESSFUL と表示されれば成功です。 上で指定したartifactIdと同じ「mysastruts」というフォルダにプロジェクトが生成されています。
  4. しかし、実は、sastrutsのアーキタイプには、.settingsフォルダや.tomcatpluginファイルのような、古いeclipseで使用する制御ファイルも含まれています。STSを使う場合にはこれらは必要ありません。 STSにインポートする前に以下のフォルダ/ファイルを消しておいてください。
    • .settingsフォルダ
    • .tomcatPluginファイル
    • .amaterasファイル
  5. こうして出来たプロジェクトをSTS上にインポートします。

  6. Existing Maven Projectを選択し、先ほど作ったmysastrutsフォルダを選択すると、自動的にpom.xmlを読み込んでMavenプロジェクトを認識してくれます。

  7. これでSTSのパッケージエクスプローラ上にmysastrutsプロジェクトが見えるようになっているはずです。 しかし、まだエラー(赤バツ印)が出ているはずです。 実はsastrutsのアーキタイプは非常に古いeclipseに対応するための設定がpom.xml上に書かれていますが、それらはSTSのような最近の開発環境では不要です。そこで、pom.xmlを開いて、<build>タグ内の下記の部分を全てコメントアウトまたは削除してください。
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
  8. pom.xmlを変更した場合には、STSのmavenプラグインでUpdateProjectしておきます。

  9. これで準備完了です。上のSpringMVCのときと同じように、mysastrutsを右クリック→Run as→Run on Server でTomcatを選択して起動してください。
  10. SAStrutsのHello World画面が表示されました!

いかがでしたか?Java開発の世界では事前準備が結構大変なのですが、 STSにはサーバーサイドJava開発に必要なツールが全て同梱されていますので、すぐにプログラミングにとりかかれます。 あとからSTSに追加すべきプラグインをしいて挙げるとすれば、我々マルチバイト言語な人間にとっては必須なのはプロパティエディタプラグインくらいなものではないでしょうか。

ところで、もっと高機能なテンプレートを公開している方もいます。詳しくは STSでSpringMVC+SpringDataJPA+HibernateなPre-Configuredテンプレートプロジェクトをクイックスタート! #jsug - BLOG.IK.AMをご覧ください。それではHappy hacking !

Friday, November 2, 2012

Spring MVC: from JSP and Tiles to Mixer2

This post is inspired by Spring MVC: from JSP and Tiles to Thymeleaf | SpringSource Team Blog. Of course, This post is not a parody but my honest opinion as the comitter of Java Template Engine mixer2.

Plain JSP

Let us get started with the below code sample:
<html> ...
 
Table is empty.
First Name Last name
${user.firstName} ${user.lastName}
</html>

And Mr Michael Isvy saying that:

2) Verbosity
Our users page is fairly small because it simply displays a list of elements. We already have 50 lines of code (the above code sample has been slightly reduced). You can imagine how big it would be if we had to display a lot of content.

3) HTML/CSS compliance
This page is not HTML/CSS compliant. Suppose a Web Designer has prototyped it, you would have to rewrite it completely in order to use intrusive JSP syntax.

I completely agree above. Also, I agree the solution of Thymeleaf template engine. But today, I suggest another solution, mixer2.

This is template file of mixer2.

<html> ...
 
Table is empty.
First Name Last name
john doe
</html> ...

This is Controller code of Spring MVC with mixer2.

@Controller
public class HomeController {

    @Autowired
    Mixer2Engine mixer2Engine;

    @RequestMapping(value="/userList", method=RequestMethod.GET)
    public ModelAndView mixer2test() 
        throws IOException, TagTypeUnmatchException {

        // load template html
        File file = ResourceUtils.getFile("classpath:userList.html");
        Html html = mixer2Engine.loadHtmlTemplate(file);

        // getting user data from database...
        List<User> userList = ....

        // remove <div id="ifempty"> tag
        html.removeById("ifempty", Div.class);

        // clear "john doe" tr/td tags.
        Tbody tbody = html.getById("users",Table.class)
                              .getTbody().get(0);
        tbody.getTr().clear();

        // embed user data
        for (User user : userList) {
            Tr tr = new Tr();
            Td td1 = new Td();
            Td td2 = new Td();
            td1.getContent().add("Lady");
            td2.getContent().add("Lady");
            tr.getThOrTd().add(td1);            
            tr.getThOrTd().add(td2);     
        }
        // above code is verbose 
        // but you can do KAIZEN by TableBuilder.
        // see http://mixer2.org/site/ja/tablebuilder.html

        Map<String,String> model = new HashMap<String,String>();
        model.put("htmlString", mixer2Engine.saveToString(html));
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("view-for-mixer2");
        modelAndView.addAllObjects(model);
        return modelAndView;
    }
}

and JSP becomes so simple. (you need only one jsp. src/main/webapp/WEB-INF/views/view-for-mixer2.jsp)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
${htmlString}

note: Above code is verbose. now I am writing "Mixer2ViewResolver" for mixer2 on SpringMVC.

Pros and Cons

On the bright side:

  • Web designer friendly template. because there are no jsp custam tag, no other name space but only html/css. 100% pure html/css !
  • You can write View of MVC by java code. You need not to learn about jsp custom tag nor tiles tag. You must only learn pure java. Ctrl+Space key on eclipse can help you.
  • You can write test case for view by pure JUnit because the view code is written by pure java code.

On the down side:

  • Mixer2 is a small open source project. not so popular now.
  • JSP or Template code is less. But java code becomes fat.

If you are a programmer, and starting on a new project with another web designer, I strongly encourage you to compare both Mixer2 and JSPs in order to figure out which one is more suitable to your needs.

other reference:

Saturday, October 20, 2012

Jenkins勉強会で実にいいプレゼンを見た。

実にいいプレゼンでした。Java言語とかビルドツールとかではない方向から、しかし継続的デリバリーの本質に迫る、しかしほんわかとした、いいプレゼンでした。

その他のまとめとか:

Saturday, October 13, 2012

.settingsフォルダや.projectファイルをVCSで管理すべきか

eclipse上のプロジェクトとしてソースコードをむさぼった後、さて、SubversionかGitにコミットする段になったとします。コミットダイアログ上では .settings/foo.bar.xml とか .project とか .classpath といったeclipse用の制御ファイルもコミット対象としてチェックボックスがオンになっています。

あなたは迷わずそのままコミットボタンを押しますか?それとも、その前にignore(無視、除外)の設定にかかりますか?

この問題に対する行動は、案外、人によって違うようです。たとえば、svn - Which eclipse files belong under Version Control - Stack Overflow 2008.12(どのeclipseファイルをバージョン管理の対象とすべきでしょうか?)という質問には、下記の回答のポイントが高いようです。

project-dir/.project
project-dir/.classpath
project-dir/.settings/*

should be in your SCM.
(訳:上記のファイルはバージョン管理下におくべきだ
The goal is that anyone can checkout/update his/her SCM workspace and import the eclipse project into the eclipse workspace. (訳:誰もが自分のSCMワークスペースにチェックアウトまたは更新できて、eclipseワークスペースにプロジェクトをインポートできる、それがゴールだ)

こんなQ&Aもあります。 java - Should Eclipse-specific files in an VCS be ignored, if using Maven? - Stack Overflow 2011.11 これはビルドツールとしてmavenを使っているプロジェクトの話のようです。

With the team's I've worked on, the general rule has been that you don't check in anything that is generated by or obtained by Maven. Since the pom.xml contains everything you need to create the .project, .classpath, and .settings files, we don't check them in.
訳:私のチームでは、mavenが生成または監視しているファイル以外は(VCSに)チェックインしないのが共通のルールです。.projectや.classpath、.settingsファイル群を作るための情報はpom.xmlに含まれているからです。だから、それらをチェックインすることはありません。

実際、eclipseにm2eプラグインを入れておけば、pom.xmlのdependenciesタグに沿って必要なライブラリを自動的にビルドパスに入れてくれます(=.classpathファイルが自動的に書かれる)。その他の制御ファイルもそうです。

一方でこんなエントリも見かけました。 a little madness » Blog Archive » Setting Up An Android Project Build

Make sure you don’t forget to check in the hidden Eclipse files .classpath, .project and .settings. By structuring the projects so they live under a single top-level folder, it should be easy to add them to the version control server of your choice.
訳:eclipseの隠しファイルである.classpath, .project, .settingsをチェックインするのを忘れないでください。一つの最上位フォルダの下にプロジェクトを構築すれば、これらのファイルをバージョン管理サーバに追加するのは簡単です。

androidのネイティブアプリを作るプロジェクトで、ビルドツールとしてantを使っているケースのようです。androidの公式サイトでの解説でも、プロジェクト直下にbuild.xmlというantのビルド定義ファイルを作ることになっています。(ただし、eclipseの制御ファイルをどうするかまでは触れられていません)

個人的には、mavenを使っている/いないに関わらず、eclipseの制御ファイルをsvnやgitで管理することには抵抗を覚えます。一人で仕事をしているのならいいのですが、他人と協力して構築してゆくプロジェクトの場合、eclipseのバージョンや環境を横並びにそろえる必要があります。しかし実際はeclipse3.7を使っている人もいればeclipse4.2を使っている人もいるのです。必ずトラブルになるでしょう。

Thursday, July 26, 2012

諸君、私はJenkinsが好きだ

諸君 私はJenkinsが好きだ

諸君 私はJenkinsが大好きだ

Email-ext pluginが好きだ
Cobertura pluginが好きだ
Maven repository server pluginが好きだ
HTML Report pluginが好きだ
Gradle pluginが好きだ
Job config history pluginが好きだ
Build pipeline pluginが好きだ

Javaアプリのビルドで
Mavenリポジトリへのdeployで
PHP Unitの実行で
Geronimoへのdeployで
バッチ処理サーバへのscpとsshで

この地上で行われる ありとあらゆるJenkinsのビルドが大好きだ

スレーブを従えたJenkinsの一斉ビルドが
轟音と共にビルドキューを吹き飛ばしていくのが好きだ
空中高く放り上げられたソースコードが
FindBugsでばらばらになった時など心がおどる

Jenkinsの操るビルド手順が
ソースコードツリーを撃破するのが好きだ
悲鳴を上げて燃えさかるリファクタリング済みソースを
JUnitとHTML report pluginが薙ぎ倒した時など
胸がすくような気持ちだった

ルールをそろえたCheckstyleの横隊が
ソースコードを蹂躙するのが好きだ
恐慌状態のプログラマーが シカトぶっこいてたeclipseの
checkstyleプラグインをあわててactiveにしている様など
感動すら覚える

SCMに乱雑にコミットされたソースコードたちを
ビルドジョブビューにつるし上げていく様などはもうたまらない
俺を先にポチってくれと泣き叫ぶビルドジョブ達が
Bulk builder pluginの振り下ろした号令とともに
次々と立ち上がるスレーブにばたばたと薙ぎ倒されるのも最高だ

哀れなプログラマー達が雑多な変更を健気にもコミットしてきたのを
Jenkinsのコミットフックビルドトリガーがchengeset番号と
コミッターのアカウント名ともにBUILD FAILUREで
つき返した時など絶頂すら覚える

後任のビルドエンジニアにJenkinsのビューを
滅茶苦茶にされるのが好きだ
必死に整理したビューが蹂躙され
無関係なビルドが追加されそこにあるはずのビルドジョブが
「すべて」にしか現れなくなる様はとても悲しいものだ

ビルド状況の雨雲の洪水に押しつぶされて殲滅されるのが好きだ
E-mail通知に追い回され 500kbを超えるコンソールログを
血眼で追うのは恥辱の極みだ

諸君 私はJenkinsを 地獄の番人の様なJenkinsを望んでいる
諸君 私に付き従う戦友諸君
君達は一体 何を望んでいる?

更なるJenkinsを望むか?
情け容赦ない 冷酷なJenkinsを望むか?
鉄風雷火の限りを尽くし 三千世界の鴉を殺す 嵐の様な執事 Jenkinsを望むか?

「Jenkins !! Jenkins !! Jenkins !!」

よろしい ならばJenkinsだ

我々は満身の力を込めて今まさにポチらんとする握り拳だ
だがこの暗い闇の底で半世紀もの間耐え続けてきた我々に
ただのビルドではもはや足りない!!

大Jenkinsを!! 一心不乱の大Jenkinsを!!

我らはわずかに一個大隊 千人に満たぬビルドチームにすぎない
だが諸君は一騎当千の古ビルド職人だと私は信仰している
ならば我らは 諸君と私で総力100万と1人の軍集団となる

我々を忘却の彼方へと追いやり 眠りこけている連中を叩き起こそう
デスマーチの葬列から引きずりだし 眼を開けさせ思い出させよう
連中に自動ビルドの味を思い出させてやる
連中にJenkinsのコンソールログの自動スクロールを思い出させてやる

プロジェクトマネージャとプログラマーのはざまには
奴らのWBSでは思いもよらない事があることを思い出させてやる

一千人のビルド職人の集団で世界のソースコードを燃やし尽くしてやる

「最後の大隊 大隊指揮官より全空中艦隊へ」
目標 法政大学 市ヶ谷キャンパス

Jenkinsユーザーカンファレンス 2012 Tokyo 状況を開始せよ

征くぞ 諸君



Inspired By 諸君 私は戦争が好きだ

Sunday, July 22, 2012

ソフトウェア開発自動化が目指すべき姿

自動化ツールを使ったプログラミングレスなシステム開発、という流行病(はやりやまい)は数年おきに出現します。10年ほど前で言えばCASEツールがありました。もっと大昔では、シグマ計画(プププ)ではSPACEなるプログラミング自動化システムが最上位ツールの一つとされていたようです。

何らかのツールに一定の形式で要件定義情報を入力すると、アプリケーションのソースコードが完全に自動生成される、というのが、最近(昔から?)よく見かけるソフトウェア開発自動化ツールの見果てぬ夢幻です。本当にそれをやろうとすると、一定形式の要件定義情報を書くこととソースコードを書くこととの複雑さの差があっという間に埋まってしまって自動化ツールの存在が無意味化するだろうということは、きちんとしたプログラミング経験のある人間であれば誰でも本能的に察知できます。

gccやjavacのようなコンパイラがソースコードをマシン語に変換する自動化ツールであるならば、その進化形として、一定の人間語で書かれた文章と図面をソースコードに変換する自動化ツールもあってしかるべきだ、というレトリックもときどき見かけます。残念ながらそれも、中国の古い故事で言うと論理の飛躍です。ソフトウェア開発はそれほど単純なものではありません。

しかし意外にも、そうした銀の弾丸と青い鳥を探し求める傾向を、ITを使う側でではなく、IT業界の内側でちらほら見るので世の中不思議です。ITの使い方を知らないIT業界人も思いのほか存在するのです。

逆に、ITの使い方を心得たうえで自動化ツールをうまく使う企業や個人もいます。と書いてから自分のことを書くのはものすごくおこがましい(ので深くはつっこまないでほしい)ですが、私の開発したmixer2では、JDK6に標準添付されているxjcコンパイラというツールを使って自動生成したJavaソースコードを大量に使っています。XML(XHTML)という分野では非常に有効な自動生成ツールの一つだからです。

著名なプログラマの一人である小野さん率いるアプレッソ社の主力製品は、DataSpiderというデータ連携のための自動化ツールです。特定の分野において、自動化ツールを使う側ではなく自動化ツールを作る側に立ち、しかもその価値を認められて10年以上にわたり生き残っていることこそ、ITにおける自動化のなんたるかを正しく心得ている証左でしょう。ちなみに彼のブログで個人的に好きなのはバグは夜更け過ぎに仕様に変わるだろう(2007年)です。

10年ほど前、インターネットバブルに踊り莫大な赤字を垂れ流す怪しげな本屋がありました。彼らは、物理的な電子回路と仮想的な電子回路とをリアルタイムに相互変換するという不可思議なツールの価値を正しく見抜き、使い方を磨き、その結果、世界有数の先進的なITサービス企業へと変貌を遂げました。Amazon Web Serviceです。大した根拠もないピーク予想値と少ない予算という不条理のなかで、HDD容量を自動見積もりするexcelシートと向き合い納品リードタイムを気にする必要はなくなりました。ハードウェアを、いや、ミドルウェアをも、ボタン一つで増減できる自動化ツールがそこにあるからです。

10年ほど前、一人の日本の若者が渡米し、XMLというデータ形式を取り扱うコードを書きまくりました。それが現在のJava6のJAXB-APIであり、先述のxjcコンパイラもその副産物です。その道程で、彼は自らの仕事に集中するために、逆に本質的ではない煩雑で退屈な仕事を自動化するためのツールを作り上げ、親しみをこめてhudsonと名づけました。現在のJenkinsです。バックエンドデータストアとしてRDBMS等に頼らず、XMLそのものの柔軟性を最大限に生かすことによって、導入と運用のハードルの低さを実現した自動化ツールの登場です。優秀な執事たるJenkinsにビルドとテストを任せることはこの業界のデファクトとなりました。来週に迫ったJenkinsユーザ・カンファレンス2012東京(at市ヶ谷 法政大学)の参加者は1000人を超えつつあります。

ソフトウェア開発自動化が目指すべき姿とはなんでしょうか?

ここまでのように私がクドクドと書くまでもなく、先人がすばらしい示唆を残してくれています。それを紹介して、このエントリを終わりましょう。

ソフトウェアアーキテクトが知るべき97のこと オライリージャパン (2009) より クリエイティブコモンズ 表示3.0に基づく全文の転載。(太字は転載者によるものです)

煩雑なことを非属人化し、創造性を高める
萩原正義

ソフトウェア開発の完全な自動化は目指すべきではありません。

DSL(ドメイン特化言語)のスコープを十分に小さくし、生成コードの実行をWebアプリケーションの特定フレームワークに限定すれば、完全な自動化は可能となるでしょう。しかし、この自動化はDSLのスコープが限定された小さな問題領域と特定の実装技術に対してのみに有効であって、一般のソフトウェア開発が対象とする広範な問題領域、実装技術の選択肢に大して有効な解とはなりません。

それでは、問題領域を小さな部分問題に分割して、それぞれの部分問題を解くためのDSLを複数組み合わせる方法や、逆に1つのDSLのスコープを広げて汎用性を高める方法でのソフトウェア開発の完全な自動化は 可能でしょうか?

前者は分割したDSL間での依存関係や相互作用がまったく存在しない「直交化」した問題に関しては可能となるかもしれませんが、一般にはこのような単純な問題は少ないでしょう。後者は、DSLはもはや汎用開発言語の範疇になり、一般のプログラミングによる開発となんら変わりがなくなり自動化は難しくなるでしょう。

DSLやその他の開発ツール、モデル駆動型開発は、繰り返し起こる煩雑な開発を自動化することで省力化を行うためであって、ソフトウェア全体を自動化する目的で使うべきではありません。それほどソフトウェア開発は単純なものではありません。 また、問題領域あるいは解決領域のスコープ自体も、ビジネスの進化、ソフトウェア技術の進化、要求の変化によって、固定的な定義で収まるものではありませんから、ソフトウェア開発の自動化の対象領域自体の前提も成立しなくなります。進化しないソフトウェアはいずれ価値を失い、自動化の有効性はなくなるでしょう。

このように間違った目的を目指して技術を適用しようとしても失敗することは歴史が証明しています。進化するビジネスや技術、変化する要求の中で、不変の規則、原理を発見し、この規則と原理に基づき自動化するならば有効な解となるでしょう。不変の規則と原理は繰り返し現れるので、煩雑さを非属人的な 方法で省くことが可能となります。

その上で、創造性や進化が求められる部分に人間の知的作業を集中させることで、ソフトウェア開発の工業化が可能となると考えます。ソフトウェア開発の工業化を大規模な製造の自動化と考えるのはよく見られる誤りです。ソフトウェア開発での工業化は、人による創造性や知的作業と自動化の分担で実現し、その意味では、ソフトウェアは完全な工業製品というより、むしろ人の創造力を含んだ工芸品を目指しているといえなくもないでしょう。

技術と創造力を融合したソフトウェア開発の在り方は、筆者の考える「ソフトウェア工芸」の捉え方の基になっています。

Sunday, May 27, 2012

mixer2がjavax.cache (JSR-107)に対応

mixer2 1.1.3をリリースしました。既にmavenセントラルリポジトリから利用可能です。

まだ一部ですが、javadocを英語と日本語の併記にしたほか、 ロードしたテンプレートをキャッシュして性能向上させられるなりました。 JSR-107として規格化されつつある javax.cache.Cache の実装クラスのインスタンスをMixer2Engine内にsetするだけで使えます。 詳しくは javadoc に。

とはいっても、実は、手元でいろいろためしたのですが、キャッシュを使ってもそれほど性能はあがりません。^^;) いまのところおそらく唯一であろうJSR107の実装であるehcache-1.4.0beta1を使ってテストしたのですが、 mixer2が普通にhtmlテンプレート文字列をロードしてHtml型オブジェクト化するのと、キャッシュからHtml型オブジェクトを取り出すのとではそれほど大きな性能差が見られませんでした。

見方はいろいろですが、mixer2の性能はもともとそれほど悪くはないと言えるのかもしれませんし、ehcacheの今後の改良次第で差が現れてくるのかもしれません。いずれにせよJSR-107を実装するキャッシュフレームワークはどんどん種類が増えてくるはずですので、今後に期待しましょう。

Sunday, May 13, 2012

Jenkins IPMessenger Pluginを作ってみた

勉強がてら、JenkinsがIPメッセンジャーでビルド結果を通知してくれるプラグインを作ってみました。 https://wiki.jenkins-ci.org/display/JENKINS/IPMessenger+Plugin

実装はゴールデンウィーク中に出来ていたのですが、jenkins-ci.orgへのdeployやwiki執筆に至る前に時間切れになってしまったので告知が遅れました。

とはいっても少々やっつけ感のある実装です。しかもまずいことに、テストを書いてませんw。 外部の通信先を必要とするようなコードのテストケースって書きづらいんですよね。受け取り側のSocketのフリをしてくれるMockとか、あるのかしらん。

メッセージの内容は設定画面である程度カスタムできます。$JOB_NAMEとか $BUILD_ID といった、メール通知なんかでもよく使われるマクロが使えます。

ただ、通信先はIPアドレスかホスト名(Windowsマシンでいうコンピュータ名で可)でしか指定できません。 これは、IPメッセンジャーの設定ユーザー名やグループ名などの情報を得るには、メッセージを送る前にまずLAN内にブロードキャストを投げて反応があったマシンから各種の情報を事前に受け取っておかなければならない、という実装部分をはしょったからです。^^;)

誰かパッチ書いてくれたらマージしますんでよろしくお願いします。

Sunday, April 22, 2012

mavenからAmateras Step Counterを使う設定サンプル

最近のAmateras StepCounterはantタスク対応&mavenリポジトリ登録(※セントラルではない)などにより、 mavenからも使いやすくなったようです。 作者の方に感謝!

  
   amateras
   Project Amateras Maven2 Repository
   http://amateras.sourceforge.jp/mvn
  
 
 
  
   
    org.apache.maven.plugins
    maven-antrun-plugin
    1.7
    
     
      jp.sf.amateras.stepcounter
      stepcounter
      3.0.1
     
    
    
     
      package
      
       run
      
      
       
        
        
         
          
          
         
        
       
      
     
    
   
  
 

これでtargetディレクトリ配下にstepcount.csvというファイルが出来上がります。 csvのカラムは左から順にファイルパス、タイプ、カテゴリ、実行行数、空行数、コメント行数、合計行数、のはずです。

Thursday, March 15, 2012

mixer2をmavenのセントラルリポジトリに登録しました

とりあえず最新版を登録しました。 http://search.maven.org/#artifactdetails|org.mixer2|mixer2|1.1.0|jar です。 以後、バージョンアップはすべてセントラルに登録していきます。

これまでは独自リポジトリでやっていたので、repositoryタグに http://mixer2.org/mvn/ を指定する必要がありましたが、 これからは普通に dependencyタグを書くだけで利用できます。

ソースコードも、プロジェクト自体をgithubに置いて晒すことにしました。 https://github.com/nabedge/mixer2です。 readme.mdファイルはそのうち作るつもりです。 これまでも*-sources.jarの形で公開はしていたのですが、これでみなさんにforkしてもらいやすくなりました。 ライセンスはApache License 2です。

詳しい説明は http://mixer2.org/site/にあります。 これからも、ごひいきに!