Home > Seasar

Seasar Archive

FishEyeが重い

ソースコードブラウザをFishEye & Crucibleへ移行したら重すぎて全然表示出来ていませんでした。富豪的にメモリを割り当てて、VMのパラメータを変えてみました。

FISHEYE_OPTS="-Xms1024M -Xmx3072M -XX:NewSize=768M -XX:MaxNewSize=768M \
-XX:PermSize=128M -XX:MaxPermSize=256M -XX:+UseConcMarkSweepGC"

でもまだ重いです。また、変えるかもしれません。iowaitしているわけでも、load averageが高いわけでもなく、ただただJavaのオブジェクト生成が大量にありGCが掛かって遅いように見えます。

2009-08-04追記: とりあえず解決しました。

ソースコードブラウザをFishEye & Crucibleへ移行

だいぶ前にリクエストをいただいてから遅くなりましたが、ViewVCからFishEye & Crucibleへ移行しました。

ViewVCよりグラフィカルに統計情報を見ることができます。

ちなみにライセンスはオープンソースライセンスを使わさせていただいています。Atlassianの中の皆さま、ありがとうございます!

入力された文字の前後の空白スペースなどの文字列を除去しても有効な文字があるか検証するValidatorと実際に取り除く処理

入力フォームでユーザに文字を入力させると、前後に空白スペースなどを入れちゃったりしますよね。Validatorで前後に空白スペースがあればエラーにしても良いですが、それはちょっと不親切なので自動的に取り除いてあげるのが親切かなと思います。また、String の trim() 関数だと全角スペース1文字がエラーになりません。得てして前後にスペースを入れちゃう人は全角のスペースを入れるのでこれもチェックしないといけません。というわけで、Cubby 1.1.x 向けにValidatorを書いてみました。

取り除きたい文字は半角・全角スペースだけとも限らないので、取り除きたい正規表現を前後別々に指定できるようになっています。引数が1個の時は前後とも同じ正規表現が使用されます。

  • RegexTrimRequiredValidator
import org.seasar.cubby.validator.MessageHelper;
import org.seasar.cubby.validator.ScalarFieldValidator;
import org.seasar.cubby.validator.ValidationContext;
import org.seasar.framework.util.StringUtil;

/**
 * 先頭と末尾を正規表現でtrimした文字列の必須検証をします。
 * <p>
 * trimした文字列の長さが0の場合、検証エラーとなります。
 * </p>
 * <p>
 * デフォルトエラーメッセージキー: valid.required
 * </p>
 *
 * @author jfut
 */
public class RegexTrimRequiredValidator implements ScalarFieldValidator {

    /** メッセージヘルパ */
    private final MessageHelper messageHelper;
    /** 文字の先頭から除去する正規表現 */
    private String prefixRegexTrim;
    /** 文字の末尾から除去する正規表現 */
    private String postfixRegexTrim;

    /** 正規表現における先頭識別文字 */
    private static final char BEGIN_CHAR = '^';
    /** 正規表現における末尾識別文字 */
    private static final char END_CHAR = '$';

    /**
     * インスタンスを作成します。
     */
    public RegexTrimRequiredValidator() {
        this(null, null);
    }

    /**
     * インスタンスを作成します。
     *
     * @param regexTrim
     *            除去する正規表現
     */
    public RegexTrimRequiredValidator(final String regexTrim) {
        this(regexTrim, regexTrim);
    }

    /**
     * インスタンスを作成します。
     *
     * @param prefixRegexTrim
     *            文字の先頭から除去する正規表現
     * @param postfixRegexTrim
     *            文字の末尾から除去する正規表現
     */
    public RegexTrimRequiredValidator(final String prefixRegexTrim,
            final String postfixRegexTrim) {
        this(prefixRegexTrim, postfixRegexTrim, "valid.required");
    }

