Monday, July 15, 2013

mixer2のサンプルアプリのプロジェクト分割バージョン

大きなWebアプリをチーム開発する場合、プロジェクトの分割がポイントになります。 そのためにServlet3.0のWeb fragmentのような仕様もあるのですが、 JSPを使う限り、htmlをjspに置き換えるという面倒な作業はまぬがれません。

mixer2をうまく使うと、プロジェクトの分割も比較的容易になります。 そんな話を今年1月のSpring勉強会にてお話した のですが、そのとき、出来上がっていたはずのデモの起動に失敗するという失態(笑)。

そんな失敗を糧に、改めてちゃんと動くように作った(はず)のがこちらです。 https://github.com/nabedge/mixer2-sample/tree/master/m2fruitshop mixer2-sampleリポジトリをcloneして、その内部のm2fruitshop配下の各ディレクトリを、mavenプロジェクトとしてIDE(eclipse等)にインポートしてください。m2fruitshop-webを起動すれば動くはずです。

少々説明が雑ですが、お暇な方はチャレンジしてみてくださいね。

Tuesday, July 9, 2013

mixer2のsnapshotバージョンの使い方

mavenプロジェクト限定ですが。

mixer2の正式バージョンはmavenのセントラルリポジトリに登録されていますので、 pom.xmlファイルにdependencyタグを書けば、特に何も考えなくてもjarとその依存jarは自動的にダウンロードされてくれます。

しかし今回のようにバグが発覚し(汗)、それを直している最中のSNAPSHOTバージョンを使いたい場合は、 pomにsnapshot用リポジトリを書いておけばよいです。こんな感じです。


    4.0.0

    com.example
    project-name
    0.0.1-SNAPSHOT

    
        
            sonatype.oss.snapshots
            Sonatype OSS Snapshot Repository
            http://oss.sonatype.org/content/repositories/snapshots
            
                false
            
            
                true
            
        
    

    
        
            org.mixer2
            mixer2
            1.2-SNAPSHOT
        
    

2013年7月現在、mixer2は1.2系統として開発されています。mixer2では、pomのversionタグをいちいち書き変えるのが面倒なので、 x.y.z-SNAPSHOTではなくx.y-SNAPSHOTの形式で開発を続けています。 いまの最新正式バージョンが1.2.6ですので、現在の1.2-SNAPSHOTは1.2.7になる予定の最新開発バージョンだと思ってください。

Sunday, July 7, 2013

mixer2-1.2.6でhtmlタグのオブジェクトのディープコピーの性能が向上しました

追記
1.2.6がバグってました。すいません。1.2.6のcopy()やclone()メソッドは、html5タグのdate-*属性やaria-*属性をうまくコピーできません。 他の一般属性は大丈夫です。直し方のアイデアはあるのですが、ちょっと時間が取れません。7月中にはなんとかしますので、 それまでは1.2.5以前をご利用ください。はー、テストが甘かった。反省します。

mixer2-1.2.6をリリースしました。数時間後にmavenセントラルリポジトリから使用可能になります。 maven使わない派の方のためにjarのセットがこちらにあります

