はじめに
SQL Anywhereはデータベースサーバとして有名ですが、それだけではありません。SQL Anywhereサーバに、データベースクエリの結果セットをWebページまたはWebサービスを介して提供できるHTTPサーバの機能があることはあまり知られていません。Google Web Toolkit(GWT)は、AJAXアプリケーションの作成を簡易化するJavaソフトウェア開発フレームワークです。開発者は、ブラウザ間の違いを意識せずに、使い慣れた環境でおなじみのツールを使ってJavaアプリケーションを作成することができます。GWTには、Javaクラスから各ブラウザ対応のJavaScriptとHTMLを生成するコンパイラが含まれています。
GWTは便利で使いやすく、SQL Anywhereにも対応しています。本稿では、SQL AnywhereのHTTPサーバ機能を利用するためのセットアップ方法と、GWTの使用方法、さらにSQL AnywhereにWebアプリケーションをデプロイする方法を説明します。また、SQL Anywhereサーバへの接続を監視するWebアプリケーションの作成手順も紹介します。SQL Anywhereでのセキュリティやエラー処理についての説明は省きます。
本稿は、SQL AnywhereやGWTの経験がない方でもお読みいただけますが、JavaとSQLの知識があることを前提としています。
ソフトウェアを入手する
GWTのWebサイトからGWTをダウンロードしましょう。本稿の執筆時点で入手可能な最新バージョンはビルド1.1.10だったので、今回はこのバージョンを使いました。
SQL Anywhereはバージョン10を使いましたが、バージョン9でもかまいません。バージョン10.0.0ビルド2719以上、または9.0.2ビルド3385以上が必要です。それ以前のバージョンにはGWTの動作を妨げるバグがありました。アップデートはSQL Anywhereのデベロッパーサイトからダウンロードすることができます。EBFのリンクに従ってください。SQL Anywhereをお持ちでない場合は、SQL AnywhereのWebサイトから無料の評価版をダウンロードすることができます。
最後に、SunのJava 2 Runtime Environment 1.4.2以上も必要です。JREはSunのWebサイトからダウンロードすることができます。本稿では最新バージョンのJava 5を使いました。また、Javaソースを編集するためのエディタも必要です。
GWTを使ってアプリケーションを作成する
本稿では、DBConsoleの接続部のような外観と振る舞いのWebアプリケーションを作成します。DBConsoleはSQL Anywhereを監視するアプリケーションで、SQL Anywhereに付属しています。
GWTには、シェルアプリケーションを自動的に生成するツールが用意されています。このツールは「applicationCreator」という名前で、インストールしたGWTの最上位のディレクトリに入っています。このディレクトリをパスに追加するか、ツールを実行するときにapplicationCreatorをパスに含める必要があります。まず、プロジェクト用のディレクトリを作成します。ここでは「w:x」を使っているので、この「w:x」を実際に使用するプロジェクトディレクトリに置き換えてください。applicationCreatorを実行するときは、クラスの修飾名を指定する必要があります。パッケージ名の後ろには必ず「.client」を付けます。ここでは、アプリケーションを「com.iAnywhere」というパッケージに入れました。アプリケーションの名前は「Console」です。
アプリケーションを作成すると、アプリケーションをビルドしてホストモードで実行するためのスクリプトも作成されます。
アプリケーションをビルドする
デフォルトのコンパイルで生成されるJavaScriptは読みにくいので、読みやすくするために「Console-compile.cmd」を編集し、コマンドラインの- outの前に-style DETAILEDを追加します。
アプリケーションをビルドするには、コマンドラインで「Console-compile.cmd」を実行します。
アプリケーションをホストモードで実行する
ホストモードでは、アプリケーションはGWTシェルで実行され、アプリケーションの実行環境となるブラウザが起動されます。ビルドした新規アプリケーションをホストモードで実行するには、コマンドラインで「Console-shell.cmd」を実行します。次の2つのダイアログが表示されます。
生成した新規アプリケーションをGoogleのブラウザで実行した場合
データベースを作成してSQL Anywhereサーバを起動する
Webアプリケーションで使うサービスやストアドプロシージャ、またはWebアプリケーションで使用するデータを保管するためには、データベースが必要です。既存のSQL Anywhereデータベースを使うことも、新規にデータベースを作成することもできます。データベースを作成するには、コマンドラインでdbinit console.dbを実行します。これで、SQL Anywhereデータベースとして初期化される「console.db」というファイルが作成されます。このデータベースファイルは、データベースファイル名をディレクトリパスで修飾しない限り、dbinitの実行先のディレクトリに作成されます。ここでは、プロジェクトディレクトリのルートである「w:x」にデータベースを置きました。
サーバを起動するには、コマンドラインに次のコードを入力します。
dbsrv10 -xs HTTP(port=80) console.db
これで、データベースサーバが起動します。データベースアプリケーションがデータベースに接続するには、正しい認証情報を持っていることが必要です。これは、SQL Anywhereサーバの管理下にあるその他のデータベースに接続する場合も同様です。.xsオプションを指定すると、SQL AnywhereはWebプロトコルをリスンします。ここでは、ポート80のHTTP要求が対象となります。
SQL Anywhereは、特定の要求に応答するために作成されたWebサービスを必要とします。例えば、fooというWebサービスを作成した場合は、次のURLを指定してこのWebサービスにアクセスします。
ただし、ページの生成中はうまくいきません。GWTは多くのファイルを生成します。こうしたファイルの中には、「Console-compile.cmd」でプロジェクトをリビルドするたびに名前が変わるものもあります。SQL Anywhereのデフォルトでは、要求されるファイルごとに異なるサービスを用意することが期待されますが、この問題には回避策があります。「root」というWebサービスを作成するのです。このrootサービスは、一致するサービスがないすべての要求を処理します。
rootサービスのソースは次のとおりです。
CREATE SERVICE "root" TYPE ’RAW’
AUTHORIZATION OFF USER "DBA" URL ON
AS call RootPage(:url);
このサービスは非常に単純です。認証を無効にし、すべての呼び出しにDBA権限を使います。さらに、呼び出しているURLを取得してRootPageストアドプロシージャに渡します。RootPageは要求をディスパッチする処理を行います。
RootPageは、GWTの出力生成先ディレクトリをURLの前に付加し、そのURLを使用してファイルを読み取り、要求側に返します。RootPageは各ファイルのファイル拡張子を認識し、適切なコンテンツタイプを設定します。ここでは「w:xwww」を使いました。「www」ディレクトリは、Console-compileが生成ファイルをデフォルトで書き込む場所です。このディレクトリは、実際にプロジェクトをビルドする場所に置き換えてください。
CREATE PROCEDURE "DBA"."RootPage"( url_part varchar(250) )
RESULT( HTML_doc XML )
BEGIN
declare root_page long varchar;
set root_page = xp_read_file( ’w:xwww’
+ HTTP_decode( url_part ) );
IF COMPARE( ’.html’, LOWER( RIGHT( url_part, 5 ) ) ) = 0 OR
COMPARE( ’.htm’, LOWER( RIGHT( url_part, 4 ) ) ) = 0 THEN
CALL dbo.sa_set_http_header( ’Content-Type’, ’text/HTML’ );
ELSEIF COMPARE( ’.js’, LOWER( RIGHT( url_part, 3 ) ) ) = 0 THEN
CALL dbo.sa_set_http_header( ’Content-Type’,
’text/javascript’ );
ELSEIF COMPARE( ’.css’, LOWER( RIGHT( url_part, 4 ) ) ) = 0 THEN
CALL dbo.sa_set_http_header( ’Content-Type’, ’text/css’ );
ELSEIF COMPARE( ’.xml’, LOWER( RIGHT( url_part, 4 ) ) ) = 0 THEN
CALL dbo.sa_set_http_header( ’Content-Type’, ’text/xml’ );
ELSEIF COMPARE( ’.gif’, LOWER( RIGHT( url_part, 4 ) ) ) = 0 THEN
CALL dbo.sa_set_http_header( ’Content-Type’, ’image/gif’ );
ELSEIF COMPARE( ’.jpe’, LOWER( RIGHT( url_part, 4 ) ) ) = 0 OR
COMPARE( ’.jpeg’, LOWER( RIGHT( url_part, 5 ) ) ) = 0 OR
COMPARE( ’.jpg’, LOWER( RIGHT( url_part, 4 ) ) ) = 0 THEN
CALL dbo.sa_set_http_header( ’Content-Type’, ’image/jpeg’ );
END IF;
select "root_page";
END;
Webサービスとストアドプロシージャの作成および維持にはDBISQLまたはSybase Centralを使用できます。DBISQLとSybase CentralはSQL Anywhereに付属のツールです。今回の例では、Sybase Centralの方が効率的であることが分かりました。
Windowsでは、DBISQLとSybase Centralは[スタート]メニューから起動できます。起動したら、作成した新規データベースに接続する必要があります。新規データベースのデフォルトのユーザーIDは「dba」で、デフォルトのパスワードは「sql」です。他に既に使用しているSQL Anywhereデータベースがある場合は、そのデータベースの名前も指定する必要があります。ここでは、「console」を使います。
Webアプリケーションをデプロイする
これでSQL AnywhereがWebサーバとして設定されました。このサーバはGWTがアプリケーションを生成するディレクトリを監視しているので、プロジェクトがコンパイルされるたびにアプリケーションがデプロイされます。アプリケーションを変更したのにブラウザで実行したときにその変更が反映されていない場合は、「www」ディレクトリの中味を削除してからアプリケーションを再度ビルドします。GWTでは、既存のファイルを上書きしないように配慮されています。
これをテストするには、アプリケーションをコンパイルした後、お気に入りのWebブラウザに移動して、生成したWebページのURLを入力します。
HTTP://localhost/com.iAnywhere.Console/Console.html
これで、SQL Anywhereで管理できる作業アプリケーションが完成しました。
コンソールアプリケーションを作成する
次に、SQL Anywhereに付属しているDBConsoleアプリケーションの接続ビューア部分と同じ働きをするブラウザベースのアプリケーションを作成してみます。
DBConsoleの接続ビューアでは、ユーザーはデータベース接続プロパティのリストから表示するものを選択し、データ更新のリフレッシュレートを設定し、その情報を表示することができます。DBConsoleはオプションダイアログを使って構成します。
同じことを行うためには、Webサービスとストアドプロシージャを設定し、表示する接続データを集める必要があります。また、接続プロパティのリストを作るためにもWebサービスとストアドプロシージャが必要です。さらに、サーバからの結果を表示するためにGWTプロジェクトを更新することも必要です。
まず、「Console.html」を編集します。編集するのは、デプロイ先のディレクトリ(デフォルトでは「www」)内のファイルではなく、「src」ディレクトリ内のファイルです。こうしないと、最初からすべてビルドし直したときに正しいHTMLファイルを取得することができません。新規アプリケーションのユーザーインターフェイスに必要なのは、GWTが自動的に生成するJavaScriptだけです。HTML要素は必要ありません。「Console.html」から次の要素を削除します。
GWTによってタイトルは自動的に「Wrapper HTML for Console」になりました。このタイトルを「Console」に変更します。GWTのグラフィカルコンポーネント(ボタンやメニューなど)は、スタイルシートを使ってカスタマイズできます。これらのコンポーネントの多くは、スタイルシートでカスタマイズしないと非常に地味です。GWTの資料には、各コンポーネントのスタイルシートプロパティのリストが含まれています。スタイルシートは非常に長いので、ここでは省略します。詳しい内容については、「Console.html」を参照してください。
どの接続プロパティをリストに表示したいと思うかは、ユーザーによって異なります。まず、ユーザーが表示データをカスタマイズできるように、接続プロパティのリストを取得する必要があります。
そのためのサービスを作成します。
CREATE SERVICE "get_connection_properties" TYPE ’XML’
AUTHORIZATION OFF USER "DBA" AS call getConnectionPropeties();
次にストアドプロシージャを作成します。
CREATE PROCEDURE "DBA"."getConnectionPropeties"()
RESULT (
property_name long varchar,
property_description long varchar,
property_is_numeric long varchar,
property_is_cumulative long varchar
)
begin
select property_name( row_num ) as prop,
property_description( row_num ),
property_is_numeric( row_num ),
property_is_cumulative( row_num )
from sa_rowgenerator(0,500)
where connection_property( prop ) is not null
order by prop
end
このリストをブラウザに表示するには、GWTが生成したConsoleクラスを更新しなければなりません。今回の例では、ConnectionOptionsDialogというクラスを追加しました。このコードは非常に長いのでここでは省略します。詳しくは、サンプルのソースファイルを参照してください。
次に、接続プロパティのリストを作るための接続データを取得します。そのために、接続データを取得するためのサービスを作成します。
CREATE SERVICE "connections" TYPE ’RAW’ AUTHORIZATION OFF
USER "DBA" AS call connections(:properties);
ストアドプロシージャを作成します。
CREATE PROCEDURE "DBA"."connections"( IN properties LONG VARCHAR )
BEGIN
DECLARE pos INT;
DECLARE start_pos INT;
DECLARE property LONG VARCHAR;
DECLARE temp_table LONG VARCHAR;
DECLARE insert_into LONG VARCHAR;
DECLARE counter INT;
DECLARE curr_cid INT;
DECLARE cid INT;
DECLARE return_value LONG VARCHAR;
DECLARE table_heading LONG VARCHAR;
DECLARE insert_html long varchar;
DECLARE cols LONG VARCHAR;
declare local temporary table t_html(
ord INT,
str LONG VARCHAR
) in SYSTEM not transactional;
SET temp_table = ’declare local temporary table t_conn_webconsole(’;
SET insert_into = ’insert into t_conn_webconsole values (’;
SET table_heading = ’<TR class="header">’;
SET cols = ’’;
SET insert_html = ’INSERT INTO t_html SELECT 1, ’’<TR>’’ ||’;
SET pos = 1;
SET start_pos = 0;
SET counter = 0;
WHILE pos > 0 LOOP
SET pos = LOCATE( properties, ’,’, start_pos );
IF pos = 0 THEN
SET property = SUBSTR( properties, start_pos,
LENGTH( properties ) - start_pos );
ELSE
SET property = SUBSTR( properties, start_pos,
pos - start_pos );
END IF;
IF counter > 0 THEN
SET temp_table = temp_table || ’, ’;
SET insert_into = insert_into || ’, ’;
SET cols = cols || ’ || ’;
SET insert_html = insert_html || ’ || ’;
END IF;
SET temp_table = temp_table || ’ "’ || property ||
’" varchar(255)’;
SET cols = cols || ’ "’ || property || ’"’;
SET insert_into = insert_into ||
’ connection_property( ’’’ || property || ’’’ , cid )’;
SET table_heading = table_heading || ’<TH>’
|| property_description( property ) || ’</TH>’;
SET insert_html = insert_html || ’’’<TD>’’ ||"’
|| property || ’" || ’’</TD>’’’;
SET start_pos = pos + 1;
SET counter = counter + 1;
END LOOP;
SET temp_table = temp_table || ’ ) in SYSTEM not transactional;’;
SET insert_into = insert_into || ’);’;
SET table_heading = table_heading || ’</TR>’;
SET insert_html = insert_html || ’ || ’’</TR>’
’ FROM t_conn_webconsole’;
EXECUTE IMMEDIATE temp_table;
set curr_cid = connection_property( ’number’ );
set cid = next_connection( NULL, NULL );
loop_label:
loop
if cid is NULL then
leave loop_label
end if;
if cid <> curr_cid then
EXECUTE IMMEDIATE insert_into;
end if;
set cid = next_connection( cid, NULL );
end loop loop_label;
INSERT INTO t_html VALUES (
0,
’<HTML><BODY><TABLE BORDER=1 CELLSPACING=1
CELLPADDING=0 COLS=’ || counter || ’>’
|| table_heading
);
EXECUTE IMMEDIATE insert_html;
INSERT INTO t_html VALUES (
99999,
’</TABLE></BODY></HTML>’
);
SELECT str FROM t_html ORDER BY ord;
END
このアプリケーションでは定期的にデータを更新します。データの収集の管理にはタイマーを使います。アプリケーションがブラウザのドキュメントにロードされたときにタイマーを始動し、アンロードされたときにタイマーを停止します。今回使用したコードを次に示します。
public void onLoad() {
_timer = new Timer() {
public void run() {
boolean success =
HTTPRequest.asyncGet( _hostName +
"connections?properties="+_connectionProperties,
new ResponseTextHandler() {
public void onCompletion( String responseText ) {
_content.setHTML( responseText );
}
});
if( !success ) {
_timer.cancel();
// Report problem to user.
Window.alert( "Error getting connection data." );
}
}
};
_timer.scheduleRepeating( _refreshRate );
}
public void onDetach() {
_timer.cancel();
}
Webアプリケーションを起動するたびに、表示するプロパティのリストをユーザーが設定し直すのでは不便です。クッキーを使用すれば、この設定を保存することができます。GWTには、クッキーの値を設定および取得するための静的なメソッドを含むCookieクラスがあります。ただし、自分のマシンから提供しているアプリケーションをテストするときには、クッキーの処理で問題が発生することがあります。クッキーはローカルホストでは正しく動作しない可能性があります。クッキーがローカルホストで正しく動作しない場合、そのコンピュータがドメイン内にあるときは、localhostではなくコンピュータの完全修飾名を使うようにしてください。
完成したWebアプリケーションは次のようになります。
完成したWebアプリケーションでオプションメニューを表示した様子
複数のブラウザをターゲットとするJavaScriptアプリケーションを作成した経験がある人ならば、すべてのブラウザで同じ動作を期待どおりに実現するのが大変であることはよくご存知でしょう。この問題を解決するために、GWTは複数の代表的なブラウザ用のスクリプトを生成します。生成されたコードは、ロード時にブラウザに合ったスクリプトを読み込みます。このGWTの素晴らしい働きのおかげで、Javaプログラマは複数のブラウザ用のWebアプリケーションを簡単にデプロイできるようになりました。
手順さえ理解すれば、GWTをSQL Anywhereで利用するのは難しいことではありません。このサンプルアプリケーションは未完成ですが、アプリケーションを作成する際の出発点としては役に立つでしょう。
参考資料
- GWT
- SQL Anywhere