tomcat6.0.18 のEL不具合

ELの独自FunctionsがTomcat6.0.18から動作しなくなってしまった。

public static String myfunc(Number number) {
    ...
}

こんな感じでFunctionsメソッドを定義していて、ここにBigDecimalを渡したところ、次ぎようなエラーになってしまった。

Cannot convert 1,200 of type class java.math.BigDecimal to class java.lang.Number

何だろうなあと思ったら、tomcat6.0.18でのバグフィックス版リリース時に仕込まれた不具合の様子。

ELSupport.coerceToType modifies BigDecimal Values

6.0.19では修正されるとのこと。

ただ、待っていられないので、Numberで受け取る代わりにObjectで受けるようにして、メソッド内でNumberへのキャストを試みるように修正。

今さらながら、tomcat6.0.18では地味にJasper周辺の変更が施されているんだけど、実は無視できない変更が多いことに気付く。

 ${bigDecimal != 0}

というEL式が正しく評価されなくなってしまったのも、上記の件が関係しているだろうか。まいっちゃうなあ...。

Enum Factory Pattern

Enum Factory Pattern

列挙型を使ったFactory Method パターン。列挙型はとても使い勝手があるんだけど、これはその好例じゃないでしょうか。実は自分でも似たようなことをやろうと思って検索したら、すでにパターンとして名前が付いていた。みんな考えることは同じかあ。Strategy パターンとの組み合わせで、かなり使えると思います。

使いまわせるようなユーティリティ型のインスタンス生成なら、列挙する際にやっちゃっても良いかもしれない。

public enum Distribution {
    NORMAL("Normal", new NormalGenerator());
    private String name;
    private DistributedGenerator generator;
    private Distribution(String name, DistributedGenerator generator) {
        this.name = name;
        this.generator = generator;
    }
    public DistributedGenerator getGenerator() {
        return this.generator;
    }

ここでは、列挙ごと(NORMAL)に生成される generator がシングルトン扱いになるので、逆にそういったニーズに対しては有効なアプローチになるのでは。一般的な Strategy パターンの実装なら、例のページに掲載されているやり方の方が間違いがなくて良いと思うけど。

Excel 形式の帳票ツール

Java で Excel 形式の帳票を作成するのに、いままでは Jakarta POI の HSSF でゴリゴリとコーディングしていたのだけど、レイアウトに対して決して直感的なソースじゃないし、フォーマット変更に対する保守性も低いので、何か別の方法はないかと思っていた。

とりあえず有名どころと思って、iReportJasperReport を触ってみたんだけど、ダメ。

iReport は非常に優れた帳票デザイナーで、これがフリーだということに驚愕すること間違いなしなんだけど、いかんせん、出てくる Excel が使えない。見た目は確かにレイアウトどおりなんだろうけど、行と列がグチャグチャ。出力後に人間が加工するような感じではない。PDF 形式で出力するのであれば、かなり優秀なので、選択肢として検討する余地はあるけど。

やっぱり、HSSF でゴリゴリ行くしかないのかな。レイアウトした xls を読み込んで、データを書き出して出力するようにすれば、iReport のように直感的でないけど、そこそこレイアウト変更にも柔軟に対応できそうな気もする。(xlsとHSSFを結びつけるための、レイアウト情報をどう管理するかっていう問題は考えないといけないけど)

BIRT も Excel 出力ができるらしい。こちらは、どうなんだろう。今度さわってみよう。

validwhen では小数の大小比較が行えない

struts の validwhen では、小数付きの数値比較が正しく行われない。

ソースを追ってみたところ、ValidWhenParser クラスで比較処理をしていたのだけど、これが int 型で比較できる場合は数値として比較、それ以外はすべて String で比較するという処理になっていた。えー、整数だけですか。全然かゆいところに手が届いていない!

これのタチ悪いところは、小数値であっても比較処理自体は行われる点。そう、文字列として比較して、その結果たまに正しい結果を返してしまう。厄介だ。

例えば、start と end という数値入力するフィールドがあって、その大小関係が逆転しないように入力チェックをするとき、

((start == null) or (*this* <= end))

と validwhen 式を書いて、

private String start = "9.5";
private String end = "11";

とすると、文字列での比較となり、1より9の方が後になるってんで、大小関係が正しいにも関わらずエラーとして検証に引っかかってしまう。

private String start = "2.5";
private String end = "5";

という場合は、小数があっても正しい検証結果を返すので、問題なし。こんな感じで一見すると正しく動いているようにみえるので、むーって感じ。ハマリました。

そんなわけで、validwhen は整数の比較と、文字列の比較のときのみ使用するようにしなくちゃいけないです。いや、もう文字列に限定して使用するものだと捉えた方が良いかもしれない。そして数値比較用には、自前で検証メソッドを作成してしまった方が、間違いがなくて良さそう。(または ValidWhenParser の処理そのものを小数対応に直しちゃうのもアリ)

BigDecimal で比較、いやせめて Double で比較してくれれば良かったのに...。

Tiles Hack のステップアップ

Tiles Hack だなんて大げさだ。

Struts の Tiles プラグインの挙動を変更したくなった。動的に definition を追加したい。どこから手を付けて良いものか悩んでいたのだけど、DefinitionsFatory を自作のものに変更できることが判明したので、これで対応した。

struts-config.xml で Tiles プラグインを設定している箇所で、definitions-factory-class プロパティに、自作の Factory クラスを指定してあげるだけ。

