midPoint にSaaSから源泉データを連携する（SmartHR編）

[midPoint by OpenStandia Advent Calendar 2024](https://qiita.com/advent-calendar/2024/midpoint-by-openstandia) の22日目は、これまで構築してきた開発/検証環境の上で、今度はSaaSアプリケーションと連携させてみます。例として、クラウド型の人事アプリケーションである[SmartHR](https://smarthr.jp/)を源泉システムとして利用する例を紹介します。

:::note info
[4日目の記事](https://qiita.com/wadahiro/items/063d8cface3d1e49adf4)で構築した開発/検証環境が必要となります。
:::

# 標準コネクターと追加のコネクター

midPointでは標準で下記5つのコネクターが組み込まれています。ManualConnectorはマニュアルオペレーションとの組み合わせを必要とする特殊なコネクターですので、実質4つです。

- AdLdapConnector
- CsvConnector
- DatabaseTableConnector
- LdapConnector
- ManualConnector

その他のシステムに接続したい場合は、以下の公式サイトのコネクターリストなどから欲しいものを探して追加する必要があります。SaaSアプリケーションに接続する場合も、まずはここから探します。

https://docs.evolveum.com/connectors/connectors/

SmartHR用のコネクターに関しても上記リストに記載があり、利用することができます。

:::note info
もし、使いたいSaaSアプリケーション用のコネクターがない場合でも、アプリケーションがSCIMプロトコルに対応していれば、SCIM用コネクターが利用できるかもしれません。ただし、SCIMによる標準化には限界があり、アプリケーション固有のクセを吸収しきれないケースもありますので、検証は必ず必要です。

SCIM用コネクターがうまく使えない、またはそもそもSaaSアプリケーションが独自のAPIしか提供していない場合は、カスタムコネクターを作って対応することも可能です。幸いなことに、コネクターリストで公開されているコネクターは基本的にOSSで開発されていますので、ソースコードを自由に参考にしつつ開発することが可能です。コネクターの開発については、以下に開発者向けのガイドが公開されています。

https://docs.evolveum.com/connectors/connid/1.x/connector-development-guide/
:::


# 準備作業

オフィシャルのmidPointコンテナイメージを使う場合、SaaS連携で外部にHTTPS接続する際に、参照するトラストストアには初期構築時に自動生成されたキーストアが使われており、そのままではサーバー証明書の検証ができずエラーになってしまいます。これを解決するには以下の2つのアプローチがあります。

1. 自動生成されたキーストアに信頼する証明書をインポートする
2. midPointの実行に使われるJavaの標準トラストストアを参照するようにmidPointの設定を切り替える

今回は後者で設定します。以下のように、`docker-compose.yml`を修正します。

```diff_yaml:./docker-compose.yml
  midpoint_server:
    image: evolveum/midpoint:${MP_VER:-latest}-alpine
    container_name: midpoint_server
    hostname: midpoint-container
    depends_on:
      data_init:
        condition: service_completed_successfully
      midpoint_data:
        condition: service_started
    command: [ "/opt/midpoint/bin/midpoint.sh", "container" ]
    ports:
      - 8080:8080
    environment:
     - MP_SET_midpoint_repository_jdbcUsername=midpoint
     - MP_SET_midpoint_repository_jdbcPassword=db.secret.pw.007
     - MP_SET_midpoint_repository_jdbcUrl=jdbc:postgresql://midpoint_data:5432/midpoint
     - MP_SET_midpoint_repository_database=postgresql
     - MP_SET_midpoint_administrator_initialPassword=Test5ecr3t
     - MP_UNSET_midpoint_repository_hibernateHbm2ddl=1
     - MP_NO_ENV_COMPAT=1
     - MP_ENTRY_POINT=/opt/entry-point
+    - MP_SET_javax.net.ssl.trustStore="/etc/ssl/certs/java/cacerts"
+    - MP_SET_javax.net.ssl.trustStoreType="JKS"
     - TZ=Asia/Tokyo
```

# 追加コネクターの導入

追加のコネクターのモジュール（JARファイル）を取得し、midPointが読み込むディレクトリに配置する必要があります。今回はJARファイルを直接ダウンロードしてきて、コンテナイメージにディレクトリをマウントして読み込ませます。

:::note info
その他、オフィシャルのコンテナイメージをベースコンテナとして独自のイメージをビルドするようにして、ビルドプロセスでJARファイルをダウンロードしてコンテナイメージ内に含めてしまう方法もあります。こちらの方が、ステージングや本番環境にデプロイする際にも同じコンテナイメージを使うとコネクターも必ず同じものを利用させることができるので、デプロイ面で有利です。
:::

配置用のディレクトリを作成します。

```console
$ mkdir ./icf-connectors
```

作成したディレクトリにSmartHRコネクターを`curl`コマンドで取得して配置します。

:::note info
SmartHRコネクターはMaven Centralリポジトリにデプロイされておりダウンロードするだけですが、コネクターはコミュニティベースで提供されているものが多く、その取得方法は異なります。自前でビルドが必要なケースもあります。
:::

```console
$ curl -O --output-dir ./icf-connectors https://repo1.maven.org/maven2/jp/openstandia/connector/connector-smarthr/1.0.3/connector-smarthr-1.0.3.jar
```

あとはディレクトリをコンテナにマウントさせるように、`docker-compose.yml`を修正します。

```diff_yaml:./docker-compose.yml
  midpoint_server:
    image: evolveum/midpoint:${MP_VER:-latest}-alpine
    container_name: midpoint_server
    hostname: midpoint-container
    depends_on:
      data_init:
        condition: service_completed_successfully
      midpoint_data:
        condition: service_started
    command: [ "/opt/midpoint/bin/midpoint.sh", "container" ]
    ports:
      - 8080:8080
    environment:
     - MP_SET_midpoint_repository_jdbcUsername=midpoint
     - MP_SET_midpoint_repository_jdbcPassword=db.secret.pw.007
     - MP_SET_midpoint_repository_jdbcUrl=jdbc:postgresql://midpoint_data:5432/midpoint
     - MP_SET_midpoint_repository_database=postgresql
     - MP_SET_midpoint_administrator_initialPassword=Test5ecr3t
     - MP_UNSET_midpoint_repository_hibernateHbm2ddl=1
     - MP_NO_ENV_COMPAT=1
     - MP_ENTRY_POINT=/opt/entry-point
     - MP_SET_javax.net.ssl.trustStore="/etc/ssl/certs/java/cacerts"
     - MP_SET_javax.net.ssl.trustStoreType="JKS"
     - TZ=Asia/Tokyo
    networks:
     - net
    volumes:
     - midpoint_home:/opt/midpoint/var
+    - ./icf-connectors:/opt/midpoint/var/icf-connectors
```

# 環境の起動

Docker Composeで環境を起動します。`midpoint_server`コンテナのログを見ると、追加したコネクターが起動時に認識されてロードされた旨を確認することができます。

```
midpoint_server  | 2024-12-21 09:14:09,904 [] [main] INFO (com.evolveum.midpoint.provisioning.ucf.impl.connid.ConnectorFactoryConnIdImpl): Discovered ICF bundle in JAR: jp.openstandia.connector.connector-smarthr version: 1.0.3
```

# SmartHRとの連携

SmartHRを源泉システムとして、従業員と部署をmidPointのユーザーと組織として取り込むようにしてみます。やることは、接続先のリソース種類が変わっただけであり、既に解説した以下の源泉取り込み系の記事と同じです。

- [5日目の記事「midPoint にCSVの源泉データをインポートする（ユーザー編）」](https://qiita.com/wadahiro/items/a239a743af5404a7325f)
- [8日目の記事「midPoint にCSVの源泉データをインポートする（組織編）」](https://qiita.com/wadahiro/items/dc4fe8a20f4e648e01dc)
- [9日目の記事「midPoint にCSVの源泉データをインポートする（ユーザーと組織のアサイン編）」](https://qiita.com/wadahiro/items/51847471e9277adac4c0)

基本的な設定の流れは上記の記事で既に解説済みのため、本記事ではSmartHR固有の設定を中心に紹介します。

# SmartHRの準備

接続先となるSmartHRの環境が必要です。本記事では、SmartHRのサンドボックス環境を利用して確認しています。試されたい方は以下のページを参照してください。

https://developer.smarthr.jp/

:::note warn
API/Webhookを用いたシステム開発の検証、連携先外部サービスの接続確認の検証及び権限に関する検証を目的としたサンドボックス環境となっていますので、ご注意ください。
:::

また、SmartHRのAPI接続のための、アクセストークンを発行しておきます。

https://support.smarthr.jp/ja/help/articles/360026266033/

# リソースの作成

SmartHRコネクターを正しく導入できていると、リソースの作成画面で「SmartHRConnector」を選択できるようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/92af29ea-0e40-6d70-a616-42e9555509c3.png)

名前は`SmartHR`としておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/d9b11c26-ad29-c362-4ff3-1436e45d1cdb.png)


SmartHRのAPI URLとアクセストークンを設定します。

:::note info
もし、インターネットアクセスにHTTPプロキシが必要な環境であれば、設定項目がありますので必要に応じて設定します。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/7c533e0c-9b00-1c39-2de4-63a49577f00b.png)

オブジェクトタイプ・スキーマは、従業員を表す`crew`と部署を表す`department`のみを今回は使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/7b8d3f7d-3e8c-21c4-1fde-53c10d8ac0a3.png)


# ケイパビリティーの設定

リソースを作成したら、最初にケイパビリティーの設定をして、midPointからSmartHR側を誤って更新や削除をしないように制限をかけておきます。

ケイパビリティーとは、そのコネクターが対応している内容（作成、参照、更新、削除などの基本となるCRUD操作や、ライブ同期といった特殊オペレーションなど）のことです。コネクターの実装であらかじめ対応されるケイパビリティーは決まっていますが、それをリソース設定で上書きして無効化することもできます。

リソースの詳細画面より「詳細」メニューを開きます。すると「ケイパビリティー一覧」が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/e90014e7-a89e-6ab5-c5ff-5125b27d84e8.png)


