静的リソースおよび動的リソースへのアクセス

Mark Basler
ステータス: Early Access

課題

コンポーネントをデザインするときに、開発者は、ほとんどの開発プロジェクトで共通の作業を繰り返す必要があることに気づくはずです。共通して必要な作業の例として、静的リソースまたは動的リソースへのアクセスがあります。静的リソースには、JavaScript ファイル、階層式スタイルシート (CSS) ファイル、画像、表示用のコードなどがあります。動的リソースは、AJAX 呼び出し、フォームの送信、およびリンクアクションなどの入力に基づいて変化する関数に関連付けられています。このエントリでは、静的リソースと動的リソースの要求を処理するさまざまな方法について検証します。

対処法

リソースにアクセスする方法は、コンポーネントの配布方法によって異なります。Web アーカイブ (WAR) またはエンタープライズアーカイブ (EAR) で配布する場合は、静的リソースでも動的リソースでも、多数のアクセス方法があります。Web ページから直接アクセスできるように、リソースをアーカイブにパッケージ化することもできます。

ほとんどの場合は、すべての関連リソースを Java アーカイブ (JAR) にパッケージ化し、アプリケーションに付属します。この方法では、リソースへのアクセス方法の選択肢が限られます。これは、コンポーネント開発者が、コンポーネントユーザー側で行う必要がある構成作業の量を最小限に抑えたいと考えているからです。たとえば、直接アクセス方式を使用してリソースを読み込むと、コンポーネントユーザーの側では、アプリケーションディストリビューション内の特定の場所にリソースファイルをコピーしたり、アプリケーションの配備記述子にアーティファクトを登録したりするなど、不要な構成作業が発生します。

このあとの各節では、静的リソースと動的リソースにアクセスするためのさまざまな方法について説明します。

直接アクセス

直接アクセス方式では、配布するアーカイブ内にリソースをパッケージ化します。これは従来の Web デザインで多く使用した方法で、画像、JavaScript ファイル、CSS ファイルなどを Web モジュール内にパッケージ化します。ユーザーは、所定の URL にアクセスし、Web サーバーまたはアプリケーションサーバーからモジュールを直接ダウンロードします。サーブレットを使用してコンポーネントのリソースにアクセスする場合もあります。この場合、コンポーネントユーザーは、Web アプリケーション標準の配備記述子でサーブレットまたはサーブレットマッピングを定義する必要があります。

直接アクセスは、Web アプリケーションでリソースにアクセスするための一般的な方法です。ただし、開発者は、コンポーネントを使用するために、追加の構成作業を行う必要があります。また、コンポーネント間でリソース名が重複する可能性もあります。リソース名が重複していると、それらのコンポーネントを同じページ内で組み合わせることができません。この方法は、コンポーネントをほかのアプリケーションで使用しない場合、または少人数の開発者がコンポーネントを使用する場合にのみ適しています。

レンダリング

コンポーネントのレンダリングは、FacesServlet を介してアクセスするリソースを配布するときに利用できます。「要求値の適用」フェーズで、レンダリングに制御が移り、レンダリングによって必要な静的リソースを提供する作業が実行されるか、動的呼び出しで適切な機能が委託または実行されます。レンダリングで作業を完了すると、FacesContext に対して responseComplete メソッドが呼び出され、残りの JSF ライフサイクルが省略されます。

この方法は、パフォーマンスと副作用の観点から、いくつかの重大な影響をもたらします。「要求値の適用」フェーズの前に「ビューの復元」フェーズでコンポーネントツリーを再構成する必要があります。この作業は、時間がかかり、特に状態をクライアント側で保持する場合はパフォーマンスに影響する可能性があります。responseComplete メソッドを使用するときは、このフェーズで実行を完了する必要があります。これにより、コンポーネントツリーに好ましくない副作用が生じる可能性があります。コンポーネントの immediate 属性が true に設定されている場合も副作用が生じる可能性があります。その場合、「要求値の適用」フェーズが終了する前に、コンポーネントのロジック (妥当性検査、変換、イベント) が実行されます。このような制限事項から判断して、レンダリングは最適な方法とはいえません。

PhaseListener