    /**
     * エラーメッセージキーを指定してインスタンスを作成します。
     *
     * @param prefixRegexTrim
     *            文字の先頭から除去する正規表現
     * @param postfixRegexTrim
     *            文字の末尾から除去する正規表現
     * @param messageKey
     *            エラーメッセージキー
     */
    public RegexTrimRequiredValidator(final String prefixRegexTrim,
            final String postfixRegexTrim, final String messageKey) {
        this.prefixRegexTrim = prefixRegexTrim;
        this.postfixRegexTrim = postfixRegexTrim;
        this.messageHelper = new MessageHelper(messageKey);
        setupRegexTrim();
    }

    /**
     * 正規表現をセットアップします。
     */
    public void setupRegexTrim() {
        // prefixRegexTrim
        if (prefixRegexTrim != null) {
            // 文字の末尾が $ の場合、取り除きます
            if (prefixRegexTrim.charAt(prefixRegexTrim.length() - 1) == END_CHAR) {
                prefixRegexTrim =
                    prefixRegexTrim.substring(0, prefixRegexTrim.length() - 1);
            }
            // 文字の先頭に ^ が無い場合、追加します
            if (prefixRegexTrim.charAt(0) != BEGIN_CHAR) {
                prefixRegexTrim = BEGIN_CHAR + prefixRegexTrim;
            }
        } else {
            prefixRegexTrim = "";
        }
        // postfixRegexTrim
        if (postfixRegexTrim != null) {
            // 文字の先頭が ^ の場合、取り除きます
            if (postfixRegexTrim.charAt(0) == BEGIN_CHAR) {
                postfixRegexTrim =
                    postfixRegexTrim.substring(1, postfixRegexTrim.length());
            }
            // 文字の末尾に $ が無い場合、追加します
            if (postfixRegexTrim.charAt(postfixRegexTrim.length() - 1) != END_CHAR) {
                postfixRegexTrim = postfixRegexTrim + END_CHAR;
            }
        } else {
            postfixRegexTrim = "";
        }
    }

    /**
     * {@inheritDoc}
     */
    public void validate(final ValidationContext context, final Object value) {
        if (value instanceof String) {
            final String str = trim((String)value);
            if (!StringUtil.isEmpty(str)) {
                return;
            }
        } else if (value != null) {
            return;
        }
        context.addMessageInfo(this.messageHelper.createMessageInfo());
    }

    /**
     * 指定された文字列の先頭と末尾をtrimします。
     *
     * @param value
     *            文字列
     * @return trimされた文字列
     */
    public String trim(String value) {
        if (value != null) {
            value = value.replaceAll(prefixRegexTrim, "");
            value = value.replaceAll(postfixRegexTrim, "");
        }
        return value;
    }
}
  • 適当なActionでの使用例

