4.7. メッセージ管理

4.7.1. Overview

メッセージとは、画面や帳票等に表示する固定文言、またはユーザの画面操作の結果に応じて表示する動的文言を指す。
また、エラーメッセージは、できるだけ細かく定義することを推奨する。

Warning

以下の場合において、運用中、あるいは運用前の試験の際、エラーの原因を究明できなくなるリスクが生じる。(開発中は、特に困らないかもしれない。)

  • エラーメッセージを、1つのみ定義している
  • エラーメッセージを、「重要」と「警告」の2つしか定義していない

その結果、開発メンバが少ない中で、メッセージの定義変更を行い、開発が進むにつれて、修正コストが増えることになる。 そのため、あらかじめメッセージは、細かい粒度で定義しておくことを推奨する。

4.7.1.1. メッセージタイプ

ユーザの画面操作の結果に応じて表示するメッセージは、内容に応じて、以下3種類のメッセージタイプに分けて管理する。
メッセージを定義する際は、出力するメッセージが、どのタイプに属するか意識すること。
メッセージタイプ カテゴリ 概要
info 情報メッセージ ユーザの操作による処理が正常に実行された後、画面に表示するメッセージ。
warn 警告メッセージ 処理は継続できるが、注意喚起が必要な状態である場合に表示するメッセージ。(例:パスワード有効期限切れが近い場合の通知メッセージ)
error 入力エラーメッセージ ユーザの入力値が不正な場合に、入力画面に表示するメッセージ。
  業務エラーメッセージ 業務ロジックでエラーと判定された場合に表示するメッセージ
  システムエラーメッセージ システム起因のエラー(データベースとの接続失敗等)が発生し、ユーザの操作でリカバリできない場合に表示するメッセージ

4.7.1.2. パターン別メッセージタイプの分類

メッセージの出力パターンを、以下に示す。

message pattern

メッセージパターンとメッセージの表示内容、及びメッセージタイプを、以下に示す。

記号 パターン 表示内容 メッセージタイプ
(A)
タイトル
画面のタイトル
-
  • 従業員登録画面

ラベル
画面の項目名
帳票の項目名
コメント
ガイダンス
-
  • ユーザー名
  • パスワード
(B)
ダイアログ
確認メッセージ
info
  • 登録してよろしいでしょうか?
  • 削除してよろしいでしょうか?
(C)
結果メッセージ
正常終了
info
  • 登録しました。
  • 削除しました。
(D)

警告
warn
  • パスワードの有効期限切れが間近です。パスワードを変更して下さい。
  • サーバが混み合っています。時間をおいてから再度実行して下さい。
(E)

単項目チェックエラー
error
  • “ユーザー名”は必須です。
  • “名前”は20桁以内で入力してください。
  • “金額”には数字を入力してください。
(F)

相関チェックエラー
error
  • “パスワード”と”パスワード(確認用)”が一致しません。
(G)

業務エラー
error
  • キャンセル可能期間を過ぎているため、予約を取り消せません。
  • 登録可能件数を超えているため、登録できません。
(H)

システムエラー
error
  • XXXシステム閉塞中のため、しばらく経ってから再度実行して下さい
  • タイムアウトが発生しました。
  • システムエラーが発生しました。

4.7.1.3. メッセージID体系

メッセージは、メッセージIDをつけて管理することを推奨する。
主な理由は、以下3つの利点を得るためである。
  • メッセージ変更時に、ソースコードを修正することなくメッセージを変更するため
  • メッセージの出力箇所を特定しやすくするため
  • 国際化に対応できるため

メッセージIDの決め方は、メンテナンス性向上のため、規約を作って統一することを強く推奨する。

メッセージパターン毎のメッセージID規約例を以下に示す。
開発プロジェクトでメッセージID規約が定まっていない場合は、参考にされたい。

4.7.1.3.1. タイトル

