# 通知設計書 4-古いデータ警告（OldDataMonitor）

## 概要

本ドキュメントは、古い形式のデータが存在する場合に管理者へ警告を表示するOldDataMonitor（古いデータ警告）の通知設計について記述する。

### 本通知の処理概要

OldDataMonitorは、JenkinsのXML設定ファイルを読み込む際に、古い形式のデータ構造や読み込み不可能なデータを検出し、管理者に対してデータ移行や整理を促す警告メッセージを表示する通知機能である。

**業務上の目的・背景**：Jenkinsは長期間の運用を通じてデータ形式が進化する。プラグインの更新やJenkins本体のアップグレードにより、設定ファイル内のデータ構造が変更されることがある。古い形式のデータは読み込み時に自動変換されるが、ファイルを再保存するまで古い形式のまま残る。また、プラグインのアンインストールにより読み込み不可能なクラス参照が残る場合もある。本通知は、これらの状況を管理者に知らせ、データの整理・移行を促すことでシステムの健全性を維持することを目的とする。

**通知の送信タイミング**：XMLファイル読み込み時（XStream unmarshalling）に、RobustReflectionConverterなどを通じて古いデータが検出された場合、`OldDataMonitor.report()`が呼び出されてデータが登録される。データが1件以上存在する場合、管理画面アクセス時に通知が有効化される。

**通知の受信者**：Jenkins管理画面（/manage）にアクセスできる管理者が対象となる。また、管理画面のリンクにBadge（件数表示）が追加され、問題の存在を視覚的に示す。

**通知内容の概要**：「古い形式のデータや読み込み不可能なデータが存在する」旨のメッセージが表示される。「Manage」ボタンで詳細管理画面への誘導、「Dismiss」ボタンで警告の非表示化が可能。詳細画面では、対象オブジェクトとバージョン範囲の一覧が表示され、一括アップグレードや破棄が可能。

**期待されるアクション**：管理者は「Manage」をクリックして詳細画面に遷移し、古いデータを含むオブジェクト一覧を確認する。「Upgrade」で新形式への移行（再保存）、「Discard Unreadable Data」で読み込み不可能なデータの破棄を実行することが期待される。

## 通知種別

アプリ内通知（管理画面バナー表示 + 管理リンクBadge）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（画面レンダリング時） |
| 優先度 | 中 |
| リトライ | 無（画面表示のため不要） |

### 送信先決定ロジック

Jenkins管理画面（/manage）にアクセスした管理者に対して表示される。dataマップに1件以上のエントリがある場合に有効化。

## 通知テンプレート

### アプリ内通知の場合

| 項目 | 内容 |
|-----|------|
| 表示位置 | 管理画面上部 + 管理リンクBadge |
| アラートタイプ | jenkins-alert-warning（警告） |
| 形式 | HTML |

### 本文テンプレート

