# 4.7. メッセージ管理

## 4.7.1. Overview

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

Warning

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

- エラーメッセージを、1つのみ定義している

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

その結果、開発メンバが少ない中でメッセージの定義変更を行い、開発が進むにつれて修正コストが増えることになる。

そのため、あらかじめメッセージは細かい粒度で定義しておくことを推奨する。

本節では、プロパティファイルに設定する際の命名規則やプロパティに設定したメッセージの表示方法、ユーザの画面操作に応じたメッセージの出力方法について説明する。

### 4.7.1.1. メッセージタイプ

ユーザの画面操作の結果に応じて表示するメッセージは、内容に応じて、以下3種類のメッセージタイプに分けて管理する。
メッセージを定義する際は、出力するメッセージが、どのタイプに属するか意識すること。

メッセージタイプ | カテゴリ | 概要
--- | --- | ---
info | 情報メッセージ | ユーザの操作による処理が正常に実行された後、画面に表示するメッセージ。
warn | 警告メッセージ | 処理は継続できるが、注意喚起が必要な状態である場合に表示するメッセージ。（例：パスワード有効期限切れが近い場合の通知メッセージ）
error | 入力エラーメッセージ | ユーザの入力値が不正な場合に、入力画面に表示するメッセージ。
| 業務エラーメッセージ | 業務ロジックでエラーと判定された場合に表示するメッセージ
| システムエラーメッセージ | システム起因のエラー（データベースとの接続失敗等）が発生し、ユーザの操作でリカバリできない場合に表示するメッセージ

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

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

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