この中で、「作成」「更新」「削除」をクリックして無効化します。「更新」をクリックした際には詳細オプションのダイアログが表示されるので、「有効」を`False`にして「OK」ボタンをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/989a1e94-cf06-16ad-0f04-648faeb6e0b7.png)

最終的に以下のようにグレーアウトされた状態になっていることを確認し、リソースの「保存」ボタンをクリックして保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/c7c126c6-1084-2171-6d38-c27e7facc2f3.png)

これで設定ミスや誤操作でSmartHR側に更新や削除処理が発生した場合に、midPointが検知してエラーを返すようになります。

:::note info
midPoint側ではでなく、SmartHR側のアクセストークンの設定で更新不可とする制御も可能ですので、不必要な権限を与えないように設定しておくとよいでしょう。
:::


# オブジェクトタイプの追加

SmartHRのデータをmidPointにユーザーと組織として取り込むため、オブジェクトタイプの追加を行います。

## Crew（従業員）の追加

SmartHRのCrew（従業員）用のオブジェクトタイプを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/e1b5a694-fd51-27fc-7af6-63169266c5f1.png)

オブジェクトクラスには`crew`を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/62dc0d10-6167-a1a0-639a-d67cf53636a5.png)

midPointのユーザーとして取り込みたいので、「タイプ」では`ユーザー`を選択します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/9403786e-ea9f-2e9b-5c7d-451f968ca3a3.png)

