# 機能設計書 52-Descriptor

## 概要

本ドキュメントは、JenkinsのDescriptor（記述子）機能の設計を記述する。Descriptorは設定可能なオブジェクト（Describable）のメタデータを管理し、設定フォームの生成、バリデーション、インスタンス生成を担う重要な基盤クラスである。

### 本機能の処理概要

**業務上の目的・背景**：Jenkinsでは、ビルドステップ、SCM、認証方式など多くのコンポーネントがユーザーによって設定可能である。Descriptorパターンにより、設定可能なオブジェクト（Describable）とそのメタデータ・ファクトリ（Descriptor）を分離し、プラグインによる新しいコンポーネントタイプの追加を容易にしている。Object/Classの関係に類似したこの設計により、設定UIの自動生成や型安全なインスタンス生成が実現される。

**機能の利用シーン**：
- ジョブ設定画面でビルドステップやトリガーを選択・設定する際
- プラグインが新しい設定可能コンポーネントを提供する際
- フォーム入力値のバリデーションを行う際
- JSONデータからDescribableインスタンスを生成する際
- ヘルプファイルを表示する際

**主要な処理内容**：
1. Describableのメタデータ（表示名、ID等）の提供
2. 設定フォーム（config.jelly）のレンダリング支援
3. フォーム送信からのインスタンス生成（`newInstance()`）
4. 入力値バリデーション（`doCheckXxx()`メソッド）
5. ドロップダウンリストの選択肢取得（`doFillXxxItems()`メソッド）
6. ヘルプファイルの提供
7. グローバル設定の永続化（`save()`/`load()`）

**関連システム・外部連携**：
- Stapler: フォームデータバインディング
- Jelly: 設定フォームのテンプレートエンジン
- XStream: XML永続化

**権限による制御**：`getRequiredGlobalConfigPagePermission()`でグローバル設定に必要な権限を指定可能。デフォルトはJenkins.ADMINISTER。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | ジョブ設定 | 主機能 | 各種Describableの設定フォーム表示 |
| 29 | システム設定 | 主機能 | グローバル設定フォームの表示 |
| - | 全設定画面 | 補助機能 | フォームバリデーション、ヘルプ表示 |

## 機能種別

メタデータ管理 / ファクトリ / フォーム処理 / 永続化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| req | StaplerRequest2 | No | フォーム送信リクエスト | null許容（デフォルト値生成時） |
| formData | JSONObject | Yes | フォームから送信されたJSON形式のデータ | 空でないこと |

### 入力データソース

- HTMLフォーム: config.jelly/global.jellyで定義された入力フィールド
- JSONObject: Staplerによって変換されたフォームデータ
- XMLファイル: `{DescriptorId}.xml`に保存されたグローバル設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| describable | T extends Describable | 設定から生成されたインスタンス |
| displayName | String | UI表示用の名前 |
| helpFile | String | ヘルプファイルのパス |
| propertyType | PropertyType | プロパティのメタデータ |

### 出力先

- メモリ: 生成されたDescribableインスタンス
- ファイル: `$JENKINS_HOME/{DescriptorId}.xml`（グローバル設定）

## 処理フロー

### 処理シーケンス

```
1. フォーム送信受信
   └─ StaplerがJSONObjectを構築
2. Descriptorの特定
   └─ $classまたはkind属性からDescriptorを検索
3. newInstance()呼び出し
   └─ JSONObjectからDescribableインスタンスを生成
4. データバインディング
   └─ @DataBoundConstructorまたはbindJSON()でプロパティ設定
5. ネストオブジェクト処理
   └─ NewInstanceBindInterceptorで再帰的にnewInstance()呼び出し
6. インスタンス返却
   └─ 検証済みDescribableを呼び出し元に返却
```

### フローチャート

