容易に分かることですが、HSQLDBをEclipseに統合することに興味を持っている開発者は2種類存在します。
- クライアント開発者:HSQLDBを単にデータのリポジトリとして使う人
- エンジン開発者:新しいスケーラー機能またはストアード・プロシージャーを追加することで、HSQLDBが認識するSQL方言を拡張したいと思っている人
1番目のタイプの開発者は、SQLステートメントを発行できるようにHSQLDB JDBCドライバーに容易にアクセスできる必要がありますが、異なるHSQLDBインスタンスを実行・停止する手軽な方法もあれば便利です(例えば、異なるテーブルのセットを必要とする2つのプロジェクトなどの場合)。もっと上級の開発者であればJavaBeans(TM)のマッピング属性をテーブルの行に生成したいかも知れませんし、データ・アクセス・オブジェクトを生成してデータベース中のデータをクエリーしたり更新したりする手段を用意したいかも知れません。
2番目のタイプの開発者が必要とするものの中には、データベース・エンジンに新しいJavaクラスを追加できる機能も含まれるでしょう。Eclipseデバッガーを使ってブレークポイントを設定し、データベース・サーバーに呼び出されたカスタム・メソッド内部の変数も調べられれば、とも思っているかもしれません。
例えば私が、文字行列のCRC32チェックサムを返すスケーラー機能を追加したいとすると、リスト1に示すようにパブリックな静的メソッドを含むクラスを作る必要があります。そうすれば、その機能はリスト2のスクリプトが実行された後にはSQLステートメントで使えるようになります。
リスト1. HSQLDBエンジンを拡張するJavaクラス
package hsqldb.functions;
import java.util.zip.CRC32;
public class CrcUtil {
public static long crc(String data) {
CRC32 crc32 = new CRC32();
crc32.update(data.getBytes());
return crc32.getValue();
}
}
|
リスト2. HSQLDBエンジンに新しいスケーラー機能を追加するSQLスクリプト・ファイル
CREATE ALIAS CRC FOR "hsqldb.functions.CrcUtil.crc";
GRANT ALL ON CLASS "hsqldb.functions.CrcUtil" TO PUBLIC;
|
クライアント開発者が望むようなフィーチャーの大部分はこのシリーズの範囲外です。多くのSQLモニター、OOリレーショナル・マッピング・ツールやその他のデータベース・ユーティリティがEclipseプラグインとして既に存在しています。これらはデータベースと独立しており、我々が作るHSQLDBプラグインと併せて使うことでクライアント開発者にとっては完全な環境ができるのです。ですからこのシリーズでは、データベース・エンジン開発者が望むようなフィーチャーについて主にとりあげることにします。
元々の予定では、このシリーズ第2回目では特別なHSQLDBビューを作ることを考えていました。これはサーバー・ポートやパスワード、それにデータベース・ファイルのパス等多くの要素が編集でき、それぞれが個別に起動停止できるからです。ただ、Eclipse JDT自体に大部分のフィーチャーが既にあることに気がついたので、計画は大幅変更になってしまいますが、HSQLDBを単純なJavaアプリケーションとして実行することにします。
とりあえず、特別なJavaプロジェクトを定義し、そこにはHSQLDBライブラリが既にできているとします。そしてこのプロジェクトをサーバーとサーバー関連のツール(データベース・マネージャーとスクリプト・ツール)を起動するためのアクションと結びつけます。もちろん、HSQLDBコネクション・パラメーターは(ワークベンチ全体に対して1回のみ、というのではなく)プロジェクト毎の単位で保存する必要があります。
Eclipse流の新しいプロジェクト定義をプロジェクト・ネイチャー(Project Nature)と呼びますが、これはEclipseプラットフォームで使用できる各種拡張ポイントの一つです。各プロジェクトは多くのネイチャーを持つことができるので、全く新規のプロジェクト・クラスを作ったり複製したり、Javaプロジェクト・クラスから継承したりする必要はありません。単にワークベンチに対して、その新しいネイチャーが既に(JDTで)定義されたJavaプロジェクト・ネイチャーに依存していることを伝えさえすればよいのです。Javaネイチャーには、(ユニット・テストケースの生成やJavaアプリケーションのデバッグといった)Javaクラスを編集するために必要なすべてのツールが揃っています。HSQLDBネイチャー・エンジンはHSQLDBを起動停止し、設定する手段を提供しています。
リスト3は新しいネイチャーを定義するplugin.xmlのコード断片です。plugin.xmlはIProjectNatureを実装する必要のあるクラスと関連づけされており、またビルダーやその他の要素をワークベンチに追加することができますが、この第2リリースではHSQLDBは何もする必要はありません。いずれにせよ、ネイチャー実装クラスを用意する必要があります。そうしないとワークベンチがプロジェクトにネイチャーを追加しません。ここでは新しいネイチャーを単純に、HSQLDBアクションが特定のプロジェクトで使えるように(または使えないように)するための手段として使います。
リスト3. HSQLDBプロジェクト・ネイチャーを定義するplugin.xmlマニフェスト・ファイルの断片
<extension
id="hsqldbEngine"
name="HSQLDB Engine"
point="org.eclipse.core.resources.natures">
<requires-nature
id="org.eclipse.jdt.core.javanature">
</requires-nature>
<runtime>
<run
class="hsqldb.nature.DBEngineNature">
</run>
</runtime>
</extension>
|
どのJavaプロジェクトでもHSQLDBメニューが使えるようになります。図1に示すように、最初はメニューには一つの項目「Add HSQLDB Engine nature」しかありません。EclipseコンソーシアムのEMFプラグイン・セットを使ったことがあれば、これも同じ概念だということに気がつくでしょう。
図1. Javaプロジェクトのコンテキスト・メニューのHSQLDBサブメニュー
ユーザーがこのアクションを選択すると、このメニューはもっと多くのオプションを表示するように変わります。オプションにはHSQLDBの起動停止、データベース・マネージャーをスタンドアローンで起動するか、クライアント/サーバー・モードで起動するかなどがありますが、これを図2に示します。ワークベンチ・ツールバー最上位の「HSQLDB」メニューは我々のプラグインの第1リリースで定義したものですが、これが消え、各Javaプロジェクトのコンテキスト・メニューの「HSQLDB」サブメニューに置き換えられます。
図2. Engineネイチャーが追加された後のHSQLDBサブメニュー
HSQLDBの第1リリースでは、各アクションはそれが実際に実行できるものかどうかをチェックしなければなりませんでした(例えばデータベース・サーバーの2つのインスタンスを起動してはならない、等)。Eclipseでは各メニュー項目の状態をプログラム的に設定する簡単な方法が無いことは既に説明しましたが、これはワークベンチが自分ですべてを行うため、ワークベンチが実際にコードを呼び出さなければならない時には、プラグインのローディングを最後の瞬間まで遅らせてしまうからなのです。
今度は各アクションがワークスペース・リソース(Javaプロジェクト)に結びついているので、ワークベンチは各項目がいつ見え、いつ使えるのかが、プラグインを呼び出さなくても分かるのです。要素visibilityとenablementには、(リソース・プロパティをクエリーして各アクション状態を設定する)objectStateのような子があります。リスト4は5つのobjectContributionを定義するplugin.xml断片を示します。
- 「Run HSQLDB SQL Script」アクションは、これは新しいネイチャーを持つプロジェクト内部にあるどの*.sqlファイルからも見ることができます。
- 「HSQLDB」サブメニューは、どのJavaプロジェクトからも見え、2つのグループからなる項目を含んでいます。両グループに関連したアクションがある場合には、2つのグループを分ける線が引かれます。
- このシリーズ第1回で定義した(サーバーの起動停止など)HSQLDBアクションはすべて、ここで新たに定義したネイチャーがJavaプロジェクトにある時にのみ見えます。
- 「Add HSQLDB Engine nature」で、プラグイン自体が定義したネイチャーがJavaプロジェクトに無い時に見えます。
- そして最後に、「Remove HSQLDB Engine nature」は、Javaプロジェクトに我々の新しいネイチャーがある時に見えます。
リスト4. HSQLDB メニュー動作を定義するplugin.xmlマニフェスト・ファイルの断片
<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.core.resources.IFile"
nameFilter="*.sql"
id="hsqldb.ui.SQLScriptFiles">
<visibility>
<objectState
name="projectNature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
</visibility>
<action
label="Run HSQLDB Script"
class="hsqldb.ui.popup.actions.HsqldbRunScript"
menubarPath="additions"
enablesFor="1"
id="hsqldb.ui.HsqldbRunScript">
<enablement>
<objectState
name="projectSessionProperty"
value="hsqldb.ui.running">
</objectState>
</enablement>
</action>
</objectContribution>
<objectContribution
objectClass="org.eclipse.jdt.core.IJavaProject"
id="hsqldb.ui.HsqldbEngineMenu">
<menu
label="HSQLDB"
path="additions"
id="hsqldb.ui.HsqldbProject">
<separator
name="hsqldb.ui.group1">
</separator>
<separator
name="hsqldb.ui.group2">
</separator>
</menu>
</objectContribution>
<objectContribution
objectClass="org.eclipse.jdt.core.IJavaProject"
id="hsqldb.ui.HsqldbEngineActions">
<visibility>
<objectState
name="nature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
</visibility>
<action
label="Run Database Manager (Standalone)"
icon="icons/dbmansa.gif"
class="hsqldb.ui.popup.actions.RunDatabaseManagerStandalone"
menubarPath="hsqldb.ui.HsqldbProject/group1"
enablesFor="1"
id="hsqldb.ui.RunDatabaseManagerStandalone">
<enablement>
<not>
<objectState
name="sessionProperty"
value="hsqldb.ui.running">
</objectState>
</not>
</enablement>
</action>
<action
label="Run Database Manager (Client)"
icon="icons/dbman.gif"
class="hsqldb.ui.popup.actions.RunDatabaseManager"
menubarPath="hsqldb.ui.HsqldbProject/group1"
enablesFor="1"
id="hsqldb.ui.RunDatabaseManager">
<enablement>
<objectState
name="sessionProperty"
value="hsqldb.ui.running">
</objectState>
</enablement>
</action>
<action
label="Stop HSQLDB Server"
icon="icons/stop.gif"
class="hsqldb.ui.popup.actions.StopHsqldbServer"
menubarPath="hsqldb.ui.HsqldbProject/group1"
enablesFor="1"
id="hsqldb.ui.StopHsqldbServer">
<enablement>
<objectState
name="sessionProperty"
value="hsqldb.ui.running">
</objectState>
</enablement>
</action>
<action
label="Start HSQLDB Server"
icon="icons/start.gif"
class="hsqldb.ui.popup.actions.StartHsqldbServer"
menubarPath="hsqldb.ui.HsqldbProject/group1"
enablesFor="1"
id="hsqldb.ui.StartHsqldbServer">
<enablement>
<not>
<objectState
name="sessionProperty"
value="hsqldb.ui.running">
</objectState>
</not>
</enablement>
</action>
</objectContribution>
<objectContribution
objectClass="org.eclipse.jdt.core.IJavaProject"
id="hsqldb.ui.JavaProjects">
<visibility>
<not>
<objectState
name="nature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
</not>
</visibility>
<action
label="Add Database Engine nature"
class="hsqldb.ui.popup.actions.AddDBEngineNature"
menubarPath="hsqldb.ui.HsqldbProject/group2"
enablesFor="1"
id="hsqldb.ui.AddDBEngineNature">
</action>
</objectContribution>
<objectContribution
objectClass="org.eclipse.jdt.core.IJavaProject"
id="hsqldb.ui.HsqldbEngineProjects">
<visibility>
<objectState
name="nature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
</visibility>
<action
label="Remove Database Engine nature"
class="hsqldb.ui.popup.actions.RemoveDBEngineNature"
menubarPath="hsqldb.ui.HsqldbProject/group2"
enablesFor="1"
id="hsqldb.ui.AddDBEngineNature">
</action>
</objectContribution>
</extension>
|
最初のobjectContributionはIFileリソースに対する最上位レベルのコンテキストに入っており、2番目のobjectContributionはJavaプロジェクトの「HSQLDB」サブメニューを定義しています。残りのobjectContributionはサブメニュー自体に表示されます。つまり3番目はgroup1に、最後はgroup2に表示されますが、ある一つのプロジェクトに対しては一つしか見えません。図3と4はPDEマニフェスト・エディターでのこうしたobjectContributionorg.eclipse.ui.popupMenusの拡張ポイントを示します。
図3. PDEマニフェスト・エディターでプラグイン(#1/2)に定義されるアクション
図4. Actions defined by the plugin (#2/2) on the PDE manifest editor
適切なプロジェクトに対してのみアクション(objectContributions)が表示されますが、各アクションはまた、使用可能にする意味のある時にのみ使用可能になります。例えばHSQLDBサーバーを停止できるのは、選択されたプロジェクトに対してHSQLDBサーバーのインスタンスが起動された後のみであって、それが停止されるまで他のインスタンスを起動することができません。これは新しいインスタンスを起動しても、既に使われている同じTCPポートをリスンすることになるはずだからです。同様に、サーバーが起動された後にのみデータベース・マネージャーをクライアント/サーバー・モードで起動できるのです。起動しているサーバーが無い時には、データベース・マネージャーをスタンドアローン・モードで実行します。これはスタンドアローン・モードではプロセス内でHSQLDBエンジンが実行するためです。
これは各プロジェクト・リソースにセッション・プロパティを関連付けることで実現されています。セッション・プロパティはワークベンチが閉じると失われるので、与えられたリソースに対してプラグインが維持する必要のある一時的な状態を保持するのに便利です。セッション・プロパティはプラグイン・マニフェストによってではなく、プログラム的に定義されます。
各Javaプロジェクトに対してカスタムのプロパティ・ページを関連付ける必要もあるので、それぞれがそのHSQLDBサーバーに対して異なる接続パラメーター(ユーザーやパスワードなど)を持つことができます。リスト5はプロパティ・ページ・エクステンションのコード断片です。とりあえずplugin.xmlはこのくらいにして、少しJavaコードを見てみましょう!
リスト5. HSQLDBプロジェクト・プロパティ・ページのplugin.xmlマニフェスト・ファイルの一部
<extension
id="hsqldb.ui.property"
point="org.eclipse.ui.propertyPages">
<page
objectClass="org.eclipse.jdt.core.IJavaProject"
name="HSQLDB Server"
class="hsqldb.ui.properties.DBEnginePropertyPage"
id="hsqldb.ui.properties.DBEnginePropertyPage">
<filter
name="nature"
value="hsqldb.ui.hsqldbEngine">
</filter>
</page>
</extension>
|
Javaプロジェクトにhsqldb.ui.hsqldbEngineネイチャーを追加するのは、リスト6に示すようにごく単純です。対応するProjectDescriptorを取得して、プロジェクトに関連づけられた全ネイチャーのIDを含む新しい要素をString配列に追加します。
IJavaProjectがIProjectではないと言われると、直感に反していると思うかも知れません。ワークベンチもJDTも並列リソース階層構造をとっており、ワークベンチはプロジェクトやフォルダー、ファイルを表しJDTはJavaオブジェクトやパッケージ、クラスなどを表すのです。「Add HSQLDB Engine nature」アクションはJavaプロジェクトに結びついているので、最初に、対応するワークベンチ・プロジェクトを取得する必要があります。それからそのプロジェクトにhsqdbEngineネイチャーを含む新しい記述を付加するのです。
(ワークベンチの)プロジェクト記述を更新した後、(Java)プロジェクトCLASSPATHをカスタマイズし、HSQLDB内部に「database」というデフォルト名のデータベースを作るようにHSQLDBに指示します。するとユーザーにその変化(新しいファイルやライブラリ)が見えるようにプロジェクトが更新されます。
このシリーズ第1回の記事で説明した通り、HSQLDBデータベース・クラス自体に関する操作の大部分はクラスHsqldbUtilの静的メソッドですが、このクラスはこの第2回で追加される、新しい静的メソッドもいくつか持つことになります。
リスト6. JavaプロジェクトにDBエンジン・ネイチャーを追加する
IProject proj = currentProject.getProject();
IProjectDescription description = proj.getDescription();
// add the hsqldbEngine nature to the project
String[] natures = description.getNatureIds();
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);
// must prefix with plugin id!!!
newNatures[natures.length] = "hsqldb.ui.hsqldbEngine";
description.setNatureIds(newNatures);
proj.setDescription(description, null);
// add the HSQLDB classpath variable
IClasspathEntry[] rawClasspath = currentProject.getRawClasspath();
IClasspathEntry[] newRawClasspath = new IClasspathEntry[
rawClasspath.length + 1];
System.arraycopy(rawClasspath, 0, newRawClasspath, 0,
rawClasspath.length);
newRawClasspath[rawClasspath.length] = JavaCore.newVariableEntry(
new Path("HSQLDB"), null, null);
currentProject.setRawClasspath(newRawClasspath, null);
// create the initial database files
IPath dbPath = proj.getLocation();
String database = dbPath.toString() + "/database";
HsqldbUtil.createEmptyHsqlDatabase(database);
// refresh project so user sees new files, libraries, etc
proj.refreshLocal(IResource.DEPTH_INFINITE, null);
|
特別なアクションを持つのとは別に、DBエンジン・プロジェクトはHSQLDBサーバー・クラスにアクセスできる必要があります。(このシリーズ第1回で)hsqldb.coreプラグインの内部にある、こうしたクラスを含むライブラリ hsqldb.jarの見つけ方は学びました。ただ、各プロジェクトがこのライブラリをファイルシステム・パスで参照する方法は、チームで仕事をする場合には向きません。チームの各メンバーが違ったファイルシステムのフォルダーにEclipseをインストールする場合を考えてみてください。これですと、それぞれがライブラリに対して違ったパスを持つことになります。CVSが前回コミットを行った開発者のパスで各開発者を更新するようなことはしたくありません。
HSQLDBプラグインの第2バージョンにhsqldb.jarへの参照を保持する新しいクラスパス変数があるのはそのためです。DBエンジンのプロジェクト・ネイチャーを追加する時には、この変数を含むようにプロジェクトのクラスパスを更新します。この定数はリスト7のワークベンチextension ポイントで定義されています。またこの変数を初期化するクラスがなければなりません。そのコードをリスト8に示します。
新しいクラスパス変数はクライアント開発者にも便利です。つまり、どんなクライアント・プロジェクトのJavaビルド・パスにも、またはどんなクライアント・アプリケーションのランタイム・クラスパスにも簡単に変数を追加することができるわけです。
リスト7. HSQLDBクラスパス変数を宣言するplugin.xmlの一部
<extension
point="org.eclipse.jdt.core.classpathVariableInitializer">
<classpathVariableInitializer
variable="HSQLDB"
class="hsqldb.ui.classpath.HsqldbVariable">
</classpathVariableInitializer>
</extension> |
リスト8. HSQLDBクラスパス変数に対するInitializer クラス
public class HsqldbVariable extends ClasspathVariableInitializer {
public HsqldbVariable() {
super();
}
public void initialize(String variable) {
// ignore the "variable" argument, since we define just one
// classpath variable
try {
JavaCore.setClasspathVariable("HSQLDB", new Path(
HsqldbUtil.getHsqldbJarPath()), null);
}
// can't create the classpath variable
catch (JavaModelException e) {
System.err.println(e);
}
}
}
|
JavaプロジェクトにHSQLDBエンジン・プロジェクト・ネイチャーを追加できるようにするなら、このネイチャーを削除できるようにしておくのも自然でしょう。そのためのステップはネイチャーを追加する場合とほぼ同じですが、今度はString配列にネイチャーIDを追加する代わりに、IDを削除するのです。リスト9はプロジェクト記述を変更する部分だけではなく、完全なアクション・クラスを示しているので、このプラグインの第2回改版用に作られたユーティリティ・メソッドいくつかについて知ることができます。
リスト9. HSQLDBエンジンのプロジェクト・ネイチャーを削除するアクション
public class RemoveDBEngineNature implements IObjectActionDelegate {
private IJavaProject currentProject;
public RemoveDBEngineNature() {
super();
}
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
}
public void run(IAction action) {
try {
IProject proj = currentProject.getProject();
IProjectDescription description = proj.getDescription();
// remove the hsqldbEngine nature to the project
String[] natures = description.getNatureIds();
String[] newNatures = new String[natures.length - 1];
for(int i = 0; i < natures.length; i++) {
if (!natures[i].equals("hsqldb.ui.hsqldbEngine"))
newNatures[i] = natures[i];
}
description.setNatureIds(newNatures);
proj.setDescription(description, null);
// refresh project so user sees changes
proj.refreshLocal(IResource.DEPTH_INFINITE, null);
// as both decorators share the same visibility rules, this will work
HsqldbRunningDecorator.updateDecorators();
}
catch (Exception e) {
Shell shell = new Shell();
MessageDialog.openInformation(
shell,
"Hsqldb Ui Plug-in",
"Cannot remove HSQLDB Database Engine nature:\n" +
ActionUtil.getStatusMessages(e));
}
Shell shell = new Shell();
MessageDialog.openInformation(
shell,
"Hsqldb Ui Plug-in",
"Removed HSQLDB Database Engine from this project.\n" +
"You must manually delete database files and libraries if wanted.");
}
public void selectionChanged(IAction action, ISelection selection) {
currentProject = ActionUtil.findSelectedJavaProject(selection);
}
}
|
クラスActionUtil(リスト10)には我々のプラグイン・アクション・クラスが呼ぶ共通タスクに対するメソッドが含まれています。そうしたメソッドの例としては、各アクションがselectionChangedメソッドへの引数として受け取る構造化選択(ISelection)からIJavaProjectリソースまたはIFileリソースを見つけるメソッドや、ワークベンチのクラスがスローするCoreException内部に保存される多くのIStatusインスタンスから詳細なエラー・メッセージを表示するメソッド、などがあります。覚えておいて欲しいのですが、このシリーズ第1回で見た通り、アクションのrunメソッドは最後の選択が何であったかについて全くヒントを受け取りません。ですから各アクションは最後の選択を覚えておく必要があるのです。
アクションrunメソッドの最後に呼ばれる、HsqldbRunningDecoratorクラスで何が提供されるかについては気にする必要はありません。これについてはすぐ後で説明します。その中で、どれがHSQLDBエンジンのプロジェクトであり、どれが実行中のサーバー・インスタンスがあるのかがユーザーに分かるように、Package Explorerビュー上でのプロジェクト・アイコンを変更する方法について説明します。
リスト10. ActionUtilクラス
public class ActionUtil {
public static IJavaProject findSelectedJavaProject(ISelection selection) {
IJavaProject currentProject = null;
if (selection != null) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection)selection;
Object obj = ss.getFirstElement();
if (obj instanceof IJavaProject) {
currentProject = (IJavaProject)obj;
}
}
}
return currentProject;
}
public static IFile findSelectedFile(ISelection selection) {
IFile currentFile = null;
if (selection != null) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection)selection;
// as this action is enabled only for a single selection,
// it's enough to get the first element
Object obj = ss.getFirstElement();
if (obj instanceof IFile) {
currentFile = (IFile)obj;
}
}
}
return currentFile;
}
public static String getStatusMessages(Exception e) {
String msg = e.getMessage();
if (e instanceof CoreException) {
CoreException ce = (CoreException)e; IStatus status = ce.getStatus();
IStatus[] children = status.getChildren();
for (int i = 0; i < children.length; i++)
msg += "\n" + children[i].getMessage();
System.err.println(msg);
ce.printStackTrace(System.err);
}
return msg;
}
}
|
ワークスペースにHSQLDBプロジェクトが持てるようになったので、プロジェクトのカスタム・アクションには実行クラスが必要です。第1回でHSQLDBサーバーとそのツールを起動停止する方法を学んだ時に大部分の作業は終わっていますが、今回はアクティブなサーバーがたくさんあるのでHsqldbServerTrackerという名前の新しいクラスを作り、次の4つを行います。
- HSQLDBサーバー・インスタンスを起動停止する
- 実行中のサーバー・インスタンスの記録をとる
- 対応するデータベースに対して実行中のサーバーがある時に、プロジェクト・セッション・プロパティを登録する
- プラグイン・アクションの外部でサーバー・インスタンスが終了した時(ユーザーがConsoleビューの停止ボタンをクリックした時など)に終了を検出し、新しいサーバーが起動できるようにプロジェクト・セッション・プロパティを更新する
このクラスはsingleton、つまりすべてのプロジェクトに対して、実行中のサーバー・インスタンスのマップが一つしかないということです。これはデバッグ・イベントをリスンしてプロセス終了が分かるように、インスタンス化する必要があります。
我々のプラグインの第1リリースでは、サーバーはEclipseワークベンチ内部の新しいスレッドとして起動しました。今度の第2回目のリリースでは、サーバーをJDTデバッガーに提出したいので、各データベース、つまり各プロジェクトには起動設定があることになります。これは第1リリースで ScriptToolを起動した仕方と似ていますが、起動設定に対して全く新しいJavaクラスパスを作る代わりに、選択されたプロジェクトのクラスパスを使う必要があります。このクラスパスは hsqdb.jarライブラリとユーザー定義のSQL機能を実行するクラスを既に含んでいます。
HsqldbUtil.runScriptToolのコードはリファクターされ、launchという名前のメソッドを生成します(リスト11を参照)。このメソッドはパラメーターとしてプロジェクトとメイン・クラス名、コマンドライン引数を取得します。新しいメソッドは書き直されたrunScriptToolメソッドやHsqldbServerTrackerクラスのstartHsqldbServerメソッドによって利用されます。
リスト11. HsqldUtilクラスからの起動メソッド
protected static ILaunch launch(IJavaProject proj, String name,
String mainClass, String args) throws CoreException {
ILaunchManager manager =
DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type =
manager.getLaunchConfigurationType(
IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
ILaunchConfiguration config = null;
// if the configuration already exists, use it!
ILaunchConfiguration[] configurations =
manager.getLaunchConfigurations(type);
for (int i = 0; i < configurations.length; i++) {
if (configurations[i].getName().equals(name))
config = configurations[i];
}
// else create a new one
if (config == null) {
ILaunchConfigurationWorkingCopy wc =
type.newInstance(null, name);
wc.setAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
proj.getProject().getName());
// current directory should be the project root
wc.setAttribute(
IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
proj.getProject().getLocation().toString());
// use the supplied args
wc.setAttribute(
IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
mainClass);
wc.setAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
args);
// saves the new config
config = wc.doSave();
}
// launches the configuration
DebugPlugin debug = DebugPlugin.getDefault();
return config.launch(ILaunchManager.RUN_MODE, null);
}
|
第1リリースのプラグインからのもう一つの変更は、操作毎に起動設定が再生成されることはないという点です。つまりプロジェクト名を持つ起動設定が既に存在する場合は、その設定が使われるのです。ですから開発者はJava VMの引数やその他の設定をカスタマイズすることができます(例えばクラスパスに、さらにJavaライブラリを追加するなど)。もちろんメインクラスを変更したり、クラスパスからHSQLDB変数を削除して起動設定を無意味にしたりすることもできます。ただこの変更も、起動設定を削除してHSQLDBプラグイン・アクションを使って再生成することで簡単に修正できるのです。起動設定を保存するとJDTデバッガー内部でサーバーを実行できるので、開発者が自分のユーザー定義SQL機能にブレークポイントを設定することができるようになります。
リスト12はメソッドstartHsqldbServerを示します。このメソッドはプロジェクトのパーシスタント・プロパティからコネクション・パラメーターをロードすると開始し、HsqldbUtil.launchメソッドを使ってサーバー・インスタンスを起動し、デバッグ・リスナーを追加します。これによって対応するプロセスがいつ終了し、セッション・プロパティを設定するかが分かるので、ワークベンチがプラグイン・アクションを有効にするように更新します。
この第2リリースのプラグインでは、プロジェクト毎に1つのデータベースしかなく、データベース名は「database」としてハードコード化されているという前提になっています。ハードコード化された名前を設定可能にする変更は、読者への練習問題としましょう。
リスト12. HsqldbServerTrackerクラスでHSQLDBサーバーを起動するメソッド
public void startHsqldbServer(IJavaProject proj) throws CoreException {
// build server command-line
HsqldbParams params = new HsqldbParams(proj);
String database = proj.getProject().getLocation() + "/database";
String args = "-database " + database + " " +
"-port " + params.port;
// starts the server as a Java app
ILaunch launch = HsqldbUtil.launch(proj, proj.getElementName(),
"org.hsqldb.Server", args);
// saves the mapping between (server) process and project
servers.put(launch.getProcesses()[0], proj);
// register a listener so we know when this process is finished
DebugPlugin debug = DebugPlugin.getDefault();
debug.addDebugEventListener(listener);
// so the UI will reflect there's already a server running
setRunning(proj, Boolean.TRUE);
}
|
HsqldbUtil.launchメソッドはILaunchインスタンスを返します。このインスタンスから、設定を起動することで生成されたJVMプロセスを取得できます。このプロセスはMapに対するキーとして使用されますが、Mapに保存された値はJavaプロジェクト(HSQLDBエンジン)への参照です。
この後、サーバー・トラッカーに登録されたデバッグ・リスナーはプロセス終了を通知されると(リスト13参照)、その終了プロセスがマップに存在するかどうかを確認します。存在すればプロジェクトからセッション・プロパティが削除され、プロジェクトのエントリーがマップから削除されます。ワークベンチによって起動されるデバッグ・イベントのおかげで、プラグイン・アクション・イネーブルメントは常に、実行中のサーバーがあるかどうかを反映しています。「実行中」のセッション・プロパティはHsqdbServerTrackerのヘルパー・メソッドによっても操作されます(リスト14参照)。
実行中のHSQLDBサーバーを停止するメソッドは、マップやセッション・プロパティを更新する必要はありません(更新はデバッグ・イベント・リスナーが行うためです)。単にJDBCコネクションを使って、shutdownSQLステートメントをサーバーに対して送りさえすればよいのです。
リスト13. HsqlServerTrackerから終了したHSQLDBサーバのイベントリスナーをデバッグする
private IDebugEventSetListener listener = new IDebugEventSetListener() {
public void handleDebugEvents(DebugEvent[] events) {
// if the event was a terminate...
if (events[0].getKind() == DebugEvent.TERMINATE) {
Object source = events[0].getSource();
if (source instanceof IProcess) {
// ...and the terminated process is a HSQLDB Server...
Object proj = servers.get(source);
if (proj != null) {
try {
//...remove it from the map and update the ui
servers.remove(source);
setRunning((IJavaProject)proj, null);
}
catch (CoreException ce) {
System.err.println("HsqldbServerTracker.handleDebugEvents");
System.err.println(ActionUtil.getStatusMessages(ce));
}
}
}
}
}
};
|
リスト14. HsqldbServerTrackerで 「実行中」のセッション・プロパティを設定・取得するためのヘルパー・メソッド
public boolean getRunning(IJavaProject javaProj) throws CoreException {
IProject proj = javaProj.getProject();
Object value = proj.getSessionProperty(new QualifiedName(
"hsqldb.ui", "running"));
return value != null;
}
public void setRunning(IJavaProject javaProj, Boolean value)
throws CoreException {
if (value != null && value.equals(Boolean.FALSE))
value = null;
IProject proj = javaProj.getProject();
proj.setSessionProperty(new QualifiedName("hsqldb.ui", "running"),
value);
HsqldbRunningDecorator.updateDecorators(javaProj);
}
|
HsqldbParamクラスはこのプラグインの最初のリリースにあったものです。単純なC風の構造でワークベンチ全体のHSQLDBコネクション・パラメーターを保持するものでしたが、もっと高度になり、この第2リリースでは、与えられたプロジェクトのワークベンチ・リソースに対してそのパラメーターをパーシスタント・セッション・プロパティとしてロード、セーブができるようになりました(リスト15参照)。
リスト15. 更新されたHsqldbParamsクラス
public class HsqldbParams {
// property names
public static final String P_PORT = "serverPort";
public static final String P_USER = "serverUser";
public static final String P_PASSWD = "serverPasswd";
public int port = 9001;
public String user = "sa";
public String passwd = "";
public HsqldbParams() {}
public HsqldbParams(IJavaProject javaProject) throws CoreException {
load(javaProject);
}
public void save(IJavaProject javaProject) throws CoreException {
IProject project = javaProject.getProject();
project.setPersistentProperty(new QualifiedName (
"hsqldb.ui", P_PORT), Integer.toString(port));
project.setPersistentProperty(new QualifiedName (
"hsqldb.ui", P_USER), user);
project.setPersistentProperty(new QualifiedName (
"hsqldb.ui", P_PASSWD), passwd);
}
public void load(IJavaProject javaProject) throws CoreException {
IProject project = javaProject.getProject();
String property = project.getPersistentProperty(new QualifiedName (
"hsqldb.ui", P_PORT));
port = (property != null && property.length() > 0) ?
Integer.parseInt(property) : port;
property = project.getPersistentProperty(new QualifiedName (
"hsqldb.ui", P_USER));
user = (property != null && property.length() > 0) ? property : user;
property = project.getPersistentProperty(new QualifiedName (
"hsqldb.ui", P_PASSWD));
passwd = (property != null && property.length() > 0) ?
property : passwd;
}
}
|
HSQLDBワークベンチ・プレファレンス・ページをHSQLDBリソース・プロパティ・ページで置き換える必要があります。図5はページがどう見えるかを示しています(基本的には元のプロパティ・ページと同じです)。また、プロパティ・ページのコードをリスト16に示します。コードは元のプレファレンス・ページよりも少し複雑です。これはフィールド・エディターが使えなくなったためですが、そのため「低レベル」SWT制御で何とかしなければなりません。幸い、PDEプロパティ・ページ・ウィザードが生成するプロパティ・ページを採用するのは簡単ですし、読者はまだSWTに深く入り込む必要はありません。対応エクステンションポイントはすでにリスト5で示してあります。
図5. HSQLDBエンジン・プロジェクトのプロパティ・ページ
リスト16. プロパティ・ページ実装クラス
public class DBEnginePropertyPage extends PropertyPage {
private HsqldbParams params;
private Text portText;
private Text userText;
private Text passwdText;
public DBEnginePropertyPage() {
super();
}
private void fillControls() {
portText.setText(Integer.toString(params.port));
userText.setText(params.user);
passwdText.setText(params.passwd);
}
private void getParams() {
params = new HsqldbParams();
try {
params.port = Integer.parseInt(portText.getText());
}
catch (NumberFormatException ne) {
// do nothing; let the default port number
}
params.user = userText.getText();
params.passwd = passwdText.getText();
}
private void addControls(Composite parent) {
Composite composite = createDefaultComposite(parent);
Label pathLabel = new Label(composite, SWT.NONE);
pathLabel.setText("&TCP Port:");
portText = new Text(composite, SWT.SINGLE | SWT.BORDER);
GridData gd = new GridData();
gd.widthHint = convertWidthInCharsToPixels(6);
portText.setLayoutData(gd);
Label userLabel = new Label(composite, SWT.NONE);
userLabel.setText("&User:");
userText = new Text(composite, SWT.SINGLE | SWT.BORDER);
gd = new GridData();
gd.widthHint = convertWidthInCharsToPixels(15);
userText.setLayoutData(gd);
Label passwdLabel = new Label(composite, SWT.NONE);
passwdLabel.setText("&Password:");
passwdText = new Text(composite, SWT.SINGLE | SWT.BORDER);
gd = new GridData();
gd.widthHint = convertWidthInCharsToPixels(15);
passwdText.setLayoutData(gd);
}
protected Control createContents(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
composite.setLayout(layout);
GridData data = new GridData(GridData.FILL);
data.grabExcessHorizontalSpace = true;
composite.setLayoutData(data);
addControls(composite);
IJavaProject proj = (IJavaProject)getElement();
try {
params = new HsqldbParams(proj);
fillControls();
}
catch (CoreException ce) {
System.err.println(ActionUtil.getStatusMessages(ce));
}
return composite;
}
private Composite createDefaultComposite(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
composite.setLayout(layout);
GridData data = new GridData();
data.verticalAlignment = GridData.FILL;
data.horizontalAlignment = GridData.FILL;
composite.setLayoutData(data);
return composite;
}
protected void performDefaults() {
params = new HsqldbParams();
fillControls();
}
public boolean performOk() {
IJavaProject proj = (IJavaProject)getElement();
getParams();
try {
params.save(proj);
}
catch (CoreException ce) {
System.err.println(ActionUtil.getStatusMessages(ce));
return false;
}
return true;
}
}
|
HSQLDBプラグインの第2バージョンの大部分は準備完了です。HsqldbUtilをちょっと手直しし、リファクターされたlaunchメソッドを使い、また引数としてIJavaProjectを取得して(特定のコネクション・プロパティが取り出せるように、)HSQLDBツールを実行するすべてのメソッドを起動すると、必要な機能はすべて用意が整いました。これで、それぞれ独自のデータベース・ファイルとカスタムのSQL機能を持った複数の実行中サーバー・インスタンスを追跡できるようになります。前に挙げたリストを手始めとして使えるような読者や、このプラグインの第2バージョンの全ソースコードを(参考文献にあるリンクから)ダウンロードできる読者にとっては、こうした変更は大したことはないはずです。
ただ、今はまだ一つ重要なフィーチャーが足りません。ユーザーには各HSQLDBエンジン・プロジェクトの状態について、なんらかの視覚的なフィードバックが必要なのです。つまりどのプロジェクトがデータベース・プロジェクトなのか、また、どのデータベースが実行中でどれが実行中でないのかの状態が分かるようにする必要があるのです。Eclipse装飾子はこうした視覚的な手掛かりのために最適なものです。図6はデータベース・プロジェクトのアイコンがどう見えるか、図7は実行中のサーバーを持つプロジェクトです。DBエンジン・プロジェクトの装飾子は標準Javaプロジェクトの「J」装飾子を隠していることに注意してください。これは望んだ通りの結果で、これで開発者はどのプロジェクトがデータベース・プロジェクトかがすぐに判断できることになります(ただし通常の場合は、プラグイン装飾子が標準のワークベンチ装飾子やJDT装飾子を隠したりすべきではありません)。
図6. DBエンジン・プロジェクトのアイコン
図7. 実行中のDBエンジン・プロジェクトのアイコン
最初の装飾子は plugin.xmlマニフェスト要素のみを使って宣言的に定義します(リスト17参照)。これがEclipse 2.1で導入された、軽量装飾子の良いところです。ただし、ネイチャーを追加するコード(リスト6)を思い出してください。LabelProviderChangedEventイベントを起動しないと、ワークベンチのビューは変更されたリソースの装飾子を正しく表示しないかも知れないのです。このイベントは、実際はクラスHsqldbRunningDecoratorにあるupdateDecoratorsによって起動されますが、これはプラグインが定義する「他の」装飾子を表しています。どちらの装飾子もワークベンチ・リソースの同じセットに対するものなので、すぐ後に説明する通り、xmlのみの装飾子はコード化された装飾子からメソッドを借りることができるのです。
リスト17. DBエンジン・プロジェクトの、xmlのみの装飾子
<extension
point="org.eclipse.ui.decorators">
<decorator
lightweight="true"
quadrant="TOP_RIGHT"
location="TOP_RIGHT"
adaptable="true"
label="HSQLDB Engine Project"
icon="icons/dec-dbnature.gif"
state="true"
id="hsqldb.ui.hsqldbEngineDecorator">
<description>
Flags Java Projects that had the HSQLDB Engine project
nature added to them.
</description>
<enablement>
<and>
<objectClass
name="org.eclipse.jdt.core.IJavaProject">
</objectClass>
<objectState
name="nature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
</and>
</enablement>
</decorator>
|
読者はリスト18に示すような、実行中のHSQLDBサーバーにプロジェクトを付加する純宣言的な(xmlのみの)装飾子を試してみたいと思うかも知れません。ところがこれはあまりうまく動作しません。あるプロジェクトに対してサーバーが初めて立ち上がった時には装飾子は表示されないのです。後でそのプロジェクトの選択を外し、再度選択すると、アイコンは実際の状態を反映するように変わるのです。
これは装飾子に影響されるリソースのリストにそのプロジェクトが無いため、LabelProviderChangedEventがとらえられないという事実によるものです。これに対処するには装飾子をすべてのDBエンジン・プロジェクトに結びつけ、サーバーが実行中の時だけ装飾子のイメージをプログラム的に適用するようにします。
リスト18. 実行中のデータベースに対してxmlのみの装飾子を定義しようとして失敗する例
<decorator
lightweight="true"
quadrant="BOTTOM_RIGHT"
location="BOTTOM_RIGHT"
adaptable="true"
label="HSQLDB Running"
state="true"
id="hsqldb.ui.hsqldbRunningDecorator">
<description>
Flags HSQLDB Engine Projects which have a running
server instance.
</description>
<enablement>
<and>
<objectClass
name="org.eclipse.jdt.core.IJavaProject">
</objectClass>
<and>
<objectState
name="nature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
<objectState
name="sessionProperty"
value="hsqldb.ui.running">
</objectState>
</and>
</and>
</enablement>
</decorator>
</extension>
|
リスト19は第2の装飾子を正しく宣言する方法を示し、リスト20は実装クラスを示します。このアイコンは第1の装飾子のイメージとは違い、クラス自体のファイル・フォルダーと同じファイル・フォルダーにある必要があり、 iconsファイル・フォルダーではないことに注意してください。装飾子をプログラム的に定義するのには利点もあります。ワークスペースにあるすべてのDBエンジン・プロジェクトを更新するのではなく、影響を受けるプロジェクトのみを参照することで、指示されたLabelProviderChangedEventイベントを送ることができるのです。これによって画面のちらつきを減らすことができ、頻繁に変更される装飾子に対してはパフォーマンスが非常に向上にするようになります。
指示されたupdateDecoratorsメソッドはHsqldbServerTrackerのsetRunningメソッドによってのみ起動しますが、DBエンジンのアクションによっても起動します。「ブロードキャスト」バージョンは、その両方を見せるためだけに使います。
リスト19. 実行中のデータベースに対してプログラム的装飾子を正しく定義する方法
<decorator
lightweight="true"
quadrant="BOTTOM_RIGHT"
location="BOTTOM_RIGHT"
adaptable="true"
label="HSQLDB Running"
state="true"
class="hsqldb.ui.decorator.HsqldbRunningDecorator"
id="hsqldb.ui.hsqldbRunningDecorator">
<description>
Flags HSQLDB Engine Projects which have a running server instance.
</description>
<enablement>
<and>
<objectClass
name="org.eclipse.jdt.core.IJavaProject">
</objectClass>
<objectState
name="nature"
value="hsqldb.ui.hsqldbEngine">
</objectState>
</and>
</enablement>
</decorator>
</extension>
|
リスト20. 実行中のデータベース装飾子に対する実装クラス
public class HsqldbRunningDecorator
extends LabelProvider
implements ILightweightLabelDecorator {
private static final ImageDescriptor runningDescriptor =
ImageDescriptor.createFromFile (
HsqldbRunningDecorator.class, "dec-running.gif");
public void decorate(Object element, IDecoration decoration) {
IJavaProject javaProj = (IJavaProject)element;
try {
if (HsqldbServerTracker.getDefault().getRunning(javaProj)) {
decoration.addOverlay(runningDescriptor);
}
}
catch (CoreException ce) {
System.err.println(ActionUtil.getStatusMessages(ce));
}
}
public static void updateDecorators(IJavaProject javaProj) {
IDecoratorManager dm =
PluginUi.getDefault().getWorkbench().getDecoratorManager();
HsqldbRunningDecorator decorator = (HsqldbRunningDecorator)
dm.getBaseLabelProvider("hsqldb.ui.hsqldbRunningDecorator");
decorator.fireUpdateDecorators(javaProj);
}
private void fireUpdateDecorators(IJavaProject proj) {
// I can generate my own event to update the decorators
// for a given resource
final LabelProviderChangedEvent ev =
new LabelProviderChangedEvent(this, proj);
Display.getDefault().asyncExec(new Runnable() {
public void run() {
fireLabelProviderChanged(ev);
}
});
}
public static void updateDecorators() {
// I can also let the workbench generate events to update all
// resources affected by a decorator
Display.getDefault().asyncExec(new Runnable() {
public void run() {
PluginUi.getDefault().getWorkbench().getDecoratorManager()
.update("hsqldb.ui.hsqldbRunningDecorator");
}
});
}
}
|
この記事ではEclipseワークベンチにHSQLDBデータベースをもたらすプラグイン・セットの第2リリースをどのように作るかを説明しました。多くのデータベース・サーバー・インスタンスを起動停止する簡単な方法を紹介したのですが、SQLステートメントやスクリプトを実行するのとは別に(Javaクラスとして実装される)ユーザー定義のSQL機能を含むこともできるものです。サーバーは通常のJavaアプリケーションとしてもJDTデバッガー内部でも実行できます。
PDEにはウィザードや特別なエディターがあるため、作業によってはこのプラグイン生成に役に立つのですが、XMLエクステンションポイント構文の知識とは別に、EclipseプラットフォームやJDTのコア・インターフェースに対する深い理解が必要なことが分かりました。大部分の作業はマニフェスト・ファイルを手動編集したり、Javaクラスを手動で生成したりする必要があるので、まだまだ改善の余地は多分にあります。
ただ、私は読者に対して明確に言い切れるのですが、EclipseプラットフォームやPDEが無かったら作業ははるかに複雑ですし、他のIDE拡張APIを使うと作業はずっと困難なのです。Eclipseはこのシリーズで開発したようなプラグイン等をホストするために全く白紙状態から開発されたのですが、これまでのところ非常に良好な結果が出ています。HSQLDBがEclipse内部から実行するように作られてはいない、などとは誰も言えませんが、データベース・マネージャー・ツールを起動する時は例外です。この例外の場合をこのシリーズ第3回の主題とします。これで2つのオープンソースのソフトウェア・パッケージ、つまりHSQLDBとEclipseの完全な統合が図れることになります。
もちろん、プラグイン設計を改善する方法には他にもいろいろあります。この記事の終わりにメールアドレスがありますので、読者の考えを遠慮無く著者にお送りください。
- この記事で使用しているソースコードをダウンロードしてください。
-
HSQLDBのバイナリとソース・ディストリビューションをダウンロードしてください。
- 「EclipseとHSQLDB 第1回」(developerWorks 2003年9月)はHSQLDBプラグインの第1リリースを生成する過程を、順を追って説明しています。
- 「EclipseでのJava Development Toolsの拡張」(developerWorks 2003年7月)はPDEを使ってEclipseプラグインを生成する過程を、順を追って説明しています。
- 「Launching Java Applications Programmatically」はJDTに用意されている起動フレームワーク・エクステンションをどのように使うかを説明しています。
- 「Contributing Actions to the Eclipse Workbench」はアクション、actionSets、objectContributionsなどのすべてについて説明しています。
- 「Project Builders and Natures」はEclipseプロジェクト・ネイチャーと、それをどう定義するかについて説明しています。
- 「Take control of your properties」はEclipseリソース・プロパティについて説明しています。
- 「We have a lift-off: The Launching Framework in Eclipse」はアクション、actionSets、objectContributionsなどのすべてについて説明しています。
- 「Understanding Decorators in Eclipse」は装飾子をどのように生成し使用可能にするかを説明しています。
-
HSQLDB Web Siteには新しいリリースやチュートリアルなどに関する情報がありますので見てください。
- ソースコードとバイナリ・ディストリビューションについてはHSQLDB SourceForge Projectを見てください。
-
JFaceDBCはEclipseプラグインとして構成された、良くできたSQLコンソールです。
Fernando Lozanoは長年オープンソースの熱心な信奉者であり、Java開発者であり、Java and GNU/Linux(ポルトガル語版のみ)の著者でもあります。またEclipse他のオープンソース・ツールを使ってJ2EEアプリケーションを開発している組織に対してサービスを販売しており、GNUプロジェクトWebサイトのポルトガル語翻訳の維持管理を補助し、またLinux Professional Institute Brazil評議会のメンバーでもあります。連絡先はfernando at lozano.eti.brです。