WebSphere® sMash 環境では、PHP などのよく使われる Web 技術をベースにインタラクティブな Web アプリケーションを素早く開発できるだけでなく、PHP スクリプトから既存の Java™ アセットを再利用することもできます。この記事では Java Bridge を紹介し、PHP から Java クラスにアクセスする方法を説明します。

Anthony Phillips, Software Developer, IBM

Photo: Ant PhillipsAnt Phillips は、英国ハーズリーにある IBM Java Technology Centre のソフトウェア開発者です。彼は現在、動的 Web アプリケーションを作成するための単純な環境、WebSphere sMash に取り組んでいます。IBM に入社する以前は、英国ニューブリーを拠点に置く革新的新興企業でテクニカル・リーダーを務めていました。その前は Sony®、Microsoft® に勤務した経験を持ち、東京やシアトル、そしてこの 2 都市の間のさまざまな場所への訪問を存分に楽しみました。余暇には、彼の妻と 2 人の子供が止めるまでスポーツに興じています。



Zoe Slattery, Software Engineer, IBM

Photo: Zoe SlatteryZoe Slattery は、英国の IBM Hursley でソフトウェア・エンジニアとして勤務しています。彼女の最近の興味は、オープンソースの開発と PHP です。これまで、IT 産業のさまざまな分野で経験を積んできました。FORTRAN プログラマーとして出発した彼女は、数値計算用の並行システムに従事し、European Centre for Medium Range Weather Forecasts のデータ・アーカイブを管理した経歴も持っています。最近では、IBM Java Virtual Machine、Java クラス・ライブラリーのオープンソース実装に取り組む開発チームのマネージャーを務めました。



2008年 9月 24日

始める前に

まずは Project Zero の Web サイトをよく読んで、このプロジェクトをよく理解してください。このサイトでは、Project Zero コミュニティーに参加してこのプロジェクトに貢献したり、ディスカッション・フォーラムに加わって開発の各段階でプロジェクトについてコメントしたりすることができます。この記事で前提とするのは、お使いのマシンに適切な JDK (Java Development Kit) がインストールされていることです。また、PHP の概念についても十分に理解している必要があります。

WebSphere sMash をダウンロードして PHP アプリケーションを作成する方法については、記事「Project Zero、WebSphere sMash、そして PHP を使い始める」を参照してください。この記事では PHP で動作する WebSphere sMash の実動バージョンが用意されていることを前提とします。前述の記事で「PHP アプリケーションの実行」セクションまでのステップを実行すれば、この前提は満たせます。

編集者からの注記: IBM® WebSphere sMash および IBM WebSphere sMash Developer Edition は、非常に高い評価を受けた Project Zero インキュベーター・プロジェクトをベースにしています。Project Zero は WebSphere sMash の開発コミュニティーであり、最新のビルドや、最新の機能、そしてコミュニティーのサポートを利用したアプリケーションを開発するための無料のプラットフォームを今後も提供していきます。


はじめに

この記事では、Java Bridge を使用して PHP から Java クラスにアクセスする方法を紹介し、Java メソッドの呼び出し、そしてフィールド (インスタンスと静的の両方) へのアクセスについて説明します。さらに、例外処理と PHP と Java の間での型変換についても取り上げています。


ZSL、WebSphere sMash、および Apache Lucene について

この記事では現実世界での例として、Apache Lucene を使用してファイルの索引付けと検索を行える簡単な検索エンジンを PHP で作成する手順をステップごとに説明します。Apache Lucene はハイパフォーマンスの充実した機能を備えたテキスト検索エンジン・ライブラリーです。このライブラリーは一貫して、フルテキスト検索が必要な多くのアプリケーションに適した技術、Java で作成されています。

ZSL が作成した WebSphere sMash アプリケーションのなかでは、この Apache Lucene が使用されています。開発者間での情報共有を改善する必要があった ZSL® では、この問題を解決するため、マッシュアップを組み立ててソース・コードと文書ライブラリー (PDF、PowerPoint、Word、Excel 他、多数) に索引を付けることにしたわけです。このアプリケーションにより、社内のどこからでも素早く簡単にコード・スニペットにアクセスできるようになっています。


WebSphere sMash でのアプリケーションの作成

