DWR with Java: misima 漢詩詩語検索

漢詩作成支援の一環で今回,詩語検索を追加した。SQLite3 で構築した詩語データベースを Java サーブレットから JDBC でアクセスする。高速化のために Ajax + Java Servlet を基本とした。近年,Ajax も便利なライブラリが出現し,Web 2.0 の世界をより簡単に構築できるようになった。今回はそのひとつ DWR (Dynamic Web Remoting) を使うことにした。

DWR の特徴は大きく二つある。第一に,JavaScript 動作の微妙に異なる数あるブラウザへの対応を吸収してくれること。第二に JavaScript からサーバの Java クラスの呼び出しを可能としたこと。とくに後者は,非同期通信インタフェースとサーバ (Servlet) の存在をプログラマから隠蔽することにより Java Servlet の約束事に精通していない Java プログラマにも (JavaScript が書けないとならないんだけど) Web サーバ・プログラミングを近しいものとする。

以下,misima 漢詩詩語検索を作成したメモを残しておく。

詩語データベースの構築

太刀掛重男著『詩語完備 だれにもできる漢詩の作り方』は,詩語に基づいて漢詩を作成する方法を述べており,合わせて詩語集を掲載している。また,詩語の韻書としては『詩韻含英異同辨』という決定版が存在する。できればこうした詩語集をデータベースに蓄積するのがよいのだが,著作権問題があり公開 DB に入れるのは憚られる(自分だけの個人辞書として構築するならよいのだろうが)。しかも,データ入力に気の遠くなるような時間を要する。私のようなサラリーマンにはとうてい不可能である。

そこで今回の詩語 DB は,どちらかというとコーパス・ビュア風のアイデアに基づくことにした。つまり,インターネットで大量に公開されている漢詩テクストを掻き集め,その詩行を 2 文字 + 3 文字 (五言詩) や 2 文字 + 2 文字 + 3 文字 (七言詩) に分解し,それぞれを「詩語」と見なし,これらに平仄・音韻分析処理を加えて,その結果をリレーショナル・データベースに仕立て上げるわけである。平仄・音韻分析用のデータベースは,『misima 漢詩平仄音韻分析』で構築済みだった。

私はロシア語作文をする際,語結合が妥当かどうか Google で検索してみる。ロシア語は「〜において」というとき,в, на のいずれの前置詞を使うのか悩ましいことが多い。「お茶会で」は на чайной церемонии でよいのか? Google で検索するとこのフレーズを含むきちんとしたページがたくさんヒットするので,不自然ではないとハッキリする。コーパス・ビュア風というのはこういう使い方を指している。漢詩詩語についても過去の偉大な詩人の作品で同じような確認ができるはずである。もちろん,詩語の意味,題詠用の分類(秋思,離別などの詩語と結びついたテーマ論)がないと片手落ちなんであるが,求める平仄,文字,字韻とそれに合致する詩語にどのようなものがあるのかが調査でき,詩語検索機能としてはそれなりのものが得られるはずだと考えた。「平-平-仄のパターンでこの文字を含む詩語」,「脚韻がこの韻目の詩語」,程度でも検索できれば便利ではないか?

『唐詩選』などの漢詩 500 首近くをネットから wget で戴いて来て,Perl HTML::TreeBuilder モジュールでもって詩テクストを解析,抽出して約 4,000 語の詩語テクストを得た。ついでに出典(詩人,題名)情報などの属性をぶら下げた。これに詩語の平仄データ,韻目などを追加して SQLite3 のデータベースが成った。

P.S.
その後,ある方から個人的に収集した詩語の Excel ファイルをいただき,本システムの詩語データベースを拡充した。2014.3 時点の詩語収録数は二字: 29,799 語,三字: 53,158 語にまで充実した。

Java 詩語検索クラスの作成

DWR は様々な Java クラスの呼び出しをサポートしている。今回は Java Beans で行くことにした。Java Beans はデータを保持するオブジェクトで,データ項目の出し入れに set〜, get〜 なるメソッドを一式用意しておくというモジュール構造であり,データ部品化の基本になっている。サーバで DB を検索し,その結果を Bean に入れ,JavaScript から DWR インタフェースで取り出す。基本設計はそのようなものである。詩語のための Java Beans SigoBean.java を以下に示す。本来なら DB に蓄積している項目はいくつもあるが,ここでは詩語,平仄,韻目のみに限定してある。