画面のタイトルに使用する、メッセージIDの決め方について説明する。
  • フォーマット

    接頭句 区切り 業務名 区切り 画面名
    title
    .
    nnn*
    .
    nnn*
  • 記述内容

    項目 位置 内容 備考
    接頭句
    1-5桁目 (5桁)
    “title” (固定)

    業務名
    可変長:任意
    spring-mvc.xmlで定義したviewResolverのprefixの下のディレクトリ(HTMLの上位ディレクトリ)

    画面名
    可変長:任意
    HTML名
    ファイル名が”aaa.html”の場合”aaa”の部分
  • 定義例

    # "/WEB-INF/views/admin/top.html"の場合
    title.admin.top=Admin Top
    # "/WEB-INF/views/staff/createForm.html"の場合
    title.staff.createForm=Staff Register Input
    

    Tip

    本例は、テンプレートレイアウトを利用する場合に有効である。詳細はThymeleafにおける画面レイアウトを参照されたい。 テンプレートレイアウトを利用しない場合は、次に説明するラベルの規約を利用しても良い。


4.7.1.3.2. ラベル

画面のラベル、帳票の固定文言に使用する、メッセージIDの決め方について説明する。

  • フォーマット

    接頭句 区切り プロジェクト区分 区切り 業務名 区切り 項目名
    label
    .
    xx
    .
    nnn*
    .
    nnn*
  • 記述内容

    項目 位置 内容 備考
    接頭句
    1-5桁目 (5桁)
    “label” (固定)

    プロジェクト区分
    7-8桁名 (2桁)
    プロジェクト名のアルファベット2桁表記

    業務名
    可変長:任意


    項目名
    可変長:任意
    ラベル名、説明文名

    Note

    入力チェックエラーのメッセージに項目名(論理名)を含める場合は、

    • フォームのモデル名 + “.” + フィールド名
    staffForm.staffName = Staff name
    
    • フィールド名
    staffName = Staff name
    

    にする必要がある。

  • 使用例

    # スタッフ登録画面のフォームの項目名
    # プロジェクト区分=em (Event Management System)
    label.em.staff.staffName=Staff name
    # ツアー検索画面に表示する説明文の場合
    # プロジェクト区分=tr (Tour Reservation System)
    label.tr.tourSearch.tourSearchMessage=You can search tours with the specified conditions.
    

    Note

    プロジェクトが複数存在する場合に、メッセージIDが重複しないようにプロジェクト区分を定義する。 単一プロジェクトの場合でも、将来を見据えてプロジェクト区分を定義することを推奨する。

4.7.1.3.3. 結果メッセージ

4.7.1.3.3.1. 業務間で共通して使用するメッセージ

同一メッセージを定義しないように、複数の業務間で共有するメッセージについて説明する。

  • フォーマット

    メッセージタイプ 区切り プロジェクト区分 区切り 共通メッセージ区分 区切り エラーレベル 連番
    x
    .
    xx
    .
    fw
    .
    9
    999
  • 記述内容

    項目 位置 内容 備考
    メッセージタイプ
    1桁目 (1桁)
    info : i
    warn : w
    error : e

    プロジェクト区分
    3-4桁目 (2桁)
    プロジェクト名のアルファベット2桁表記

    共通メッセージ区分
    6-7桁目 (2桁)
    “fw” (固定)

    エラーレベル
    9桁 (1桁)
    0-1 : 正常メッセージ
    2-4 : 業務エラー(準正常)
    5-7 : 入力チェックエラー
    8 : 業務エラー(エラー)
    9 : システムエラー

    連番
    10-12桁目 (3桁)
    連番で利用する(000-999)
    メッセージ削除となっても連番は空き番として、削除しない
  • 使用例

    # 登録が成功した場合(正常メッセージ)
    i.ex.fw.0001=Registered successfully.
    # サーバリソース不足
    w.ex.fw.9002=Server busy. Please, try again.
    # システムエラー発生の場合(システムエラー)
    e.ex.fw.9001=A system error has occurred.
    
4.7.1.3.3.2. 各業務で個別に使用するメッセージ

