レベル: 初級 安達 宜隆, IM 開発 & サービス / ソフトウェア開発研究所,
IBM
2008年 2月 29日 IBM OmniFind Enterprise Edition(以下OmniFind)は、イントラネットに存在する様々な情報を検索可能にするエンタープライズ向けサーチシステムを提供するソフトウェアです。エンドユーザーはOmniFindに付属のESSearchApplicationというWebアプリケーションを通じて、必要な情報を簡単に検索することが可能になります。またそれだけでなく、OmniFindではSIAPI(Search and Indexing API)と呼ばれる検索エンジン向けAPIを公開しています。そのため、オリジナル検索アプリケーションを作成することも可能になっています。
ところで、Webアプリケーションの開発の分野では、現在Ruby on Railsが非常に注目を集めています。オブジェクト指向スクリプト言語であるRubyと、「Don't Repeat Yourself」や「Convention over Configuration」を基本理念としているRailsフレームワーク。これらを組み合わせたRuby on Railsによって、Webアプリケーションの開発生産性を飛躍的に高めることが可能になりました。
本稿では、OmniFindとRuby on Railsを組み合わせ、検索アプリケーションを高速開発する方法を説明します。
開発環境の準備
1. Java及びJRubyの設定
最初にOmniFindの開発環境を準備します。JDKをインストールし、OmniFindのSIAPIライブラリ(jarファイル)用に環境変数の設定を行います(表1)。OmniFindのSIAPIを利用するためのjarファイルは、siapi.jar、esapi.jar、es.security.jarの3つです。これらは、OmniFindのインストールフォルダ(ES_INSTALL_ROOT環境変数で設定されているフォルダ)の下のlibフォルダにあります(デフォルトの設定でインストールした場合、Windowsの場合はC:\Program Files\IBM\es\libフォルダにあります)。これら3つのjarファイルに対して、CLASSPATHの設定を行います。
次に、Rubyの処理系(インタプリタ)をインストールします。Rubyのサイトからは、各種OS向けのRuby処理系をダウンロードすることが可能です。しかし、これらのRubyからは、OmniFindのSIAPI(Java)をそのまま利用することができません。そこで、JVM上のRuby処理系であるJRubyをインストールすることにします。JRubyを使用することによって、Rubyプログラムの中からJavaのクラスを簡単に使用することが可能になります。
JRubyの処理系は、JRubyのサイトからダウンロードできます。ダウンロードしてきたファイルを適当なフォルダに解凍し、そのフォルダに対してJRUBY_HOME環境変数を設定します。また、JRubyのbinフォルダに対してPATHも設定します。これでコマンドプロンプトからJRubyを利用可能になります。コマンドプロンプトから
と入力して、rubyのバージョンが表示されれば成功です。
以上、必要な環境変数の設定は以下の通りです。
表 1. 開発に必要な環境変数
| JAVA_HOME | JDKをインストールしたフォルダ |
|---|
| JRUBY_HOME | JRubyを解凍したフォルダ |
|---|
| PATH | %PATH%;%JAVA_HOME%\bin;%JRUBY_HOME%\bin |
|---|
| CLASSPATH | %CLASSPATH%;
%ES_INSTALL_ROOT%\lib\siapi.jar;
%ES_INSTALL_ROOT%\lib\esapi.jar;
%ES_INSTALL_ROOT%\lib\es.security.jar |
|---|
2. Rails のインストール
RailsはRubyのパッケージ管理システムであるgemsを使用して簡単にインストールできます。コマンドプロンプトから、以下のコマンドを入力してください。
$ jruby -S gem install rails -y
|
これでRailsが自動的にインストールされ、JRubyから利用可能になります。
以上で、JRubyからRailsを使用したWebアプリケーションを作成する準備が整いました。では、実際にWebアプリケーションを作ってみます。ESSearchApplicationという名前のWebアプリケーションを作成するには、以下のコマンドを入力します。
$ jruby -S rails ESSearchApplication
|
これで、ESSearchApplicationというフォルダが作られ、その中にWebアプリケーションに必要なファイルが作られました。動作確認のため、以下のコマンドを入力しサーバーを起動してみます。
$ cd ESSearchApplication
$ jruby script/server
|
しばらくするとWebサーバーが起動します。ブラウザからhttp://localhost:3000/にアクセスし、Railsの画面が表示されることを確かめてください。
検索アプリケーションの開発
ここからは、OmniFindの検索アプリケーションを作成してきます。今回作成するアプリケーションは、検索と結果の2つの画面のみを持つ非常に簡単なアプリケーションです。
図 1. 検索画面
図 2. 結果画面
RailsではMVCアーキテクチャを採用しています。RailsにおけるMVCのModelはデータ(データベース)、Viewはrhtmlファイル、Controllerはrubyのクラス(rubyスクリプト)です。ユーザーがブラウザにアクセスすると、Railsでは最初に、処理を担当するController(クラス)と、処理を表すAction(メソッド)を決定します。そして対応するrubyのメソッドを実行します。Controllerのメソッドが終了したら、次はViewの処理になります。RailsではActionに対応したrhtmlを使ってhtmlをレンダリングします。rhtmlはJSPに似たスクリプトを埋め込んだファイルのことです。rhtmlによって、Controllerで処理されたデータがhtmlのレスポンスに変換されます。こうしてできたhtmlがユーザーへ返されて、1回のアクセスが終了します。
例えば、これから作成するアプリケーションでhttp://localhost:3000/omnifind/indexにアクセスすると、以下のような処理が行われます。
図 3. http://localhost:3000/omnifind/indexにアクセスしたときの処理の流れ
今回作成するアプリケーションも、このRailsのMVCアーキテクチャに沿った形にします。ControllerとしてOmnifindControllerを作成し、検索(初期画面)と結果表示に対応するアクションを、それぞれindexアクションとsearchアクションとして作成します。同時に、それぞれのアクションに対応するViewをindex.rhtmlとsearch.rhtmlとして作成します。
では、実際にプログラムを作成していきます。
まずは、Omnifindという名前のControllerを作成します。これは、次のコマンドで作成できます。
$ jruby script/generate controller omnifind
|
すると、ESSearchApplication/app/controllersフォルダにomnifind_controller.rbというファイルができました。このファイルに、実際の処理を記述していきます。
次はActionの作成です。これはOmnifindControllerに、各Actionに対応するメソッドを作成し、Viewに相当するrhtmlファイルをapp/view/omnifind/フォルダに作成することで実現できます。今回はindexアクションとsearchアクションの2つのアクションを作成しますので、omnifind_controller.rbにindexメソッドとsearchメソッドを、app/view/omnifindフォルダにindex.rhtmlとsearch.rhtmlを作成します。それぞれのソースは、下記のようになります。
omnifind_controller.rb
require 'java'
import 'java.lang.System'
import 'java.util.Properties'
import 'java.util.ArrayList'
import 'com.ibm.siapi.common.ApplicationInfo'
import 'com.ibm.siapi.search.Query'
import 'com.ibm.siapi.search.Result'
import 'com.ibm.siapi.search.ResultSet'
import 'com.ibm.siapi.search.SearchFactory'
import 'com.ibm.siapi.search.SearchService'
import 'com.ibm.siapi.search.Searchable'
import 'com.ibm.es.api.imc.Identity'
import 'com.ibm.es.api.imc.IdentityManagementFactory'
import 'com.ibm.es.api.imc.IdentityManagementService'
import 'com.ibm.es.api.imc.SecuredDataSource'
import 'com.ibm.es.api.imc.SecurityContext'
import 'com.ibm.es.api.imc.UserInformation'
class OmnifindController < ApplicationController
$HOST_NAME = ''
$APPLICATION_NAME = ''
$COLLECTION_ID = ''
$PORT = '80'
def index
end
def search
@search_word = params[:SEARCH_WORD]
if @search_word.empty? then
@message = 'コレクションおよび外部ソースを検索するには、照会条件を指定する必要があります。'
return render :action => 'index'
end
# OmniFindのSIAPI Search Factoryの取得
cls = java.lang.Class.for_name("com.ibm.es.api.search.RemoteSearchFactory")
factory = cls.newInstance()
# OmniFindのSIAPI Search Serviceの取得
config = Properties.new
config.putAll({'hostname' => $HOST_NAME, 'port' => $PORT})
searchService = factory.getSearchService(config)
# OmniFindのSIAPI Searchableの取得
applicationInfo = factory.createApplicationInfo($APPLICATION_NAME)
searchable = searchService.getSearchable(applicationInfo, $COLLECTION_ID)
# OmniFindのSIAPI Queryの作成
q = factory.createQuery(@search_word)
# OmniFindの検索の実行
@result_set = searchable.search(q)
@number_of_results = @result_set.get_available_number_of_results
end
end
|
index.rhtml
<html>
<head>
<title>エンタープライズ・サーチの検索アプリケーション</title>
</head>
<body>
<p style="color: red"><%= @message %></p>
<%= form_tag (:action=>"search") %>
<table border="0">
<tr><td colspan="2">検索対象:</td></tr>
<tr><td><%= text_field_tag "SEARCH_WORD", @search_word %></td>
<td><%= submit_tag "検索" %></td></tr>
</table>
</body>
</html>
|
search.rhtml
<style>
.OFHighlightTerm1 { background-color: #CBDBF3 }
.OFHighlightTerm2 { background-color: #FF99CC }
.OFHighlightTerm3 { background-color: #66FF66 }
.OFHighlightTerm4 { background-color: #FFFF33 }
.OFHighlightTerm5 { background-color: #CDBBF3 }
.OFDefaultField { color: #008000 }
table.results {border-collapse: collapse; }
td.result1 {padding: 10px; vertical-align:top; border: 1px solid}
td.result2 {border: 1px solid}
</style>
<% require 'date' %>
照会:<%= @search_word %>
<% if @number_of_results > 0 then %>
<div><%= @number_of_results %> 件の検索結果が戻されました </div>
<table class="results">
<% @result_set.get_results.each {|result| %>
<tr>
<td class="result1"><%= format '%10.4G', result.get_score %>%</td>
<td class="result1"><%= Date.parse result.get_date.to_string %></td>
<td class="result2">
<% uri = result.get_document_uri == nil ? '' : result.get_document_uri %>
<%= link_to result.get_title, uri %><br />
<%= result.get_description %></td>
</tr>
<% } %>
</table>
<% else %>
<div>この照会の結果はありませんでした</div>
<% end %>
|
なお、omnifind_controllerの定数である、HOSTNAME, APPLICATION_NAMEなどは、環境に合わせて適宜変更する必要があります。
omnifind_controllerのsearchメソッドでは、SIAPIを使って検索を行っています。これらのクラスを使用するために、omnifind_controller.rbの最初で使用するSIAPIのクラスをimportしています。同時に、これらのクラスはJavaのクラスですので、require javaという命令で、Javaを使用することを宣言しています。JRubyでは、このようにすることで、JavaのクラスをあたかもRubyのクラスのように取り扱うことが可能です。
メソッドの中身は、Rubyプログラムであるにも関わらず、ほとんどJavaと同じです。SIAPIの流儀に従って、search serviceやsearchableを取得し、Queryオブジェクトを引数にして検索を実行しています。検索結果は、omnifind_controllerのインスタンス変数である@result_setに格納します。Railsでは、Viewであるrhtmlからcontrollerのインスタンス変数を参照することができます。そのため、omnifind_controllerの@result_setインスタンス変数の値をsearch.rhtmlから取得して、検索結果をHTMLとして表示させることが可能になっています。
変数の型宣言がないこと、コンストラクタの呼び出し方が違うことなど、あくまでRubyのプログラムではあるのですが、SIAPIの使い方などはほとんどJavaと同じです。JavaやSIAPIに詳しい方でしたら、特にRubyに詳しくなくてもプログラムの挙動が理解できると思います。
サーバーを起動する前に、設定を1つ行います。Ruby on RailsではDB接続にActiveRecordという機能を使用するのですが、今回のアプリケーションではDB接続がないためActiveRecordを使用しません。そのため、config/environment.rbにて以下の行を追加し、ActiveRecordを無効にします。
enrivonment.rb
Rails::Initializer.run do |config|
config.frameworks -= [ :active_record ]
end
|
以上で、プログラムは完成です。
では、実際に実行してみましょう。ESSearchApplicationのフォルダに移動し
と入力します。サーバーが起動しましたら、http://localhost:3000/omnifind/にアクセスしてください。図1の検索画面が表示され、OmniFindの検索ができるようになります。
セキュア検索
次に、先ほど作成したアプリケーションを拡張したいと思います。ここでは、Omnifindの特徴の1つであるセキュア検索を行うアプリケーションを作成します。
セキュア検索を行うには、データソース毎にユーザー名とパスワードを入力する必要があります。検索アプリケーションは受け取ったユーザー名とパスワードを用いてデータソースに対して認証を行い、そのユーザーが属するグループを取得します。そして検索時に、キーワードと一緒にそれらのユーザー情報をOmniFindに渡す必要があります。
これらのユーザーに関する処理はIdentity Management Component API(IMC API)として公開されていますので、今回はそれを使用することにします。
検索画面は、データソース毎にユーザー名とパスワードを入力可能にするために、図4のようにします。
図 4. 検索画面
検索画面の変更にあわせて、omnifind_controllerのindexメソッドにセキュアデータソースを取得する処理を、index.rhtmlにデータソースに対応するユーザー情報を入力するためのボックスを追加します。
omnifind_controller#index
def index
# IMC Serviceの取得
cls = java.lang.Class.forName("com.ibm.es.api.imc.IdentityManagementFactory")
factory = cls.newInstance()
config = Properties.new
config.putAll({'hostname' => $HOST_NAME, 'port' => $PORT})
imcService = factory.getIdentityManagementService(config)
imcProperties = imcService.getProperties();
# セキュアデータソースの取得
applicationInfo = factory.createApplicationInfo($APPLICATION_NAME)
@datasources = imcService.getSecuredDataSources(applicationInfo)
end
|
index.rhtml
<% if @datasources.length > 0 then %>
<table>
<tr>
<th>Domain</th>
<th>type</th>
<th>User Name</th>
<th>Password</th></tr>
<% @datasources.each {|datasource| %>
<tr>
<td><%= datasource.getDomain %></td>
<td><%= datasource.getType %></td>
<td><%= text_field_tag \
"USERNAME_" + datasource.getDomain, \
params["USERNAME_" + datasource.getDomain] %></td>
<td><%= password_field_tag \
"PASSWORD_" + datasource.getDomain, \
params["PASSWORD_" + datasource.getDomain] %></td>
</tr>
<% } %>
</table>
<% end %>
|
そして、omnifind_controllerのsearchメソッドの最初で、ユーザー認証を行う処理を追加します。
omnifind_controller#search
cls = java.lang.Class.forName("com.ibm.es.api.imc.IdentityManagementFactory")
factory = cls.newInstance()
config = Properties.new
config.putAll({'hostname' => $HOST_NAME, 'port' => $PORT})
imcService = factory.getIdentityManagementService(config)
imcProperties = imcService.getProperties();
applicationInfo = factory.createApplicationInfo($APPLICATION_NAME)
@datasources = imcService.getSecuredDataSources(applicationInfo)
identities = ArrayList.new
@datasources.each {|datasource|
if params["USERNAME_" + datasource.getDomain] then
identity = Identity.new
identity.setDomain(datasource.getId())
identity.setType(datasource.getType())
identity.setUsername(params["USERNAME_" + datasource.getDomain])
identity.setPassword( \
imcService.encrypt( \
params["PASSWORD_" + datasource.getDomain], imcProperties))
identity.setProperties(datasource.getProperties)
identity.setEncrypted(true);
begin
if imcService.validateUserInformation(nil, identity) then
ui = imcService.getUserInformation(nil, identity)
identity.setGroups(ui.getGroups)
identities.add identity
end
rescue
end
end
}
ids = Identity[identities.size].new
ids = identities.toArray(ids)
context = SecurityContext.new "temp", ids
|
また、検索を行う前に、Queryにユーザー情報を追加します。
omnifind_controller#search
if context then
q.setACLConstraints("@SecurityContext::'" + @context.serialize(true) + "'")
end
|
これでセキュア検索が可能になりました。実際にセキュリティをONにしたコレクションを使用してセキュア検索を試してみましょう。
まとめ
このように、Ruby On RailsとJRubyを使用することで、高速にOmniFindの検索アプリケーションを作成ことが可能になります。また、機能追加をしていくことで、セキュア検索のような高度な処理も行えるようになります。
このRuby on Railsの生産性の高さは非常に魅力的と言えます。
一方で、OmniFindにはカスタマイズ可能な検索アプリケーションとして、Strutsによって作成されたESSearchApplicationが付属しています。ESSearchApplicationにはセキュア検索以外にも数多くの機能が含まれており、OmniFindをインストールすれば直ぐに使用可能です。また検索カスタマイザーを使用して、GUIから設定変更を行うことも可能になっています。
いくらRuby on Railsの生産性が高いといっても、ESSearchApplicationと同じ機能を実装するのは多くの時間と労力がかかってしまいます。また、JavaやWebSphere Application Serverの安定性、性能はRubyにはない魅力です。本番環境で長期間運用するのには、ESSearchApplicationが適しているといえるでしょう。
プロトタイプの作成や機能の確認などでRuby on Railsの生産性の高さを生かし、本番環境ではESSearchApplicationの安定性、堅牢性を享受する。このように適材適所に使用することで、より良い検索システムが実現すると考えられます。
またOmniFindは、SIAPIとしてJavaのAPI(jarファイル)を公開していますが、工夫次第でJava以外の言語からの利用も可能です。今回はJRubyを使用して直接SIAPIを使用する形をとりましたが、例えばWeb Service APIを使用して、PHPやC#などからもOmniFindを利用することが可能です。こうした機能を使うことで、既存の他のアプリケーションとOmniFindを容易に統合することも可能になります。
是非本稿を参考に、魅力的な検索アプリケーションを作成して下さい。
参考文献
著者について  | |  | 安達 宜隆は、ソフトウェア開発研究所のソフトウェア・エンジニアであり IBM OmniFind Enterprise Edition の開発・保守に携わっています。休日も含め毎日キーボードをバシバシ叩く生活を送っています。潤いがほしい今日この頃です。 |
記事の評価
|