アプリケーションの作成に取り掛かるための最初のステップは、Eclipse で新しいプロジェクトを作成することです。

  1. Select File -> New -> Project... の順に選択し、表示されたダイアログで Zero カテゴリーを展開します。
  2. WebSphere sMash PHP Application を選択して Next をクリックします (図 1 を参照)。
  3. プロジェクトの名前 (例えば、MyJavaProject) を入力して Finish をクリックします。これで、プロジェクトが作成されます。
    図 1. WebSphere sMash の新規プロジェクト作成ダイアログ
    WebSphere sMash の新規プロジェクト作成ダイアログ

Java オブジェクトの作成と呼び出し

次に、Java オブジェクトを作成して呼び出す PHP スクリプトを作成してください。

  1. public フォルダーを右クリックして New -> File を選択します。
  2. ファイル名 (例えば、Java.php) を入力して Finish をクリックします。
  3. このファイルに以下のコードを追加します。
    <?php
        $file = new Java("java.io.File", __FILE__, FALSE);
        var_dump($file);
        var_dump($file->isDirectory());
    ?>
  4. Eclipse でプロジェクト名を右クリックし、Run As -> WebSphere sMash Application の順に選択してサンプル・コードを実行します。
  5. Web サーバーがローカル・ホストのポート 8080 で起動します。
  6. ブラウザーで http://localhost:8080/Java.php にアクセスすると、以下の図 2 に示す出力が表示されます。
    図 2. Web ブラウザーに出力された Java オブジェクトの呼び出し結果
    Web ブラウザーに出力された Java オブジェクトの呼び出し結果

このサンプル・コードが示しているのは、組み込み Java クラスを使用した PHP スクリプトです。この Java クラスが Java クラスのインスタンスを作成し、スクリプトからの引数を渡して最も適合するコンストラクターを呼び出します。この例では引数として “java.io.File” と __FILE__、FALSE を渡しているため、ルート・ディレクトリーは「/」であり、スクリプトによって現在実行中のファイルに対するオブジェクトが $file という PHP 変数に格納されます。続いて、スクリプトは通常の PHP オブジェクトであるかのようにこのオブジェクトのメソッドを呼び出します。この例では、isDirectory というメソッドを呼び出しています。

この強力な機能により、PHP スクリプトはあらゆる Java クラスにアクセスできるようになります。注意する点として、Java クラスはアプリケーションのクラス・パス、java.io.File に配置してください。こうすれば Java クラスがコア Java クラス・ライブラリーの一部となり、常に使用可能になります。


Java コレクション・クラスの使用

Java にはマップ、セット、リスト、キューをはじめ、豊富なコレクション・クラスが揃っています。このサンプル・コードでは、PHP スクリプトではどのようにコレクション・クラスを利用できるかを説明します。前と同じように新しい PHP スクリプト (例えば MoreJava.php) を作成して、以下のコードを追加してください。

<?php
    $map = new Java("java.util.HashMap");
    
    $map->put("title", "Java Bridge!");
    $array = array(1, 2, 3, 4, 5);
    $map->put("stuff", $array);
    var_dump($map->get("stuff"));
    echo $map->get("title");
?>

ブラウザーで http://localhost:8080/MoreJava.php にアクセスすると、以下の図 3 に示す出力が表示されます。

図 3. Web ブラウザーに出力された Java コレクション・クラスの使用結果
Web ブラウザーに出力された Java コレクション・クラスの使用結果

この PHP スクリプトの内容は以下のとおりです。

  • Java HashMap クラスのインスタンスを作成します。
  • Java Bridge! が含まれるストリングをマップに格納します。
  • Java と PHP 間での型の相互運用性を強調表示します。
  • 以下のコードに示すように、PHP 配列を作成して Java マップに格納します。
    $array = array(1, 2, 3, 4, 5);
    $map->put("stuff", $array);

このマップで put が呼び出されると、PHP 配列がそれに最も近い Java の型である Java Map に変換されます。同様に、get 呼び出しは $map から値を読み取って、その値を通常の PHP 配列に戻します。コピーを使わずにこのような変換が可能な理由は、PHP 配列には、PHP 配列と Java マップという 2 つの性格があるからです。


Java コレクションでの繰り返し処理

MoreJava.php スクリプトを以下のコードに置き換えてみてください。

<?php
	$list = new Java("java.util.ArrayList");
	var_dump($list);
	$date = new Java("java.util.Date", 70, 9, 4);
	echo "<br/>";
	
	$list->add("Java Bridge!");
	$list->add($date);
	$list->add(array(1, 2, 3, 4, 5));
	
	$iterator = $list->iterator();
	while ($iterator->hasNext() == TRUE) { 
	     var_dump($iterator->next()); echo "<br/>";
	}