記号 | パターン | 表示内容 | メッセージタイプ | 例
--- | --- | --- | --- | ---
(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の下のディレクトリ（JSP/HTMLの上位ディレクトリ） |
  画面名 | 可変長：任意 | JSP名/HTML名 | ファイル名が”aaa.jsp”や”aaa.html”の場合”aaa”の部分

- 定義例

      # "/WEB-INF/views/admin/top.jsp"の場合
      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.xx.fw.0001=Registered successfully.
      # サーバリソース不足
      w.xx.fw.9002=Server busy. Please, try again.
      # システムエラー発生の場合（システムエラー）
      e.xx.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.xx.yy.0001={0} upload completed.
      # パスワードの推奨変更期間が過ぎている場合
      w.xx.yy.2001=The recommended change interval of password has passed. Please change your password.
      # ファイルサイズが制限を超えている場合
      e.xx.yy.8001=Cannot upload, Because the file size must be less than {0}MB.
      # データに不整合がある場合
      e.xx.yy.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

      @Bean("messageSource")
      public MessageSource messageSource() {
          ResourceBundleMessageSource bean = new ResourceBundleMessageSource(); // (1)
          bean.setBasenames("i18n/application-messages"); // (2)
          return bean;
      }

- applicationContext.xml

      <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`を使用する。 また、Bean名には必ず`messageSource`を設定すること。 `messageSource`が見つからない場合、空の`DelegatingMessageSource`が`MessageSource`として使用される。
(2) | 使用するメッセージプロパティの基底名を定義する。クラスパス相対で指定する。 この例では”src/main/resources/i18n/application-messages.properties”を読み込む。

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

##### 4.7.2.1.2.1. デフォルト(ISO-8859-1)

- application-messages.properties

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

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

上記で設定したメッセージをJSPからは、`<spring:message>`タグを用いて表示できる。
インクルード用の共通JSPの作成の設定が必要である。

    <spring:message code="label.aa.bb.year" />
    <spring:message code="label.aa.bb.month" />
    <spring:message code="label.aa.bb.day" />

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

    <form:form modelAttribute="sampleForm">
        <form:label path="year">
            <spring:message code="label.aa.bb.year" />
        </form:label>: <form:input path="year" />
        <br>
        <form:label path="month">
            <spring:message code="label.aa.bb.month" />
        </form:label>: <form:input path="month" />
        <br>
        <form:label path="day">
            <spring:message code="label.aa.bb.day" />
        </form:label>: <form:input path="day" />
    </form:form>

上記で設定したメッセージは、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>

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

Note

日本語などISO-8859-1で表現できない文字は、`native2ascii`コマンドでISO-8859-1に変換してからpropertiesファイルに設定する。または、次に記載する文字コードの指定を行うことで設定ができる。

##### 4.7.2.1.2.2. 文字コードの指定

文字コードにUTF-8を指定することでpropertiesファイルに直接日本語等を設定することが出来る。

- application-messages.properties

      label.aa.bb.year=年
      label.aa.bb.month=月
      label.aa.bb.day=日

この場合、以下のように、`ResourceBundleMessageSource`に読み込む文字コードを指定することでUTF-8として解釈される。

- applicationContext.xml

      @Bean("messageSource")
      public MessageSource messageSource() {
          ResourceBundleMessageSource bean = new ResourceBundleMessageSource();
          bean.setBasenames("i18n/application-messages");
          bean.setDefaultEncoding("UTF-8");
          return bean;
      }

- applicationContext.xml

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

##### 4.7.2.1.2.3. 国際化

異なる言語で表示するためには、以下のようにそれぞれの言語に対応したpropertiesファイルを準備し、`ResourceBundleMessageSource`に複数のpropertiesファイルを指定することで対応する。

    src/main/resources/i18n
                        ├ application-messages.properties (デフォルトで使用する言語)
                        ├ application-messages_fr.properties (フランス語)
                        ├ ...
                        └ application-messages_ja.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、または、メッセージ本文を持つクラス。

この結果メッセージをJSPで表示するためのJSPタグライブラリとして、`<t:messagesPanel>`タグも提供される。
`ResultMessage`にメッセージIDを登録した場合、メッセージの解決には`org.springframework.context.MessageSource`が使用される。`org.springframework.context.MessageSource`の定義方法についてはプロパティを使用する際の設定を参照されたい。

#### 4.7.2.2.1. 基本的な結果メッセージの設定方法

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

Tip

Thymeleafの各テンプレートHTML画面共通の機能として作成する方法については、汎用的な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 {

          @GetMapping
          public String hello(Model model) {
              ResultMessages messages = ResultMessages.error().add("e.xx.yy.9001"); // (1)
              model.addAttribute(messages); // (2)
              return "message/index";
          }
      }

  項番 | 説明
  --- | ---
  (1) | `ResultMessages`にメッセージタイプとメッセージIDを設定する。 上記例では、メッセージタイプを”error”、メッセージIDを”e.xx.yy.9001”として`ResultMessage`を設定している。 Note メッセージIDを指定する場合は`ResultMessage`オブジェクトの生成を省略できるが、省略しない場合の設定は以下となる。 `ResultMessages.error().add(ResultMessage.fromCode("e.xx.yy.9001"));`
  (2) | `ResultMessages`をModelに追加する。 属性名はデフォルトで`resultMessages`が設定される。

- View

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

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Result Message Example</title>
    </head>
    <body>
        <h1>Result Message</h1>
        <t:messagesPanel /><!-- (1) -->
    </body>
    </html>

項番 | 説明
--- | ---
(1) | `<t:messagesPanel>`タグをデフォルト設定で使用する。 デフォルトでは、属性名が`resultMessages`のオブジェクトを表示する。 そのため、ContorollerでModelに`ResultMessages`を設定する際に属性名を設定していない場合は属性名を指定する必要はない。

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`を使用して繰り返し取得し、出力する。

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

出力される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."));`というように、メッセージの本文をハードコードすることもできるが、保守性を高めるため、メッセージIDを使用して`ResultMessage`オブジェクトを作成し、メッセージ本文はプロパティファイルから取得することを推奨する。

#### 4.7.2.2.2. プレースホルダーを使用した結果メッセージの設定方法

以下のように、メッセージにプレースホルダーを指定することができる。

    e.xx.yy.8001=Cannot upload, Because the file size must be less than {0}MB.

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

    ResultMessages messages = ResultMessages.error().add("e.xx.yy.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.xx.yy.8001=Cannot upload, Because the file size must be less than {0,number,#}MB.

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

#### 4.7.2.2.3. 複数の結果メッセージの設定方法

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

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

この場合は、次のようなHTMLが出力される(JSP/テンプレート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>

#### 4.7.2.2.4. メッセージタイプの変更方法

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

メッセージタイプ | `ResultMessages`オブジェクトの作成 | デフォルトで出力されるclass名 | 分類 | 使い分け（例）
--- | --- | --- | --- | ---
success | `ResultMessages.success()` | alert alert-success | info | 成功メッセージやポジティブな通知。
info | `ResultMessages.info()` | alert alert-info | info | 一般的な情報提供。
primary | `ResultMessages.primary()` | alert alert-primary | info | 主要な情報や重要な通知。
secondary | `ResultMessages.secondary()` | alert alert-secondary | info | 補助的な情報やそれほど重要ではない通知。
warning | `ResultMessages.warning()` | alert alert-warning | warning | 注意喚起や警告メッセージ。
light | `ResultMessages.light()` | alert alert-light | warning | 軽いトーンの通知やメッセージ。
dark | `ResultMessages.dark()` | alert alert-dark | warning | 重いトーンの通知やメッセージ。
danger | `ResultMessages.danger()` | alert alert-danger | error | 深刻なエラーメッセージや重要な警告。
error | `ResultMessages.error()` | alert alert-error | error | 深刻なエラーメッセージや重要な警告。 errorはBootstrapのAlertsコンポーネントには含まれていない。

メッセージタイプに応じて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.xx.yy.9001")`を出力した例

- `ResultMessages.warning().add("w.xx.yy.2001")`を出力した例

- `ResultMessages.info().add("i.xx.yy.0001", "XXXX")`を出力した例

Caution

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

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

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

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

- Controller

      @GetMapping(value = "showMessages")
      public String showMessages(Model model) {

          model.addAttribute("messages1", ResultMessages.warning().add("w.xx.yy.2001")); // (1)
          model.addAttribute("messages2", ResultMessages.error().add("e.xx.yy.9001")); // (2)

          return "message/showMessages";
      }

  項番 | 説明
  --- | ---
  (1) | メッセージタイプが”warning”である`ResultMessages`を、属性名”messages1”でModelに追加する。
  (2) | メッセージタイプが”info”である`ResultMessages`を、属性名”messages2”でModelに追加する。

- WEB-INF/views/message/showMessages.jsp

      <h1>Result Message</h1>
      <h2>Messages1</h2>
      <t:messagesPanel messagesAttributeName="messages1" /><!-- (1) -->
      <h2>Messages2</h2>
      <t:messagesPanel messagesAttributeName="messages2" /><!-- (2) -->

  項番 | 説明
  --- | ---
  (1) | 属性名が”messages1”である`ResultMessages`を表示する。
  (2) | 属性名が”messages2”である`ResultMessages`を表示する。

- WEB-INF/views/message/showMessages.html

      <h1>Result Message</h1>
      <h2>Messages1</h2>
      <div th:if="${messages1 != null}" th:text="${messages1}" class="alert" th:classappend="|alert-${messages1.type}|" /><!-- (1) -->
      <h2>Messages2</h2>
      <div th:if="${messages2 != null}" th:text="${messages2}" class="alert" th:classappend="|alert-${messages2.type}|" /><!-- (2) -->

  項番 | 説明
  --- | ---
  (1) | 属性名が”messages1”である`ResultMessages`を表示する。
  (2) | 属性名が”messages2”である`ResultMessages`を表示する。

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

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

`org.terasoluna.gfw.common.exception.BusinessException`と`org.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クラス

      @PostMapping(value = "create")
      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.xx.yy.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. <t:messagesPanel>タグの属性変更

`<t:messagesPanel>`タグには、表示形式を変更する属性がいくつか用意されている。

`<t:messagesPanel>`タグ 属性一覧
オプション | 内容 | defaultの設定値
--- | --- | ---
panelElement | 結果メッセージ表示パネルの要素。 | `div`
panelClassName | 結果メッセージ表示パネルのCSS class名。 | `alert`
panelTypeClassPrefix | CSS class名の接頭辞。 | `alert-`
messagesType | メッセージタイプ。この属性が設定された場合。設定されたメッセージタイプが`ResultMessages`がもつメッセージタイプより優先されて使用される。 | デフォルト値なし
outerElement | 結果メッセージ一覧を構成するHTMLの外側のタグ。 | `ul`
innerElement | 結果メッセージ一覧を構成するHTMLの内側のタグ。 | `li`
disableHtmlEscape | HTMLエスケープ処理を無効化するためのフラグ。 `true`を指定する事で、出力するメッセージに対してHTMLエスケープ処理が行われなくなる。 この属性は、出力するメッセージにHTMLを埋め込むことで、メッセージの装飾などができるようにするために用意している。 Caution trueを指定する場合は、XSS対策が必要な文字がメッセージ内に含まれない事が保証されていること。 | `false`

以下に、属性変更の例を示す。

#### 4.7.4.1.1. class名の変更

例えば、CSSフレームワークBlueTripでは以下のようなCSSが用意されている。

    .error,.notice,.success {
        padding: .8em;
        margin-bottom: 1.6em;
        border: 2px solid #ddd;
    }

    .error {
        background: #FBE3E4;
        color: #8a1f11;
        border-color: #FBC2C4;
    }

    .notice {
        background: #FFF6BF;
        color: #514721;
        border-color: #FFD324;
    }

    .success {
        background: #E6EFC2;
        color: #264409;
        border-color: #C6D880;
    }

このCSSを使用したい場合、`<div class="error">...</div>`というようにメッセージを出力する必要がある。
この場合、`<t:messagesPanel>`タグを以下のように使用すればよい(Controllerは修正不要である)。

    <t:messagesPanel panelClassName="" panelTypeClassPrefix="" />

出力されるHTMLは以下のようになる。

    <div class="error">
        <ul>
            <li>There are inconsistencies in the data.</li>
        </ul>
    </div>

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

#### 4.7.4.1.2. HTML Elementの変更

例えば、メッセージ一覧を表示するために`<ul>`タグを使用したくない場合は、`outerElement`属性と`innerElement`属性を使用することでカスタマイズできる。

以下のように属性を設定した場合は、

    <t:messagesPanel outerElement="" innerElement="span" />

次のようにHTMLが出力される。

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

以下のようにCSSを設定することで、

    .alert > span {
        display: block; /* (1) */
    }

項番 | 説明
--- | ---
(1) | “alert”クラスの要素の子となる`<span>`タグをブロックレベル要素にする。

ブラウザで次のように表示される。

#### 4.7.4.1.3. HTMLエスケープ処理の無効化

disableHtmlEscape属性を`true`にした場合、以下のような出力イメージにする事ができる。
下記の例では、メッセージの一部のフォントを「16pxの赤字」に装飾している。

- jsp

      <spring:message var="informationMessage" code="i.xx.yy.0001" />
      <t:messagesPanel messagesAttributeName="informationMessage"
          messagesType="alert alert-info"
          disableHtmlEscape="true" />

- properties

      i.xx.yy.0001 = Please confirm order content. <font style="color: red; font-size: 16px;">If this orders submitted, cannot cancel.</font>

- 出力イメージ

> disableHtmlEscape属性が`false`(デフォルト)の場合は、HTMLエスケープされて以下のような出力となる。

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

`<t:messagesPanel>`タグは`ResultMessages`オブジェクト以外にも

- `java.lang.String`

- `java.lang.Exception`

- `java.util.List`

オブジェクトも出力できる。

通常は`<t:messagesPanel>`タグは`ResultMessages`オブジェクトの出力用に使用するが、
フレームワークがリクエストスコープに設定した文字列(エラーメッセージなど)を表示する場合にも使用できる。
例えば、Spring Securityのデフォルトの設定で使用される、`org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler`では、認証エラー時に発生した例外オブジェクトを`SPRING_SECURITY_LAST_EXCEPTION`という属性名で、リダイレクト時はセッション、フォワード時はリクエストスコープに格納する。
この例外メッセージを、結果メッセージ同様に`<t:messagesPanel>`タグで出力したい場合は、以下のように設定すればよい。

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Login</title>
    <style type="text/css">
    /* (1) */
    .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>
        <c:if test="${param.containsKey('error')}">
            <t:messagesPanel messagesType="error"
                messagesAttributeName="SPRING_SECURITY_LAST_EXCEPTION" /><!-- (2) -->
        </c:if>
        <form:form
            action="${pageContext.request.contextPath}/authentication"
            method="post">
            <fieldset>
                <legend>Login Form</legend>
                <div>
                    <label for="username">Username: </label><input
                        type="text" id="username" name="username">
                </div>
                <div>
                    <label for="username">Password:</label><input
                        type="password" id="password" name="password">
                </div>
                <div>
                    <input type="submit" value="Login" />
                </div>
            </fieldset>
        </form:form>
    </body>
    </html>

項番 | 説明
--- | ---
(1) | 結果メッセージ表示用のCSSを再掲する。実際はCSSファイルに記述することを強く推奨する。
(2) | `Exception`オブジェクトが格納されている属性名を`messagesAttributeName`属性で指定する。 また、`ResultMessages`オブジェクトとは異なり、メッセージタイプの情報をもたないため、 `messagesType`属性で、明示的に、メッセージタイプを指定する必要がある。

`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>

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

Tip

ログイン用のJSPの内容については、認証を参照されたい。

### 4.7.4.3. メッセージID定数クラスの自動生成ツール

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

1. メッセージID定数クラスの作成

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

>     package com.example.common.message;

>     public class MessageIds {

>     }

1. 自動生成クラスの作成

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

>     package com.example.common.message;

>     import java.io.BufferedReader;
>     import java.io.IOException;
>     import java.io.InputStream;
>     import java.io.InputStreamReader;
>     import java.io.PrintWriter;
>     import java.nio.file.Files;
>     import java.nio.file.Path;
>     import org.springframework.core.io.ClassPathResource;

>     public class MessageIdsGen {

>         public static void main(String[] args) throws IOException {
>             // message properties file
>             Class<?> targetClazz = MessageIds.class;
>             Path output = Path.of("src/main/java", targetClazz.getName().replace(".", "/") + ".java");

>             try (InputStream inputStream =
>                     new ClassPathResource("i18n/application-messages.properties").getInputStream();
>                     BufferedReader bufferedReader =
>                             new BufferedReader(new InputStreamReader(inputStream));
>                     PrintWriter pw = new PrintWriter(Files.newBufferedWriter(output))) {

>                 pw.println("package " + targetClazz.getPackageName() + ";");
>                 pw.println("");
>                 pw.println("/**");
>                 pw.println(" * Message Id");
>                 pw.println(" */");
>                 pw.println("public class " + targetClazz.getSimpleName() + " {");

>                 String line = null;
>                 while ((line = bufferedReader.readLine()) != null) {
>                     String[] vals = line.split("=", 2);
>                     if (vals.length > 1) {
>                         String id = vals[0].trim();
>                         String value = vals[1].trim();
>                         pw.println("    /** " + id + "=" + value + " */");
>                         pw.println("    public static final String "
>                                 + id.toUpperCase().replace(".", "_").replace("-", "_") + " = \"" + id
>                                 + "\";");
>                     }
>                 }
>                 pw.println("");
>                 pw.println("    private " + targetClazz.getSimpleName() + "() {}");
>                 pw.println("}");
>             }
>         }
>     }

1. メッセージプロパティファイルの用意

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

>     i.xx.yy.0001={0} upload completed.
>     w.xx.yy.2001=The recommended change interval has passed password. Please change your password.
>     e.xx.yy.8001=Cannot upload, Because the file size must be less than {0}MB.
>     e.xx.yy.9001=There are inconsistencies in the data.

1. 自動生成クラスの実行

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

>     package com.example.common.message;
>     /**
>      * Message Id
>      */
>     public class MessageIds {
>         /** i.xx.yy.0001={0} upload completed. */
>         public static final String I_XX_YY_0001 = "i.xx.yy.0001";
>         /** w.xx.yy.2001=The recommended change interval has passed password. Please change your password. */
>         public static final String W_XX_YY_2001 = "w.xx.yy.2001";
>         /** e.xx.yy.8001=Cannot upload, Because the file size must be less than {0}MB. */
>         public static final String E_XX_YY_8001 = "e.xx.yy.8001";
>         /** e.xx.yy.9001=There are inconsistencies in the data. */
>         public static final String E_XX_YY_9001 = "e.xx.yy.9001";
>     }