  <plug-in className="org.apache.struts.tiles.TilesPlugin">
    <set-property property="definitions-factory-class" value="com.deftrash.struts.OriginalI18nFactorySet" />
    <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml"/>
    <set-property property="moduleAware" value="true"/>
  </plug-in>

definitions-factory-class を指定しない場合は、デフォルトで、org.apache.struts.tiles.xmlDefinition.I18nFactorySet が使用される。tiles-defs.xml を読み込んで、ComponentDefinition を生成するクラスだ。

動的に definition を追加するために、自作 Factory を、この I18nFactorySet を継承して作成。createDefaultFactory メソッドをオーバーライドして改造することにした。

    /* (非 Javadoc)
     * @see org.apache.struts.tiles.xmlDefinition.I18nFactorySet#createDefaultFactory(javax.servlet.ServletContext)
     */
    protected DefinitionsFactory createDefaultFactory(ServletContext servletContext)
            throws DefinitionsFactoryException, FileNotFoundException {

DefinitionsFactory factory = super.createDefaultFactory(servletContext);
// TODO 動的にComponentDefinitionを追加する処理をここに
return factory;

たったこれだけで、望むべき動作が得られました。動的に definition を追加する以外のニーズがあっても、ここを起点にいくらでも改造できそうな感じ。まあ、I18nFactorySet クラスで xml を読み出すところが private になっているのが、扱いづらいけど。

すでに struts は枯れて細かいところまで見ずにいたけど、実際に手を入れていこうとすると、改造しやすいように窓口が開いていて、風の通しがすごい良いことに気付く。こういう懐の深いデザインは、見習っていきたいですね。

Strutsで独自のModuleConfigFactoryを利用する方法

struts-config.xml で定義した内容を動的に変更したくなって、ActionConfig を改造する方法を考えていたんだけど、大元の ModuleConfigFactory を切り替えて、独自の ModuleConfig を生成する Factory クラスを用意すれば良いことが分かった。なるほどー、よくできてるなー。

web.xml で configFactory パラメータに、独自の ModuleConfigFactory のクラスを指定してあげればOK。

  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>configFactory</param-name>
      <param-value>com.deftrash.OriginalModuleConfigFactory</param-value>      
    </init-param>
    <init-param>
      <param-name>debug</param-name>
      <param-value>2</param-value>
    </init-param>
    <init-param>
      <param-name>detail</param-name>
      <param-value>2</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

いろいろ調べてようやく分かったんだけど、よくよく考えてみると SAStruts の仕組みを見れば一発で分かったんだよなあ。ううう。

そして、もともとはフォワード先のパスを変えたいだけだったので、それなら Filter で ActionConfig を見てごにょごにょやった方がシンプルだったかも。この方法は、少し大げさな感じもする。こういう手段があるのが分かっただけで、前進ですが。

ActionServlet クラスのソースを見ると、他にも細かな制御ができるようになっているのが分かる。このあたりを押さえておくと、プロジェクトに応じて Struts をより柔軟にコントロールできる。知らないって、スゴイ損ですねえ。ふぅ。

FreeBSDのJVMでエラーが起きる

FreeBSD のサーバに、Java5 と Tomcat5.5 を乗っけて起動したら、OOME で落ちてしまった。ログを見ると、かつて見たことのない内容が。

Exception in thread "CompilerThread0" java.lang.OutOfMemoryError: 
requested 1168080 bytes for Chunk::new. Out of swap space?

スワップ領域って何のことだろうか。JVMのなかの話だと思うのだけど。

メモリ関連ということで、-Xmx や -Xss などのパラメータをいじるものの状況は改善せず。んで、ひとつひとつ除いていったら分かりました。

CATALINA_OPTS="-server"

というように、サーバクラスを指定していたのだけど、これが原因だった。FreeBSD全部がそうなのか分からないけど、今回のケースではクライアントVMでしか起動できないという罠。うー、しびれるなあ。

サーバクラスマシンの検出にあるとおり、Java5からはサーバ環境に応じて自動でVMを切り替えてくれるということで、下手に指定なんてしなくても良かったんですね。(まあ、FreeBSDのJVMでも互換性があるのかは分かりませんが...)

Linux の方が上だとか言うつもりは毛頭ないけど、個人的には FreeBSD より Linux の方が良いなあ。習慣もあるんだろうけども、エンタープライズ用途だと何かと情報が得やすい Linux や Solaris でないと、ちょっとひるんでしまいますねえ。

ロープ・データ構造

ロープ: 理論と実践

String や StringBuilder に代わる効率的なデータ構造「ロープ」。java で大量文字列を処理する場合の、新しい解だ。ストリングに対してロープとな。なるほど。

意味が理解できたところで、さて何に使ったら良いもんか。「顕著なパフォーマンスの改善が期待できるのは、大規模なストリングを大々的に変更するような場合のみです」と言われても、それこそ例として挙げられているテキスト・エディタのようなものしか思い浮かばない。そもそも、単一のストリングがそこまで大規模になったケースを知らない。うーん。

ま、アイデアとしてストックしておけば、他でも使えるかな。

ちなみに、「O(n)」とか「O(log n)」とかをビッグオー記法と呼ぶのを、この記事で初めて知りました。これ、意味は分かるんだけど、どのくらいのオーダーの差があるのか直感的に分かりづらい...。早く自然数と対数がサラッと比較できるようなビジネスマンになりたいです。

tomcat6 のログ出力を Log4j で行う方法

これで、かなり快適なロギング環境を構築することができました。すごい便利。

事の始まりは、tomcat6 のログローテションを自前で行おうとしたことだった。tomcat6 では、デフォルトで日付付きのログファイルを出力するのだけど、それだと自前のローテーション処理とぶつかってしまう。これはどげんかせんといかん、と言うわけで、公式マニュアルを見たら、Log4J を使う方法が書いてあったので、「これだ!」と。

しかし、Log4J の効果はローテーションに留まらず。ログ出力内容の細かなコントロールも可能になったことで、運用上必要なログのみを出力するようにできたり、かなり嬉しいことに。やってて良かった公文式。(違う)

以下、Log4J 導入までの流れ。(ほとんどマニュアルと同じですが)

続きを読む "tomcat6 のログ出力を Log4j で行う方法"

JUnit4 を試してみる

今度のお仕事でようやく Java を 1.4 から 5.0 に進化させることができたので、合わせて Junit も 3.8 から 4.3 にシフト。Java5 を触り始めて、もはやアノテーション無しではコードを書けない身体になってしまったので、ここは外せない。

アノテーション 説明 JUnit3.8では
@Test テストメソッドであることを示す testXxx()
@Before 各テストメソッドの実行前に呼ばれるメソッドであることを示す setup()
@After 各テストメソッドの実行後に呼ばれるメソッドであることを示す tearDown()
@BeforeClass このテストクラスを呼び出す前に1回だけ実行するメソッドを示す
@AfterClass このテストクラスを呼び出した後に1回だけ実行するメソッドを示す
@Ignore 無視するテストメソッドであることを示す

JUnit3.8 では、テスタークラスを作るために、必死で TestCase を継承して、チマチマとコードを埋めていくということをやっていたけれども、JUnit4.3 ではアノテーションのおかげもあって、かなりラクチンに書けますね。単純なテストケースなら、public なメソッドに @Test アノテーションを付与してあげるだけで、OK。

assert の処理など細かいところは、これから見ていきまっす。でも、static import すれば、3.8 と変わらないように使えそうなので、ムダな学習コストもかからなそう。ふむふむ。

3.8 で言うところのテストスイートを書くのであれば、@SuiteClasses アノテーションを使って以下のような感じで書けばOKっぽい。

@SuiteClasses ( { Sample1Test.class, Sample2Test.class } );
public class AllSampleTests {

}

まあ、でも Eclipse3.2 以降であれば、JUnit4 に対応しているようなので、テストスイートを用意せんでも、テスト用パッケージから実行してしまえば全部できるので、テストケースに集中すればOK。というか、テストスイートの対象テストクラスを @SyuteClasses に全て羅列しなくちゃいけないのは面倒なので、テストのまとめ実行は IDE からやるのがオススメでしょうか。

お疲れさまでした。

厳密な日付の判定

入力された日付情報を String 型で受け取って、Date 型なり Timestamp 型に変換するというのは、よくある話。そして、入力された日付の妥当性を検証するというのも、よくある話。

そんなときは、 Calendar クラスや DateFormat クラスの setLenient メソッドを使えば、らくちんOK。

例えば年月日のフィールドが分かれているケースでは、Calendar クラスを使って次のように妥当性検証ができる。

String year = 2008;
String month = 2;
String day = 30;

try { Calendar c = Calendar.getInstance(); c.setLenient(false); c.set(Integer.parseInt(year), Integer.parseInt(month) - 1, Integer.parseInt(day)); c.getTime(); } catch (IllegalArgumentException e) { // 不正な日付の場合の処理 }

年月日を1フィールドに入力させるケースでは、DateFormat がらくちん。

String date = 2008/2/30

try { DateFormat formatter = new SimpleDateFormat("yyyy/M/d"); formatter.setLenient(false); formatter.parse(date); } catch (ParseException e) { // 不正な日付の場合の処理 }

setLenient(false) にしないと、例えば 2008/2/30 という日付でも例外をスローせず、勝手に 2008/3/1 と解釈してそのまま次の処理を行ってしまう。余計なお世話な気もする。

この話は Java の FAQ のような気がするんだけど、2日連続で方々から質問されたので、メモっときます。

jconsole が接続できない

リモートサーバで動いているWebアプリケーションを、jconsole で見てやろうと思ったが、どうしてか接続できない。

開発サーバということで、認証もSSLもなしという最小構成。

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8099
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

これで接続できないとな。認証不要に設定しても、やっぱりユーザ名を指定したりするんだっけか、とか色々と試すもやっぱりムリ。困った。

結論から言うと、サーバの hosts の設定に問題があったみたい。hostname -i で 127.0.0.1 になるとダメらしい。FAQだった。

FAQ - JConsole とリモート管理

ということで、 /etc/hosts を編集してOK。

127.0.0.1	localhost.localdomain
192.168.xxx.xxx	app.localdomain

ちゃんと hostname -i で 192.168.xxx.xxx が返ってきて接続できるようになりますた。開発環境ならでは、という感じはするけど、一応メモ。

ジェネリクスのインスタンスを生成する方法

java でジェネリクス(総称型)のインスタンスを生成するには、どうするのか悩んでいる土曜日の昼下がり。

例えば、次のようにメソッドの総称型を定義してみる。

public  T add(T date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.DATE, 1);
    return (T) calendar.getTime();
}

これだと、引数に java.util.Date を渡した場合は問題ないが、そのサブクラスである java.sql.Date なんかを渡すと、最後の総称型へのキャストで、ClassCastException でコケてしまう。そりゃそうだ。

同じ型のインスタンスを生成できれば、そこに値を詰めて返せるんだけど、総称型はその名のとおりクラス名を特定できないから、さて困った。

T result = new T();

みたいなことができれば良いんだけど、当然できるわけもなく。それじゃあ、と思って次に試したのがコレ。

public  T add(T date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.DATE, 1);

T result = date.clone(); result.setTime(calendar.getTimeInMillis());
return result; }

型が分からないなら、もうクローンしちゃえば良いんじゃね、と。これは確かに動くのだけど、いやいやインスタンスを生成するのと、クローンするのでは意味が違う。

public  T add(T date) throws Exception {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.DATE, 1);

T result = date.getClass().newInstance(); result.setTime(calendar.getTimeInMillis());
return result; }

ということで、今のところ、これが一番しっくり来てる。

このケースだと java.util.Date のサブクラス限定だから、このくらいで良いけど、もっと汎用化するなら、newInstance() じゃなくて Constructor とか使って生成していかないといけないんだろうなあ。でも、引数はどこから持ってくるんだ? なんてことを考えると、「やっぱクローンでも良いんじゃね?」とか思える。

どっかに良いお作法が転がってないかなあ…。

引数final

Java の言語仕様で、メソッドの引数にも final 修飾子を与えることができる。

public void execute(final List list) {
    //
}

これ、今までずーっと意味を勘違いしてました。「引数 object への変更は認めない」ではなくて、「引数 list への代入は認めない」という意味のようだ。なので、次のリストのようなことになる。

public void execute(final Object object) {
    list.add("aaa"); // これはOK
    list = new ArrayList(); // これはコンパイルエラー
}

つまり、引数 final は、その引数インスタンスそのものに対する意味であって、内部データを保護する意味はないのね。完全に期待していたのと違った意味付けになっていて、ホントに狼狽した。せっかくの修飾子なんだけど、ここに final を付けることの方が、余計に意味が分かりにくくなっている気がする。

ただ、唯一意味があるのは、メソッド内部で匿名クラスを使用するような場合。

public void add(Button button, final Label label) {
    button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
            label.setText("hoge");
        }
    });
}