業務で個別に使用するメッセージについて説明する。

  • フォーマット

    メッセージタイプ 区切り プロジェクト区分 区切り 業務メッセージ区分 区切り エラーレベル 連番
    x
    .
    xx
    .
    xx
    .
    9
    999
  • 記述内容

    項目 位置 内容 備考
    メッセージタイプ
    1桁目 (1桁)
    info : i
    warn : w
    error : e

    プロジェクト区分
    3-4桁目 (2桁)
    プロジェクト名のアルファベット2桁表記

    業務メッセージ区分
    6-7桁目 (2桁)
    業務IDなど業務毎に決める2桁の文字

    エラーレベル
    9桁 (1桁)
    0-1 : 正常メッセージ
    2-4 : 業務エラー(準正常)
    5-7 : 入力チェックエラー
    8 : 業務エラー(エラー)
    9 : システムエラー

    連番
    10-12桁目 (3桁)
    連番で利用する(000-999)
    メッセージ削除となっても連番は空き番として、削除しない
  • 使用例

    # ファイルのアップロードが成功した場合
    i.ex.an.0001={0} upload completed.
    # パスワードの推奨変更期間が過ぎている場合
    w.ex.an.2001=The recommended change interval of password has passed. Please change your password.
    # ファイルサイズが制限を超えている場合
    e.ex.an.8001=Cannot upload, Because the file size must be less than {0}MB.
    # データに不整合がある場合
    e.ex.an.9001=There are inconsistencies in the data.
    

4.7.1.3.4. 入力チェックエラーメッセージ

入力チェックでエラーがある場合に出力するメッセージについては、エラーメッセージの定義を参照されたい。

Note

入力チェックエラーの出力場所に関する基本方針を、以下に示す。

  • 単項目入力チェックエラーのメッセージは、対象の項目がわかるように項目の横に表示させる。
  • 相関入力チェックエラーのメッセージは、ページ上部などにまとめて表示させる。
  • 単項目チェックでもメッセージを項目の横に表示させにくい場合は、ページ上部に表示させる。
    その場合は、メッセージに項目名を含める。

4.7.2. How to use

4.7.2.1. プロパティファイルに設定したメッセージの表示

4.7.2.1.1. プロパティを使用する際の設定

メッセージ管理を行うorg.springframework.context.MessageSourceの実装クラスの定義を行う。

  • applicationContext.xml

    <!-- Message -->
    <bean id="messageSource"
        class="org.springframework.context.support.ResourceBundleMessageSource"> <!-- (1) -->
        <property name="basenames"> <!-- (2) -->
            <list>
                <value>i18n/application-messages</value>
            </list>
        </property>
    </bean>
    
    項番 説明
    (1)
    MessageSourceの定義。ここではResourceBundleMessageSourceを使用する。
    (2)
    使用するメッセージプロパティの基底名を定義する。クラスパス相対で指定する。
    この例では”src/main/resources/i18n/application-messages.properties”を読み込む。

4.7.2.1.2. プロパティに設定したメッセージの表示

  • application-messages.properties

    ここでは、application-messages.propertiesにメッセージを定義する例を示す。

    label.aa.bb.year=Year
    label.aa.bb.month=Month
    label.aa.bb.day=Day
    

    Note

    文字コード「ISO-8859-1」では表現できない文字(日本語など)はnative2asciiコマンドで ISO-8859-1に変換して使用することが多かった。しかし、JDK 6からは文字コードを指定できるようになったため、 変換する必要はない。文字コードUTF-8にすることで、propertiesファイルに直接日本語等を使用できる。

    • application-messages.properties

      label.aa.bb.year=
      label.aa.bb.month=
      label.aa.bb.day=
      

    この場合、以下のように、ResourceBundleMessageSourceにも読み込む文字コードを指定する必要がある。

    • applicationContext.xml

      <bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
          <property name="basenames">
              <list>
                  <value>i18n/application-messages</value>
              </list>
          </property>
          <property name="defaultEncoding" value="UTF-8" />
      </bean>
      

    デフォルトではISO-8859-1が使用されるため、日本語等をpropertiesファイルに直接記述したい場合は、 必ずdefaultEncodingを設定すること。

  • テンプレートHTML

    上記で設定したメッセージは、Thymeleafのメッセージ式 #{} を用いて表示できる。

    <span th:text="#{label.aa.bb.year}"></span>
    <span th:text="#{label.aa.bb.month}"></span>
    <span th:text="#{label.aa.bb.day}"></span>
    

    フォームのラベルと使用する場合は、以下のように使用すれば良い。

    <form th:object="${sampleForm}" method="post">
        <label for="year" th:text="#{label.aa.bb.year}">
        </label>: <input th:field="*{year}">
        <br>
        <label for="month" th:text="#{label.aa.bb.month}">
        </label>: <input th:field="*{month}">
        <br>
        <label for="day" th:text="#{label.aa.bb.day}">
        </label>: <input th:field="*{day}">
    </form>
    

    ブラウザで表示すると以下のように出力される。

    ../../_images/message-management-ymd.png

    Tip

    国際化に対応する場合は、

    src/main/resources/i18n
                        ├ application-messages.properties (英語メッセージ)
                        ├ application-messages_fr.properties (フランス語メッセージ)
                        ├ ...
                        └ application-messages_ja.properties (日本語メッセージ)
    

    というように各言語用のpropertiesファイルを作成すればよい。 詳細は、国際化を参照されたい。