?>

これでブラウザーから http://localhost:8080/MoreJava.php にアクセスすると、以下の図 4 に示す出力が表示されます。

図 4. Web ブラウザーに出力された Java コレクションでの繰り返し処理の結果
Web ブラウザーに出力された Java コレクションでの繰り返し処理の結果

この例の PHP は、Java ArrayList クラスを使用しています。さらに ArrayList からイテレーターを取得してコレクションを最初から最後まで調べ、イテレーターのコンテンツを Java Bridge! というストリングから Java の Date オブジェクト、そして最後の 5 つの数値が含まれる PHP 配列まで順に書き出しています。


静的メソッドおよびフィールドへのアクセス

静的メソッドと静的フィールドにアクセスするには、JavaClass を使用します。これは Java とは多少異なり、静的メソッドおよびフィールドにはクラス名を使って直接アクセスします。以下のコードで示しているのは、java.lang.SystemcurrentTimeMillis を呼び出す方法です。 java.lang.System:

<?php
	$system = new JavaClass("java.lang.System");
	var_dump($system);
	echo("</br>Current time: ".
		$system->currentTimeMillis()."</br>");
?>

図 5 に、このスクリプトを実行した場合のブラウザーの出力を示します。

図 5. Web ブラウザーに出力された静的メソッドへのアクセス結果
Web ブラウザーに出力された静的メソッドへのアクセス結果

静的フィールドにアクセスする方法も同様です。以下のコードは、java.lang.Integer クラスの MIN_VALUE 静的フィールドを表示します。

<?php
	$integerClass = new JavaClass("java.lang.Integer");
	var_dump($integerClass->MIN_VALUE);
?>

このスクリプトを実行すると、ブラウザーには図 6 の出力が表示されます。

図 6. Web ブラウザーに出力された静的フィールドへのアクセス結果
Web ブラウザーに出力された静的フィールドへのアクセス結果

PHP での Java 例外の捕捉

Java Bridge は Java 例外を JavaException のインスタンスに変換します。これは PHP スクリプトで捕捉される汎用的な PHP 例外クラスです。以下のコード・スニペットに、java.lang.System での無効な getProperty 呼び出しを示します。

<?php
	try { 
		$system = new JavaClass("java.lang.System");
    		$system->getProperty(FALSE);
	} catch (JavaException $exception) { 
	    echo "Cause: ".$exception->getCause();
	}
?>

図 7 は、このスクリプトを実行した場合のブラウザーの出力です。

図 7. Web ブラウザーに出力された Java 例外の捕捉結果
Web ブラウザーに出力された Java 例外の捕捉結果

WebSphere sMash 1.0の場合、getCause メソッドが返すのは例外そのものではなく、基礎となる Java 例外のクラス名であることに注意してください。最新の Project Zero ビルドでは、この異常な振る舞いは修正されて実際の Java 例外が返されるようになっています。


Java から PHP への型変換

表 1 に、それぞれの Java 型がどの PHP 型に変換されるかを記載します。一般的な手法は、情報の損失の可能性が最も少ない型に変換するというものです (例えば、intbyte に変換するなど)。変換は boxing が行われた Java 型と unboxing が行われた Java 型 (Integerint など) に同じように適用されることにも注意してください。

表 1. Java から PHP への型変換
Java 型PHP 型注釈
nullnull 
Integer/intint 
Double/doubledouble 
Boolean/booleanbool 
Byte/byteint 
Character/charint 
Short/shortint 
Long/longint 
Float/floatdouble 
byte[]string 
StringstringPHP ストリングはランタイム・エンコード方式によってエンコードされます。
Maparrayネストされたマップを含め、個々の要素の型はこの表に従って変換されます。
Object[]array配列の変換を参照
その他すべて適用外Java Bridge によってラップされ、汎用的な PHP オブジェクトになります。

型変換についての詳細は、Project Zero の Web サイトを参照してください。


Java Bridge の制約事項

Java Bridge が目的としているのは、PHP スクリプトが Java クラスを簡単に使用できるようにすることです。その点から言うと、Java Bridge にはいくつか不足している拡張機能があります。そのうち最も顕著なのは、オーバーロードされたメソッドを確実に呼び出す機能です。