このようなケースでは、引数 Label に final 修飾子が付いていないと、逆にコンパイルエラーになってしまう。(メソッド内部で final 宣言した変数に入れ替えても動くので、引数 final が必須ではないけれども)

自分が言語仕様をちゃんと把握してなくてダメだって話なんだけど、またいつか別のプロジェクトではまりそうな気がするので、コーディング規約として禁止したいなあ。アノテーションもそうだけど、余計な意味付けがなされていると、深読みしちゃいますよネ。ね?

列挙型は継承ができない

列挙型をいくつも作成して、そこに似たようなメソッドを定義していると、「列挙型も継承できたらいいのに」と思ってしまう。が、残念ながら、列挙型は継承ができない。それも当然で、abstract な列挙型なんてありえない。仕方ないなあ。

ただ、インタフェースの実装はできるので、これを活用するのが正解。

public interface Foo {
    public int getNumber();
}

というインタフェースを定義しておいて、

public enum FooEnum implements Foo {

ONE (1), TWO (2), THREE (3);
private int number;
private FooEnum (int arg0) { this.number = arg0; }
/* (non-Javadoc) * @see Foo#getNumber() */ @Override public int getNumber() { return number; } }

という感じで、列挙型を定義してあげる。

public class Sample {
    public static void main(String[] args) {
        for (Foo foo : FooEnum.values()) {
            System.out.println(foo.getNumber());
        }
    }
}

実際にこんな感じでインタフェースプログラミングができる。

Foo インタフェースを実装した列挙型を量産する際に、いちいちコンストラクタとフィールドを用意するのが、非常にかったるいわけだけど、その列挙型ではそこを意識しなくて良くなるから、それがせめてもの救い。

ちなみに、列挙型って1.xあたりでの java 育ちな人には、あんまり積極活用されていないような気がする。文化として根付かないのかな。スゴイ便利なんだけどなあ。