より良い環境を求めて このページをアンテナに追加 RSSフィード

2010-11-29

[] UiBinderで独自クラスを使う場合のコンストラクタ

例えば、SimplePagerでfastForwardRowsの行数を変えたい場合*1


package myapp.client.widget;

import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiConstructor;
import com.google.gwt.user.cellview.client.SimplePager;

//see: http://code.google.com/p/google-web-toolkit/issues/detail?id=5471
public class SimplePager2  extends SimplePager {
    @UiConstructor
    public SimplePager2(int fastForwardPage){
        this(TextLocation.CENTER, fastForwardPage);
    }
    public SimplePager2(SimplePager.TextLocation location, int fastForwardPage){

        super(location,
                (SimplePager.Resources)GWT.create(SimplePager.Resources.class),
                true, fastForwardPage, true);
    }
}

WidgetでUiConstructorアノテーションを使う。

それからUiBinderでSimplePager2を読み込んで

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'
    xmlns:p1="urn:import:com.google.gwt.user.cellview.client"
    xmlns:p2="urn:import:myapp.client.widget">

...

<p2:SimplePager2  ui:field='pager' fastForwardPage="100"/>

...

こんな感じでコンストラクタ引数を指定できる。


ちなみに元々のSimplePagerはGWT Designerで配置した場合にエラーになったが、それはSimplePagerのコンストラクタ

  @UiConstructor
  // Hack for Google I/O demo
  public SimplePager(TextLocation location){
    this(location, getDefaultResources(), true, DEFAULT_FAST_FORWARD_ROWS,
         false);
  }

となっているためで、locationを(たとえば location="CENTER" と)指定する必要がある。

なんかコメントが気になるが…。

ここで使われているgetDefaultResources()とDEFAULT_FAST_FORWARD_ROWSはprivateで宣言されているのでちょっとめんどくさいことになっていて、そのせいでここの値を変更するには上のようなsnippetを使わないといけない。


DEFAULT_FAST_FORWARD_ROWSは1000で定義されているので、テスト中は10などの動きが分かる値にした方がいいと思う。

2010-11-26

[] GWT MVPあれこれ

Place, Activity, View, Presenter を使った開発が段々分かってきた。


以下一連の流れ。だいたいは http://code.google.com/intl/ja/webtoolkit/doc/trunk/DevGuideMvpActivitiesAndPlaces.html ここの通り。

  1. まずclient.viewパッケージ以下にFooViewインターフェースを作り、取り敢えず空のPresenterインターフェースとsetPresenterメソッドだけ定義する。
  2. GWT DesignerのUiBinderなどを使ってFooViewImplクラスを作り、FooViewをimplementsする。
  3. それからclient.placeパッケージ以下にPlaceを継承したFoo(FooPlace)を作りインナークラスでPlaceTokenizer<Foo>を実装したTokenizerを定義する。TokenizerのgetPlaceでreturn new Foo()し、getTokenは空文字でも返しておく。
  4. ビューが出来たので、FooViewに対応するFooActivityをclient.activityパッケージ以下に作る。これはFooView.Presenterを実装してコンストラクタでClientFactoryを受け取り、startメソッドでお決まりのコード(※)だけ書く。
  5. 最後にAppActivityMapper#getActivityに処理を追加してAppPlaceHistoryMapperのアノテーションにTokenizerを追加すると、空のページ表示までが完成。

    private FooView view;
    @Override
    public void start(AcceptsOneWidget panel, EventBus eventBus){
        view = clientFactory.getFooView();
        view.setPresenter(this);
        panel.setWidget(view);
    }

あとはUiBinderでビューを作っていって、onClickなどの処理はpresenter(Activity)に委譲する。また、presenterでRPCなどを実行したあとに結果を設定する場合は、上のコードで書いたように保持しておいたviewのsetResultなどを呼び出し結果を設定する。

一つのビューでtokenを使って状態を管理したい場合は、FooActivityのコンストラクタにFoo(FooPlace)を渡してFoo#getTokenの内容によってstartメソッド初期化を切り替える。