```mermaid
flowchart TD
    A[フォーム送信] --> B[JSONObject構築]
    B --> C{$class/kind属性}
    C -->|あり| D[Descriptor検索]
    C -->|なし| E[エラー]
    D --> F[newInstance呼び出し]
    F --> G{カスタム実装?}
    G -->|Yes| H[オーバーライドnewInstance]
    G -->|No| I[bindJSON実行]
    H --> J[インスタンス生成]
    I --> J
    J --> K{ネストオブジェクト?}
    K -->|Yes| L[再帰的newInstance]
    K -->|No| M[インスタンス返却]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-52-01 | ID一意性 | DescriptorのIDはシステム内で一意 | 常時 |
| BR-52-02 | 内部クラス規約 | Descriptorは通常Describableの静的内部クラス | 推奨パターン |
| BR-52-03 | シングルトン | 各Descriptor型につき1インスタンス | 常時 |
| BR-52-04 | ヘルプファイル規約 | help-{fieldName}.htmlをリソースに配置 | ヘルプ提供時 |

### 計算ロジック

Descriptor IDの算出:
- デフォルト: `clazz.getName()`（Describableクラスの完全修飾名）
- カスタム: `getId()`をオーバーライドして独自IDを返却可能

## データベース操作仕様

### 操作別データベース影響一覧

本機能はデータベースを直接操作しない。設定はXMLファイルで永続化される。

| 操作 | 対象ファイル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| グローバル設定保存 | `$JENKINS_HOME/{DescriptorId}.xml` | WRITE | Descriptorのフィールドを保存 |
| グローバル設定読込 | `$JENKINS_HOME/{DescriptorId}.xml` | READ | Descriptorのフィールドを復元 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | FormException | フォームバリデーション失敗 | エラーメッセージを表示、フォーム再表示 |
| - | AssertionError | Descriptor/Describableの不整合 | 開発者向けログ出力 |
| - | LinkageError | インスタンス生成失敗 | スタックトレース付きログ |

### リトライ仕様

リトライは行わない。エラー発生時はユーザーに修正を促す。

## トランザクション仕様

- `save()`は`BulkChange`でバッチ保存をサポート
- `SaveableListener`で保存イベントを通知

## パフォーマンス要件

- Descriptor取得: O(1)（DescriptorExtensionListでキャッシュ）
- プロパティタイプ取得: 遅延計算、以後キャッシュ
- ヘルプファイル検索: クラスパスからの読み込み

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

- `getRequiredGlobalConfigPagePermission()`でグローバル設定アクセス権限を制御
- フォームバリデーションでXSS等の入力検証を実施
- `Secret`クラスによる機密データの暗号化をサポート

## 備考

- DescriptorはExtensionPointを直接継承していないが、拡張機構で管理される
- `@Symbol`アノテーションでPipeline DSLからの参照名を定義可能

---

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

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

### 推奨読解順序

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

まず、Descriptor/Describableの関係性と基本構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Descriptor.java | `core/src/main/java/hudson/model/Descriptor.java` | Descriptorクラスの定義、clazzフィールドの役割 |
| 1-2 | Describable.java | `core/src/main/java/hudson/model/Describable.java` | Describableインターフェースの定義 |
| 1-3 | PropertyType (内部クラス) | `core/src/main/java/hudson/model/Descriptor.java` | プロパティメタデータの構造 |

**読解のコツ**: Descriptor<T>のジェネリクス型パラメータTがDescribable<T>を継承している点に注目。これがObject/Classの関係を模倣している。

#### Step 2: エントリーポイントを理解する

フォームからのインスタンス生成フローを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Descriptor.java | `core/src/main/java/hudson/model/Descriptor.java` | newInstance()メソッドの実装 |

**主要処理フロー**:
1. **590-596行目**: `newInstance(StaplerRequest2, JSONObject)`がエントリーポイント
2. **607-628行目**: `newInstanceImpl()`でバインディング処理
3. **636-666行目**: `bindJSON()`でStaplerのデータバインディングを実行
4. **676-743行目**: `NewInstanceBindInterceptor`でネストオブジェクトを再帰処理

#### Step 3: メタデータ取得を理解する

表示名やヘルプファイル等のメタデータ取得を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Descriptor.java | `core/src/main/java/hudson/model/Descriptor.java` | getDisplayName()、getHelpFile()の実装 |

**主要処理フロー**:
- **327-330行目**: `getDisplayName()`でUI表示名を返却
- **348-350行目**: `getId()`でDescriptorの一意識別子を返却
- **783-823行目**: `getHelpFile()`でヘルプファイルのパスを検索

#### Step 4: 永続化を理解する

グローバル設定の保存・読込処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Descriptor.java | `core/src/main/java/hudson/model/Descriptor.java` | save()、load()メソッド |

**主要処理フロー**:
- **962-971行目**: `save()`でXMLファイルに保存
- **981-992行目**: `load()`でXMLファイルから読込
- **994-996行目**: `getConfigFile()`で設定ファイルパスを取得

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

```
Stapler (フォーム送信)
    │
    ├─ Descriptor.newInstance(StaplerRequest2, JSONObject)
    │      │
    │      └─ Descriptor.bindJSON(StaplerRequest2, Class, JSONObject)
    │             │
    │             └─ NewInstanceBindInterceptor.instantiate(Class, JSONObject)
    │                    │
    │                    └─ (再帰) Descriptor.newInstance()
    │
    └─ 結果のDescribableインスタンス
```

### データフロー図

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

HTMLフォーム            Stapler                        Describable
(config.jelly)  ───▶   (JSON変換) ───▶  Descriptor ───▶ インスタンス
    │                                   .newInstance()      │
    ▼                                        │              ▼
JSONObject                              bindJSON()      ジョブ設定等
                                             │          に保存
                                             ▼
                                    @DataBoundConstructor
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Descriptor.java | `core/src/main/java/hudson/model/Descriptor.java` | ソース | メインクラス |
| Describable.java | `core/src/main/java/hudson/model/Describable.java` | ソース | 設定可能オブジェクトのインターフェース |
| DescriptorExtensionList.java | `core/src/main/java/hudson/DescriptorExtensionList.java` | ソース | Descriptor用ExtensionList |
| DescriptorVisibilityFilter.java | `core/src/main/java/hudson/model/DescriptorVisibilityFilter.java` | ソース | Descriptor表示制御 |
| DescriptorByNameOwner.java | `core/src/main/java/hudson/model/DescriptorByNameOwner.java` | ソース | 名前によるDescriptor解決 |
| PersistentDescriptor.java | `core/src/main/java/jenkins/model/PersistentDescriptor.java` | ソース | 自動load()呼び出し用マーカー |
