luceneで日本語のインデックスを作る

lucene

Java全文検索エンジンLuceneは残念なことにディフォールトでは日本語(のインデックス作成)に対応していない。これは日本語に対応したAnalizer/Tokenizer(文章を単語などの小さい単位に切り出すもの)が無いためで、調べて見るとすでに2つのAnalizer/Tokenizerが公開されていて、それぞれに短所・長所がある様子。


CJKAnalizer

CJKAnalizerはLuceneのサブプロジェクトで、Lucene Sandbox(Luceneに対する色々な拡張を集めたもの)に入っている。ファイル2つだけなので、CVSから直接取ってくるといい。http://cvs.apache.org/viewcvs.cgi/jakarta-lucene-sandbox/contributions/analyzers/src/java/org/apache/lucene/analysis/cjk/


このCJKAnalizerはCJK文字を見つけるとN-gram式でインデックスを作るために切り出し、それ以外(英語など)は単語ごとに切り出す。良くも悪くもN-gram方式なので、、、


長所としては、、、

  • 単語のヒット率が高い
  • シンプルで速い。


短所としては、、、

  • インデックスが大きくなる。
  • ファジーサーチ(Fuzzy Searches)やレンジサーチ(Range Searches)が使えない。


Sen

Sen(http://yamaguch.sytes.net/~tora/opensource/sen/
)はC++形態素解析エンジンmecabhttp://chasen.org/~taku/software/mecab/)をJavaに移植した物。これは形態素解析をしているので、、、


長所としては、、、

  • インデックスが小さい。
  • ファジーサーチ(Fuzzy Searches)やレンジサーチ(Range Searches)が使える。


短所としては、、、

  • 多少遅い。特に初期化。(注:Ver0.44でテスト時。1.0では速くなっているらしい)
  • 単語のヒット率がN-gram方式よりは低い(しかし通常十分実用にはなる)


どちらがいいか?