リリース頻度が早すぎるので少し落ち着かせる、と宣言してから1か月ちょいしかたっていませんが、 気になっていた部分の改善ができてしまったので、そこは、まあ。(^^;

気になっていたのはcopy()メソッドの性能です。これまではよく見かけるserialize/deserializeを駆使した手法を使っていたので、どうしてもディープコピー処理に時間がかかっていました。 1.2.6では、各htmlタグにマッピングされる全てのクラスにclone()、hashcode()、equals()メソッドが追加されています。 従来のcopy()メソッドは内部ではclone()を呼び出しているだけになっています。もちろん正確にディープコピーします。

これにより、htmlテンプレートをloadHtmlTemplate()した結果のHtmlオブジェクトをキャッシュしておき、二回目以降はキャッシュのディープコピーで済ませることで、アプリケーションの性能を高めやすくなります。従来はcopy()メソッドが重かったため、どんなキャッシュ機構を作っても大した効果は得られませんでした。

ごく簡単なキャッシュ機構を作って試したところ、いちいちloadHtmlTemplate()するよりも30%前後は性能がよくなりそうです。もちろんhtmlテンプレートの内容によって数値は異なるでしょう。また、htmlテンプレート全体ではなく、その一部のタグをcopyしながら使いまわすような処理を書く場合でも、この性能向上の恩恵は受けられます。

それから、キャッシュキャッシュ言ってますが、逆に、1.2.5まではあった、javax.cacheインターフェースを使ったキャッシュ機構を、1.2.6では完全に廃止しました。@depricatedだったメソッドも完全に削除されています。これはキャッシュ機構はmixer2本体に持たせるよりも、使う側で独自に作ってもらうほうがよいと判断したためです。mixer2が依存するjarは本当に必要最低限にしておくほうがライブラリとして使いやすいはずです。 もしやるとしても、たとえばmixer2-cachewrapperみたいな別プロジェクト/ライブラリとしてキャッシュ機構を付加したラッパーライブラリを提供する形になるでしょう。(誰か作ってください^^;)

さて、さすがにもう眠いんで寝ます。

Sunday, May 19, 2013

mixer2-1.2.3をリリース

mixer2-1.2.3をリリースしました。

特に大きな機能追加はありません。逆に、キャッシュ機構はすべて@Deprecated(廃止予定)にしました。 1年前の1.1.3でJSR-107のcache APIを使えるようにメソッド等を用意したものの、 JAXBのアンマーシャラーだけでけっこう性能が出るっぽくて、 キャッシュ使うとそのコストとどっこいになっちゃうみたいだぜ? という出オチをひっくり返すことが結局できませんでした。

まあ、よいことなんだと思います。 で、無理にmixer2本体にcache-API対応メソッドを実装してコードを肥大化させてしまうよりは、 早めに削るという判断をしてみました。 mixer2を使う側で好きなキャッシュ機構を使ってラップしてもらうほうがよいと思ってます。

@Deprecatedにしたもののメソッドはまだ残っています。 なので、cache-api-0.5.jarへの依存性はまだ消してないですが、そのうち消します。 そうなると、mixer2が依存するライブラリはcommons-lang,commons-beanutils,commons-loggingだけになります。

なお、ここんところリリース頻度が早すぎる気がして反省してます。 本体で気になっている部分はだいぶ直せたので、 今後はここ数か月ほどのリリース頻度ではないと思います(バグがあったらすぐ直しますけど)

なお、ドキュメントの加筆は今後もちょくちょく続けようと思います。

Sunday, April 21, 2013

JJUG CCCにて講演予定:「さらばJSP – JAXBとテンプレートエンジンmixer2」

5月11日(土)に新宿で開催される、日本Javaユーザーグループのクロスコミュニティカンファレンス(以下JJUG CCC)にて講演します。 演題は「さらばJSP – JAXBとテンプレートエンジンmixer2」です。

さらばJSP、とか勢いでぶちあげちゃった感アリアリですがw、 とりあえずGW中にじっくり資料考えるつもり。

追記

無事、講演を終えました。40名様ほぼ満席。関係者のみなさん、本当にありがとうございました。

Thursday, March 7, 2013

mixer2-1.2.1リリース

mixer2 version 1.2.1をリリースしました。 htmlテンプレートの改行が取り除かれてしまうバグが解消されています。 このバグ対応は、利用するアプリの側からすると従来の挙動が変わるのと同等の影響がありうるため、versionは1.1ではなく1.2系統としました。 といっても1.1系統のメンテは停止する可能性が高いですが。

version1.1.xではインラインのjavascript内部もやはり改行が取り除かれてしまいましたが、1.2系はそういうことはありません。 ただ、xhtml的に正しい書き方をしなければならないことに変わりはないので、コメントアウトとCDATAを組み合わせて書く必要があります。 たとえば、GoogleAnalyticsを使うためのインラインjavascriptであれば、テンプレートにはこんな風に埋め込んでください。


2行目と14行目がポイントです。全体として、javascriptとして正しいのでちゃんと動きますし、xhtmlとしても正しいのでmixer2でテンプレートとしてロードすることも可能です。

SpringMVC用のviewクラスも同梱しています。 使い方はjavadocか、あるいはmavenアーキタイプでできるコードを参考にしてください。

Sunday, February 17, 2013

SpringMVCとmixer2を組み合わせて使うためのmavenアーキタイプを公開しました

WebアプリケーションフレームワークとしてのSpringMVCの完成度は非常に高く、 そこにテンプレートエンジンとしてmixer2を組み合わせると鬼に金棒です。

ところが、開発のスタート地点となる「最初のプロジェクト」を作るのが意外に面倒です。 mixer2でJSPレスなSpringMVCアプリケーションを作ってみたでお話ししたFlower shop sample appを自分のプロジェクト用の土台に作り替えるという手もあります。が、プロジェクト名やGAV情報の変更、Javaのパッケージ名の変更、いらないコントローラやテンプレートの削り落としなどの作業が必要になります。

そこで、ある程度の設定ファイルと最低限のHelloWorld的な画面をひとつ、 初めから組み込んであるプロジェクトを簡単に作れるmavenアーキタイプを作りました。 任意のプロジェクト名、GAV情報(mavenのgroupId,artifactId,versionのこと)、javaパッケージ名で新規プロジェクトを作ることができます。 しかもSpringMVCとmixer2に必要な設定ファイルとごく基本的なヘルパークラスまで含まれています。

詳しい使い方は公式サイトの mixer2 - Maven archetype for SpringMVC and mixer2 に書いてあります。お試しください。

SpringMVC+Mixer2でデザイナーとプログラマが仲良く仕事できるWebアプリ開発プロジェクトを、あなたにも!

えっ?いまだに生のservletとjspだけでWebアプリを作っている? ありえません。かなり重症ですね...。

Monday, February 11, 2013

Spring勉強会にて講演しました

先月、JSUG(Japan Spring Users Group)の勉強会にて講演の機会をいただきました。下のが講演資料です。

意外にも会場(50人くらい?)が満席!実際ATNDの申し込み画面でもキャンセル待ちが出てたようです。 つたない講演で満足いただけたかわかりませんが、足を運んでいただいた皆様には感謝です。 土岐さん、長谷川さんほかSpringユーザー会の皆様、会場を貸してくださっているVMWare社さん、本当にありがとうございました。 見に来てくれた新旧の同僚の方々、軽くヤジってくれたってよかったんだぜw。

Sunday, January 20, 2013

JUnitとDbUnitとSelenium

メモがてら。

DBUnitを使えば、excelで用意したテスト用のデータをテスト実行前にDBに投入するとかがラクになる。 ソースはここ

DbUnit用のデータセットはデータベースのテーブルへの出し入れだけにしか使えないということはない。 たとえばDBデータではない、テストのパラメータとかをテストコードの外で管理するために使ってもかまわない。ソースは上のやつ参照。

JUnitテストケース上で @Theories, @Thory, @DataPoint, @DataPoints といったアノテーションを使えば、パラメータ化テストが楽になる。詳しくは「JUnit実践入門」をアマゾンで買いましょう。

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 !