たぶんこんな感じ。


View、View.Presenter、ViewImpl、Activityは一蓮托生のようになっていて、一応疎結合なんだけどもワンセットになる。FooActivityがFooView.Presenterを実装しているので、Activityの状態によってビューを切り替えるということは出来ない。この場合はActivity、Placeもビューごとに作った方が良い。

または、ActivityはView.Presenterを継承せずに委譲するようにしたら切り替えが出来るかな。どっちがいいんだろう。


Placeの中でViewを表示する前に、何らかの状態によって別のPlaceに切り替えるということもやめた方が良さそう。

例えばViewを持たないCheckPlaceを作り、ログイン状態によってMemberPlaceとGuestPlaceに切り替えるっていう方法はイマイチ。ブラウザバックが効かなくなってCheckPlace以前の履歴に戻れなくなる。

じゃぁどうすればいいのかというと…今のところ考え中。何にせよPlaceから別のPlaceに転送したらブラウザバックが効かなくなるので、権限のないPlaceに来たらエラー用Viewを表示するのがいいのかな。まぁ正規の手順でブラウジングしてたら権限のないPlaceには来ることはないのでブラウザバックを諦めるという考え方もあるか。

GUIプログラミングの経験がほとんどないから知らないだけで、実はセオリーがあるのかも。



なんだかファイルが多くなっただけで現状ではメリットを感じない。clientのテストをまだ書いてないからかな。

A key concept of MVP development is that a view is defined by an interface. This allows multiple view implementations based on client characteristics (such as mobile vs. desktop) and also facilitates lightweight unit testing by avoiding the time-consuming GWTTestCase.

テストが楽って書いてあるしね。あとViewの切り替えも楽って書いてある?

あぁ…つまり、会員ページと非会員ページを切り替えるんじゃなくて、全ての機能が同じだけれど表示が異なるパソコンとiPhoneの表示を切り替えるのが容易で確実になるってことか。

それと、完全に別ページを切り替えるんじゃなくて、例えばネットショップならログイン済みユーザーのカート表示と非ログインユーザーのカート表示を切り替えるっていうことならできるのか。インターフェースの設計が難しそうだねぇ…。




GWT.reate(AppPlaceHistoryMapper.class)で生成したPlaceHistoryMapperは、Placeとtokenの区切り文字は : に固定されている。

実体は com.google.gwt.place.rebind.PlaceHistoryMapperGeneratorから作られる com.google.gwt.place.impl.AbstractPlaceHistoryMapper にあるようだ。

GWT.createによってPlaceHistoryMapperGenerator#writeGetPrefixAndTokenが呼び出され、ここで動的にメソッドを生成している模様。


区切り文字を変えたければ、自分でPlaceHistoryMapperをimplementsしたクラスを作る必要がある?

結構な量なので自分で作るのはやめたいが、区切り文字のコロンはAbstractPlaceHistoryMapperにハードコードされているので現状どうしようもなさそう。

PlaceHistoryMapperインターフェースにはgetPlaceメソッドとgetTokenメソッドしか定義されていないので、これをimplementsして自分で区切り文字を解析してPlaceをnewするクラスを作るのが早いのかもしれない。これだと自由にtokenとPlaceの対応付けができる。ただclient側のコードではClass#getSimpleNameやClass#getCanonicalNameが使えなかった。



あと、tokenの名前がよくないね。

PlaceHistoryMapper#getTokenのtokenはClassName:tokenのことで、Place#getTokenはコロン以降の文字だけを指すみたい。

[] 一時的なデータ置き場

Javaで一時的なデータをどこに保存しようかと調べていた。

File.createTempFile はサーバーでは使えなさそうだし…ということで、EHCacheを試す。


no title

簡単な使い方はここ。


ダウンロード

http://sourceforge.net/projects/ehcache/

ここ?毎回Javaライブラリダウンロードって迷う…。


EHCacheのtimeToLiveSecondsとtimeToIdleSecondsの違い - Bouldering & Com.