どちらが良いかは一概に言えない様子。(ポピュラーな全文検索エンジンNamazuでも、KakasiがいいかChasenがいいか意見が分かれている模様。http://www.namazu.org/FAQ.html#kakasi-or-chasen


僕のプロジェクトではとりあえず、検索結果が多少よかったので、CJKAnalizerを使っていますが、将来的にクラスター分析などができないかと考えているので、その時にはSenに移行する必要がありそうです。

jettyでのBASIC認証の設定。

jetty

サーブレット・コンテナ上でのBASIC認証等の設定は、どのサーブレットでも共通な部分と、jettyやtomcat等、特有な部分があり、その両方を設定しなければならない。


共通な部分

これは、web.xmlファイルに、を追加する。


  
    admin page 
    /admin/* 
  
  
    admin 
  
  
    NONE 
  


  BASIC 
  admin 


  admin 


Jettyに特有な設定

Jettyの設定ファイル「jetty.xml」に以下を追加して、realm.propertiesファイルを使うように宣言する。


  
    
      admin 
      /usr/local/jetty/etc/realm.properties 
      admin 
    
  


realm.propertiesファイルの内容は、、、

# This is a HashUserRealm defining users passwords and roles.
# The format is
# : [, ...]
#
# Passwords may be clear text, obfuscated or checksummed. The class
# org.mortbay.util.Password should be used to generate obfuscated
# passwords or password checksums

admin: AdminPasswd, admin

で、ユーザー名、パスワード、ロール名、を羅列する。


参考URL

  • 「web.xml 要素リファレンス」の「付録(コラム) 」

http://www.sk-jp.com/java/servlet/webxml.html#h111

http://www2s.biglobe.ne.jp/~yuuki_ki/opensource/js_tomcat9.htm

Servletでの設定ファイルの置き場所

Servletで使用するライブラリ等の設定ファイル「XXXX.properties」などの置き場所には、どうも大きく分けて2種類の場所があるようで、戸惑いがちです。


無理やり簡単にまとめると、1つは「/WEB-INF」でServlet関連の設定ファイルを置くことが多いようです、もう1つは「/WEB-INF/classes」で汎用ライブラリなどの設定ファイルを置く事が多いようです。


/WEB-INFに置く設定ファイル

  • web.xml
  • struts-config.xml
  • velocity.properties
  • などなど、、、


これらは、ウエブ・アプリケーション(ServletContext?)のルート・ディレクトリからパスを指定してファイルにアクセスしている場合で、ルートに置くとブラウザから見えちゃうので、/WEB-INFに置いているのでしょう。


ちなみにJavaプログラムからファイルの絶対パスを取るためには、例えば、、、

ServletContext.getRealPath("WEB-INF/web.xml");

などとやると、「/WEB-INF/web.xml」の絶対パスが取得できます。


/WEB-INF/classesに置く設定ファイル

  • commons-logging.properties
  • log4j.properties
  • hibernate.properties
  • などなど、、、


これらは、java.util.ResourceBundle等を使用してリソース中から置き場所を探している場合です。


リソースとは、CLASSPATHに入っている全てのコードやファイルを対象にした場所の指定方法で、設定ファイルをjarファイルの中に置くことまでもできます。Servletの場合には「/WEB-INF/classes」がCLASSPATHに入っているので、例えばJavaプログラムから、、、

ResourceBundle.getBundle("log4j");

とすると、「/WEB-INF/classes/log4j.properties」が取得できます。(注:ResourceBundle.getBundle()の場合、引数のファイル名からは「.properties」は外す必要がある。)


このリソースには各国語化したリソースを別ファイルにして、自動的に切り替えたりする機能もあります。

logのレベル設定方法

StrutsなりHibernateなりいろいろなライブラリがjakarta-commons-loggingに対応しています*1。commons-loggingは使用する下位のロギングAPIが何であっても共通的にログが取れます。


これはこれでいいのですが、いかんせん、commons-loggingはログレベルの設定機能をサポートしていません。ログの量・詳しさが、多すぎる、とか、少なすぎる、と言う時に、設定ファイルを書き換えてログのレベルを設定する方法は、ロギングAPIの方法によってまちまちで、結構わかりずらく、初めて使うときには混乱してしまいます。


commons-logは設定がないときには、使えるログAPIが何があるか自動的に探します*2。commons-loggingはLog4jと相性がいいようなので、ここではまずLog4jが使えるようになっている(CLASSPATH内にlog4j.jarがある)事を前提に設定して見ます。


Log4jの場合の設定

CLASSPATHには、、、

commons-logging-xxx.jar
log4j-xxx.jar

プロパティファイルの置き場所には、、、

log4j.properties
もしくは
commons-logging.properties

を用意します。


実際のログレベルの設定は、log4j.properties の中に、、、

log4j.rootCategory=XXXX, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Target = System.out
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d  %C : %m %n

と書きます。

この一行目のXXXXの部分がディフォールトのログレベルの設定で、commons-loggingの設定と同じく、DEBUG, INFO, WARN, ERROR, FATALの5つが使えます。


ログレベルを特定のパッケージにのみ設定したい場合には、、、

log4j.category.net.sf.hibernate=DEBUG

のように、頭に「log4j.category.」を付けてパッケージ名を指定すれば、それ以下のパッケージ(上の例では「net.sf.hibernate」)のログレベルだけを設定できます。


JDK1.4ロギングの場合の設定

JDK1.4に標準で含まれているロギングAPI(パッケージ java.util.logging)は、少々Log4jよりは使いづらそうです。(
http://www.ingrid.org/jajakarta/log4j/jakarta-log4j-1.1.3/docs-ja/critique.html )


CLASSPATHにはcommons-logging-xxx.jarだけがあればいいですが、設定ファイルはJavaシステム全体の物を使います。(注:他の方法を見つけられなかった。FIXME)。設定ファイルは、Javaシステムディレクトリ内の、jre/lib/logging.properties ファイルを使用します。


設定は以下の2点を変更します。

#.level= INFO
.level= ALL
                                                                                              • -
#java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.level = ALL

上の物は、標準のログレベルを設定します。下の方は、設定されているコンソール用のConsoleHandlerが、標準ではINFO以上のログをフィルターしているのを変更しています。


設定できるログレベルは、commons-loggingのものとは少々異なり、SEVERE(=OFF)、WARNING、INFO、CONFIG、FINE、FINER、FINEST(=ALL)の7つが設定できます。


ログレベルを特定のパッケージにのみ設定したい場合には、同じくjre/lib/logging.properties ファイルの一番最後に、「パッケージ名+.level」と言うプロパティを設定します、、

net.sf.hibernate.level = ALL

とすると、「net.sf.hibernate」以下のパッケージのログレベルを「ALL」に設定できます。


メモ: commons-logのログAPI検索方法

commons-logは以下の順序でログ先を検索するようです。

1: 設定ファイル

まず、設定ファイルにorg.apache.commons.logging.Logがあればそこに指定してある物を使います。

2: システム・プロパティ

次に、システム・プロパティにorg.apache.commons.logging.Logがあればそこに指定してある物を使います。

3: Log4j

そして、log4j.jarがCLASSPATHにあればLog4jを使います。(今回の説明はこれを使っています。)

4: JDK 1.4

次に、JDK 1.4以上の場合には標準のJDKログを使います。

5: SimpleLog

最後に見つからないときには内臓のSimpleLogを使います。

*1:本当は結構commons-loggingに対応していないライブラリも多かったりする。例えばVelocityはAvalonLogKitとlog4jにしか対応していない。JavaのログAPIはちょっと混乱している。どれか1つ選ぶならLog4jだろうか、、、

*2:上のメモ「commons-logのログAPI検索方法」参照

JettyにVirtualHostを設定する。

jetty

Jettyの設定ファイル「jetty.xml」はちょっとわかりづらい。


と言うのも、これ「(フォーマットで)決められたデータを書いてゆく」と言うよりも、「Javaのクラスやメソッド等を指定して、実行させる」と言う仕様になっていて、結局JavaDocを見ながら設定内容を追加する方法を考えて(プログラムして)、それを設定ファイルに書いてゆく事になる。


例えば、、、


でクラスを指定して、、、

  

でメソッドを指定して、、、

    /

で引数を渡すことを指定する。


VirtualHostsの設定

VirtualHostsの設定は一応、ディフォールトの「jetty.xml」にもサンプルが書いてある。

  
    /context
    ./webapps/root

    false

    org/mortbay/jetty/servlet/webdefault.xml

    
      
        
        127.0.0.1
        localhost
        www.acme.com
      
    
  

これは、、、


1)2つ引数のメソッド「addWebApplication()」を呼ぶ。
(JavaDoc - http://jetty.mortbay.org/javadoc/org/mortbay/jetty/Server.html#addWebApplication(java.lang.String,%20java.lang.String) )

2)extractWAR、defaultsDescriptorのプロパティを「Set」する。

3)virtualHostsプロパティに、、、

 の4つのString[]配列を「Set」する。

 と、VirtualHostを設定できるようだ。


alt

他にもいくつか方法があるようで、ここの説明では、、、
http://feeling.zive.net/javapages/jetty/usingjetty/editdonfig.html


上で使ったaddWebApplication()の3つパラメータ版の方を使って設定している。(VirtualHostが1つだけなら、この方が簡単)

日本語Form入力

まずは日本語Form入力。

servletで、GETやPOSTで送られてくるパラメータを取得するには、

request.getParameter("text");

と、やりますが、ディフォールとのままでは、日本語のパラメータは化けてしまいます。
なので、例えばWindows-31J*1のページから投稿された場合、リクエストを処理する一番最初に、、、

request.setCharacterEncoding("Windows-31J");

エンコーディングを設定してから取得すると、ちゃんと取得できます。(Servlet2.3-API以降)

Servlet2.2-API以前には、、、

value = request.getParameter("text");
value = new String(value.getBytes("iso-8859-1"), "Windows-31J");

のようにして、1つひとつ値を変換していたようです。


しかし、、、

調子に乗ってこんなコードを書いてみたら動きません。

String encoding = request.getParameter("encoding");
if (encoding == null)
    encoding = "Windows-31J";
request.setCharacterEncoding(encoding);
String searchText = (String) request.getParameter("SearchText");

ようするに"encoding"と言うパラメータを用意して、その内容にしたがってsetCharacterEncoding()しようとしたのですが、なぜか、ENCTYPE="multipart/form-data"でPOSTしたときにだけ、日本語が取得できて、あとは"multipart/x-www-form-data"の時やGETの時には文字化けしてしまいます。

なぜでしょう。

Servletのrequest.setCharacterEncoding()のJavaDocを見ると、、、

This method must be called prior to reading request parameters or reading input using getReader().

なので、ようするに、request.setCharacterEncoding()をする前にrequest.getParameter()を呼んでいると、無効になるのですね。(エラーにしてくれればいいのに、、、)

先にJavaDocをよく読んでおけばよかったです、、、

ちなみに、上のようなことをしたいときには、まず、、、

String encoding = request.getParameter("encoding");

を読んだ後に、Servlet2.2-APIの頃の方法と同様に、、、

String value = request.getParameter("PARAM");
value = new String(value.getBytes("iso-8859-1"), encoding);

として一つ一つ変換して行く必要があるようです。(これ用にStrutsのDynamiyActionForm等をサブクラスしてあげると良いかも。)

実例1

StrutsのActionServletをサブクラスする場合。

/**
 * 日本語FORM入力をハンドルするためのActionServletクラス。
 * 
 */
public class JapaneseActionServlet extends ActionServlet {

    /**
    * 日本語FORM入力をハンドルするためのActionServlet.process().
    * requestにsetCharacterEncoding()することで以降のgetParameter()からの日本語
    * パラメータを、文字化けさせずに変換する。ここではShift_JIS(Windows-31J)で
    * 書かれたページからのFORM投稿を想定している。
    * 
    */
    protected void process(javax.servlet.http.HttpServletRequest request,
	        javax.servlet.http.HttpServletResponse response)
            throws java.io.IOException, javax.servlet.ServletException {
        request.setCharacterEncoding("Windows-31J");
        super.process(request, response);
    }
}

これに対応するようにweb.xmlも変更する。

    
        action
        path.to.JapaneseActionServlet
         ...

実例2

Servlet2.3-APIからは、Servletの処理の前に処理される、Filter機能が追加された。このFilterでsetCharacterEncoding()する物を作れば、複数のServletがあるWebAppにも適応できる。

/**
 * 日本語FORM入力をハンドルするためのServletFilter用クラス。
 * 
 */
public class EncodingFilter implements Filter {

    /**
    * 日本語FORM入力をハンドルするためのFilter.doFilter().
    * requestにsetCharacterEncoding()することで以降のgetParameter()からの日本語
    * パラメータを、文字化けさせずに変換する。ここではShift_JIS(Windows-31J)で
    * 書かれたページからのFORM投稿を想定している。
    */
    public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain)
        throws IOException, ServletException {

        request.setCharacterEncoding("Windows-31J");
        chain.doFilter(request, response);
    }

    public void init(FilterConfig arg0) throws ServletException {
        // do nothing.
    }

    public void destroy() {
        // do nothing.
    }
}

このFilterを使うためのweb.xmlファイル設定は、、、

EncodingFilter path.to.EncodingFilter EncodingFilter *

追記メモ

tomcatの5.xでGETなFORMでのエンコーディングは、tomcatの設定ファイルservlet.xmlに、、、


のように、「useBodyEncodingForURI="true"」を入れておかなければならない。


参考リンク

*1:シフトJISとほぼ同義

はじめに

Java


いままでC++を中心にいろいろとプログラムを書いてきたのですが、なんだかんだで、JavaでWebアプリケーション(?)なる物を書くことになりました。今までにもJavaは簡単なAppletやiAppliなんかの規模の小さいものは書いたことがあるのですが、なんだか見ているとこの分野はいろいろと面白そうなことが進んでいるし、また他の言語の感じと違う所なんかも多いので、上手くまとめられないかなぁ、、、といった感じでこの備忘録を始めてみることにします。


で、とりあえず、一番最初の備忘録は、「備忘録(びぼうろく)」」です。完璧に「防忘録」と間違えていました。反省。
http://www.tt.rim.or.jp/~rudyard/kaego003.html