// -*- coding: utf-8; mode: java; -*-
/**
 *  misimaKansi SigoBean 漢詩詩語
 *  Copyright(c) 2011, isao yasuda, All Rights Reserved.
 */
public class SigoBean
{
    /**
     *  SQLite3 database
     *  詩語 sg, 詩語平仄 hs1, 詩語韻 in1
     */
    private String sg  = null; // 詩語
    private String hs1 = null; // 平仄
    private String in1 = null; // 韻目
 
    /** Constructor */
    public SigoBean(String sg,  // 詩語
                    String hs1, // 平仄
                    String in1) // 韻目
    {
        this.sg  = sg;
        this.hs1 = hs1;
        this.in1 = in1;
    }
 
    /** Getter メソッド */
    public String getSigo()    { return sg;  }
    public String getHyosoku() { return hs1; }
    public String getSigoIn()  { return in1; }
 
    /** Setter メソッド */
    public void setSigo(String sg)     { this.sg  = sg;  }
    public void setHyosoku(String hs1) { this.hs1 = hs1; }
    public void setSigoIn(String in1)  { this.in1 = in1; }
 
    /** テキスト取得用メソッド */
    public String getSigoBean(String sep) {
        return sg + sep + hs1 + sep + in1;
    }
}

さて,実際に詩語データベースを検索し,上記詩語 Bean の配列テーブルとして結果をストアする Java クラスが当然必要である。SigoTable.java コードを以下に示す。ここで,SQLite3 DB アクセスは JDBC インタフェースを使っている。SQLite 用 JDBC ドライバは SQLiteJDBC – Xerial – Trac から sqlite-jdbc-3.7.2.jar (2010.8.27 版) を落として,クラスパスに通しておくとともに,Servlet コンテナ Tomcat の $CATALINA_HOME/common/lib/ にもコピーしておく。

// -*- coding: utf-8; mode: java; -*-
import java.util.ArrayList;
import java.util.List;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
 *  misimaKansi SigoTable 漢詩詩語検索
 *  Copyright(c) 2011, isao yasuda, All Rights Reserved.
 */
public class SigoTable
{
    /** 詩語 DB path */
    private static final String dbpath = "/usr/local/etc/misima/SIGO.db";
    /** SQL */
    private String sqlcond = null;
    /** 詩語テーブル行データ */
    private ArrayList<SigoBean> dbRowList = null;
    /** データ行数 */
    private int gyosu;
   
    /**
     * 詩語 DB を検索し,詩語テーブルを SigoBean 配列として返却する
     * @return 詩語テーブル
     */
    public SigoBean[] getSigoTable(String cond) throws Exception {
        sqlcond = "select * from SIGOTBL where " + cond + ";";
        dbRowList = new ArrayList<SigoBean>();
        /** DB検索 */
        Class.forName("org.sqlite.JDBC");
        Connection conn = null;
        try {
            // create a database connection
            conn = DriverManager.getConnection("jdbc:sqlite:" + dbpath);
            Statement sttmnt = conn.createStatement();
            sttmnt.setQueryTimeout(30); // set timeout to 30 sec.
            ResultSet rset = sttmnt.executeQuery(sqlcond);
            while(rset.next()) {
                // read the result set
                dbRowList.add(new SigoBean(rset.getString("sg"),
                                           rset.getString("hs1"),
                                           rset.getString("in1")));
            }
            rset.close();
        }
        catch(Exception e) {
            System.err.println(e.getMessage());
        }
        finally {
            try {
                if(conn != null)
                    conn.close();
            }
            catch(Exception e) {
                // connection close failed.
                System.err.println(e);
            }
        }
        gyosu = dbRowList.size();
        SigoBean[] dbrows = new SigoBean[gyosu];
        return dbRowList.toArray(dbrows);
    }
   
    /**
     * 詩語 DB 行数を返却する (for debug)
     * @return 行数
     */
    public int size() {
        return gyosu;
    }
}

DWR 環境の作成