PhaseListener を登録することにより、「ビューの復元」フェーズで要求を PhaseListener で処理できます。この場合、JSF 1.2 の延期メソッド (JSF 1.1 のメソッドバインド方式の代替) を使用して、管理対象 Bean にタスクを委託できます。静的な要求の場合、PhaseListener では、phaseEvent.getFacesContext().getViewRoot().getViewId() で渡される要求 URL の一部のような、何らかの識別機構を使用してリソースが特定されます。または、呼び出し phasesEvent.getFacesContext().getExternalContext().getRequestParameterMap() で PhaseEvent 引数を使用して特定できる HttpServletRequest のパラメータのマップから値が抽出されます。

各コンポーネントに固有のニーズに合わせて PhaseListener をコーディングするときの大きな問題は、アプリケーションにバンドルされたすべての faces-config.xml ファイルに登録されているすべての JAR ファイル内の PhaseListener が、実行順序を保証されずに逐次実行されます。実際に、私たちもリソース要求が何度も処理されるという問題に遭遇したことがあります。その結果、応答ではリソースファイルの複数のコピーが返されました。このような問題は、複数の開発者が異なる方法でコンポーネントをコーディングしたときに頻繁に起こります。厳密に管理された開発環境であっても、衝突が発生する可能性はあります。また、複数の PhaseListener が存在するということ自体が、パフォーマンスの低下要因になります。登録されているすべての PhaseListener が要求ごとに実行されるからです。

サードパーティーのライブラリ

既存の PhaseListener に機能を追加することを開発者に対して禁止することは困難です。静的リソースと動的リソースにアクセスする、もっとも良い方法は、カスタムの PhaseListener を一切使用しないことです。そこで、私たちは、Shale-Remoting ライブラリを使用して、すべての静的または動的な要求を処理しています。Shale-Remoting では、開発者による構成の必要がない単一の PhaseListerner を使用して、静的な要求が処理され、管理対象 Bean の延期メソッドに動的な要求が委託されます。ライブラリ内のコンポーネントでこの方法を使用すれば、要求を伝達するためのカスタムコードを開発せずに、すべての静的または動的な要求を処理できます。また、Shale-Remoting を使用する方法では、Web コンテキストルートから始まる URL を使用します。そのため、ページデザイナは、Web アプリケーション内でのページの場所を追跡して、ページが FacesServlet を経由することを確認する必要がありません。

静的な例:

次に示す静的な例のコードは、com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer クラスからの抜粋です。わかりやすくするため、変更を少し加えてあります。コンポーネントのパッケージ化を計画する際、私たちは JAR の /META-INF ディレクトリ内の該当するコンポーネント名の下にリソースを格納することにしました。たとえば、FileUpload の JavaScript ファイルは、/META-INF/fileupload/fileupload.js に格納しました。次に示すコードでは、Shale-Remoting API を使用して、JavaScript ファイル (fileupload.js) と CSS ファイル (fileupload.css) の静的リソースにアクセスする方法を示します。

import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;

/**
* <p>リソースのリンケージを構築するためのステートレスヘルパー Bean</p>
*/
private static XhtmlHelper helper = new XhtmlHelper();


public void encodeEnd(FacesContext context, UIComponent component) throws IOException {

if ((context == null) || (component == null)) {
throw new NullPointerException();
}
ResponseWriter writer = context.getResponseWriter();

....

//Shale-Remoting によるリソースの取得
helper.linkJavascript(context, component, writer,
Mechanism.CLASS_RESOURCE, "/META-INF/fileupload/fileupload.js");
helper.linkStylesheet(context, component, writer,
Mechanism.CLASS_RESOURCE, "/META-INF/fileupload/fileupload.css");

...
}

動的な例:

次に示す動的な例のコードは、com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer クラスからの抜粋です。わかりやすくするため、変更を少し加えてあります。この例では、Shale-Remoting を使用して、アプリケーションの faces-config.xml ファイルに登録されている、管理対象 Bean (bpui_fileupload_handler) の handleFileUpload メソッドに動的にアクセスする方法を示します。動的呼び出し String を作成したら、JavaScript イベントハンドラ関数 onsubmit のパラメータとして使用します。onsubmit 関数は、Dojo bind 関数によって処理される XMLHttpRequest URL を生成します。AJAX (XMLHttpRequest) を使用して動的な Shale-Remoting 呼び出しを実行すると、管理対象 Bean のメソッドに制御が移り、適切な機能が実行されます。次に、org.apache.shale.remoting.faces.ResponseFactory を使用して、javax.faces.context.ResponseWriter を作成します。これにより、startElement と EndElement の各簡易メソッドを使用して要素を記述して、適切な応答が返されるようにすることができます。