4.7.2.2. 結果メッセージの表示

サーバサイドでの処理の成功や、失敗を示す結果メッセージを格納するクラスとして、
共通ライブラリでは、org.terasoluna.gfw.common.message.ResultMessages、およびorg.terasoluna.gfw.common.message.ResultMessageを提供している。
クラス名 説明
ResultMessages
結果メッセージの一覧とメッセージタイプを持つクラス。
結果メッセージの一覧はList<ResultMessage>、メッセージタイプはorg.terasoluna.gfw.common.message.ResultMessageTypeインタフェースで表現される。
ResultMessage
結果メッセージのメッセージID、または、メッセージ本文を持つクラス。

4.7.2.2.1. 基本的な結果メッセージの使用方法

ControllerでResultMessagesを生成して画面に渡し、結果メッセージを表示する方法を説明する。 なお、以下では、TERASOLUNAのJSPタグである <t:messagesPanel> のデフォルト設定で出力するHTMLを生成する、ThymeleafのテンプレートHTMLを記述している。

  • Controllerクラス

    ResultMessagesオブジェクトの生成方法、および画面へメッセージを渡す方法を示す。 application-messages.proertiesには、各業務で個別に使用するメッセージの例が定義されていることとする。

    package com.example.sample.app.message;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.terasoluna.gfw.common.message.ResultMessages;
    
    @Controller
    @RequestMapping("message")
    public class MessageController {
    
      @RequestMapping(method = RequestMethod.GET)
      public String hello(Model model) {
        ResultMessages messages = ResultMessages.error().add("e.ex.an.9001"); // (1)
        model.addAttribute(messages); // (2)
        return "message/index";
      }
    }
    
    項番 説明
    (1)
    メッセージタイプが”error”であるResultMessagesを作成し、
    メッセージIDが”e.ex.an.9001”である結果メッセージを設定する。
    この処理は次と同義である。
    ResultMessages.error().add(ResultMessage.fromCode("e.ex.an.9001"));
    メッセージIDを指定する場合は、ResultMessageオブジェクトの生成を省略できるため、省略することを推奨する。
    (2)
    ResultMessagesをModelに追加する。
    属性は指定しなくてよい。(属性名は”resultMessages”になる)
  • テンプレートHTML

    WEB-INF/views/message/index.htmlを、以下のように記述する。

    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org"> <!-- (1) -->
    <head>
    <title>Result Message Example</title>
    </head>
    <body>
        <h1>Result Message</h1>
        <div th:if="${resultMessages} != null" class="alert"
            th:classappend="|alert-${resultMessages.type}|"> <!-- (2) -->
            <ul>
                <li th:each="message : ${resultMessages}"
                     th:text="${message.code} != null ? ${#messages.msgWithParams(message.code, message.args)} : ${message.text}"></li> <!-- (3) -->
            </ul>
        </div>
    
    </body>
    </html>
    
    項番 説明
    (1)
    スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。
    (2)
    属性名が”resultMessages”のオブジェクトがnullでないとき、 <div> とその配下の要素が実行される。
    th:classappend 属性は設定した値をもとからある class 属性の値に追加するもので、ここでは可変であるメッセージタイプを設定している。
    この class 属性と th:classappend 属性によって、出力されるHTMLにおける class 属性が構築される。
    (3)
    属性名が”resultMessages”のオブジェクトに格納された message 変数を、Thymeleafのメッセージ式 #messages を使用して繰り返し取得し、出力する。

    ブラウザで表示すると、以下のように出力される。

    ../../_images/message-management-resultmessage-basic.png

    出力されるHTMLを、以下に示す(説明しやすくするために整形している)。

    <div class="alert alert-error"><!-- (1) -->
      <ul><!-- (2) -->
        <li>There are inconsistencies in the data.</li><!-- (3) -->
      </ul>
    </div>
    
    項番 説明
    (1)
    メッセージタイプに対応して”alert-error”クラスが付与されている。デフォルトでは<div>タグのclassに”alert alert-[メッセージタイプ]”が付与される。
    (2)
    結果メッセージのリストが<ul>タグで出力される。
    (3)
    メッセージIDに対応するメッセージがMessageSourceから解決される。

    出力されるメッセージはclassが付けられているにすぎないため、その見栄えは出力されたclassに合わせてCSSでカスタマイズする必要がある(後述する)。

    Note

    ResultMessages.error().add(ResultMessage.fromText("There are inconsistencies in the data."));というように、 メッセージの本文をハードコードすることもできるが、保守性を高めるため、メッセージキーを使用してResultMessageオブジェクトを作成し、 メッセージ本文はプロパティファイルから取得することを推奨する。