### インバウンドマッピングの設定

以下のように設定します。「departments」の「ターゲット」はアサインを行う設定をRAW編集で行うため、一旦未設定で保存します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/7ef1ea03-4dc4-ccad-9b54-124787b60352.png)

RAW編集画面を開き、以下を設定します。[9日目の記事「midPoint にCSVの源泉データをインポートする（ユーザーと組織のアサイン編）」](https://qiita.com/wadahiro/items/51847471e9277adac4c0)で解説したように、`<assignmentTargetSearch>`を利用して所属組織に自動アサインするように設定します。

CSVの時と違い、少し複雑な設定になっています。SmartHRのAPIでは、主務と兼務も混ざった状態で`departments`という項目に配列で格納されており、主務はその配列の先頭という仕様になっています。よって、midPointで主務と兼務を区別するには少し工夫が必要です。というのも、マルチバリューである`departments`の値は1つずつ展開されてマッピングに渡されて評価されるため、マッピング処理時に配列の何番目であるか判断できないためです。

そこで、`<relativityMode>absolute</relativityMode>`という設定を入れることで、マルチバリュー全体の値をマッピング中の`input`変数にバインドするモードに切り替えます。そうすることで配列の先頭を主務に、残りを兼務としてmidPointにマッピングするようにしています。


```diff_xml:SmartHRリソースのRAW編集
            <attribute id="...">
                <ref>ri:departments</ref>
                <inbound id="...">
                    <strength>strong</strength>
+                    <expression>
+                        <assignmentTargetSearch>
+                            <relativityMode>absolute</relativityMode>
+                            <targetType>c:OrgType</targetType>
+                            <filter>
+                                <q:equal>
+                                    <q:path>identifier</q:path>
+                                    <expression>
+                                        <queryInterpretationOfNoValue>filterNone</queryInterpretationOfNoValue>
+                                        <script>
+                                            <code>
+                                            <![CDATA[
+                                                input?.size() > 0 ? input[0] : null
+                                            ]]>
+                                           </code>
+                                        </script>
+                                    </expression>
+                                </q:equal>
+                            </filter>
+                            <assignmentProperties>
+                                <subtype>SMART_HR_PRIMARY</subtype>
+                            </assignmentProperties>
+                        </assignmentTargetSearch>
+                    </expression>
+                    <target>
+                        <path>assignment</path>
+                        <set>
+                            <condition>
+                                <script>
+                                    <code>
+                                       assignment?.subtype.contains("SMART_HR_PRIMARY")
+                                   </code>
+                                </script>
+                            </condition>
+                        </set>
+                    </target>
+                </inbound>
+                <inbound>
+                    <strength>strong</strength>
+                    <expression>
+                        <assignmentTargetSearch>
+                            <relativityMode>absolute</relativityMode>
+                            <targetType>c:OrgType</targetType>
+                            <filter>
+                                <q:equal>
+                                    <q:path>identifier</q:path>
+                                    <expression>
+                                        <queryInterpretationOfNoValue>filterNone</queryInterpretationOfNoValue>
+                                        <script>
+                                            <code>
+                                            <![CDATA[
+                                               input?.size() > 1 ? input[1..-1] : null
+                                            ]]>
+                                           </code>
+                                        </script>
+                                    </expression>
+                                </q:equal>
+                            </filter>
+                            <assignmentProperties >
+                                <subtype>SMART_HR_SECONDARY</subtype>
+                            </assignmentProperties>
+                        </assignmentTargetSearch>
+                    </expression>
+                    <target>
+                        <path>assignment</path>
+                        <set>
+                            <condition>
+                                <script>
+                                    <code>
+                                       assignment?.subtype.contains("SMART_HR_SECONDARY")
+                                   </code>
+                                </script>
+                            </condition>
+                        </set>
+                    </target>
                </inbound>
            </attribute>
```

### 同期の設定

[5日目の記事「midPoint にCSVの源泉データをインポートする（ユーザー編）」](https://qiita.com/wadahiro/items/a239a743af5404a7325f)と同じ同期の設定をしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/0d1ee5d0-98a5-620d-275c-81a26e7ecccf.png)

### Correlationの設定

今回は省略します。


## Department（部署）の追加

SmartHRのDepartment（部署）用のオブジェクトタイプを追加します。「用途」は`department`としておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/ecde844d-0713-d0fe-7981-78f215383a8f.png)

オブジェクトクラスには`department`を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/379c7c01-848e-7948-ca2d-c150b25cd88f.png)