タイムアウトの設定。


jarは ehcache-core-2.3.0.jar、slf4j-api-1.5.11.jar、slf4j-jdk14-1.5.11.jar をクラスパスに入れた。

後はサンプル通り。特に迷うこともなく使えた。

2010-11-19

[] JavaMailは遅くなかった

http://d.hatena.ne.jp/n314/20101118/1290101208

Postfixが遅かった。

原因は不明。 下に追記


開発マシン

time /usr/sbin/sendmail -t < /tmp/body.txt

real    0m0.032s
user    0m0.004s
sys     0m0.008s

サーバー

time /usr/sbin/sendmail -t < /tmp/body.txt

real    0m0.082s
user    0m0.004s
sys     0m0.004s

同じHWのサーバー

time /usr/sbin/sendmail -t < /tmp/body.txt

real    0m0.008s
user    0m0.004s
sys     0m0.004s

この違いは…。

user + sys と real の差は一体何だろう。



straceやtcpdumpなどを眺めているが未だに原因が分からず。

vmstatの結果は少し違う。


開発マシン

vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0 705312 184984 278020 348600    1    1     0     1    1    1  3  2 95  0
 0  0 705312 184976 278020 348600    0    0     0     8   73 1190  4  1 94  0
 0  0 705312 184976 278020 348600    0    0     0     0   88 1269  4  1 95  0
 0  0 705312 184976 278020 348600    0    0     0     0   65 1298  5  2 93  0
 0  0 705312 184952 278024 348604    0    0     0   456  202 2501 14  2 84  0
 0  0 705312 184944 278024 348604    0    0     0     0  183 5961 16  5 79  0
 0  0 705312 184864 278024 348604    0    0     0     0  161 7605 33  9 58  0
 0  0 705312 184520 278024 348604    0    0     0     0  124 4470 24  8 68  0
 5  0 705312 183164 278024 348604    0    0     0     0  124 4635 23  7 70  0

サーバー

vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0      0 2741248 154124 2390480    0    0     0     4    2    2  0  0 100  0
 0  0      0 2740248 154124 2390480    0    0     0     0 1978 1278  0  1 99  0
 0  0      0 2740336 154124 2390480    0    0     0     0 1989  866  0  0 100  0
 0  0      0 2740496 154124 2390480    0    0     0     0 1809  674  0  0 100  0
 0  0      0 2740496 154124 2390480    0    0     0     0 1807  686  0  0 100  0
 0  0      0 2741612 154124 2390484    0    0     0     0 1852  696  0  0 100  0
 0  0      0 2740868 154124 2390488    0    0     0     0 1450  541  0  0 100  0
 0  0      0 2740212 154124 2390488    0    0     0     0 1937  862  0  0 100  0
 0  0      0 2740248 154124 2390488    0    0     0     0 1831  680  0  0 100  0
 0  0      0 2740248 154124 2390488    0    0     0     0 1810  669  0  0 100  0
 0  0      0 2737628 154124 2390492    0    0     0     0 1975  834  0  0 100  0


同じHWのサーバー


vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0      0 914648 268996 4328256    0    0     0     8    1    1  0  0 100  0
 0  0      0 914640 268996 4328256    0    0     0     0  407  109  0  0 100  0
 0  0      0 914648 268996 4328256    0    0     0     0  174  102  0  0 100  0
 0  0      0 914656 268996 4328256    0    0     0   132  150  114  0  0 100  0
 0  0      0 914672 268996 4328256    0    0     0     0  115   93  0  0 100  0
 0  0      0 914672 268996 4328256    0    0     0     0  109   90  0  0 100  0
 0  0      0 914672 268996 4328256    0    0     0     0  111   96  0  0 100  0

何もしていない状態でコレ。

サーバーtomcatや各種デーモン、あとDRBDとheartbeatも動いているので、その辺のせい?プロセスの個数やCPU負荷は高くないが、systemの値が高い。と言っても微々たるものな気がするが…0.1秒以下の処理には影響するのか?それともDRBDの通信?ネットワークの構成もちょっと違う。sendmailコマンドの実行時間にネットワーク環境って影響するのかな。