メッセージのプレースホルダに値を埋める場合は、次のようにaddメソッドの第二引数以降に設定すればよい。

ResultMessages messages = ResultMessages.error().add("e.ex.an.8001", 1024);
model.addAttribute(messages);

この場合、以下のようなHTMLが出力される。

<div class="alert alert-error">
  <ul>
    <li>Cannot upload, Because the file size must be less than 1,024MB.</li>
  </ul>
</div>

Note

ResourceBundleMessageSourceはメッセージを生成する際にjava.text.MessageFormatを使用するため、1024は カンマ区切りで1,024と表示される。カンマが不要な場合は、プロパティファイルには以下のように設定する。

e.ex.an.8001=Cannot upload, Because the file size must be less than {0,number,#}MB.

詳細は、Javadocを参照されたい。


以下のように、複数の結果メッセージを設定することもできる。

ResultMessages messages = ResultMessages.error()
    .add("e.ex.an.9001")
    .add("e.ex.an.8001", 1024);
model.addAttribute(messages);

この場合は、次のようなHTMLが出力される(テンプレートHTMLの変更は、不要である)。

<div class="alert alert-error">
  <ul>
    <li>There are inconsistencies in the data.</li>
    <li>Cannot upload, Because the file size must be less than 1,024MB.</li>
  </ul>
</div>

infoメッセージを表示したい場合は、次のようにResultMessages.info()メソッドでResultMessagesオブジェクトを作成すればよい。

ResultMessages messages = ResultMessages.info().add("i.ex.an.0001", "XXXX");
model.addAttribute(messages);

以下のようなHTMLが、出力される。

<div class="alert alert-info"><!-- (1) -->
  <ul>
    <li>XXXX upload completed.</li>
  </ul>
</div>
項番 説明
(1)
メッセージタイプに対応して、出力されるclass名が”alert alert-info”に変わっている。

標準では、以下のメッセージタイプが用意されている。 標準のメッセージタイプはCSSフレームワークであるBootstrapAlertsコンポーネントに対応しており、<t:messagesPanel />のデフォルト設定で利用できる。

メッセージタイプ ResultMessagesオブジェクトの作成 デフォルトで出力されるclass名 備考
success
ResultMessages.success()
alert alert-success
-
info
ResultMessages.info()
alert alert-info
-
warning
ResultMessages.warning()
alert alert-warning
Boostrap v3の「alert-warning」に対応するため、5.0.0から追加。
error
ResultMessages.error()
alert alert-error
-
danger
ResultMessages.danger()
alert alert-danger
-
primary
ResultMessages.primary()
alert alert-primary
Boostrap v4の「alert-primary」に対応するため、5.7.0から追加。
secondary
ResultMessages.secondary()
alert alert-secondary
Boostrap v4の「alert-secondary」に対応するため、5.7.0から追加。
light
ResultMessages.light()
alert alert-light
Boostrap v4の「alert-light」に対応するため、5.7.0から追加。
dark
ResultMessages.dark()
alert alert-dark
Boostrap v4の「alert-dark」に対応するため、5.7.0から追加。

メッセージタイプに応じてCSSを定義されたい。以下に、CSSを適用した場合の例を示す。

.alert {
  margin-bottom: 15px;
  padding: 10px;
  border: 1px solid;
  border-radius: 4px;
  text-shadow: 0 1px 0 #ffffff;
}
.alert-info {
  background: #ebf7fd;
  color: #2d7091;
  border-color: rgba(45, 112, 145, 0.3);
}
.alert-warning {
  background: #fffceb;
  color: #e28327;
  border-color: rgba(226, 131, 39, 0.3);
}
.alert-error {
  background: #fff1f0;
  color: #d85030;
  border-color: rgba(216, 80, 48, 0.3);
}
  • ResultMessages.error().add("e.ex.an.9001")を出力した例

    ../../_images/message-management-resultmessage-error.jpg
  • ResultMessages.warning().add("w.ex.an.2001")を出力した例

    ../../_images/message-management-resultmessage-warn.jpg
  • ResultMessages.info().add("i.ex.an.0001", "XXXX")を出力した例

    ../../_images/message-management-resultmessage-info.jpg

    Warning

    本例では、メッセージキーをハードコードで設定している。しかしながら、保守性を高めるためにも、メッセージキーは、定数クラスにまとめることを推奨する。

    メッセージキー定数クラスの自動生成ツールを参照されたい。

4.7.2.2.2. 結果メッセージの属性名指定

ResultMessagesをModelに追加する場合、基本的には属性名を省略できる。
ただし、ResultMessagesは一つのメッセージタイプしか表現できない。
1画面に異なるメッセージタイプのResultMessages同時に表示したい場合は、明示的に属性名を指定してModelに設定する必要がある。
  • Controller (MessageControllerに追加)

    @RequestMapping(value = "showMessages", method = RequestMethod.GET)
    public String showMessages(Model model) {
    
        model.addAttribute("messages1",
                    ResultMessages.warning().add("w.ex.an.2001")); // (1)
        model.addAttribute("messages2",
                    ResultMessages.error().add("e.ex.an.9001")); // (2)
    
        return "message/showMessages";
    }
    
    項番 説明
    (1)
    メッセージタイプが”warning”である、ResultMessagesを属性名”messages1”でModelに追加する。
    (2)
    メッセージタイプが”info”である、ResultMessagesを属性名”messages2”でModelに追加する。
  • テンプレートHTML (WEB-INF/views/message/showMessages.html)

    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org"> <!-- (1) -->
    <head>
    <meta charset="utf-8">
    <title>Result Message Example</title>
    <style type="text/css">
    .alert {
        margin-bottom: 15px;
        padding: 10px;
        border: 1px solid;
        border-radius: 4px;
        text-shadow: 0 1px 0 #ffffff;
    }
    
    .alert-info {
        background: #ebf7fd;
        color: #2d7091;
        border-color: rgba(45, 112, 145, 0.3);
    }
    
    .alert-warning {
        background: #fffceb;
        color: #e28327;
        border-color: rgba(226, 131, 39, 0.3);
    }
    
    .alert-error {
        background: #fff1f0;
        color: #d85030;
        border-color: rgba(216, 80, 48, 0.3);
    }
    </style>
    </head>
    <body>
        <h1>Result Message</h1>
        <h2>Messages1</h2>
        <div th:if="${messages1 != null}" th:text="${messages1}" class="alert" th:classappend="|alert-${messages1.type}|" /><!-- (2) -->
        <h2>Messages2</h2>
        <div th:if="${messages2 != null}" th:text="${messages2}" class="alert" th:classappend="|alert-${messages2.type}|" /><!-- (3) -->
    </body>
    </html>
    
    項番 説明
    (1)
    スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。
    (2)
    属性名が”messages1”であるResultMessagesを表示する。
    (3)
    属性名が”messages2”であるResultMessagesを表示する。

    ブラウザで表示すると、以下のように出力される。

    ../../_images/message-management-multiple-messages.jpg

4.7.2.2.3. 業務例外メッセージの表示

org.terasoluna.gfw.common.exception.BusinessExceptionorg.terasoluna.gfw.common.exception.ResourceNotFoundException
内部でResultMessagesを保持している。
業務例外メッセージを表示する場合は、ServiceクラスでResultMessagesを設定したBusinessExceptionをスローすること。
ControllerクラスではBusinessExceptionをキャッチし、例外中の結果メッセージをModelに追加する。
  • Serviceクラス

    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
        // omitted
    
        public void create(...) {
    
            // omitted...
    
            if (...) {
                // illegal state!
                ResultMessages messages = ResultMessages.error()
                                                        .add("e.ex.an.9001"); // (1)
                throw new BusinessException(messages);
            }
        }
    
    }
    
    項番 説明
    (1)
    エラーメッセージをResultMessagesで作成し、BusinessExceptionに設定する。
  • Controllerクラス

    @RequestMapping(value = "create", method = RequestMethod.POST)
    public String create(@Validated UserForm form, BindingResult result, Model model) {
        // omitted
    
        try {
            userService.create(user);
        } catch (BusinessException e) {
            ResultMessages messages = e.getResultMessages(); // (1)
            model.addAttribute(messages);
    
            return "user/createForm";
        }
    
        // omitted
    }
    
    項番 説明
    (1)
    BusinessExceptionが保持するResultMessagesを取得し、Modelに追加する。