入力値をチェックしつつ、不要な文字を取り除いても有効な文字がある時は、同じ条件で不要な文字をtrimできるようにインスタンス化して使用します。ポイントはパラメータがバインドされたインスタンスをtrimするために、ValidationRulesでValidateしつつ、最後にインナークラスRegexTrimValidationRuleFilterでtrimを実行しておくところです。これによりアクションメソッド実行時には既にtrimされた値が入ったインスタンスを使うことができます(参考: [リクエストからアクション実行までのフロー|http://cubby.seasar.org/action.html#リクエストからアクション実行までのフロー]、Action#preactionなんてのがあるとそこが適切かも?Interceptorだとちょっと書きにくいし)。

この例では、入力値の前後の半角・全角スペースをすべて取り除いても有効な文字があるかどうかをチェックし、そして、RegexTrimValidationRuleFilterで実際に不要な文字を取り除きます。

@Path("register")
public class RegisterAction extends Action {

    // -------------------------------------------------- [Validation]

    private final RegexTrimRequiredValidator trimRequiredValidator =
        new RegexTrimRequiredValidator("[\\s ]*");

    @Binding(bindingType = BindingType.NONE)
    public ValidationRules registerValidation = new DefaultValidationRules() {
        @Override
        public void initialize() {
            add("token", new TokenValidator());
            add("lastName", trimRequiredValidator);
            add("firstName", trimRequiredValidator);
            add("lastNameEnglish", trimRequiredValidator);
            add("firstNameEnglish", trimRequiredValidator);
            add("mailAddress1", trimRequiredValidator, new EmailValidator());
            add("mailAddress2", trimRequiredValidator, new EmailValidator());
            add("inside", new RequiredValidator());
            // add("uid", validator);
            add("laboratory", trimRequiredValidator);
            add("promotion", trimRequiredValidator);
            add(...他のValidationRule...);
            add(new RegexTrimValidationRuleFilter());
        }
    };

    // -------------------------------------------------- [DI Filed]

    @Resource
    private UserService userService;

    // -------------------------------------------------- [Attribute]

    protected RegisterFormDto registerFormDto;

    // -------------------------------------------------- [Action Method]

    public ActionResult index() {
        return new Forward("/register/index.html");
    }

    @Accept(POST)
    @Form("registerFormDto")
    @Validation(rules = "registerValidation", errorPage = "/register/index.html")
    public ActionResult confirm() {
        return new Forward("/register/confirm.html");
    }

    @Path("process")
    @OnSubmit("apply")
    @Accept(POST)
    @Form("registerFormDto")
    @Validation(rules = "registerValidation", errorPage = "/register/confirm.html")
    public ActionResult processApply() {
        registerFormDto.mailAddress = registerFormDto.mailAddress1;
        User user =
            Beans.createAndCopy(User.class, registerFormDto).execute();
        userService.insertAndSendMail(user);
        return new Forward("/register/success.html");
    }

    ... 他のアクションメソッド省略 ...

    // -------------------------------------------------- [Helper Method]

    // -------------------------------------------------- [Validation Class]

    private class RegexTrimValidationRuleFilter implements ValidationRule {
        public void apply(Map<String, Object[]> params, Object form,
                ActionErrors errors) {
            // BeanDesc と PropertyDesc を使って汎用的にしても良いですね
            registerFormDto.lastName =
                trimRequiredValidator.trim(registerFormDto.lastName);
            registerFormDto.firstName =
                trimRequiredValidator.trim(registerFormDto.firstName);
            registerFormDto.lastNameEnglish =
                trimRequiredValidator.trim(registerFormDto.lastNameEnglish);
            registerFormDto.firstNameEnglish =
                trimRequiredValidator.trim(registerFormDto.firstNameEnglish);
            registerFormDto.mailAddress1 =
                trimRequiredValidator.trim(registerFormDto.mailAddress1);
            registerFormDto.mailAddress2 =
                trimRequiredValidator.trim(registerFormDto.mailAddress2);
            registerFormDto.laboratory =
                trimRequiredValidator.trim(registerFormDto.laboratory);
            registerFormDto.promotion =
                trimRequiredValidator.trim(registerFormDto.promotion);
        }
    }

    ...
}
  • RegexTrimRequiredValidatorTest

テストケースも書いておきます(半角・全角スペースの違いが判り難いかも)。

public class RegexTrimRequiredValidatorTest {
    @Test
    public void test1() {
        RegexTrimRequiredValidator validator;
        validator = new RegexTrimRequiredValidator();
        assertNull(validator.trim(null));
        assertEquals("", validator.trim(""));
        assertEquals(" ", validator.trim(" "));
        assertEquals("abc", validator.trim("abc"));

        validator = new RegexTrimRequiredValidator("[\\s ]*");
        assertNull(validator.trim(null));
        assertEquals("", validator.trim(""));
        assertEquals("", validator.trim(" "));
        assertEquals("", validator.trim(" "));
        assertEquals("a b", validator.trim(" a b "));
        assertEquals("a b", validator.trim("   a b   "));
        assertEquals("abc", validator.trim("abc"));

        validator = new RegexTrimRequiredValidator("^[\\s ]*$");
        assertNull(validator.trim(null));
        assertEquals("", validator.trim(""));
        assertEquals("", validator.trim(" "));
        assertEquals("", validator.trim(" "));
        assertEquals("a b", validator.trim(" a b "));
        assertEquals("a b", validator.trim("   a b   "));
        assertEquals("abc", validator.trim("abc"));

        validator = new RegexTrimRequiredValidator("^[\\s ]*$", "^[\\s]*$");
        assertNull(validator.trim(null));
        assertEquals("", validator.trim(""));
        assertEquals("", validator.trim(" "));
        assertEquals("", validator.trim(" "));
        assertEquals("a b", validator.trim(" a b "));
        assertEquals("a b   ", validator.trim("   a b   "));
        assertEquals("abc", validator.trim("abc"));
    }
}

書いてみてなかなか便利だったので3月に書いたアプリでは大活躍でした(^^)。

[2009-04-17 14:09追記]: 日記用にActionクラスを適当に書き得てたとこがおかしかったので修正。

Seasar Conference 2009 White

先週の14日(土)に Seasar Conference 2009 White が開催されました。関係者の皆様お疲れ様でした。回を重ねるごとに当日準備にもすっかり慣れてだいぶ余裕になりぶらぶらセッション見て回っていましたが、今回もたくさんの参加者があり盛り上がっていました。

また、最近触っているぶりについてid:makotanさん?id:imai78さん?にいろいろ質問したりも出来て良いタイミングのイベントでした。さらに懇親会でid:j5ik2oさんが取り組んでおられるぶりからS2Dao外してS2JDBCだけにするコードも少し見せていただき、既にだいぶコード書かれていて正式に動くようになるのが楽しみです!少し見せていただいたコードで、BuriPathDataビューもEntityに定義して使用されている箇所があり、なるなどなーっと思い、今日早速今書いているコードでも真似て現在の状態を取得するのに使ってみました。

懇親会は最近タイミング合わず出ていなかったのですが、久し振りに参加してid:tksmdさん?とインフラまわりの話がたくさん出来て楽しかったです。既に書かれていますが、自宅鯖5台はやりすぎです(^^;;。僕はまだ物理的には2台なのできっとセーフです(^^)。HPのIPMIカード(Lights-Out 100)のRemote KVMについての話題が出ましたが、下記のページのスクリーンショットに映っているのがリモートから繋いでる画面です。

何気に Java Applet 製だったりします。IPMIカード内蔵のWEBサーバが生成するHTMLに書かれた<APPLET CODE>の<PARAM>設定で接続しようとするとIPMIカードに設定してあるローカルIPアドレス宛に接続しにいってしまうので、外部ネットワークから接続する時は、別途用意した静的HTMLファイルにSSHでポートフォワードしたポート番号を使うか、ルータのWAN側グローバルIPとNAPT用ポート番号に接続するようにして繋いでいます。仮想ドライブ機能で接続元PCにあるISOファイルから遠隔ブートしたりもできるので、これでオプション +24,150円 (今日時点だとこの値段でした) は安いです。

また、話変わって、Seasarのサーバ群も昨年末からちょっとずつ作り直しているので、メインのサーバを移行終えたらまたどこかで紹介したいと思います。

仮型引数を持つ関数を型引数を持つ関数でOverrideしたクラスからその関数をリフレクションで取得するとそれぞれ別の関数として見つかる

Buri + S2JDBC対応 ExampleのS2JDBCToDataAccessRule#insertでinsert関数が複数見つかる場合があったので試しにコード書いてみたらそうでした。Genericsはコンパイル時に型が解釈されるから当然なんだろうけど、普段コンパイルエラーで生成できないバイトコードが生成されるんですね。

以下、コードと実行結果。

  • S2AbstractService
// S2-TigerのS2AbstractServiceから抜粋
public abstract class S2AbstractService<T> {
    ... 省略 ...
    public int insert(T entity) {
        return jdbcManager.insert(entity).execute();
    }
    ... 省略 ...
}
  • AbstractService
public abstract class AbstractService<ENTITY> extends S2AbstractService<ENTITY> {
    public int insert(ENTITY entity) {
        return super.insert(entity);
    }
}
  • HogeService
public class HogeService extends AbstractService<Hoge> {
    @Override
    public int insert(Hoge hoge) {
        hoge.registTime = new Timestamp(new Date().getTime());
        return super.insert(hoge);
    }
    // insert(Hoge hoge)関数とObject型が重複するので自分で書くとコンパイルエラー
    // public int insert(Object object) {
    //     ...
    // }
}
  • 出力するためのコード、@Testついてるけど何もテストはしていない
@RunWith(Seasar2.class)
@RootDicon("app.dicon")
public class HogeServiceTest {
    @Test
    public void testFindInsertMethod() {
        Method methods[] = HogeService.class.getMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("insert")) {
                System.out.println("# method: " + method.toGenericString());
            }
        }
        // Overrideされた関数があるかどうかを探します
        try {
            Method method =
                ClassUtil.getMethod(
                    HogeService.class,
                    "insert",
                    new Class[] { Hoge.class });
            if (method != null) {
                System.out.println("## found: " + method.toGenericString());
            }
        } catch (NoSuchMethodRuntimeException e) {
            System.out.println("## not found");
        }
    }
}
  • 出力結果
# method: public int org.example.service.HogeService.insert(org.example.entity.Hoge)
# method: public int org.example.service.HogeService.insert(java.lang.Object)
## found: public int org.example.service.HogeService.insert(org.example.entity.Hoge)

Buri + S2JDBC対応 Example

id:imai78さん某のブログのぶり入門記マトメ, id:j5ik2oさんのBuriをS2JDBC対応にしてみる その3, id:jfluteさんのDBFlute: Buri対応のプロトタイプ公開, [id:makotanさんのburi]新たに拡張ポイント追加 に触発されて作ってみました(ほとんどid:j5ik2oさんのS2JDBCToDataAccessRuleのおかげです)。

  • Buri + S2JDBC対応 Example の buri-example4 (Eclipseプロジェクトをexportしたもの)
    • [2009-03-13 11:17追記] HogeServiceにOverrideしたinsert関数があるとinsert用OGNL式が重複するのを修正 (S2JDBCToDataAccessRule#insertSetup)

Buriの内部でS2Daoが使用されているので依存ライブラリとしての2Daoは残っていますが、とりあえず利用者からはS2Dao意識しなくてOKです。DocumentProcessorTestで実行してみた感じ、たぶんちゃんと動いてそうです。詳細は家に帰った後にやる気があればかまた明日あたり書きました。

注意として、buri-core 1.0.1-SNAPSHOTはSVNから最新のコードをチェックアウトして自分のローカル環境に mvn install してください(最新のburi-core 1.0.1-SNAPSHOTをmvn deployして欲しいかも)。

[2009-03-12 00:16追記] ファイルの説明を追加

ファイルの説明。S2JDBC-Genで自動生成させるとBuri関係のEntity、Names、Serviceも生成されますが、判りやすいようにExampleでは削除してあります。

  • src/main/java
    • entity.Document
      • S2JDBC-Genで生成したEntityクラス、publicフィールドしかありません、デフォルトのままで改変なし
    • entity.names.DocumentNames
      • S2JDBC-Genで生成したNamesクラス、このExampleでは出番ないですが実際にアプリを書くときは大活躍します、デフォルトのままで改変なし
    • service.AbstractService
      • S2JDBC-Genで生成したものにS2JDBCToDataAccessRule用に public ENTITY select(Long id) を追加してあります
    • service.DocumentService
      • S2JDBC-Genで生成したServiceクラス、デフォルトのままで改変なし、実際にアプリを書くときはここに主にDBへの様々な方法でアクセスするためのコードを追加していきます
    • org.escafe.buri.compiler.util.impl.rules.DataAccessCheckRule
      • protected void checkKeyName(BuriDataFieldType src) でS2JDBC用に javax.persistence.Id アノテーションでプライマリキーを探すようにコードを追加してあります、buri-share.dicon をいじりたくなかったので同名クラスでごまかしています・・・要課題?
    • org.escafe.buri.compiler.util.impl.rules.S2JDBCToDataAccessRule
  • src/main/resources
    • buri/dicon/buri-user.dicon
      • id:makotanさんの新たに拡張ポイント追加で追加された userDataFieldRuleSet で S2JDBCToDataAccessRule を設定、ただ、diconファイルのinclude順の問題で、<include path="buri/dicon/event.dicon" /> と ClassDefUtilImpl のコンポーネント定義を追加
    • buri/dicon/selectByPath.sql
      • BuriPathDataを参照するS2JDBC用の2Way-SQLファイル、使い方は DocumentProcessorTest に
    • 他のdiconファイル
      • 普通のSMART deploy構成、ぶりと関係ないdiconファイルも入っていますが気にしないでください
  • src/test/resources
    • DocumentProcessorTest
      • 動作テスト用のテストクラス、id:imai78さんのぶり入門記ベースです

これで今やっている期限が来週の一人プロジェクトで楽が出来そうです。

なお、BuriAutoSelectProcessorでの簡単な動作確認しかしてないので、それ以外の複雑なことをしたらどのようになるかは試してみないと判らないです(^^;;。

寝込んで復活したらBuri周りが素敵なことになっていた

id:j5ik2oさんがBuriをS2JDBC対応に!素晴らしい!!

ParticipantのIDが反映されるように!素晴らしい!!

Buriを使ってみようか迷ってたところでしたが、使うしかないですね。

Seasar Conference 2009 Whiteのお知らせ

3月14日(土)に Seasar Conference 2009 White が開催されます。今回もたくさんのセッションがあります。奮ってご参加くださいー。

開催日: 2009年 3月 14日(土) 12:30 - 17:45 (12:00開場)
会場: 法政大学市ケ谷キャンパス 外濠校舎3F・4F
主催: 特定非営利活動法人Seasarファウンデーション
後援: 法政大学情報科学部
参加費用: 無料

積もったタスク消化中: Hudson Plugin 完了

昨日作ったPublisherで、ジョブの設定ファイル(config.xml)に値を保存し、その値をcronで回して処理するスクリプトが完成しました。Seasarコミッタの方で、マルチジョブ実行時に複数軸用のテストデータベース環境が必要な方は、下記のページを参考に設定してご利用ください。

作ったHudson Plug-inは次のとおりです。本当はPlug-inから直接DB環境作った方が良いんでしょうが、Tomcatの動作権限の問題でこのPlug-inはほぼ何もしません。。なので見ても参考にならないでしょうが下記にアップしてあります。

<hudson.plugins.testdb.TestDBPublisher>
  <amountOfAdditionalTestDB>3</amountOfAdditionalTestDB>
</hudson.plugins.testdb.TestDBPublisher>

検索結果からHTMLメールのゴミを除外

Seasarの全文検索で検索するとHTMLメールのHTML部分が添付ファイルとして生成されたもの(Mailmanによって生成されたattachment-XXXX.html)が大量に引っ掛かって、大変残念な感じになっていたので、これらが検索結果に出ないように直しました。

HTMLメールであっても重要なメール本文は別のページに出力されていて、そっちが検索にヒットするのでこの添付ファイルを除外しても問題ありません。

また、title (begin), title (include)でも検索できるように設定を変えました。

ホーム > Seasar

検索
フィード
メタ情報

Return to page top