デーモンの影響だとすると、こればっかりはどうしようもないので別のSMTPサーバーを使うのがいいか…。



追記

DRBDを切ったら速くなった。

time /usr/sbin/sendmail -t < /tmp/body.txt

real    0m0.019s
user    0m0.008s
sys     0m0.000s

http://www.postfix-jp.info/ML/arc.1/msg00958.html

これか…。キューのファイルはsyncされるんだね。

このディレクトリをDRBDから外すべきか…。

2010-11-18

[] JavaMailが遅い

SMTPを喋ってるから遅いのかな?と思ってsendmail実行に切り替えてみた。

class MailService {
    ...
    private final boolean useSendMail;
    public MailService(){
        if (new File(SENDMAIL_PATH).exists())
            useSendMail = true;
        else
            useSendMail = false;
    }

...
    public void  doSend(String to, String body) throws IOException {
        if (!useSendMail){
            // senderはMimeMessage + Transport のラッパーのようなもの
            sender.setTo(to)
                  .setBody(body)
                  .send();
            return;
        }

        String[] cmd = {SENDMAIL_PATH, "-t", "-f", ENVELOPE_FROM};
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(cmd);
            procSend(process, to, body);
            process.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (process != null)
                process.destroy();
        }
    }

    private void procSend(Process process, String to, String body) throws IOException {
        OutputStream out = null;
        InputStream in = null;
        InputStream err = null;
        String[] ignoreHeaders = {"Message-ID"};
        try {
            out = process.getOutputStream();
            in  = process.getInputStream();
            err = process.getErrorStream();

            sender.setTo(to)
                  .setBody(body)
                  .writeTo(out, ignoreHeaders);
        }finally {
            if (err != null)
                err.close();
            if (out != null)
                out.close();
            if (in != null)
                in.close();
        }
    }
}

こんな感じ。

execの例外処理の方法がよく分からなかった。

out、in、errそれぞれのストリームは使わなくても全部closeしなければいけないという記事を見かけたのでcloseしたけど、closeも例外をスローするよね。なんか冗長。このコードの場合、err.closeでIOExceptionが発生したらoutとinは閉じられないよね…tryを三つ書くとか?正しいやり方がわからん。


書きながら今この記事を見付けた。

http://d.hatena.ne.jp/hidepon_mory/20090511/1242043434

出力があるなら読み込む必要もある?更にめんどくさそう…。


また別の問題もあった。

http://d.hatena.ne.jp/Kazumi007/20090930/1254319746

Javaでexecはやめた方がいいのかもわからんね。



で、例外処理はおいといて上のコードでSMTPとコマンド実行を試してみたんだけど、誤差程度しか速度が変わらない。waitForしているから?destroyの書き方が間違ってるぽい?


ややこしい…これだとSMTPでいいかな…。でもwriteToのignoreHeaders引数はMessage-IDを消せるので魅力的だ。

しかしコマンドを実行するだけでこんなに面倒くさいなんて。


あとで

http://www.ne.jp/asahi/hishidama/home/tech/java/process.html#Process

ここもよく読む。

2010-11-16

[][] GWTRPCの共通処理

http://d.hatena.ne.jp/n314/20101026/1288070773

ここでRPC処理も含めてjarを作れば簡単に共通化できるよねと書いたんだけど、うまくいかない。

これを書いたときはうまくいってたのかな…ちょっと覚えてない。


jarの中のServiceAsyncを呼び出すと、IncompatibleRemoteServiceExceptionが発生するようになってしまった。

GWTコンパイル時に(?)バージョンかシリアルIDかよく分からないけど何らかの識別子が入ってブラウザをリロードしてくれというメッセージが出る。

RPCを含んだ独自jarのバージョンやGWTのバージョンを個別に上げることもあるだろうし、GWT RPCのServiceImplはアプリそれぞれでラッパークラスを作って、Seasar管理のクラスをそのまま呼び出すのが無難か。