通常、エラーメッセージ表示する場合は、ControllerでResultMessagesオブジェクトを作成するのではなく、 こちらの方法を使用する。


4.7.3. How to extend

4.7.3.1. 独自メッセージタイプを作成する

メッセージタイプを追加したい場合の、独自メッセージタイプ作成方法について説明する。
通常は、用意されているメッセージタイプのみで十分であるが、採用しているCSSライブラリによっては
メッセージタイプを追加したい場合がある。例えば”notice”というメッセージタイプを追加する場合を説明する。
まず、以下のようにorg.terasoluna.gfw.common.message.ResultMessageTypeインタフェースを実装した
独自メッセージタイプクラスを作成する。
import org.terasoluna.gfw.common.message.ResultMessageType;

public enum ResultMessageTypes implements ResultMessageType { // (1)
    NOTICE("notice");

    private ResultMessageTypes(String type) {
        this.type = type;
    }

    private final String type;

    @Override
    public String getType() { // (2)
        return this.type;
    }

    @Override
    public String toString() {
        return this.type;
    }
}
項番 説明
(1)
ResultMessageTypeインタフェースを実装したEnumを定義する。定数オブジェクトで作成してもよいが、Enumで作成することを推奨する。
(2)
getTypeの返り値が出力されるCSSのclass名に対応する。
このメッセージタイプを使用して以下のようにResultMessagesを作成する。
ResultMessages messages = new ResultMessages(ResultMessageTypes.NOTICE) // (1)
        .add("w.ex.an.2001");
