トップ 一覧 ヘルプ RSS ログイン

BestPracticesの変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
!!!ベストプラクティス

このセクションではClickアプリケーションのデザインと構築に関するベストプラクティスについてお話します。以下のトピックがカバーされています:

*[セキュリティ|click?page=BestPractices#p1] - J2EEのロールベースのセキュリティ
*[ページの自動マッピング|click?page=BestPractices#p3] - Convention over Configurationの利用
*[ナビゲーション|click?page=BestPractices#p4] - フォワード、リダイレクトにページクラスを使用する
*[テンプレーティング|click?page=BestPractices#p5] - Webアプリケーションの標準化
*[メニュー|click?page=BestPractices#p6] - 画面遷移の集約
*[ロギング|click?page=BestPractices#p7] - 基底クラスでLog4jを使用する
*[エラーハンドリング|click?page=BestPractices#p8] - カスタムエラーページの利用法
*[パフォーマンス|click?page=BestPractices#p9] - ページのパフォーマンスを向上します

!!セキュリティ

アプリケーションのセキュリティのためにJ2EEサーブレットパスロールベースのセキュリティモデルを使用することを強く推奨します。Clickページは独自のセキュリティモデルを実現するためにonSecurityCheck()メソッドを提供しますが、J2EEのモデルのほうが多くの利点があります。

これらの利点には以下のようなものがあります:

*業界標準のパターンであるため開発とメンテナンスがより簡単になります。
*一般的にアプリケーションサーバはLDAPディレクトリ、リレーショナルデータベースを含む多くのセキュリティインフラとの統合手段を提供します。
*サーブレットセキュリティモデルはページをブックマークしているユーザもサポートします。ユーザがあとでこれらのページにアクセスすると、コンテナはリソースへのアクセスを許可する前に自動的に認証処理を行うでしょう。
*このセキュリティモデルを使用することで、ページにセキュリティに関するコードを含める必要がなくなります。これはコードの再利用性の促進につながりますし、少なくともより書きやすいコードになります。

もし、あなたのアプリケーションにきめ細かい、もしくは複雑なセキュリティ要件が求められる場合、JEE宣言型セキュリティモデルとプログラム型セキュリティモデルを必要に応じて組み合わせる必要があります。この場合、宣言型セキュリティを荒い粒度のアクセスコントロールで使用し、プログラム型セキュリティをきめ細かいアクセスコントロールで使用することを推奨します。

!宣言型セキュリティ

J2EEサーブレットセキュリティモデルは、ユーザが保護されたリソースにアクセスする前に正しいロールで認証されている必要があります。J2EEの仕様の多くと比較すると、サーブレットセキュリティモデルは驚くほど簡単です。

例えば管理者用のページを保護するためにセキュリティ制約をweb.xmlに追加します。これによってユーザはadminディレクトリ配下のリソースにアクセスするためにadminロールが必要になります。

{{code xml
<security-constraint>
   <web-resource-collection>
      <web-resource-name>admin</web-resource-name>   
      <url-pattern>/admin/*</url-pattern>
   </web-resource-collection>
   <auth-constraint>
      <role-name>admin</role-name>
   </auth-constraint>
</security-constraint>
}}
ユーザのロールはweb.xmlのsecurity-role要素で定義します。

{{code xml
<security-role>
   <role-name>admin</role-name>
</security-role>
}}

サーブレットのセキュリティモデルは3つの異なる認証方法をサポートしています。

*BASIC - セキュリティがそれほど重要でない内部向けのアプリケーションにおいてのみ推奨されます。これは最も簡単な認証方法で、保護されたリソースにアクセスする前にシンプルなダイアログを表示します。BASIC認証はユーザ名とパスワードをBase64エンコードされた文字列としてサーバに送信するため、それほど安全ではありません。
*DIGEST - 適度なセキュリティレベルが必要な内部向けのアプリケーションで推奨されます。BASIC認証と同じく保護されたりソースにアクセスする前に認証のためのダイアログを表示します。すべてのアプリケーションサーバがDIGEST認証をサポートしているというわけではなく、Apache Tomcatの比較的最近のバージョンだけがこの方法をサポートしています。
*FORM - カスタムログインページが必要なアプリケーションにおいて推奨されます。高いレベルのセキュリティを必要とするアプリケーションにおいてHTTP上でFORM認証を用いることが推奨されます。

認証方法は<login-method>要素で指定します。例えばBASIC認証を使用する場合は以下のように指定します:

{{code xml
<login-config>
   <auth-method>BASIC</auth-method>
   <realm-name>Admin Realm</realm-name>
</login-config>
}}

FORM認証を使用するためにはログインページのパスとエラーページのパスも指定する必要があります:

{{code xml
<login-config>
   <auth-method>FORM</auth-method>
   <realm-name>Secure Realm</realm-name>
   <form-login-config>
      <form-login-page>/login.htm</form-login-page>
      <form-error-page>/login.htm?auth-error=true</form-error-page>
   </form-login-config>
</login-config> 
}}

Clickのlogin.htmページでは、j_usernameとj_passwordというフィールドを含むj_security_checkという特別なフォームが必要です:

{{code html
#if ($request.getParameter("auth-error"))
<div style="margin-bottom:1em;margin-top:1em;color:red;">
  Invalid User Name or Password, please try again.<br/>
  Please ensure Caps Lock is off.
</div>
#end

<form method="POST" action="j_security_check" name="form">
<table border="0" style="margin-left:0.25em;">
 <tr>
   <td><label>User Name</label><font color="red">*</font></td>
   <td><input type="text" name="j_username" maxlength="20" style="width:150px;"/></td>
   <td>&nbsp;</td>
  </tr>
  <tr>
   <td><label>User Password</label><font color="red">*</font></td>
   <td><input type="password" name="j_password" maxlength="20" style="width:150px;"/></td>
   <td><input type="image" src="$context/images/login.png" title="Click to Login"/></td>
  </tr>
</table>
</form>

<script type="text/javascript">
  document.form.j_username.focus(); 
</script>
}}

FORM認証を使用する場合、Clickのログインページクラスの役割は単にログインフォームを表示することだけであるため、アプリケーションロジックを入れないようにしてください。もし、ログインページクラスにナビゲーションロジックを実装しようとするなら、J2EEコンテナは単にそれを無視するか、またはエラーを投げるかもしれません。

以下はadminパスとuserパス配下のページにセキュリティ制約を実現するweb.xmlの一部です。この設定は認証にFORM認証を使用し、認証されていないリクエスト(403)を/not-authorized.htmページにリダイレクトします。

{{code xml
<web-app>

    ..

    <error-page>
        <error-code>403</error-code>
        <location>/not-authorized.htm</location>
    </error-page>	

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>admin</web-resource-name>   
            <url-pattern>/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>user</web-resource-name>   
            <url-pattern>/user/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>Secure Zone</realm-name>
        <form-login-config>
            <form-login-page>/login.htm</form-login-page>
            <form-error-page>/login.htm?auth-error=true</form-error-page>
        </form-login-config>
    </login-config>

    <security-role>
        <role-name>admin</role-name>
    </security-role>

    <security-role>
        <role-name>user</role-name>
    </security-role>

</web-app>
}}

!代替のセキュリティソリューション
JEEで利用可能でない拡張された特徴を持つ代替のセキュリティソリューションも提供されます。RememberMe機能やより良いリソースマッピングとPostログインページサポートなどのように。(Postログインページサポートはユーザがログイン成功後に転送される1つのデフォルトURLの指定を許可します。この機能はすべての非セキュアページへのログインフォームの埋め込みとユーザ認証成功後のホームページへの転送を許可します。)
JEEでは利用できないRememberMe機能やより良いリソースマッピングとPostログインページサポートなどのように拡張された特徴を持つ代替のセキュリティソリューションも提供されます。(Postログインページサポートはユーザがログイン成功後に転送される1つのデフォルトURLの指定を許可します。この機能はすべての非セキュアページへのログインフォームの埋め込みとユーザ認証成功後のホームページへの転送を許可します。)

以下にいくつかの代替のセキュリティソリューションがあります:
*[Spring Security|http://static.springframework.org/spring-security/site/index.html]
*[SecurityFilter|http://securityfilter.sourceforge.net/]
*[JSecurity|http://www.jsecurity.org/]

!その他のリソース

セキュリティに関するより多くの情報を得るために以下のリソースを参照してください:

* [Form Based Authentication|http://infinity.crosslogic.com/clinfinity/ContentPath/crosslogic/Resources/WebPages/cl_site/downloads/white_papers/FormBasedAuthentication.pdf] by Louis E. Mauget
* [Servlet Specification|http://java.sun.com/products/servlet/download.html] by Sun Microsystems
* [Basic authentication scheme|http://en.wikipedia.org/wiki/Basic_authentication_scheme]
* [Digest authentication scheme|http://en.wikipedia.org/wiki/Digest_access_authentication]
* [Https URI scheme|http://en.wikipedia.org/wiki/Https]

!!ページの自動マッピング

あなたはClickのページ自動マッピング機能を使用すべきです。詳しくは[ページの自動マッピング|http://click.sourceforge.net/docs/configuration.html#application-automapping]のトピックを参照してください。

自動マッピングを利用するとclick.xmlにURLのパスとページクラスのマッピングを手動で設定する必要がなくなります。あなたがこの規約に従えば、アプリケーションのメンテナンスはとても容易になり、対応するページクラスとHTMLテンプレートを迅速に決定することができます。

以下はclick.xmlの自動マッピングの設定例です:

{{code xml
<click-app>
  <pages package="com.mycorp.dashboard.page" automapping="true"/>
</click-app> 
}}

ページテンプレートがどのようにページクラスにマッピングされるかを見るために、アプリケーションモードをdebugに設定すると、起動時にマッピングがリストアップされます。この例の場合、以下のようなリストが出力されます:

 [Click] [debug] automapped pages:
 [Click] [debug] /category-tree.htm -> com.mycorp.dashboard.page.CategoryTree
 [Click] [debug] /process-list.htm -> com.mycorp.dashboard.page.ProcessList
 [Click] [debug] /user-list.htm -> com.mycorp.dashboard.page.UserList

!!ナビゲーション

フォワードやリダイレクトを使用してページ間の遷移を行う際、パスよりもみむしろページクラスを使用することでターゲットとなるページを示すべきです。これはコンパイル時のチェックを提供します。そしてもしページの移動があった場合にはJavaコード中のパス文字列を書き換える必要がなくなります。

ページクラスを使用してフォワードするには以下のようにします:

{{code java
public class CustomerListPage extends Page {

    private ActionLink customerLink = 
        new ActionLink("customerLink", this, "onCustomerClick");
    
    ..
    
    public boolean onCustomerClick() {
        Integer id = customerLink.getValueInteger();
        Customer customer = getCustomerService().getCustomer(id); 
   
        CustomerDetailPage customerDetailPage = (CustomerDetailPage)
            getContext().createPage(CustomerDetailPage.class);

        customerDetailPage.setCustomer(customer);
        setForward(customerDetailPage);
   
        return false;
    }
}
}}

ページクラスを使用して別のページにリダイレクトするために、Contextからページのパスを得ることができます。以下の例ではxリクエストパラメータとして顧客IDをターゲットページに送信しています:

{{code java
public class CustomerListPage extends Page {

    private ActionLink customerLink = 
        new ActionLink("customerLink", this, "onCustomerClick");
    
    ..
    
    public boolean onCustomerClick() {
        String id = customerLink.getValueInteger();
   
        String path = getContext().getPagePath(CustomerDetailPage.class);		
        setRedirect(path + "?id=" + id);
   
        return false;
    }
}
}}

別ページへリダイレクトする迅速な方法は、単にターゲットクラスを参照することです。以下の例ではセッションを無効にすることでユーザをログアウトさせ、アプリケーションのホームページにリダイレクトしています:

{{code java
    public boolean onLogoutClick() {
        getContext().getSession().invalidate();
        
        setRedirect(HomePage.class);
        
        return false;
    }
}}

!!テンプレーティング

ページのテンプレーティングの利用は強く推奨されます。ページテンプレートは以下を含む多くの利点を提供します:

*メンテナンスする必要のあるHTMLmp量を大幅に減少させます
*アプリケーション全体で統一されたルック&フィールを確約します
*アプリケーションのグローバルな変更を非常に簡単にします

テンプレートの利用方法については[ページ・テンプレーティング|http://click.sourceforge.net/docs/pages.html#page-templating]のトピックを参照してください。また、[Clickのサンプル|http://click.sourceforge.net/docs/examples.html]がページ・テンプレーティングを使用していますので、そちらも参考にしてください。

!!メニュー

多くのアプリケーションにとって、アプリケーションの画面遷移を集約するために[Menu|http://click.sourceforge.net/docs/extras-api/net/sf/click/extras/control/Menu.html]コントロールはとても有用です。メニューはWEB-INF/menu.xmlで定義されており、変更はとても容易です。

メニューは通常、ページボーダーテンプレートで定義されるので、アプリケーション全体で使用可能です。MenuコントロールはHTMLレンダリングをサポートしないので、あなたはプログラム的にメニューをレンダリングするためにVelocityマクロを記述する必要があります。あなたはボーダーテンプレートで以下のようにマクロを呼び出すでしょう:

{{code html
#writeMenu($rootMenu) 
}}

メニューをレンダリングするためにマクロを使用することの利点は、コードを異なるアプリケーションで再利用することができるという点です。そして、メニューを変更するには単にWEB-INF/menu.xmlを修正するだけでよいのです。マクロを定義するのに最もよい場所はWebアプリケーションルートの/macro.vmです。このファイルはClickによって自動的にインクルードされます。

マクロを使用することで、isUSerInRoles()でアクセスが許可されているユーザが認証されている場合のみ項目を表示するような動的なメニューを作成することも可能です。

{{code html
#if ($menu.isUserInRoles())
   ..
#end 
}}

また、JavaScriptを使用してドロップダウンメニューなどの動的な振る舞いを実現することもできます。例として[Clickのサンプル|http://click.sourceforge.net/docs/examples.html]のMenuのページを参照してください。

!!ロギング

ページでのロギングのためにあなたは[Log4j|http://logging.apache.org/log4j/docs/]を使用するべきです。代替ライブラリは[Commons Logging|http://jakarta.apache.org/commons/logging/]です。もしCommons Loggingを使用している場合、このライブラリにはいくつかのアプリケーションサーバにおいてクラスローダに関する問題があったことに注意してください。Commons Loggingを使用する場合は必ず最新版を使うようにしてください。

ロガーを定義するのに最も良い場所は共通の基底ページです。例えば:

{{code java
public class BasePage extends Page {
    
    protected Logger logger;
    
    public Logger getLogger() {
        if (logger == null) {
            logger = Logger.getLogger(getClass());
        }
        return logger;
    }
}
}}

このパターンでは、すべてのページクラスはBasePageを継承することでgetLogger()メソッドを使用できます。

{{code java
public class CustomerListPage extends BasePage {
    
    public void onGet() {
        try {
            ..
        
        } catch (Exception e) {
            getLogger().error(e);
        }
    }
}
}}

非常に重いデバッグステートメントがある場合、isDebugEnabledスイッチを使用すべきかもしれません。それによってデバッグが必要でなければ呼び出されないようになります。

{{code java
public class CustomerListPage extends BasePage {
    
    public void onGet() {
        if (getLogger().isDebugEnabled()) {
            String msg = ..
            
            getLogger().debug(msg);
        }
        
        ..
    }
}
}}

Clickのロギングファシリティはアプリケーションのためでなく、Click内部での利用のためにデザインされていることに注意してください。Clickがプロダクションモードで実行されている場合、全くログは出力されません。

!!エラーハンドリング

Clickでは、ハンドルされないエラーは表示するために[ErrorPage|http://click.sourceforge.net/docs/click-api/net/sf/click/util/ErrorPage.html]に向けられます。もしアプリケーション独自のエラーハンドリングが必要な場合、カスタムエラーページをWEB-INF/click.xmlに登録することができます。

例を以下に示します:

{{code xml
<page path="click/error.htm" classname="com.mycorp.page.AppErrorPage"/>
}}

一般にアプリケーションは、サービス層のコードもしくは[サーブレットフィルタ|http://click.sourceforge.net/docs/servlet-api/javax/servlet/Filter.html]を使用することでトランザクションエラーを処理するため、エラーページにエラーハンドリング用のロジックを含む必要はないでしょう。

カスタムエラーページの潜在的な用途としては、独自のロギングも含まれています。

例えばアプリケーションがハンドルされないエラーを標準出力ではなくアプリケーションのログに出力する必要がある場合、カスタムエラーページを使うべきかもしれません。エラーのロギングを提供するAppErrorPageの例を示します:

{{code java
package com.mycorp.page.AppErrorPage;
..

public class AppErrorPage extends ErrorPage {
    
    public void onDestory() {
        Logger.getLogger(getClass()).error(getError());
    }
}
}}

!!パフォーマンス

Yahooは、Web アプリケーションのパフォーマンスを改善する[ベストプラクティス|http://developer.yahoo.com/performance/rules.html]のリストを公開しました。

Click Frameworkには、 これらのルールのうちのいくつかを提供する [PerformanceFilter|http://incubator.apache.org/click/docs/extras-api/net/sf/click/extras/filter/PerformanceFilter.html]があります。

ここでは、PerformanceFilterでは対応していないルール -
[ルールその1: ファイルを連結することによってHTTPリクエストを最小化する|http://developer.yahoo.com/performance/rules.html#num_http] と 
[ルールその10: JavascriptとCSSを小さくする|http://developer.yahoo.com/performance/rules.html#minify]
- を採用する方法についての要点を述べます。

ルールその1に関して元記事の中では[CSSスプライト|http://alistapart.com/articles/sprites]についての言及がありますが、ここではCSSスプライトについての話題はに触れません。

指摘したいのは、Webアプリケーション中の個々のページをやみくもに最適化する必要はないということです。その代わりにWebサイトのいわゆるホームページのような良く参照されるであろうページをを候補に絞ることになるでしょう。

このルールその1とその10を適用するのに有用ないくつかのツールが世の中には存在します。

*[YUICompressor|http://developer.yahoo.com/yui/compressor/] - JavaScriptとCSSファイルを縮小化および圧縮し、通信量を小さくします
*[Ant Task for YUICompressor|http://code.google.com/p/javaflight-code/] - YUICompressorを使用するAnt のタスクです
*[JSMin|http://www.crockford.com/javascript/jsmin.html] - YUICompressorと同様のツールですが、縮小化のみで圧縮は行いません。このJSMinがYUICompressorよりまさる部分は実行速度が速いことと、YUICompressorはおおよそビルド時に使われるのに対してJSMinは実行時にもJavaScriptを縮小化するために利用することができる点です。

以下の記事は、YUICompressorとAntを使ってJavaScriptとCSSファイルを連結、圧縮する方法の概要です。

* [Article|http://www.julienlecomte.net/blog/2007/09/16/] explaining how to use Ant and YUICompressor for compression.
* [Article|http://javaflight.blogspot.com/2008/01/introducing-yui-compressor-ant-task.html] outlining how to use a special YUICompressor Ant Task for compression.

上記のアプローチをとると、ClickでもPageで使用する全てのJavaScriptとCSSは2つのファイルに集約されます。ここでは仮にその2つのファイル名をhome-page.cssとhome-page.jsとして話を進めます。
もちろんこの2つのファイルにはPageとControlを利用したときに生成される全てのJavaScriptとCSSが含まれていなければなりません。
その上でClickに対して、これら圧縮された2つのファイル(home-page.cssとhome-page.js)"だけ"をincludeするよう指定します。

Clickは、ユーティリティクラスである[PageImports|http://incubator.apache.org/click/docs/click-api/net/sf/click/util/PageImports.html]を利用して、CSSとJavaScriptをincludeしています。
PageImports は[setInitialized(boolean)|http://incubator.apache.org/click/docs/click-api/net/sf/click/util/PageImports.html#setInitialized(boolean)]メソッドを公開しており、これによりPageImportsがいつ完全に初期化されるかを制御できます。
一旦PageImportsの初期化が完了すると、CSSもJavaScriptもincludeされることがなくなります。

[Page.GetPageImports()|http://incubator.apache.org/click/docs/click-api/net/sf/click/Page.html#getPageImports()]をオーバーライドできることをふまえて、
必要なJavaScriptとCSSファイルをimportし、
PageImportsを初期化された状態にセットし、
PageImportsが他のCSSやJavaScriptファイルをスキップするよう強制します。

以下がサンプルになります。

{{code java
public class HomePage extends Page {

    private Form form = new Form("form");

    public void onInit() {
        form.add(new DateField("date");
        addControl(form);
    }

    public void getPageImports () {
        PageImports pageImports = super.getPageImports();
        String contextPath = getContext().getRequest().getContextPath();

        String cssInclude = contextPath + "/assets/css/home-page.css";
        pageImports.addImport("<link type=\"text/javascript\" href=\"" + cssInclude + "\"/>");

        String jsInclude = contextPath + "/assets/js/home-page.js";
        pageImports.addImport("<script type=\"text/javascript\" src=\"" + jsInclude + "\"></script>");

        // Set pageImports to initialized so that no other CSS and JavaScript files will be included.
        pageImports.setInitialized(true);
    }
} 
}}

以下のborder-template.htmを使います。

{{code html
<html>
  <head>
    <title>Click Examples</title>
    ${cssImports}
  </head>
  <body>

  ...

  ${jsImports}
  </body>
</html>
}}

出力されるHTMLは、ひとつのCSSファイルとひとつのJavaScriptファイルをimportします。

{{code html
<html>
  <head>
    <title>Click Examples</title>
    <link type="text/css" rel="stylesheet" href="/click-examples/assets/css/home-page.css" title="Style"/>
  </head>
  <body>

  ...

  <script type="text/javascript" src="/click-examples/assets/js/home-page.js"></script>
  </body>
</html>
}}

実際に動作するデモが[こちら|http://www.avoka.com/click-examples/general/page-imports-example.htm]にあります。