[][] GWTHudsonビルドスクリプト

GWT + Git + Hudson の設定 - より良い環境を求めて の続き


ほとんど変更がないけれども


#!/bin/bash

USAGE="Usage: gwt-build.sh com.company.appname.ModuleName dirName appPath"
if [ "$1" == "" ]; then
    echo "ERROR: Required module name."
    echo $USAGE
    exit 1
fi

if [ "$2" == "" ]; then
    echo "ERROR: Required dir name to deploy."
    echo $USAGE
    exit 1
fi

if [ "$3" == "" ]; then
    echo "ERROR: Required application path."
    echo $USAGE
    exit 1
fi

echo "setup..."
# copy libs and other files
GWT_HOME=/usr/local/gwt-2.1.0
SHARED_LIB=lib
rm -rf war/WEB-INF/lib; mkdir -p war/WEB-INF/lib
cp $GWT_HOME/gwt-servlet.jar war/WEB-INF/lib/
cp $SHARED_LIB/*.jar war/WEB-INF/lib/
rm -rf war/WEB-INF/classes; mkdir -p war/WEB-INF/classes
cd src
find . ! -name "*.java" -type f | cpio -pd ../war/WEB-INF/classes > /dev/null 2>&1
cd ..
echo product > war/WEB-INF/classes/env.txt
rm -f war/WEB-INF/classes/log4j.properties

echo "compile java..."
# compile java
CLASSPATH=$GWT_HOME/gwt-user.jar:$GWT_HOME/gwt-dev.jar
for jar in `ls $SHARED_LIB`; do
  CLASSPATH=$CLASSPATH:$SHARED_LIB/$jar
done
find src -name "*.java" | xargs javac -sourcepath src -d war/WEB-INF/classes -g:lines.vars.source -cp $CLASSPATH
RET=$?
if [ $RET -ne 0 ]; then
        exit $RET
fi

echo "compile gwt..."
# compile gwt
java -cp src:war/WEB-INF/classes:$CLASSPATH com.google.gwt.dev.Compiler -war war $1
RET=$?
if [ $RET -ne 0 ]; then
        exit $RET
fi


# upload and reload app
rsync -avz --delete war/ user@webapp_host:~$2
wget -O /dev/null --http-user=user --http-passwd=pass \
  "http://webapp_host:8080/manager/html/reload?path=$3"

リポジトリにlib/*.jar を含めた場合。

あとMVPのテストをしたらGWTコンパイルでエラーが出たのでクラスパスにWEB-INF/classesを含めた。

2010-11-15

[] Ext GWTライセンスとかバージョンとか

検索したら、Ext GWTを使うとGPLだから公開しないといけないと書かれた記事が複数見つかったけど、受託開発とかじゃなかったらGPLは関係ないよね?


http://www.slideshare.net/naotori/080826-ext3slideshare-presentation

これとか、一見詳しいんだけど…。



で、Ext GWTってどうなんだろう。

GWT 2.1 が出て、ドキュメントに凄い勢いでnewマーカーが付いてる。

どっちも捨てがたい。


http://ja.wikipedia.org/wiki/Google_Web_Toolkithttp://dev.sencha.com/deploy/gxt-2.2.0/release_notes.html を見ると、GWT 1.7が2009年7月20日GWT 2.0が2009年12月8日にリリースして、Ext GWTは 2009年7月22日GWT 1.7 と GWT 2.0 に同時に対応している?あれ?2.0対応早すぎない?




…と思ったらあった。

Release 2.2.1 (Released November 10th, 2010)

...

Added support for GWT 2.1

http://dev.sencha.com/deploy/gxt-2.2.1/release_notes.html

http://www.sencha.com/products/gwt/download.php

ダウンロードのページには「For use with GWT 2.0」と書いてあるが、どういうことなんだろう。GWT 2.1の新機能は使わずに互換性だけの修正なんだろうか。

とりあえず後で使ってみよう。

2010-11-14