model.addAttribute(messages);
項番 説明
(1)
ResultMessagesのコンストラクタに対象のResultMessageTypeを指定する。

この場合、以下のようなHTMLが出力される。

<div class="alert alert-notice">
  <ul>
    <li>The recommended change interval has passed password. Please change your password.</li>
  </ul>
</div>

Tip

拡張方法は、org.terasoluna.gfw.common.message.StandardResultMessageTypeが参考になる。


4.7.4. Appendix

4.7.4.1. ResultMessagesを使用しない結果メッセージの表示

ResultMessagesオブジェクト以外にも、フレームワークがリクエストスコープに設定した文字列(エラーメッセージなど)を表示することができる。

例えば、Spring Securityは認証エラー時に、”SPRING_SECURITY_LAST_EXCEPTION”という属性名で発生した例外クラスを
リクエストスコープに設定する。
この例外メッセージを、 ResultMessages を使用したときとと同様に出力したい場合は、以下のように設定すればよい。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"> <!-- (1) -->
<head>
<title>Login</title>
<style type="text/css">
/* (2) */
.alert {
    margin-bottom: 15px;
    padding: 10px;
    border: 1px solid;
    border-radius: 4px;
    text-shadow: 0 1px 0 #ffffff;
}

.alert-error {
    background: #fff1f0;
    color: #d85030;
    border-color: rgba(216, 80, 48, 0.3);
}
</style>
</head>
<body>
    <div th:if="${!param.keySet().contains('error')}" th:remove="tag">
        <div th:if="${SPRING_SECURITY_LAST_EXCEPTION != null}" class="alert alert-error" /> <!-- (3) -->
            <ul>
                <li th:text="${SPRING_SECURITY_LAST_EXCEPTION.message}"></li>
            </ul>
        </div>
    </div>
    <form th:action="@{/authentication}" method="post">
        <fieldset>
            <legend>Login Form</legend>
            <div>
                <label for="username">Username: </label><input
                    id="username" name="username">
            </div>
            <div>
                <label for="password">Password:</label><input
                    type="password" id="password" name="password">
            </div>
            <div>
                <input type="submit" value="Login">
            </div>
        </fieldset>
    </form>