```html
<div class="jenkins-alert jenkins-alert-warning">
  <form method="post" action="${rootURL}/${it.url}/act" name="${it.id}">
    <f:submit name="yes" value="Manage"/>
    <f:submit name="no" value="Dismiss"/>
  </form>
  ${%You have data stored in an older format and/or unreadable data.}
</div>
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| rootURL | JenkinsのルートURL | Jenkins.get().getRootUrl() | Yes |
| it.url | モニターのURL | AdministrativeMonitor.getUrl() | Yes |
| it.id | モニターのID | AdministrativeMonitor.id | Yes |
| data | 古いデータを持つオブジェクトのマップ | ConcurrentHashMap<SaveableReference, VersionRange> | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| システムイベント | XMLファイル読み込み時 | 古いデータ形式検出 | OldDataMonitor.report()呼び出し |
| 画面操作 | 管理画面アクセス | !data.isEmpty() | データマップが空でない場合 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| disable(true)が呼び出された場合 | 管理者が「Dismiss」をクリックした場合 |
| data.isEmpty() | 古いデータが存在しない場合 |
| 対象オブジェクトが保存/削除された場合 | SaveableListener/ItemListener/RunListenerで自動削除 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[XMLファイル読み込み] --> B{古いデータ検出?}
    B -->|No| C[通常処理]
    B -->|Yes| D[OldDataMonitor.report呼び出し]
    D --> E[dataマップに登録]
    E --> F[管理画面アクセス]
    F --> G{isActivated?}
    G -->|No| H[表示しない]
    G -->|Yes| I[警告バナー表示]
    I --> J{ユーザーアクション}
    J -->|Manage| K[詳細管理画面表示]
    J -->|Dismiss| L[disable true]
    K --> M{操作選択}
    M -->|Upgrade| N[doUpgrade - 再保存]
    M -->|Discard| O[doDiscard - 破棄]
    N --> P[dataマップから削除]
    O --> P
    L --> Q[終了]
    P --> Q
    H --> Q
    C --> Q
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（ファイルベースの設定を参照）

### 参照データ構造

| データ構造 | 用途 | 備考 |
|-----------|------|------|
| ConcurrentHashMap<SaveableReference, VersionRange> | 古いデータを持つオブジェクトの追跡 | メモリ上で管理 |

### 更新ファイル

| ファイル | 操作 | 概要 |
|---------|------|------|
| 各Saveableオブジェクトの設定ファイル | UPDATE | doUpgrade()でsave()呼び出し時に新形式で保存 |
| $JENKINS_HOME/config.xml | UPDATE | disable()呼び出し時にdisabledAdministrativeMonitorsを更新 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 保存失敗 | save()時のException | ログ出力、処理継続 |
| オブジェクト参照失敗 | SaveableReference.get()がnull | スキップして次の処理へ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（個別オブジェクトの保存失敗は無視して継続） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

該当なし（画面表示のため）

### 配信時間帯

制限なし（常時有効）

## セキュリティ考慮事項

- doAct(), doUpgrade(), doDiscard()は`@RequirePOST`で保護されている
- 管理者権限が必要
- ACL.as2(ACL.SYSTEM2)でシステムコンテキストで操作実行

## 備考

- Symbol: `oldData`
- パッケージ: hudson.diagnosis
- ID: "OldData"
- 関連クラス: VersionRange（バージョン範囲管理）、SaveableReference（オブジェクト参照）
- 管理リンク: ManagementLinkImpl（ManagementLink拡張）
- メッセージリソース: hudson/diagnosis/Messages.properties

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

VersionRangeとSaveableReferenceの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | OldDataMonitor.java | `core/src/main/java/hudson/diagnosis/OldDataMonitor.java` | VersionRangeインナークラス、SaveableReferenceインターフェース |

**読解のコツ**: ConcurrentHashMapで並行アクセスに対応している点、SaveableReferenceでオブジェクト参照を抽象化している点に注目。

#### Step 2: データ登録処理を理解する

OldDataMonitor.report()メソッドがエントリーポイント。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | OldDataMonitor.java | `core/src/main/java/hudson/diagnosis/OldDataMonitor.java` | report()メソッド群、データ登録ロジック |

**主要処理フロー**:
1. **174-189行目**: `report(Saveable, String)` - バージョン指定でのデータ登録
2. **197-199行目**: `report(UnmarshallingContext, String)` - XStream経由での登録
3. **214-247行目**: `report(Saveable, Collection<Throwable>)` - エラー情報付きの登録
4. **249-300行目**: VersionRangeクラス - バージョン範囲の管理

#### Step 3: 自動削除処理を理解する

リスナーによるデータ自動削除。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | OldDataMonitor.java | `core/src/main/java/hudson/diagnosis/OldDataMonitor.java` | changeListener, itemDeleteListener, runDeleteListener |

**主要処理フロー**:
- **143-149行目**: SaveableListener - オブジェクト保存時に自動削除
- **151-157行目**: ItemListener - アイテム削除時に自動削除
- **159-165行目**: RunListener - ビルド削除時に自動削除

#### Step 4: 管理画面処理を理解する

doAct(), doUpgrade(), doDiscard()。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | OldDataMonitor.java | `core/src/main/java/hudson/diagnosis/OldDataMonitor.java` | アクションメソッド群 |

**主要処理フロー**:
- **319-327行目**: `doAct()` - Manage/Dismissの処理分岐
- **333-344行目**: `doUpgrade()` - バージョン指定での一括アップグレード
- **350-355行目**: `doDiscard()` - 読み込み不可データの一括破棄

### プログラム呼び出し階層図

```
[データ登録]
XMLファイル読み込み
    │
    ├─ RobustReflectionConverter
    │      └─ OldDataMonitor.report(context, version)
    │              └─ addErrorInContext()
    │
    └─ 読み込み完了時
           └─ OldDataMonitor.report(Saveable, Collection<Throwable>)
                   └─ data.putIfAbsent(ref, versionRange)

[自動削除]
Saveable.save() → SaveableListener.onChange()
                        └─ OldDataMonitor.remove(obj, false)

Item削除 → ItemListener.onDeleted()
                └─ OldDataMonitor.remove(item, true)

Run削除 → RunListener.onDeleted()
               └─ OldDataMonitor.remove(run, true)

[管理画面]
Jenkins.getAdministrativeMonitors()
    │
    ├─ OldDataMonitor.isActivated()
    │      └─ return !data.isEmpty()
    │
    └─ OldDataMonitor.doUpgrade() / doDiscard()
           └─ saveAndRemoveEntries(predicate)
                   ├─ entry.getKey().get().save()
                   └─ data.keySet().removeAll(removed)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

XMLファイル読み込み ───▶ 古いデータ検出 ───▶ dataマップ登録
                              │
                              └─ OldDataMonitor.report()
                                        │
                                        ▼
                              管理画面アクセス時
                                        │
                                        ▼
                              警告バナー + Badge表示
                                        │
                                        ▼
                              詳細管理画面
                                        │
                                ┌───────┴───────┐
                                ▼               ▼
                           Upgrade         Discard
                           (save)         (save)
                                │               │
                                └───────┬───────┘
                                        ▼
                              dataマップから削除
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| OldDataMonitor.java | `core/src/main/java/hudson/diagnosis/OldDataMonitor.java` | ソース | メインクラス |
| AdministrativeMonitor.java | `core/src/main/java/hudson/model/AdministrativeMonitor.java` | ソース | 基底クラス |
| RobustReflectionConverter.java | `core/src/main/java/hudson/util/RobustReflectionConverter.java` | ソース | XStream変換時のエラー収集 |
| SaveableListener.java | `core/src/main/java/hudson/model/listeners/SaveableListener.java` | ソース | 保存イベントリスナー |
| ItemListener.java | `core/src/main/java/hudson/model/listeners/ItemListener.java` | ソース | アイテムイベントリスナー |
| RunListener.java | `core/src/main/java/hudson/model/listeners/RunListener.java` | ソース | ビルドイベントリスナー |
| message.jelly | `core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly` | テンプレート | 警告バナーUI |
| manage.jelly | `core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly` | テンプレート | 詳細管理画面 |
| description.jelly | `core/src/main/resources/hudson/diagnosis/OldDataMonitor/description.jelly` | テンプレート | 説明文 |
| Messages.properties | `core/src/main/resources/hudson/diagnosis/Messages.properties` | リソース | メッセージ定義 |
| OldDataMonitorTest.java | `test/src/test/java/hudson/diagnosis/OldDataMonitorTest.java` | テスト | 単体テスト |