midPointの組織として取り込みたいので、「タイプ」では`組織`を選択します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/4b4109da-3ba4-b8e8-c3b9-82c51ad44907.png)


### SmartHRの部署のルート組織の作成

SmartHRの部署データは、親組織のない最上位の部署を複数作ることができます。そのままmidPointに取り込むと、きれいな組織ツリーにならないため、SmartHRの組織用のルート組織を今回は作成しておきます。

以下の内容で組織を作成しておきます。**識別子** を使って、SmartHRからの部署取り込み時にこの組織を自動アサインするため、注意してください。

- **名前**：`smarthr-root`
- **表示名**：`SmartHR組織`
- **識別子**：`smarthr-root`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/1149838a-5d1e-ce91-a226-2f79f78b9bfb.png)

### インバウンドマッピングの設定

以下のように設定します。「parent_id」の「ターゲット」は[8日目の記事「midPoint にCSVの源泉データをインポートする（組織編）」](https://qiita.com/wadahiro/items/dc4fe8a20f4e648e01dc)と同様に、一旦未設定で保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/163b5e04-1570-58bc-aff7-1221d9616d34.png)

RAW編集画面を開き、以下を設定します。

CSVの時と異なるポイントとして、`<includeNullInputs>true</includeNullInputs>`を設定しています。これは、インプットとなるソースのデータが`null`の時に、`null`を含めてマッピングを評価させるかどうかを決定するオプション設定です。SmartHRではトップレベルの部署については親組織が存在しないため、「parent_id」は`null`となります。この`null`をハンドリングして先ほど作成した「SmartHR組織」を親組織としてアサインしたいので、`true`に設定しています。

```diff_xml
            <attribute id="...">
                <ref>ri:parent_id</ref>
                <inbound id="...">
                    <strength>strong</strength>
+                    <expression>
+                        <assignmentTargetSearch>
+                            <includeNullInputs>true</includeNullInputs>
+                            <targetType>c:OrgType</targetType>
+                            <filter>
+                                <q:equal>
+                                    <q:path>identifier</q:path>
+                                    <expression>
+                                        <queryInterpretationOfNoValue>filterNone</queryInterpretationOfNoValue>
+                                        <script>
+                                            <code>
+                                               input == null ? "smarthr-root" : input
+                                           </code>
+                                        </script>
+                                    </expression>
+                                </q:equal>
+                            </filter>
+                            <assignmentProperties>
+                                <subtype>SMART_HR</subtype>
+                            </assignmentProperties>
+                        </assignmentTargetSearch>
+                    </expression>
+                    <target>
+                        <path>assignment</path>
+                        <set>
+                            <condition>
+                                <script>
+                                    <code>
+                                       assignment?.subtype.contains("SMART_HR")
+                                   </code>
+                                </script>
+                            </condition>
+                        </set>
+                    </target>
                </inbound>
            </attribute>

```

### 同期の設定

[8日目の記事「midPoint にCSVの源泉データをインポートする（組織編）」](https://qiita.com/wadahiro/items/dc4fe8a20f4e648e01dc)と同じ同期の設定をしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/a7087470-aa10-c250-e294-4326dc50f97b.png)


### Correlationの設定

今回は省略します。


# タスクの作成

[5日目の記事「midPoint にCSVの源泉データをインポートする（ユーザー編）」](https://qiita.com/wadahiro/items/a239a743af5404a7325f)、[8日目の記事「midPoint にCSVの源泉データをインポートする（組織編）」](https://qiita.com/wadahiro/items/dc4fe8a20f4e648e01dc)と同様に、それぞれ取り込み用のタスクを作成しておきます。

- 「SmartHR import users」インポートタスク
- 「SmartHR recon orgs」リコンシリエーションタスク

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/afc824c3-c78d-6d60-636b-0b254a7da4cb.png)


# SmartHR側のテストデータの準備

サンドボックス環境を利用すると、デフォルトでユーザーに関しては登録されていますが、部署情報は入力しておく必要がありますので、適当なデータを入れておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/1b213f7b-4d48-f9a6-e3e6-89899f9d4735.png)

作成した部署データと従業員の紐付けも実施しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/ea0023cd-898a-3f28-1976-1a52885fa28e.png)