Java Bridge がメソッドまたはコンストラクターの選択基準としているのは、指定された引数の数だけです。複数のメソッドまたはコンストラクターが考えられる場合、Java Bridge は最初の可能性を選択して試行しますが、これはあまりにも単純で、コンストラクターやメソッドが誤った引数の型で呼び出さると例外がスローされる結果となります。

シグニチャーによるオーバーロードの選択

適切なオーバーロードを選択する上での問題は、最新の Project Zero ビルド (WebSphere sMash 1.0では使用不可) では解決されています。その方法は、新しい JavaSignature クラスを追加することでした。JavaSignature を使って以下のように検索する引数の型を定義すると、スクリプトはどのコンストラクターまたはメソッドを呼び出すかを正確に指定することができます。

<?php
	$signature = new JavaSignature(JAVA_STRING);
	$string = new Java("java.lang.String", 
          $signature, "Hello World!");

	var_dump($string->toLowerCase());
	var_dump($string->split(" "));
	var_dump($string->toUpperCase());
?>

JavaSignature の引数は、以下の PHP 定数の中から使われます。

  • JAVA_BOOLEAN
  • JAVA_BYTE
  • JAVA_CHAR
  • JAVA_SHORT
  • JAVA_INT
  • JAVA_LONG
  • JAVA_FLOAT
  • JAVA_DOUBLE
  • JAVA_STRING
  • JAVA_OBJECT

上記の例では単一の Java String (JAVA_STRING) を引数として取り、java.lang.String でコンストラクターを選択しています。一方、引数が複数ある場合にはカンマで区切り、例えば newJavaSignature(JAVA_STRING, JAVA_INT) のようにします。また、JAVA_ARRAY 修飾子を使用して Java 型の配列を指定することもできます。例えば newJavaSignature(JAVA_STRING | JAVA_ARRAY) とすると、ストリングの配列を選択することができます。

以下に記載するスニペットは、java.lang.StringvalueOf メソッドのオーバーロードを選択する JavaSignature です。メソッド呼び出しには、シグニチャーが最初の引数としてどのように渡されるかに注目してください。これで、Java Bridge はチェックする対象がシグニチャーであることがわかります。

<?php
	$class = new JavaClass("java.lang.String");
	$signature = new JavaSignature(JAVA_INT);
	var_dump($class->valueOf($signature, 1234567890));
?>

大/小文字の区別があるメソッド名

PHP のメソッドでは大/小文字が区別されませんが、Java では区別されます。Java Bridge は大/小文字を区別するため、PHP のメソッド名は Java メソッド名と完全に一致させなければなりません。

静的メソッドおよびフィールド

Java 開発者はクラス名を使って静的メソッドおよびフィールドを呼び出すことに慣れていますが (例えば、Integer.MAX_VALUE)、PHP ではこの方法をまだ使えないため、JavaClass を使用しなければなりません。スクリプトは JavaClass のインスタンスを作成し、そのインスタンスを使って静的メソッドを呼び出したり、静的フィールドにアクセスしたりします。これは例外的なことです。なぜなら開発者がインスタンスではない (静的) メソッドとフィールドにアクセスするためだけにオブジェクトのインスタンスを作成しなければならないためです。

コレクションでの繰り返し処理

前に記載したサンプル・コードで、Java コレクションで繰り返し処理を行う方法を示しました。これは PHPの foreach 文に比べると、かなり長く、表現力にも劣ります。現時点では、Java Bridge は Java イテレーターと PHP の foreach 文を統合していません。そのため PHP で Java イテレーターを使用するには、以下のコードに示す方法を使用します。

$iterator = $list->iterator();
while ($iterator->hasNext() == TRUE) { 
	 var_dump($iterator->next()); echo "<br/>";
}

実際の例ですべてを組み合わせる

次のセクションでは、これまでのセクションをすべてまとめ、Java Bridge を実際に使用する方法を説明します。このサンプルでは、Apache Lucene を使用してファイルに索引を付けて検索できる単純な検索エンジンを PHP で作成します。Apache Lucene は充実した機能を備えたハイパフォーマンスのテキスト検索エンジン・ライブラリーです。このライブラリーは一貫して Java で作成されているため、特にクロス・プラットフォームのアプリケーションをはじめ、フルテキスト検索が必要なアプリケーションのほとんどすべてに適しています。詳細については、Apache Lucene サイトを参照してください。

索引の作成