</body>
</html>
項番 説明
(1)
スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。
(2)
結果メッセージ表示用のCSSを再掲する。実際はCSSファイルに記述することを強く推奨する。
(3)
Exception オブジェクトが格納されている属性名をThymeleafの変数式 ${} に指定する。
また、 ResultMessages オブジェクトとは異なり、メッセージタイプの情報をもたないため、 class 属性で alert alert-error を明示的に指定している。

認証エラー時に出力されるHTMLは

<div class="alert alert-error"><ul><li>Bad credentials</li></ul></div>

であり、ブラウザでは以下のように出力される。

../../_images/message-management-login-error.jpg

Tip

ログイン用のテンプレートHTMLの内容については、認証を参照されたい。

4.7.4.2. メッセージキー定数クラスの自動生成ツール

これまでの例ではメッセージキーを文字列のハードコードで設定していたが、
メッセージキーは定数クラスにまとめることを推奨する。
ここでは、簡易ツールとして、propertiesファイルからメッセージキー定数クラスを
自動生成するプログラムおよび使用方法を紹介する。必要に応じてカスタマイズして利用されたい。
  1. メッセージキー定数クラスの作成

    まず空のメッセージキー定数クラスを作成する。ここではcom.example.common.message.MessageKeysとする。

    package com.example.common.message;
    
    public class MessageKeys {
    
    }
    
  2. 自動生成クラスの作成

    次にMessageKeysクラスと同じパッケージにMessageKeysGenクラスを作成し、以下のように記述する。

    package com.example.common.message;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.util.regex.Pattern;
    
    import org.apache.commons.io.FileUtils;
    import org.springframework.core.io.ClassPathResource;
    
    public class MessageKeysGen {
        public static void main(String[] args) throws IOException {
            // message properties file
            Class<?> targetClazz = MessageKeys.class;
            File output = new File("src/main/java/" + targetClazz.getName()
                    .replaceAll(Pattern.quote("."), "/") + ".java");
    
            try (InputStream inputStream = new ClassPathResource("i18n/application-messages.properties")
                    .getInputStream();
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    PrintWriter pw = new PrintWriter(FileUtils.openOutputStream(
                            output))) {
                pw.println("package " + targetClazz.getPackage().getName() + ";");
                pw.println("/**");
                pw.println(" * Message Id");
                pw.println(" */");
                pw.println("public class " + targetClazz.getSimpleName() + " {");
    
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    String[] vals = line.split("=", 2);
                    if (vals.length > 1) {
                        String key = vals[0].trim();
                        String value = vals[1].trim();
                        pw.println("    /** " + key + "=" + value + " */");
                        pw.println("    public static final String " + key
                                .toUpperCase().replaceAll(Pattern.quote("."), "_")
                                .replaceAll(Pattern.quote("-"), "_") + " = \"" + key
                                + "\";");
                    }
                }
                pw.println("}");
                pw.flush();
            }
        }
    }
    
  3. メッセージプロパティファイルの用意

    src/main/resource/i18m/application-messages.propertiesにメッセージを定義する。ここでは例として、以下のように設定する。

    i.ex.an.0001={0} upload completed.
    w.ex.an.2001=The recommended change interval has passed password. Please change your password.
    e.ex.an.8001=Cannot upload, Because the file size must be less than {0}MB.
    e.ex.an.9001=There are inconsistencies in the data.
    
  4. 自動生成クラスの実行

    ../../_images/message-management-messagekeysgen.png

    MessageKeysクラスが、以下のように上書きされる。

    package com.example.common.message;
    /**
     * Message Id
     */
    public class MessageKeys {
        /** i.ex.an.0001={0} upload completed. */
        public static final String I_EX_AN_0001 = "i.ex.an.0001";
        /** w.ex.an.2001=The recommended change interval has passed password. Please change your password. */
        public static final String W_EX_AN_2001 = "w.ex.an.2001";
        /** e.ex.an.8001=Cannot upload, Because the file size must be less than {0}MB. */
        public static final String E_EX_AN_8001 = "e.ex.an.8001";
        /** e.ex.an.9001=There are inconsistencies in the data. */
        public static final String E_EX_AN_9001 = "e.ex.an.9001";
    }