リソースファイル、DB、サーバファイルの扱い
概要
"ファイル(リソースファイル、サーバ上のファイル)読込み"の仕組み、Struts の DB 連携の仕組みについて学びます。
チュートリアルでは、
・Struts 流のリソースファイルの扱い方
・Struts 流の DB の扱い方
・Struts 流(というより Servlet 流)のサーバファイルの扱い方
について理解します。
リソースファイルの扱い方
リソースファイルの扱い方について学びます。ここでリソースファイルとは struts-config.xml において、 < message-resources > タグで指定されるプロパティファイルのことです。
例題アプリケーション
第1回の演習問題の解答例である
ログインアプリケーションを
例に説明します(下図参照)。
ログインアプリケーションはユーザに名前とパスワードを入力してもらい、両者が一致すれば Success ページを、
両者が一致しなければ Error ページを表示します。
このログインアプリケーションは StrutsBlank アプリケーションから作成したアプリケーションです。 そのため、このアプリケーションの struts-config.xml には以下の記述があります(しかし、実際には利用していません)。
<message-resources parameter="MessageResources" />
リソースファイルの設定形式
リソースファイルはタグ <message-resources> を利用して指定します。 主に使用する属性は parameter と key の 2 種類です。
| 属性 | 説明 |
|---|---|
| parameter |
リソースファイルの場所を指定します。 ただし、リソースファイルは必ず WEB-INF/classes 以下に配置されていなければならず、 paramter で指定する場所は WEB-INF/classes からのパスで指定します。 また、パス区切り文字は".(ドット)"であり、拡張子(.properties)は省略します。 |
| key | ひとつのアプリケーションで複数のリソースファイルを指定する場合は必ず指定します。 |
例えば、下図のような構成にある2つのリソースファイル
(WEB-INF/classes/resources/{messeages.properties, password.properties})を利用するには、
struts-config.xml に次のように設定します。
<message-resources parameter="resources.messages" key="messages" /> <message-resources parameter="resources.password" key="password" />
上記の設定により、"messages" というキーに対するリソースファイル、および "password" というキーに対するリソースファイルを このアプリケーションで利用する準備が出来ました。
JSP プログラムでのリソースファイルの利用法
JSP プログラムでリソースファイルを利用する方法について説明します。
JSP プログラムでリソースファイルから値を取得するには、struts のタグライブラリ "struts-bean.tld" を利用して、
<接頭辞:message> というタグを使います。
より直感的な説明としては、
1. JSP ファイルの先頭に <%@ taglib uri="/tags/struts-bean" prefix="bean" %> を記述
2. JSP ファイル内で <bean:message> タグを利用する
ということになります。
以降、このタグライブラリを <bean:message> タグと呼びます。
<bean:message> タグで指定する主な属性は以下の通りです。
| 属性 | 説明 |
|---|---|
| key(必須) | リソースファイルから取得する値のキーを指定します。 |
| bundle |
複数のリソースファイルが指定されている場合、そのバンドルを指定します。 struts-config.xml で指定した message-resources の key 属性がこれに相当します。 |
| arg0 | リソースファイルの値中、{0} で指定された箇所をこの属性の値で書き換えます |
| arg1 | リソースファイルの値中、{1} で指定された箇所をこの属性の値で書き換えます |
| arg2 | リソースファイルの値中、{2} で指定された箇所をこの属性の値で書き換えます |
| arg3 | リソースファイルの値中、{3} で指定された箇所をこの属性の値で書き換えます |
| arg4 | リソースファイルの値中、{4} で指定された箇所をこの属性の値で書き換えます |
では、実際にログインアプリケーションの login.jsp においてリソースファイルを利用します。
利用するリソースファイルは messages.properteis です。
以下のような内容の messages.properteis を用意し、WEB-INF/classes/resoureces 以下に用意し、
struts-config.xml に
<message-resources parameter="resources.messages" key="messages" />
という記述を追加してください。
messages.welcome=Welcome
messages.withargs=We are {0} {1} you.
このリソースファイルを利用するためには、login.jsp の先頭に
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
を記述します。
次に、キー"messages.welcome"で指定されている値(Welcome) を取得するには、
<bean:message bundle="messages" key="messages.welcome" />
を記述します。
また、キー"messages.withargs"で指定されている値(We are {0} {1} you.)において、
{0} を "wating"、{1} を "for" に書き換えた値を取得するには
<bean:message bundle="messages" key="messages.withargs" arg0="waiting" arg1="for" />
を記述します。
ここで、以下にこのチュートリアルのお約束(?)、<bean:message> を利用する際に遭遇する可能性のあるエラーを列挙しておきます。
javax.servlet.ServletException: キー "xxx.xxx" に対応するメッセージが見つかりません
考えられ得る原因
-
struts-config.xml の記述ミス。
<message-resources> の parameter に実際には存在しないパス、(例えば、"resources.messa"など)を記述している場合 -
<bean:message> の記述ミス。
属性 key の値がリソースファイル中に存在しない場合
javax.servlet.ServletException: キー xxx に対するメッセージリソースが見つかりません
考えられ得る原因
-
<bean:message> or struts-config.xml の記述ミス。
属性 bundle の値が struts-config.xml 中、<message-resources> の属性 key の値と異なっている場合。リソースファイル中に存在しない場合
Action クラスでのリソースファイルの利用法
Action クラスでリソースファイルを利用する方法について説明します。
Action クラスでは、org.apache.struts.util.MessageResources オブジェクトを利用してリソースファイルを操作します。
org.apache.struts.action.Action には、MessageResources を取得するためのメソッド
1. protected MessageResources getResources(HttpServletRequest, String)
2. protected MessageResources getResources(HttpServletRequest)
が用意されています。
MessageResoureces オブジェクトを利用してリソースファイルの値を取得するには、
public String getMessage(args...)
メソッドを利用します。
(ここでは省略しますが、getMessage の引数は <bean:message> の属性と同じ意味を持ちます。
これらは類推する or 自分で調べる等してください。)
では、実際にログインアプリケーションの LoginAction においてリソースファイルを利用します。
利用するリソースファイルは password.properteis です。
以下のような内容の password.properteis を用意し、WEB-INF/classes/resoureces 以下に用意し、
struts-config.xml に
<message-resources parameter="resources.password" key="password" />
という記述を追加してください。
nkaneko=KANEKO sue=SUE2GU
最初のログインアプリケーションにおける認証は、「ユーザ名=パスワード」なら認証成功とするロジックでした。
今度は、上記のリソースファイルを利用して、
「ユーザ名がリソースファイルのキーである」かつ「パスワードが該当するキーの値である」
場合に認証成功とするロジックに変更します。
具体的には、LoginAction.java のソースを以下のように変更します。
... 略 ...
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
InputForm inputForm = (InputForm) form;
MessageResources passwordResouce = this.getResources(request, "password");
if (inputForm.getPasswd().equals(passwordResouce.getMessage(inputForm.getUsername()))) {
request.setAttribute("username1", inputForm.getUsername());
return mapping.findForward("success");
} else {
return mapping.findForward("error");
}
}
}
以下に Action クラスでリソースファイルを扱う際に遭遇する可能性のあるエラーを列挙しておきます。
javax.servlet.ServletException と java.lang.NullPointerException
考えられ得る原因
-
getResources() で指定したバンドルの誤り or struts-config.xml の記述ミス
getResources() で指定したバンドルが struts-config.xml 中、<message-resources> の属性 key の値と異なっている場合。
この場合、getResources() の返り値は null となり、返り値のオブジェクトを参照しようとした場合に NullException が発生します。
この節で扱ったサンプルアプリケーション
この節で扱ったサンプルアプリケーションを こちらに置いておきます。
DB の扱い方
Web アプリケーションを開発する場合、 DB との連携は、ほぼ必須の要件となります。
(世の中には「Web アプリケーション = DB の Web インタフェース」と呼ぶ人がいるくらいです。)
Struts では、DB との連携の仕組みを Framework レベルでサポートしています。
具体的には、アプリケーション起動時に javax.sql.DataSource インタフェースを実現するオブジェクトをインスタンス化し、
そのインスタンスを管理する枠組み(リソースファイルと同様に論理名によるインスタンスの指定が可能)を持っています。
何はともあれ、まずは Struts が提供する DB 連携サービスを利用してみましょう。
grape.sapid.org で動作している PostgreSQL に loginsample というデータベースを用意しました。
ユーザ名: "webtutorial"、パスワード: "webweb" でアクセス可能にしてあります。
データベースにはテーブル"UserInfo"があります。"UserInfo"のテーブル定義は以下のようになっています。
CREATE TABLE USERINFO ( userId VARCHAR UNIQUE, password VARCHAR, name VARCHAR, PRIMARY KEY (userId) );
まずはこのデータベースに対する DataSource の設定を行います。
先程のログインアプリケーションの struts-config.xml に以下を記述してください。
<struts-config>
<data-sources>
<data-source type="org.apache.commons.dbcp.BasicDataSource" key="loginDB">
<set-property property="driverClassName" value="org.postgresql.Driver" />
<set-property property="url" value="jdbc:postgresql://grape.sapid.org/loginsample" />
<set-property property="username" value="webtutorial" />
<set-property property="password" value="webweb" />
</data-source>
</data-sources>
....
<data-source> タグの属性 type には、javax.sql.DataSource インタフェースを実現するクラスを指定します。
今回は、StrutsBlank の例にも書いてある org.apache.commons.dbcp.BasicDataSource を指定しました。
また、属性 key は message-resoureces の時と同様、この DataSource に対する論理名を指定します。
<data-source> の子要素、<set-property> は、<data-source> タグの属性 type で指定したクラスの
setter メソッドに対して値をセットしています。
<set-property> の属性 property によってセットする属性の名前を、value によって実際にセットする値を記述します。
上の例では、
・driverClassName に "org.postgresql.Driver" をセット(JDBC ドライバの指定)
・url に "jdbc:postgresql://grape.sapid.org/loginsample" をセット(DB の URL を指定)
・username に "webtutorial" をセット(DB にアクセスするユーザを指定)
・password に "webweb" をセット(DB にアクセスするユーザのパスワードを指定)
しています。
struts-config.xml の編集が終わったら、PostgreSQL の JDBC ドライバを WEB-INF/lib 以下にコピーします
ログインアプリケーションを再起動し、アプリケーションにアクセスします。
java.lang.NoClassDefFoundError: org/apache/struts/legacy/GenericDataSource
ログインアプリケーションを再起動すると、上記のようなエラーメッセージが表示されます。
実は、このエラーは struts-config.xml の記述ミスではなく、Struts の問題(少なくとも 1.1 では)です。
Struts 1.1 では、struts.jar というパッケージと struts-legacy.jar というパッケージが存在しています。
struts-legacy.jar は、その名の通り、レガシーとして追いやった(今後はなるべく使わないクラスを集めた)パッケージなのですが、
<datasouces> に関するロジックでは、(リファクタリングに失敗したのか) struts-legacy.jar パッケージに含まれる
クラスを利用します。
よって、今回のエラーを回避するためには、struts-legacy.jar を Web アプリケーションのライブラリに登録しなければなりません。
struts の lib から strus-legacy.jar をコピーして、Web アプリケーションの WEB-INF/lib 以下にコピーし、 再度 Web アプリケーションを起動してください。
action が利用できません
また、<data-source> の記述を間違えた場合(e.g. DB の URL を間違えるなど)には、上記のようなエラーメッセージが吐かれます。 今回のエラーは action クラスが存在しないために発生したのではなく、 Struts アプリケーションの初期化処理中に回避不能な例外(loginDB の初期化に失敗)が発生したために生じたエラーです。
(ログインアプリケーションのログ) 2005-05-31 04:10:19 StandardContext[/lec2-login]サーブレット /lec2-login がload()例外を投げました javax.servlet.UnavailableException: データソース loginDB の初期化 at org.apache.struts.action.ActionServlet.initModuleDataSources
インポートする jar postgresql.jar struts-legacy.jar
Action クラスでの DataSource の利用
では、いよいよ Action クラスで DataSource を利用したプログラムを記述します。
struts-config.xml で設定した DataSource は、ServletContext の attribute に指定した key でセットされています。
Action クラスにおいて DataSource を取得するには、Action クラスの属性 servlet から、
SerrvletContext を取得し、ServletContext の attribute を key で指定することによって取得します。
少し長いですが、ソースで記述すると以下のような感じです。
DataSource ds = (DataSource) servlet.getServletContext().getAttribute("loginDB");
後は、この DataSource を DB 操作プログラムに与えて利用すれば OK です。
話を簡単にするために、先程の DB を操作するクラス db.UserInfoAccessor を作成しました。
このクラスはメソッド "getUser(userId, password)" を利用することで loginsample から、
引数に与えられた userId と password を持つユーザ情報(db.UserInfo) を返します。
また、該当するユーザ情報がない場合は null を返します。
これらのクラス(db.UserInfoAccessor, db.UserInfo) を利用して、
ログインアプリケーションを loginsample データベースからユーザ情報を取得するアプリケーションへと進化させます。
具体的には、
(a). ServletContext から DataSource を取得する
(b). ユーザが入力したユーザ名とパスワードをキーに DB からユーザ情報を取得する。
(c). ユーザ情報がある場合には、カラム"name"の値を success.jsp で表示する
です。
LoginAction クラスは以下のように変更されます。
細かい説明は省きますが、上記の (a). (b). に対応する箇所にはコメントを入れておきました。
loginsample には、ユーザ nkaneko(パスワード HOGE)とユーザ sue(パスワード cocoon)がいますので、
動作確認に利用してください。
public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
InputForm inputForm = (InputForm) form;
// (a).
DataSource ds = (DataSource) servlet.getServletContext().getAttribute("loginDB");
// (b).
UserInfoAccessor userInfoAccessor = new UserInfoAccessor();
userInfoAccessor.setDs(ds);
UserInfo userInfo = userInfoAccessor.getUser(inputForm.getUsername(), inputForm.getPasswd());
if (userInfo != null) {
//(c).
request.setAttribute("username1", userInfo.getName());
return mapping.findForward("success");
} else {
return mapping.findForward("error");
}
}
この節で扱ったサンプルアプリケーション
この節で扱ったサンプルアプリケーションを
こちらに置いておきます。
db.UserInfoAccessor クラスは内部で DBUtils というコンポーネントを利用しているので、WEB-INF/lib 以下に
・commons-dbcp-1.2.1.jar
・commons-dbutils-1.0.jar
・commons-pool-1.2.jar
をコピーして使ってください。
サーバファイルの扱い
Struts アプリケーションでは、設定ファイルとしてリソースファイルを、永続データとして DB を取り扱う枠組みが用意されているため、
サーバファイルを直接読み書きすることは少ないかも知れません。
ですが、サーバファイルの操作にはファイルパスの取得、ファイルのアクセス権について多少のクセがありますのでここで紹介しておきます。
サーバファイルの扱いを説明するため、以下を前提とします。
・Web アプリケーションは /usr/local/tomcat/webapps/test 以下に配備されている。
・Web アプリケーションのコンテキストパスは /sample として設定されている。
さて、このような状況で /usr/local/tomcat/webapps/test/hoge/sample.txt ファイルを操作するにはどうすれば良いでしょうか?
一番単純な方法は File file = new File("/usr/local/tomcat/webapps/test/hoge/sample.txt"); とすることかも知れません。
しかし、この方法を選択した場合、あなたの Web アプリケーションの配備場所が変わるたびにファイルの場所を変えなくてはなりません。
一般に(Struts に限らず)、Web アプリケーションではこのような状況を防ぐため、ServletContext オブジェクトの
getRealPath(String path) メソッドを利用します。
getRealPath() の引数は抽象パス(Web アプリケーション上のパス)であり、返り値は実際のファイルシステム上のパスです。
例えば、上の Web アプリケーションで getRealPath(""); を行った場合、
その返り値は "/usr/local/tomcat/webapps/test" となります。
終わりに
今回は、Struts フレームワークにおいて、リソースファイル、DB、サーバ上のファイルをどのように扱うかについて学びました。 特に、リソースファイルと DB 接続は Web アプリケーションを作る上で、頻繁に利用する技術ですので抑えておくべきポイントです。
演習問題 (おまけ)
DB 接続の節で利用した db.UserInfoAccessor には、テーブルに存在する全ユーザのデータを取得するメソッド (public UserInfo[] getUser()) も実装されています。 このメソッドを使って、loginsample に登録されている全てのユーザ情報を表示する Web アプリケーションを作成しなさい。
by Nobuyuki KANEKO


