TERASOLUNA Server Framework for Java (5.x) Development Guideline を自分用に要約する：『4.8 コードリスト』

### はじめに
こんにちは。TERASOLUNAの勉強を始めました。

- 勉強方法としては、しばらくは[ガイドライン](http://terasolunaorg.github.io/guideline/)を読むことにしていますが、このガイドライン、とにかく情報量が重厚なので、自分なりに要約しながら進めていこうと思います。
- （数か月おきに試験があるのですが、試験中はこのガイドラインを読むことが出来るので、全体を完璧に覚えるというよりはどこに問題の答えとなるトピックスがあるかを覚えていきたいです）
- __方針として、__「Note」や「Tip」といった本線を逸れていると見せかけた内容が試験に出てくるので、そういったところも見逃さないようにしていきます（ここを引用の形で掲載する？予定）。

# 4.8 コードリスト

# infomation

## バージョン

[5.3.1.RELEASE (2017-11-10)](http://terasolunaorg.github.io/guideline/5.3.1.RELEASE/ja/ArchitectureInDetail/WebApplicationDetail/Codelist.html)

## 履歴

+ 2018年1月20日 執筆開始

# 本編

## 1.OverView

- コードリストとは、「コード値(value)とその表示名(label)」の集合である。画面のセレクトボックスなどコード値を画面で表示する際のラベルへのマッピング表として利用される。
- 共通ライブラリでは、以下を提供している。（応用として国際化対応・キャッシュされたコードリストのリロードもサポート）
    - xmlファイルやDBに定義されたコードリストをアプリケーション起動時に読み込みキャッシュする機能
    - JSPやJavaクラスからコードリストを参照する機能
    - コードリストを用いて入力チェックする機能

> 標準でリロードが可能なのは、DBに定義されたコードリストを使用する場合のみである。

- 共通ライブラリで提供しているコードリスト実装
    - `SimpleMapCodeList` xmlファイルに直接記述した内容を使用する。
    - `NumberRangeCodeList` 数値の範囲のリストを作成する際に使用する。
    - `JdbcCodeList` DBから対象のコードをSQLで取得して使用する。
    - `EnumCodeList` `Enum`クラスに定義した定数からコードリストを作成する際に使用する。
    - `SinpleI18nCodeList` java.util.Localeに応じたコードリストを使用する。

##### クラス図構成（[ガイドライン](http://terasolunaorg.github.io/guideline/5.3.1.RELEASE/ja/ArchitectureInDetail/WebApplicationDetail/Codelist.html)より）

![](http://terasolunaorg.github.io/guideline/5.3.1.RELEASE/ja/_images/codelist-class-diagram.png)
[ガイドライン](http://terasolunaorg.github.io/guideline/5.3.1.RELEASE/ja/ArchitectureInDetail/WebApplicationDetail/Codelist.html)より


## 2. How to use

### 2.1. SimpleMapCodeListの使用方法

xmlファイルに定義したコード値をアプリケーション起動時に読み込み、そのまま使用する。

#### コードリスト設定

- bean定義ファイル（xxx-codelist.xml）は、コードリスト用に作成することを推奨する。
- コードリスト用bean定義ファイルを作成後、既存bean定義ファイル（xxx-domain.xml）にimportを行う必要がある。

#### JSPでのコードリスト使用

共通ライブラリから提供しているインタセプターを用いることで、リクエストスコープに自動的に設定し、JSPからコードリストを容易に参照する。

- bean定義ファイル（spring-mvc.xml）に設定を行う。
    - `<mvc:mapping path="/**">`で適用対象のパスを設定する（この場合は全体に設定）。
    - CodeListInterceptor クラスをbean定義する。`<propery name="CodeListIdPattern" value="CL_.+"/>`とすることで、自動でリクエストスコープに設定するコードリストのbeanIDのパターンを、idが"CL_XXX"形式で定義されているデータのみを対象とすることが出来る。ここに当てはまるbeanIDがJSPで使用可能になる。
    - `codeListPattern`を省略した場合は、すべてのコードリストがJSPで使用可能になる。

- JSPに実装する（セレクトボックスの場合）。
    - valueに空文字を設定することで先頭にダミーの値を設定できる。
    - ` <form:options items="${CL_XXX}" />`とすることで、beanIDに当てはまる選択肢が出力される。

#### Javaクラスでのコードリスト使用

`@Inject`、`@Named`を用いてコードリストをインジェクションする。`@Named`には、コードリストIDを指定する。

- CodeList#asMapメソッドで、コードリストをMap形式で取得する。

### 2.2. NumberRangeCodeListの使用方法

アプリケーション起動時に、指定した数値の範囲をリストにするコード。主に数だけのセレクトボックス、月や日付などのセレクトボックスに使用することを想定している。

> NumberRangeCodeListはアラビア数字のみ対応しており、漢数字やローマ数字には対応していない。 漢数字やローマ数字を表示したい場合はJdbcCodeList、SimpleMapCodeListに定義することで対応可能である。

#### コードリスト設定例（Fromの値をToをより小さくする場合の実装例）

- `<property name="from" value=""/>` 範囲開始の値（デフォルトは0）
- `<property name="to" value=""/>` 範囲終了の値（必須）
- `<property name="valueFormat" value=""/>` コード値のフォーマット形式を設定（`java.lang.String.format`の形式が使用される）（デフォルトは`%s`）
- `<property name="labelFormat" value=""/>` コード名のフォーマット形式を設定（`java.lang.String.format`の形式が使用される）（デフォルトは`%s`）
- `<property name="interval" value=""/>` 増加する値を設定する（デフォルトは1）

#### JSPでのコードリスト使用

SimpleMapCodeListの使用方法と同じ。

#### Javaクラスでのコードリスト使用

SimpleMapCodeListの使用方法と同じ。

### 2.3. JdbcCodeListの使用方法

アプリケーション起動時にDBから値を取得し、コードリストを作成するクラス。アプリケーション起動時にキャッシュを作るので、リスト表示時はDBアクセスによる遅延がない。
起動時の読み込み時間を抑えるために、取得数の上限を設定することが出来る。`org.springframework.jdbc.core.JdbcTemplate`の`fetchSize`に上限を設定することで変更できる。

#### コードリスト設定例

- bean定義ファイル（xxx-codelist.xml）に定義する。
    - `org.springframework.jdbc.core.JdbcTemplate`クラスをbean定義する。
    - `fetchSize`を設定する。デフォルト値が全件取得の場合は処理性能に関わるため注意。
    - それぞれの`JdbcCodeList`で、以下の設定を行う。
        - querySqlプロパティに取得するSQLを記述する際、必ず「ORDER BY」を指定し、順序を確定させる。
        - valueColumnプロパティに、MapのKeyに該当する値を設定する。
        - labelColumnプロパティに、MapのValueに該当する値を設定する。

#### JSPでのコードリスト使用

SimpleMapCodeListの使用方法と同じ。

#### Javaクラスでのコードリスト使用

SimpleMapCodeListの使用方法と同じ。

### 2.4. EnumCodeListの使用方法

Enumクラスに定義した定数からコードリストを作成するクラス。
> 以下の条件に一致するアプリケーションでコードリストを扱う場合は、 EnumCodeListを使用して、コードリストのラベルをEnumクラスで管理することを検討してほしい。 コードリストのラベルをEnumクラスで管理することで、 コード値に紐づく情報と操作をEnumクラスに集約する事ができる。
> - コード値をEnumクラスで管理する必要がある(つまり、Javaのロジックでコード値を意識した処理を行う必要がある)
> - UIの国際化(多言語化)の必要がない

<br/>

> EnumCodeListでは、Enumクラスからコードリストを作成するために必要な情報(コード値とラベル)を取得するためのインタフェースとして、 org.terasoluna.gfw.common.codelist.EnumCodeList.CodeListItemインタフェースを提供している。
> EnumCodeListを使用する場合は、作成するEnumクラスでEnumCodeList.CodeListItemインタフェースを実装する必要がある。

#### コードリスト設定例

- 共通ライブラリから提供している、`EnumCodeList.CodeListItem`を実装したEnumクラスを作成する。
`CodeListItem`インタフェースには、コードリストを作成するために必要な情報（コード値とラベル）を取得するためのメソッドとして、以下が定義されている。
    - `getCodeValue()`
    - `getCodeList()`
- 定数を定義する。この際、必要な情報（コード値、ラベル）を指定する。

> EnumCodeListを使用した際のコードリストの並び順は、定数の定義順となる。

- コードリストを作成するために必要な情報を保持するプロパティ、受け取るコンストラクタ、定数が保持するコード値、ラベルを返却するメソッドをそれぞれ用意する。

#### JSPでのコードリスト使用

SimpleMapCodeListの使用方法と同じ。

#### Javaクラスでのコードリスト使用

SimpleMapCodeListの使用方法と同じ。

### 2.5. SimpleI18nCodeListの使用方法

国際化に対応しているコードリスト。ロケールごとにコードリストを設定し、ロケールに対応したコードリストを返却できる。

#### コードリスト設定例

行が`Locale`、列がコード値、セルの内容がラベルである2次元のテーブルをイメージすると理解しやすい。
設定方法は3通り用意されているが、基本的には「行単位でLocale毎のCodeListを設定する」方法が推奨されている。

```xml:xxx-codelist.xml
<bean id="CL_I18N_PRICE"
    class="org.terasoluna.gfw.common.codelist.i18n.SimpleI18nCodeList">
    <property name="rowsByCodeList"> <!-- (1) -->
        <util:map>
            <entry key="en" value-ref="CL_PRICE_EN" />
            <entry key="ja" value-ref="CL_PRICE_JA" />
        </util:map>
    </property>
</bean>
```
- (1) `rowsByCodeList`プロパティにkeyが`java.lang.Locale`のMapを設定する。Mapには、keyにロケール、value-refにロケールに対応したコードリストクラスの参照先を指定する。Mapのvalueは各ロケールに対応したコードリストクラスを参照する。

Locale毎のコードリストは、前述の`SimpleMapCodeList`や`JdbcCodeList`を用意することが出来る。このとき、`SimpleI18nCodeList`はreloadableに対応しておらず、独自実装によって対応させる必要がある。

### 2.6. 特定のコード値からコード名を表示する

JSPからコードリストを参照する際には、Mapインタフェースと同じ方法で参照することができる。
例: `Order Status : ${f:h(CL_ORDERSTATUS[orderForm.orderStatus])}`
（取得したMapインタフェースキーとしてコード値（この例ではorderStatus）に格納された値を指定することで、対応するコード名を表示することができる）

### 2.7. コードリストを用いたコード値の入力チェック

入力値がコードリスト内に定義されたコード値であるかどうかチェックするような場合、 共通ライブラリでは、BeanValidation用のアノテーション、 `org.terasoluna.gfw.common.codelist.ExistInCodeList` を提供している。
（このとき、デフォルトのエラーメッセージをアプリケーションの要件に合わせて変更する必要がある）

#### `@ExistInCodeList`の設定例

```xml:xxx-codelist.xml
<bean id="CL_GENDER" class="org.terasoluna.gfw.common.codelist.SimpleMapCodeList">
    <property name="map">
        <map>
            <entry key="M" value="Male" />
            <entry key="F" value="Female" />
        </map>
    </property>
</bean>
```

```java:Person.java
public class Person {
    @ExistInCodeList(codeListId = "CL_GENDER")  // (1)
    private String gender;

    // getter and setter omitted
}
```
- (1)入力チェックを行いたいフィールドに対して、`@ExistInCodeList`を設定し、cordListIdにチェック元となるコードリストを指定する。

> @ExistInCodeList の入力チェックでサポートしている型は、 CharSequenceインタフェースの実装クラス(Stringなど) または Characterのみである。 そのため、 @ExistInCodeListをつけるフィールドは意味的に整数型であっても、Stringで定義する必要がある。(年・月・日等)
> また、@ExistInCodeListはコレクション内の値には対応していないため、複数選択可能な画面項目（チェックボックスや複数選択ドロップダウンなど）に@ExistInCodeListアノテーションを対応させるためには実装を工夫する必要がある。

## 3. How to extend

### 3.1. コードリストをリロードする場合

コードリストのマスタデータを更新した時、コードリストも更新したい場合がある。
共通ライブラリでは、`org.terasoluna.gfw.common.codelist.ReloadableCodeList`インタフェースを用意している。これを実装し、refreshメソッドを呼ぶことでコードリストの更新が可能となる。
コードリストの更新方法は、以下の2点。

- Task Schedulerで実現する
- Contoller（Service）クラスでrefreshメソッドを呼び出す

**ガイドラインではSpringから提供されているTask Schedulerを使用して、コードリストを定期的にリロードする方式が推奨されている。**
（任意のタイミングでリフレッシュする必要がある場合はControllerからでOK）

#### Task Schedulerでrefreshする

```xml:xxx-codelist.xml
<task:scheduler id="taskScheduler" pool-size="10"/>  <!-- (1) -->

<task:scheduled-tasks scheduler="taskScheduler">  <!-- (2) -->
    <task:scheduled ref="CL_AUTHORITIES" method="refresh" cron="${cron.codelist.refreshTime}"/>  <!-- (3) -->
</task:scheduled-tasks>

<bean id="CL_AUTHORITIES" parent="AbstractJdbcCodeList">
    <property name="querySql"
        value="SELECT authority_id, authority_name FROM authority ORDER BY authority_id" />
    <property name="valueColumn" value="authority_id" />
    <property name="labelColumn" value="authority_name" />
</bean>
```
1. `<task:scheduler>`のpool-size属性にスレッドのプールサイズを指定する。（デフォルトで1）
2. `<task:scheduled-tasks>`のscheduler属性に、`<task:scheduler>`のIDを設定する。
3. `<task:scheduled>`のmethod属性に、refreshメソッドを指定する。cron属性に、`org.springframework.scheduling.support.CronSequenceGenerator`でサポートされた形式で記述する。cron属性は環境によってリロードするタイミングが変わることが想定されるため、プロパティファイルや、環境変数から取得することを推奨する。

#### Contoller（Service）クラスでrefreshメソッドを呼び出す

- Controllerでは、特に何かをする必要がなく、refresh用のServiceクラスを呼び出せばOK。
- Serviceクラスでは、前述した「Javaクラスでのコードリスト使用」と同様に、コードリストのインジェクションするが、このとき、フィールドの型に`ReloadableCodeList`インタフェースを定義する。よって、Bean定義の段階で`ReloadableCodeList`を実装していることが必要になる。
- Serviceクラスでrefreshメソッドを定義し、その中でReloadableCodeListインタフェースのrefreshメソッドを実行する。

### 3.2. コードリストを独自カスタマイズする方法

共通ライブラリで提供している4種類のコードリストで実現できない場合は、独自にカスタマイズする。作成できるコードリストの種類は以下の2つ。
- `AbstractCodeList` Reloadable不要の場合に用いる。`asMap`をオーバーライドする。
- `AbstractReloadableCodeList` Reloadableが必要な場合に用いる。`retrieveMap`をオーバーライドする。

##### コードリストクラス

例として、今年の来年の年のリストを作るコードリストについて説明する。

```java:DepYearCodeList.java
@Component("CL_YEAR") // (1)
public class DepYearCodeList extends AbstractCodeList { // (2)

    @Inject
    JodaTimeDateFactory dateFactory; // (3)

    @Override
    public Map<String, String> asMap() {  // (4)
        DateTime dateTime = dateFactory.newDateTime();
        DateTime nextYearDateTime = dateTime.plusYears(1);

        Map<String, String> depYearMap = new LinkedHashMap<String, String>();

        String thisYear = dateTime.toString("Y");
        String nextYear = nextYearDateTime.toString("Y");
        depYearMap.put(thisYear, thisYear);
        depYearMap.put(nextYear, nextYear);

        return Collections.unmodifiableMap(depYearMap);
    }
}
```

1. `@Component`で、コードリストをコンポーネント登録する。valueにはbean定義で設定したコードリストインターセプトによりコードリストをコンポーネント登録する。
2. `AbstractCodeList`を継承する。
3. `JodaTimeDateFactory`をインジェクションする。システム日付のDateクラスを作成することが出来る。
4. `asMap()`メソッドをオーバーライドして、今年と来年の年のリストを作成する。

> リロード可能であるCodeListを独自カスタマイズする場合、スレッドセーフになるように実装すること。

## 4. Appendix

### 4.1. SimpleI18nCodeListのコードリスト設定方法

項目2.5.で説明した以外の方法について。

#### 行単位でLocale毎のMapを設定する

rowsプロパティに対して、”MapのMap”を設定する。外側のMapで先にロケールを設定し、内側のMapでkeyごとのvalueを定義する。

#### 列単位でコード値毎のMapを設定する

外側のMapでkeyを先に決め、内側のMapでロケールとそれに対応するvalueを設定する。





###### 参考文献：TERASOLUNA Server Framework for Java (5.x) Development Guideline