DWR のパッケージは DWR ダウンロードページ から dwr.war (Version 3. RC-1) を落として使った。DWR のサーバ側環境設定は大きく 3 点。以下簡単に示す。

  1. ダウンロードした dwr.war を詩語検索 Web アプリの WEB-INF/lib ディレクトリ直下に格納する。
  2. dwr.xml を,以下の内容で WEB-INF/ 下に格納する。これは JavaScript の記述とサーバ側 Java クラスを結びつけるための記述である。create タグで上記 SigoTable クラスが呼び出されるよう,convert タグによってそのデータ構造へのアクセスは JavaBeans SigoBean を介して行われるよう指定している。詳細は Configuring dwr.xml を参照のこと。
  3. <!DOCTYPE dwr PUBLIC
        "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"
        "http://directwebremoting.org/schema/dwr30.dtd">
    <!-- -*- coding: UTF-8; -*- 
        misima 漢詩詩語検索用 dwr.xml
        Copyright (c) 2011, isao yasuda, All Rigths Reserved.
    -->
    <dwr>
      <allow>
        <create creator="new" javascript="SigoTable">
          <param name="class" value="SigoTable"/>
        </create>
        <convert converter="bean" match="SigoBean"/>
      </allow>
    </dwr>

  4. 詩語検索 Servlet 用のデプロイメント・ディスクリプタ web.xmlWEB-INF/ 下に準備する。今回,旧字・旧仮名遣い変換 misimaservlet のサブシステムとして作成したので,すでにある web.xml に DWR 関連用の servlet タグ及び servlet-mapping タグを追加した。DWR 部分は以下とまったく同じでよいと思う。詳細は DWR WEB-INF Reference を参照のこと。
  5. <!DOCTYPE web-app
        PUBLIC  "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
        "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
    <!-- -*- coding: UTF-8; -*- 
        misimaServlet web.xml デプロイメントデスクリプタ
        Copyright (c) 2007, isao yasuda, All Rigths Reserved.
    -->
    <web-app>
      <!-- misimaservlet サーブレット -->
      <servlet>
        <servlet-name>misimaServlet</servlet-name>
        <servlet-class>misimaServlet</servlet-class>
    ... (略) ...
      </servlet>
     
      <!-- DWR サーブレット -->
      <servlet>
        <servlet-name>dwr-invoker</servlet-name>
        <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>true</param-value>
        </init-param>
      </servlet>
     
      <!-- misimaservlet サーブレットのマッピング -->
      <servlet-mapping>
        <servlet-name>misimaServlet</servlet-name>
        <url-pattern>/convert</url-pattern>
      </servlet-mapping>
      
      <!-- DWR サーブレットのマッピング -->
      <servlet-mapping>
        <servlet-name>dwr-invoker</servlet-name>
        <url-pattern>/dwr/*</url-pattern>
      </servlet-mapping>
    </web-app>

クライアント・ページ JavaScript の作成

最後に DWR のキモであるクライアント側 JavaScript を作成する。サービス用 HTML から呼び出す JavaScript として DWR ライブラリを次のように指定しておく。

<script type="text/javascript" src="dwr/engine.js"></script>
<script type="text/javascript" src="dwr/util.js"></script>
<script type="text/javascript" src="dwr/interface/SigoTable.js"></script>

はじめの 2 行はおまじないのようなものである。dwr/interface/SigoTable.js は,dwr.xml における javascript="SigoTable" という指定を受けて DWR が自動生成するスクリプトであり,ユーザアプリに応じて名前を変えて指定しなければならない。

さて,DB を検索し,その結果が挿入される HTML は以下の通りである。input タグに SQL 条件を入力し,「検索」ボタンを押下すると,retrieveSigo() 関数(後述)を起動するコードになっている。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
          "http://www.w3.org/TR/html4/loose.dtd">
<!-- Copyright(c) 2011, isao yasuda, All Rights Reserved. -->
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>misima 漢詩詩語検索</title>
  </head>
  <body>
    <dl compact>
      <dt>実行 SQL</dt>
      <dd>select * from SIGOTBL where <br> 
        <input type="text" size="70" id="condition" />;
        <input type="button" value="検索" onClick="retrieveSigo();" />
      </dd>
    </dl>
    <!-- 詩語検索結果挿入するテーブル -->
    <table border="0">
      <thead>
        <tr style="background-color: #ccc;">
          <th width="80px">詩語</th>
          <th width="120px">詩語平仄</th>
          <th width="400px">詩語韻</th>
        </tr>
      </thead>
      <tbody id="sigotable"></tbody>
    </table>
    <script type="text/javascript" src="dwr/engine.js"></script>
    <script type="text/javascript" src="dwr/util.js"></script>
    <script type="text/javascript" src="dwr/interface/SigoTable.js"></script>
    <script type="text/javascript" src="./sigo.js"></script>
  </body>
</html>

検索ボタンに紐づけられた検索実行制御処理 retrieveSigo() 関数は以下の通り。上記 HTML の sigo.js にこれを定義しておく。ここでサーバとの非同期通信を担っているのは SigoTable.getSigoTable(sql.value, ...); の部分である。サーバ側にある SigoTable クラスの getSigoTable() メソッドをコールしている。これは Java クラスを操作するのと同様の様式であり,DWR の特徴となっている。このコーリング書式は クラス名.メソッド名(メソッド引数, コールバック関数); である。サーバで実行されたメソッドの返値を引数として コールバック関数 が実行される。このなかでブラウザ表示に必要な処理を記述する。

// -*- coding: utf-8; mode: javascript; -*- 
// misima 漢詩詩語検索
function retrieveSigo() {
    var sql = document.getElementById("condition").value;
    var cellFuncs = [
        function(data) { return data.sigo; },    // 詩語
        function(data) { return data.hyosoku; }, // 平仄
        function(data) { return data.sigoIn; }   // 韻目
    ];
    // insert table contents from DB    
    SigoTable.getSigoTable(
        sql,
        function(data){ // callback
            dwr.util.removeAllRows("sigotable");
            dwr.util.addRows("sigotable", data, cellFuncs, {
                // 偶数行の背景色を変更
                rowCreator:function(options) {
                    var row = document.createElement("tr");
                    if (options.rowIndex % 2 != 0) {
                        row.style.background = "#cccccc";
                    }
                    return row;
                },
                escapeHtml:false 
            });
        });
}

retrieveSigo() 関数の引数のなかに関数が記述されておりそこでさらに関数が入れ子になっている。わかりにくいのでもう少し説明しておく。ここでサーバからの返値は function(data) 関数 (コールバック関数) に渡され,こちらは DWR util.js のユーティリティ addRows() 関数で HTML のテーブル (上記 HTML の <tbody id="sigotable"> の位置) にデータを展開している。addRows() 関数の書式は以下の通りである。

dwr.util.addRows(id, array, cellfuncs, [options]);
- id: テーブル要素の id。テーブル要素は <tbody id="id"> とするのがよい。
- array: 表にしたいデータ配列
- cellfuncs: 行データの各セルに応じた処理を行う関数の配列
- options: オプション (rowCreator, cellCreator, escapeHtml から複数指定可)

retrieveSigo() ではサーバからの出力 datacellFuncs という名の関数配列で項目を抽出しつつ id="sigotable" のテーブル要素に編集している。この際,rowCreator オブジェクト・オプションを指定し,これに偶数行ごとに背景色を変える関数(= その返値オブジェクト)を対応づけている。また,escapeHtml:false オプションを指定して HTML タグのエスケープをしないようにしている。addRows() 関数は実行のたびに行を追加するだけなので,removeAllRows() 関数で予め行を削除する前処理を行っている。

DWR の JavaScript の書き方,関数仕様詳細は DWR サイト・ドキュメント を参照。悩んだのは cellFuncs 関数配列中の data.sigo 等のオブジェクト指定であった。このうちの sigo の部分は JavaBeans の get〜 メソッドの を指定しなくちゃならないことに気づくのにえらく時間を要してしまった。それでも MVC デザイン・パターンに従って,しこしこ JSP をコーディングしていたのに比べると,遥かにわかりやすいと思う。DWR なら MVC のうちの C (Controller つまり Servlet) をほぼ意識せずに Web アプリを書くことができるからである。

以上でリソースは整った。Java コードをコンパイルした上で,リソース一式を Tomcat 環境にデプロイしておく。

実行イメージ

misima 漢詩平仄音韻分析』ページに「詩語検索」のボタンを追加した。これをクリックすると,詩語検索のウィンドウがオープンして,詩語の検索ができる。詩語と平仄パターン,韻目が表示される。以下に検索イメージ図を掲げておく。これは平仄パターンが「●-●-○」(仄-仄-平)の 3 字詩語で,かつ「蒼」という文字を末尾に含む詩語を検索した結果である。ただし,今回作成した版は,直接ユーザが SQL を入力するというイビツにして危険なものであるため,公開は友人限定としている。

P.S.
その後,詩語簡易検索機能を一般公開した。その詳細は記事『misimaKansiServlet 漢詩平仄音韻分析・詩語簡易検索』を参照ください。
sqlsigosearch-screen.png
詩語検索イメージ

参考文献

参考にした文献は次。やっぱりオライリー。DWR についてもきちんと書かれている。

※ 2011.12.23 付記
アタックが多いので限定公開としました。悪しからず。