James君!〜クラスローダ問題

さてまあこれで読者の多くは「MatcherだろうがMailetだろうが何でも来い!」状態になったのではないか?とも思うのだが、まあ、実戦投入ともなると、「DB連携...それも Hibernate とか Spring 越しで!」とかなるのが今時だ。

で、そうなると少しばかり James の特殊体質について知っておかなくてはならなくなる。まあ考えてみれば変な開発プロセスである... 開発した Mailet&Matcher を jar ファイルに固めて(ここまではイイが)、SAR-INF ディレクトリ自体を james.sar に固め直して配備するわけである。要するに Phoenix の開発プロセスがそうだから...なんだが、本質的には James 自体の開発プロセスみたいなことをしているわけだ。

で James はサーバだ。セキュリティに関して神経質になるは当然なのだが、そういうわけで ClassLoader を自前で定義している。org.apache.avalon.phoenix.components.classloader.PolicyClassLoader という奴である。これが james.sar の中身を知っていて、正しくクラスをロードするんだが...セキュリティと絡んで、少しややこしい問題がある。

まあ、最近の便利ツールっていうと、XML で設定ファイルを書いて...というのがやたらと多い。フツーは、そういう設定ファイルのデフォルトを書いて、jar に固めるわけだが、たまたま設定を変えたくなったら、クラスパスの通ったディレクトリにそういうファイルを置いて、それを変更したらそっちが優先、というのに慣れているんではなかろうか。

で、実は Phoenix のクラスローダはこれが出来なくなっている。言い替えると、クラスパスは開発した Mailet,Matcher に対して全然効かないんである!

実際、いろいろな設定ファイル(やプロパティファイル)は、実際に Mailet & Matcher を固める jar ファイル(≠ james.sar)に一緒に固めるしか参照させる手段がない。これは結構頑固なやり方だな...というわけで、外部のファイルとして「設定ファイルをクラスパスに置いて...」というのはまったく出来ないのである。例えば CommandListservManager Mailet の場合、<resources> オプションでXML形式のプロパティファイルを読み込むが、オプションが「フルパス指定」であることから判るように、これは単にフルパス指定のファイルを開いているに過ぎない。

だから、それでも「実際の設定ファイルを指定して読み込ませる」なんてことのできるツールの場合には、トンチで解決することもできなくない。たとえば、James の起動スクリプトを、

#!/bin/sh

export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/java/jdk1.5.0_04/bin
export JAVA_HOME=/usr/java/jdk1.5.0_04

JAMES=/usr/local/james-2.2.0

DAEMON=${JAMES}/bin/phoenix.sh

# NOTICE !!!
# in current directory, must be log4j.properties!!!
cd ${JAMES}/apps/james
export CLASSPATH=.:${JAMES}/apps/james

su james -c "$DAEMON $*"
exit 0

こんな具合に書いてやって、起動ディレクトリを固定してやる。そして、ソース側では、

public class YemniMailet extends GenericMailet {
    protected Logger log;

    /* 追加の Lo4j 設定ファイルはカレントディレクトリのもの */
    private static final String LOG4J_PROPERTIES = "./log4j.properties";

    public void init() throws MessagingException {
        // カレントディレクトリから log4j.properites を読もうと試みる
	File log4j = new File( LOG4J_PROPERTIES );
	if( log4j.exists() && log4j.canRead() ) {
	    // あれば Log4j に設定をさせる
	    PropertyConfigurator.configure( LOG4J_PROPERTIES );
	}
	log = Logger.getLogger( this.getClass() );
    }

なんてことをすれば、カレントディレクトリにある log4j.properties を使うことができるようになる。が、まあ、こういう器用なことができないケースもあるだろうから、少し頭に入れて設計した方がよさそうだ。



copyright by K.Sugiura, 1996-2006