# 動作確認

## SmartHRの部署の取り込み

「SmartHR recon orgs」リコンシリエーションタスクを実行して、部署情報を組織として取り込みます。タスク実行完了後、組織ツリー画面を開いて想定通りツリー構造で取り込まれているかどうか確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/7e66db3f-b97e-c262-e6bb-7c875e03fd5f.png)


## SmartHRの従業員の取り込み

「SmartHR import users」インポートタスクを実行して、従業員情報をユーザーとして取り込みます。タスク実行完了後、ユーザー一覧画面を開いて取り込まれているかどうか確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/45b24753-7ce7-f50b-7e14-13f20f2a5935.png)

主務と兼務も想定通り付いているか確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/8836f826-9de1-0d63-ddad-9a9d48d0a4f4.png)

主務と兼務の区別がついているかは、各アサインの詳細画面を開いてサブタイプを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/b4c50122-1352-c186-f9d6-a2207e86c164.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/4ebad9c4-437a-e353-349b-709e174edc08.png)


# まとめ

22日目では、SaaSアプリケーションの一つであるSmartHRとの連携方法について解説しました。既にCSVファイルを源泉とした取り込みについては、[5日目](https://qiita.com/wadahiro/items/a239a743af5404a7325f)、[8日目](https://qiita.com/wadahiro/items/dc4fe8a20f4e648e01dc)、[9日目](https://qiita.com/wadahiro/items/51847471e9277adac4c0)の記事で解説済みでした。そのため、今回の連携ではコネクターが異なるだけで、midPoint側ではほぼ同じ設定手順で対応できることをご理解いただけたかと思います。

オンプレミスのアプリケーションであれ、SaaSアプリケーションであれ、midPointではコネクターを通じてデータ連携を標準化しています。そのため、リソース設定においては、主に項目間のマッピング調整が必要な作業となります。

今回紹介したSmartHR以外にも、[コネクターリスト](https://docs.evolveum.com/connectors/connectors/)にはいくつかSaaSアプリケーション向けのコネクターが掲載されていますので、ぜひ試してみてください。ただし、コネクターの数に関しては、大手ベンダーが提供するIDM/IGAソリューション[^1]と比べると、midPointにはまだ課題が残っています。もし連携を希望するSaaSアプリケーションに対応するコネクターが見つからない場合は、コメントや[X（旧Twitter）](https://x.com/wadahiro)などを通じてフィードバックをお寄せいただければ、何かお手伝いできるかもしれません。是非お気軽にご連絡ください。


[^1]: SailPoint、Savyintといった大手IGAベンダーや、Microsoft Entra ID、Oktaといった大手IDaaSベンダーのソリューションでは、コネクターの数はやはり豊富です。