FileUploadRenderer のコード:

import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;

private static XhtmlHelper helper=new XhtmlHelper();

public void encodeBegin(FacesContext context, UIComponent component) throws IOException {

if ((context == null) || (component == null)) {
throw new NullPointerException();
}
ResponseWriter writer = context.getResponseWriter();

....

// Shale Remoting によるステータスのコールバック
String fileUploadCallback = helper.mapResourceId(context, Mechanism.DYNAMIC_RESOURCE,
"/bpui_fileupload_handler/handleFileUpload");
outComp.getAttributes().put("onsubmit", "return bpui.fileupload.submitForm(this, '" + retMimeType + "', '" + retFunction + "','" +
progressBarDivId + "', '" + fileUploadCallback + "')");

...
}

注: Shale-Remoting の動的呼び出しを使用する場合は、要求 URL に変更がないときにブラウザで応答データをキャッシュできるように応答ヘッダーを設定します。これにより、FileUpload ProgressBar などのコンポーネントでは問題が発生する可能性があります。このようなコンポーネントでは、同じ URL を使用してステータスの更新がポーリングされます (特に Internet Explorer)。この問題は Shale-Remoting の今後のリリースでは解決する予定ですが、それまでは、応答ヘッダーを設定することで、応答をキャッシュしないようにブラウザに指示できます。次に示すコードは、com.sun.javaee.blueprints.components.ui.fileupload.FileUploadHandler クラスの handleFileStatus メソッドからの抜粋です。ここでは、応答がキャッシュされないようブラウザに指示しています。最新のブラウザではこのようなヘッダーは不要かもしれませんが、この方法は Struts で使用されており、Shale-Remoting でも使用される予定です。
        FacesContext context=FacesContext.getCurrentInstance();
HttpServletResponse response=(HttpServletResponse)context.getExternalContext().getResponse();
response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);

faces-config.xml のコード:

Shale-Remoting を使用して管理対象 Bean に要求をリンクするには、次に示す管理対象 Bean を使用します。bpui_fileuploag_handler 管理対象 Bean で使用するように fileUploadStatus 管理対象 Bean を事前に設定しています。
    <managed-bean>
<managed-bean-name>bpui_fileupload_handler</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.fileupload.FileUploadHandler</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>fileUploadStatus</property-name>
<value>#{fileUploadStatus}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>fileUploadStatus</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.fileupload.FileUploadStatus</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

HttpServerRequest からのパラメータを管理対象 Bean に設定する場合、formInputElement というフォーム要素の managed-property 宣言は次のようになります。
     <managed-property>
<property-name>formInputElementValue</property-name>
<value>#{param.
formInputElement}</value>
</managed-property>
JSF の暗黙オブジェクトについては、『JavaEE5 チュートリアル』を参照してください。HttpServerRequest のパラメータ値は、HttpServletRequest パラメータのマップを使用して、管理対象 Bean 内で取得することもできます。マップは、呼び出し FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap() を使用して特定できます。

FileUploadHandler のコード:

Shale-Remoting ライブラリを使用して管理対象 Bean に要求をリンクしたら、ResponseFactory を使用して ResponseWriter を作成し、要求の応答が返されるようにします。
import org.apache.shale.remoting.faces.ResponseFactory;
import javax.faces.context.ResponseWriter;

private static ResponseFactory factory=new ResponseFactory();

public void handleFileUpload() {
FacesContext context=FacesContext.getCurrentInstance();

...

try {
ResponseWriter writer = factory.getResponseWriter(context, "text/xml");
writer.startElement("response", null);
writer.startElement("message", null);
writer.write(status.getMessage());
writer.endElement("message");
...

writer.endElement("response");
writer.flush();
} catch (IOException iox) {
getLogger().log(Level.SEVERE, "response.exeception", iox);
}
}

参考資料


© Sun Microsystems 2006. Java BluePrints Solutions Catalog の内容はすべて著作権保護されており、サン・マイクロシステムズ社の書面による許可なしに他の著作物に発表することを禁止します。