最初のステップは、Lucene を入手することです。この例では、Lucene 2.2.0 をベースとする Lucene の PHP 実装と比較するため、最新バージョンの Lucene は使用しません (ただし、最新バージョンでも完全に機能します)。

  1. lucene-2.2.0.tar.gz をダウンロードします。このファイルは、例えばミラー・サイトの http://mirror.cc.columbia.edu/pub/software/apache/lucene/java/archive/ からダウンロードすることができます。
  2. ファイルを解凍します (または、tar -xvzf lucene-2.2.0.tar.gz を実行します)。
  3. lucene-core-2.2.0.jarlucene-demos-2.2.0.jar という 2 つの JAR ファイルを見つけてください。

次のステップでは、Lucene 検索用の索引を作成する PHP スクリプトを作成します。

  1. Java パースペクティブで、新しいアプリケーションを作成するために File -> New -> Other の順に選択します。WebSphere Smash PHP Application を選択し、Lucene という名前を付けます。
  2. public フォルダーを右クリックして New -> File の順に選択します。
  3. ファイル名として index.php と入力し、Finish をクリックします。
  4. 前のステップで見つけた 2 つの Lucene JAR ファイルを Lucene/lib ディレクトリーにコピーします。
  5. WebSphere sMash が Lucene Java ライブラリーを使用することを確実にするため、プロジェクト名の Lucene を右クリックして WebSphere sMash Tools -> Resolve の順に選択します。
  6. ファイルに以下のコードを追加します。
    <html>
    <head>
       <title>Search Index</title>
    </head>
    <body>
    	<form name="input" action="/index.php" method="POST">
    		<label for="directory">Directory:</label>
       	    <input type="text" name="directory">
    		<label for="extension">File Extension:</label>
        	<input type="text" name="extension">
          	<input type="submit" name="action" value="Index!">
       	</form>   
    </body>
    </html>
  7. プロジェクト名の Lucene を右クリックし、WebSphere sMash Application -> Run の順に選択してアプリケーションを実行します。Web ブラウザーでローカル・サーバー (例えば、http://localhost:8080/index.php) にアクセスすると、図 8 のような画面が表示されます。
    図 8. ディレクトリーの選択とファイル拡張子の入力ページ
    ディレクトリーの選択とファイル拡張子の入力ページ
  8. 他にも追加するコードがあるため、この時点ではまだ索引を付けないでください。最終的にフォームがサブミットされると、PHP スクリプトが Lucene 検索用の索引を作成し、ディレクトリー内の拡張子が一致するすべてのファイルに索引を設定します。PHP スクリプトはさらに、このディレクトリーを再帰的に検索してファイルを追加していきます。
  9. ここで、以下の PHP コードを index.php に追加します。
    <?php
    
    $directory = dirname(__FILE__)."/../index";
    if (file_exists($directory) === FALSE) {
    	mkdir($directory);
    }
    
    define("INDEX_DIRECTORY", $directory);
    
    try {	
    	$extension = zget('/request/params/extension');
    	if (strlen($extension) > 0) {
    		$directory = zget('/request/params/directory');
    		if (strlen($directory) > 0) {
    			index_directory($directory, $extension);
    		}
    	}
    } catch (JavaException $exception) {
    	echo "Index creation failed [".
    	$exception->getMessage()."]</br>";	
    }
    ?>
  10. まだ完成したわけではないので、実行するのは待ってください。このコードはグローバル・コンテキストからフォーム変数を取得し、変数が入力されているかどうかをチェックします。変数が入力されている場合は index_directory 関数を呼び出します。この関数は次のリストに説明されているように、一致するファイルをすべて Lucene 検索用索引に追加する役目を果たします。
  11. 今度は以下の PHP コードを index.php に追加してください。
    /**
     * This creates an index from scratch and adds all the documents
     * by recursing from the directory passed in. It also checks
     * each candidate file to see if it matches the file extension.
     */
    function index_directory($path, $extension) {
        	echo "Indexing! [".$path.",".$extension."]</br>";
        	
    	// Uses the SimpleAnalyzer because we will do a performance comparison 
              with the PHP 
    	// implementation of Lucene in the Zend Framework and it is the closest match	
    	$analyser = new Java("org.apache.lucene.analysis.SimpleAnalyzer");
    	$policy = new Java("org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy");
    	
    	$file = new Java("java.io.File", INDEX_DIRECTORY, FALSE);
    	$file_directory = new JavaClass("org.apache.lucene.store.FSDirectory");
    	$directory = $file_directory->getDirectory($file);
    	
    	$writer = new Java("org.apache.lucene.index.IndexWriter", 
    		$directory, TRUE, $analyser, TRUE, $policy);
    	
    	$writer->setUseCompoundFile(FALSE);
    
    	// Insert some calls to microtime() for comparison
    	$start_time = get_microtime();	
    	recursive_index_directory($writer, $path, $extension);
    	$count = $writer->docCount();
    
    	// Lucene only matches the first 10,000 tokens by default
    	$writer->setMaxFieldLength(1000000);
    	$end_index_time = get_microtime();
    	
    	$writer->optimize();
    	$end_time = get_microtime();
    	$writer->close();	
    	
    	echo "Finished indexing [".$count." documents]</br>";
    	$t1 = $end_index_time - $start_time;
    	$t2 = $end_time - $end_index_time;
    	echo "Time to index  = $t1 </br>";
    	echo "Time to optimize  = $t2 </br>";
    }

    Java Lucene API の詳細については、この記事では説明しません。一言で言えば、このコードが作成しているのは IndexWriter オブジェクトです。これは主要な索引付けオブジェクトで、スクリプトがディレクトリーを再帰的に検索するときには、このオブジェクトに対してファイルを追加します。注目すべき点は、RAM ディスクなどの多種多様なソースごとに索引を付けられることです。この例では通常のファイル・システムからファイルを読み取っているので、FSDirectory クラスを使用しています。

    IndexWriter がセットアップされると、スクリプトは実際の索引付けを行うために recursive_index_directory を呼び出します。この関数には、開始ディレクトリーである IndexWriter と、候補ファイルを突き合わせるファイル拡張子が渡されます。

    コードのこの後に続くセクションによって、索引付けスクリプトは完成します。このコードの大部分は汎用の PHP スクリプトで、ディレクトリー内のすべてのファイルを列挙して、それぞれを順に処理します。スクリプトは索引を付けるファイルを決定すると、FileDocument を作成します。このオブジェクトには該当するファイルへの完全修飾パスを設定して、IndexWriter に追加します。
    /**
     * Processes a file by adding it to the indexer.
     */
    function index_file($writer, $path) {
    	echo "Indexing file [".$path."]</br>";
    
    	try {		
    		// A few of the files we indexed in the examples have non
    		// UTF-8 characters so we just skip indexing those files!
    		
    		$file = new Java("java.io.File", $path, FALSE);
    		$file_document = new JavaClass("org.apache.lucene.demo.FileDocument");
    		$document = $file_document->Document($file);
    		$writer->addDocument($document);
    
    	} catch (JavaException $exception) {
    		echo "Invalid characters in file!\n";
    	}	
    }
    
    function get_microtime(){
    	list($part_one,$part_two) = explode(' ',microtime());
    	return ((float) $part_one + (float) $part_two);
    }
    
    /**
     * Indexes all matching files (by extension) in the directory tree. 
     */
    function recursive_index_directory($writer, $path, $extension) {
        echo "Indexing directory [".$path."]</br>";
    	
        // Remove any trailing slash first
        if (substr($path, -1) == '/') {
            $path = substr($path, 0, -1);
        }
        
        // Make sure the directory is valid
        if (is_dir($path) == TRUE) {
    	    if (is_readable($path) == TRUE) {
    		 $handle = opendir($path);
    		
    		  // Scan through the directory contents
        		  $extension_length = strlen($extension);
    		  while (FALSE !== ($item = readdir($handle))) {
    		    if ($item != '.') {
    		        if ($item != '..') {
    		        $index_path = ($path.'/'.$item);
    		        if (is_dir($index_path) == TRUE) {
    		             recursive_index_directory(
    			        $writer, $index_path, $extension);
    		   } else {
    
    		         $position = strpos(strtolower($index_path), $extension);
    		                	
    		         // Very rough and ready way to check for trailing extension!
    		         if ($position == (strlen($index_path)-$extension_length)) {
    			  index_file($writer, $index_path, $extension);
    		                	}
    		                }
    		            }
    		        }
    		    }		
    		    closedir($handle);
    	    }
        }
        return TRUE;
    }
  12. Web ブラウザーでこのスクリプトにアクセスして、図 9 に示すようにフォーム変数を入力してください。
    図 9. Web ブラウザーに出力されたディレクトリーの索引付け結果
    Web ブラウザーに出力されたディレクトリーの索引付け結果
  13. Index! をクリックすると、スクリプトによって、選択されたファイルに索引が付けられます。上記の例では、スクリプトによって C ソース・コードが指定され、5 つのソース・ファイルに索引が付けられています。Eclipse プロジェクトを最新の表示にすると、Index という新しいディレクトリーが作成されているはずです。このディレクトリーに、Lucene 検索エンジンによって生成された検索用索引ファイルが配置されます (図 10 を参照)。
    図 10. WebSphere sMash アプリケーションのディレクトリー構造
    WebSphere sMash アプリケーションのディレクトリー構造

最後のステップは、ユーザーが索引に照らし合わせて検索を実行できるようにするためのフォームを作成することです。

  1. public フォルダーを右クリックして New -> File の順に選択します。
  2. ファイル名として search.php と入力し、Finish をクリックします。
  3. ファイルに以下のコードを追加します。
    <html>
    <head>
       <title>Query</title>
    </head>
    <body>
    	<form name="input" action="/search.php" method="POST">
    		<label for="query">Search Query:</label>
       	    <input type="text" name="query">
          	<input type="submit" name="action" value="Search!">
       	</form>   
    </body>
    </html>
  4. このスクリプトを実行すると、Web ブラウザーには図 11 のようなページが表示されます。
    図 11. 検索クエリー・ページ
    検索クエリー・ページ
  5. 以下の PHP コードを search.php に追加します。
    <?php
    
    /**
     * This runs a search through an index already created.
     */
    function search_index($path, $query) {
    	echo "Searching for [".$query."]</br>";
    
    	$file = new Java("java.io.File", $path, FALSE);
    	$file_directory = new JavaClass("org.apache.lucene.store.FSDirectory");
    	$directory = $file_directory->getDirectory($file);
    	$searcher = new Java("org.apache.lucene.search.IndexSearcher", $directory);	
    	$analyser = new Java("org.apache.lucene.analysis.SimpleAnalyzer");
    	$parser = new Java("org.apache.lucene.queryParser.QueryParser", 
    		"contents", $analyser);
    		
    	$parsed_query = $parser->parse($query);	
    	$hits = $searcher->search($parsed_query);	
    	$count = $hits->length();
    	for ($index = 0; $index < $count; $index++) {
    		$document = $hits->doc($index);
    		echo $index.") ".$document->get("path")."</br>";
    	}
    	echo "</br>Finished searching [".$count." hits]</br>";
    }
    
    try {	
    	$directory = dirname(__FILE__)."/../index";
    	define("INDEX_DIRECTORY", $directory);
    	$query = zget('/request/params/query');
    	if (strlen($query) > 0) {
    		search_index($directory, $query);
    	}
    } catch (JavaException $exception) {
    	echo "Index search failed [".$exception->getMessage()."]</br>";	
    }
    ?>
  6. 前と同じく、このスクリプトではいくつかの Lucene クラスを活用しています。このスクリプトで重要な部分は、index.php のように IndexWriter クラスを使用するのではなく、代わりに IndexSearcher を使用しているという点にあります。これは、前に作成した索引ファイルと同じディレクトリーで構成され、ユーザーによってフォームに入力されたストリングを使用してクエリー・オブジェクトを作成します。クエリー・ストリングを解析するには、Lucene QueryParser を使用すると簡単です。

    クエリーが解析された時点で、スクリプトは IndexSearcher で検索を実行できるようになります。検索結果として返されるリストには、スクリプトが一致項目を列挙し、各項目のパスを表示します。
  7. Web ブラウザーに search.php を表示して、何らかの検索語を入力してください (図 12 を参照)。
    図 12. Web ブラウザーに出力された検索クエリーの実行結果
    Web ブラウザーに出力された検索クエリーの実行結果

上記の例では、「TSRM」と「int」というキーワードに一致する 5 件の結果が見つかっています。Lucene には、多種多様な検索語をサポートする強力なクエリー構文があります。可能な検索クエリーについての詳細は、Apache Lucene サイトを参照してください。

パフォーマンスの比較

index.php に追加したソース・コードを注意して見てみると、microtime の呼び出しとコメントがいくつか追加されていることに気付くはずです。これは、パフォーマンスをチェックするためのものです。

私たちが実行したのは、単純に時間を計測するというチェックです。興味の対象は、索引を作成するまでの時間を以下の 3 つの異なるソフトウェアを使って比較することでした。

  • WebSphere sMash Java Bridge によって呼び出される Lucene の Java 実装
  • Java アプリケーションから呼び出される Java Lucene
  • Zend Framework での Lucene の PHP 実装

公平に比較できるよう、Zend 実装がベースにしている Lucene Version 2.2.0 を使用しました。また、Lucene SimpleAnalyser も使用しています。Zend 実装についての詳細については、この記事では説明しませんが、これは Lucene コードを忠実に移植したもので、Java バージョンによって生成されるフォーマットとまったく同じフォーマットの索引を生成します。

パフォーマンスを比較する方法として、PHP 5.3 ソース・ツリーの下にあるすべての PHP テスト・スクリプト (*.phpt ファイル) ファイルに索引を付けました。索引を作成して最適化するまでにかかった時間を表 2 に記載します。

表 2. Lucene 検索のパフォーマンス比較
技術時間 (秒)
WebSpere sMash Java Bridge 9
Java Lucene 8
Zend Search Lucene 200

上記の表から、この 3 つの技術を「そのまま」使用した場合の時間の差がひと目でわかります。いずれの時間でも Java JIT がオンになっていますが、それによって Lucene のようなアプリケーションでは実行時間に大幅な差が生じます。

いずれの理由にしても、Zend 実装を使用しない理由にはなりません。実際、Java は使用せずに原則として PHP を開発言語として使用する場合には、同じく PHP で作成された検索エンジンを使用すると多くの利点がもたらされます。コードを容易に理解して変更できるようにするといった配慮のほうが、パフォーマンスを向上させるための配慮より重要視されるはずです。

さらに興味深い点は、PHP と Java Bridge を使用した場合と Java アプリケーションを使用した場合との比較です。この 2 つの場合の実行にかかる時間があまり変わらないという事実は、Java Bridge、あるいは実際には Java VM で PHP を実行してもあまり余分に時間がかからないということを示しています。

もちろん、Java Bridge に対しては他の PHP もあります。例えば、Zend Platform での商用実装や、sourceforge.net から入手できるオープンソースの実装などです。私たちはこのいずれの実装もまだ使用していませんが、そのような実装が存在するという事実が、Java の得意分野 (アルゴリズムに関するパフォーマンス) には Java を使用し、その他は容易に使える PHP を利用するという意見を後押しします。

この実験を繰り返してみれば、作成される索引にはわずかな違いがあることに気付くはずです。Zend 実装が持つ便利な機能の 1 つは、Java 実装とまったく同じフォーマットの索引を作成するという機能です。つまり、作成された索引は標準 Java ツールでチェックすることができます (ツールの一例は Luke で、このツールは Luke サイトからダウンロードできます)。違いはいずれも比較的簡単に説明できるもので、時間の比較には影響しません。例えば、PHP アナライザーと Java アナライザーには多少の違いがあるなどです。


まとめ

この記事で説明した内容は以下のとおりです。

  • PHP と WebSphere sMash でアプリケーションを作成する方法
  • Java Bridge を使用して Java オブジェクトを作成し、呼び出す方法
  • PHP スクリプトから Java コレクションを使用する方法
  • Java Bridge がどのように型を強制し、例外を処理するかについての説明
  • Java Lucene ライブラリーをベースに検索エンジンを開発する方法
  • Java Lucene ライブラリーのパフォーマンスについての検討

この記事を最後まで読み終えた今、Java ライブラリーの使用方法と PHP スクリプトの作成を独自に拡張する力は身についているはずです。例えば WebSphere sMash でさらに多くの Java ライブラリーを PHP と組み合わせてみるなど、皆さん独自にどのような拡張を行ったかを Project Zero フォーラムに報告してください。Zero グローバル・コンテキストやその他の関連トピックについて詳しく学ぶには、「参考文献」セクションに記載した「WebSphere sMash Developer's Guide」を参照してください。

謝辞

この記事の作成に協力してくださった ZSL Inc. の Naveen Noel Jakkamsetti 氏に感謝いたします。

参考文献

学ぶために

製品や技術を入手するために

  • PHP 版 WebSphere sMash のダウンロードおよびインストール手順についての詳細は Project Zero サイトに記載されています。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=WebSphere, Java technology
ArticleID=347395
ArticleTitle=WebSphere sMash で Java と PHP を統合する
publish-date=09242008