# 機能設計書 63-XMLファイル操作

## 概要

本ドキュメントは、Jenkins における設定ファイルの XML シリアライズ機能（XmlFile、XStream2）の設計について記述する。

### 本機能の処理概要

XMLファイル操作機能は、Jenkins の各種設定やジョブ情報を XML 形式で永続化するための基盤機能である。XStream ライブラリをベースに、Jenkins 固有の拡張を追加した XStream2 を使用している。

**業務上の目的・背景**：Jenkins はファイルベースの永続化を採用しており、ジョブ設定、プラグイン設定、システム設定などすべての構成情報を XML ファイルとして保存する。この設計により、バージョン管理システムでの設定管理、バックアップ、移行が容易になる。XMLファイル操作機能は、オブジェクトと XML 間の変換を安全かつ効率的に行い、データ形式の進化（スキーマ変更）にも対応する。

**機能の利用シーン**：
- ジョブ設定（config.xml）の読み書き
- ビルド履歴（build.xml）の読み書き
- システム設定の保存・読み込み
- プラグイン設定の永続化
- Descriptor の設定保存
- CLI/API 経由での設定エクスポート・インポート

**主要な処理内容**：
1. Java オブジェクトから XML への変換（シリアライズ）
2. XML から Java オブジェクトへの変換（デシリアライズ）
3. XML エンコーディングの自動検出
4. アトミックなファイル書き込み（データ整合性保証）
5. 旧データ形式からの移行サポート
6. トップレベルオブジェクトの特別な処理

**関連システム・外部連携**：
- XStream: XML シリアライズライブラリ
- AtomicFileWriter: アトミックなファイル書き込み
- OldDataMonitor: 旧データ形式の検出・報告
- Descriptor: 設定可能なオブジェクトのメタデータ

**権限による制御**：XMLファイル操作自体には権限制御はないが、設定ファイルへのアクセスは Jenkins のファイルシステム権限に依存する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | ジョブ設定 | 主画面 | ジョブ設定の保存・読み込み |
| 29 | システム設定 | 主画面 | システム設定の保存・読み込み |

## 機能種別

データ永続化 / ユーティリティ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| file | File | Yes | XML ファイルのパス | 存在チェック（読み込み時） |
| xs | XStream | No | 使用する XStream インスタンス | - |
| object | Object | Yes | シリアライズ対象オブジェクト（書き込み時） | - |
| force | boolean | No | fsync を強制するか（デフォルト true） | - |

### 入力データソース

- ファイルシステム上の XML ファイル
- Java オブジェクト（シリアライズ対象）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| object | Object | デシリアライズされた Java オブジェクト |
| xmlString | String | XML 文字列表現 |

### 出力先

- ファイルシステム（XML ファイル）
- メモリ上の Java オブジェクト

## 処理フロー

### 処理シーケンス

```
【読み込み処理】
1. XmlFile インスタンスの生成
   └─ ファイルパスと XStream インスタンスを指定

2. ファイル存在確認
   └─ exists() メソッドでチェック

3. エンコーディング検出
   └─ sniffEncoding() でXML宣言からエンコーディングを取得

4. XML パース・デシリアライズ
   └─ read() または unmarshal() でオブジェクトに変換

5. 旧データ形式の検出（オプション）
   └─ OldDataMonitor への報告

【書き込み処理】
1. 親ディレクトリの作成
   └─ mkdirs() で必要なディレクトリを作成

2. AtomicFileWriter の生成
   └─ 一時ファイルへの書き込み準備

3. XML 宣言の出力
   └─ <?xml version='1.1' encoding='UTF-8'?>

4. オブジェクトのシリアライズ
   └─ xs.toXML() でオブジェクトを XML に変換

5. アトミックなファイル置換
   └─ commit() で一時ファイルを本番ファイルに移動

6. fsync 実行（force=true の場合）
   └─ ファイルシステムへの同期
```

### フローチャート

```mermaid
flowchart TD
    subgraph 読み込み
        A1[開始] --> B1{ファイル存在?}
        B1 -->|Yes| C1[sniffEncoding]
        B1 -->|No| E1[IOException]
        C1 --> D1[read/unmarshal]
        D1 --> F1[オブジェクト返却]
        F1 --> G1[終了]
    end

    subgraph 書き込み
        A2[開始] --> B2[mkdirs]
        B2 --> C2[AtomicFileWriter 生成]
        C2 --> D2[XML 宣言出力]
        D2 --> E2[toXML でシリアライズ]
        E2 --> F2[commit]
        F2 --> G2{force?}
        G2 -->|Yes| H2[fsync]
        G2 -->|No| I2[終了]
        H2 --> I2
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-63-01 | エンコーディング | デフォルトは UTF-8、XML 宣言から検出も可能 | 読み込み時 |
| BR-63-02 | アトミック書き込み | 書き込みは一時ファイル経由で行い、完了後に置換 | 書き込み時 |
| BR-63-03 | XML バージョン | XML 1.1 を使用 | 書き込み時 |
| BR-63-04 | データ形式進化 | transient フィールドで旧フィールドを残し、新フィールドに移行 | スキーマ変更時 |
| BR-63-05 | 循環参照防止 | beingWritten マップでトップレベル書き込み中のオブジェクトを追跡 | 書き込み時 |

### 計算ロジック

該当なし

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

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

該当なし（XMLファイル操作機能はファイルシステムを使用）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| IOException | 入出力エラー | ファイル読み書き失敗 | エラーログ出力、呼び出し元に伝播 |
| SAXException | パースエラー | XML 形式不正 | IOException にラップして伝播 |
| RuntimeException | 変換エラー | XStream 変換失敗 | IOException にラップして伝播 |

### リトライ仕様

リトライ処理は行わない。書き込み失敗時は AtomicFileWriter により一時ファイルが自動削除される。

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

AtomicFileWriter により擬似的なトランザクションを実現：
- 書き込みは一時ファイルに対して行われる
- commit() 成功時のみ本番ファイルに置換
- 失敗時は abort() により一時ファイルを削除

## パフォーマンス要件

- 大きなファイルの読み込みは BufferedInputStream を使用
- force=false オプションで fsync をスキップ可能（データ整合性とのトレードオフ）

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

- XML 外部エンティティ（XXE）攻撃への対策として DOCTYPE 禁止
- XMLConstants.FEATURE_SECURE_PROCESSING の有効化
- 機密情報は Secret クラスと連携して暗号化保存

## 備考

- JENKINS-45892 対策として replaceIfNotAtTopLevel() メソッドを提供
- XStream2 は XStream の拡張で、Jenkins 固有のコンバーターを提供
- unmarshalNullingOut() で既存オブジェクトのフィールドを null 初期化して読み込み可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | XmlFile.java | `core/src/main/java/hudson/XmlFile.java` | XmlFile クラスの基本構造（122-147行目） |

**読解のコツ**: XmlFile は XStream インスタンスと File パスを保持するシンプルなクラス。force パラメータで fsync の動作を制御している。

#### Step 2: 読み込み処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | XmlFile.java | `core/src/main/java/hudson/XmlFile.java` | read() メソッド（160-169行目）、unmarshal() メソッド（178-201行目） |

**主要処理フロー**:
1. **160-169行目**: read() - 新しいオブジェクトとして読み込み
2. **164行目**: BufferedInputStream でバッファリング
3. **165行目**: xs.fromXML() でデシリアライズ
4. **178-201行目**: unmarshal() - 既存オブジェクトに読み込み

#### Step 3: 書き込み処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | XmlFile.java | `core/src/main/java/hudson/XmlFile.java` | write() メソッド（203-227行目） |

**主要処理フロー**:
- **207行目**: mkdirs() で親ディレクトリ作成
- **208-210行目**: AtomicFileWriter 生成（force に応じて動作変更）
- **212行目**: XML 宣言出力
- **213-219行目**: beingWritten 追跡とシリアライズ
- **221行目**: commit() でアトミック置換

#### Step 4: エンコーディング検出を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | XmlFile.java | `core/src/main/java/hudson/XmlFile.java` | sniffEncoding() メソッド（317-377行目） |

**主要処理フロー**:
- **318-324行目**: SAXException を使った早期終了パターン
- **329-332行目**: XXE 対策の設定
- **354-359行目**: Locator2 からエンコーディング取得
- **369行目**: 検出失敗時のデフォルト UTF-8

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

```
XmlFile 操作
    │
    ├─ read()
    │      ├─ BufferedInputStream 生成 (164行目)
    │      └─ xs.fromXML() (165行目)
    │
    ├─ unmarshal(object)
    │      ├─ BufferedInputStream 生成 (191行目)
    │      └─ xs.unmarshal() / XStream2.unmarshal() (194-196行目)
    │
    ├─ write(object)
    │      ├─ mkdirs() (207行目)
    │      ├─ AtomicFileWriter 生成 (208-210行目)
    │      ├─ XML 宣言出力 (212行目)
    │      ├─ beingWritten.put() (213行目)
    │      ├─ xs.toXML() (216行目)
    │      └─ w.commit() (221行目)
    │
    └─ sniffEncoding()
           ├─ SAXParserFactory 生成 (329-332行目)
           ├─ DefaultHandler (333-360行目)
           └─ Locator2.getEncoding() (356-357行目)
```

### データフロー図

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

XML ファイル ────────▶ sniffEncoding() ───▶ エンコーディング検出
                              │
                              ▼
                       read() / unmarshal()
                              │
                              ▼
                       XStream.fromXML() ───────▶ Java オブジェクト

Java オブジェクト ───▶ write()
                              │
                              ▼
                       AtomicFileWriter
                              │
                              ▼
                       XStream.toXML() ─────────▶ 一時ファイル
                              │
                              ▼
                       commit() ────────────────▶ XML ファイル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| XmlFile.java | `core/src/main/java/hudson/XmlFile.java` | ソース | XML ファイル操作の主要クラス |
| XStream2.java | `core/src/main/java/hudson/util/XStream2.java` | ソース | XStream の Jenkins 拡張 |
| AtomicFileWriter.java | `core/src/main/java/hudson/util/AtomicFileWriter.java` | ソース | アトミックなファイル書き込み |
| OldDataMonitor.java | `core/src/main/java/hudson/diagnosis/OldDataMonitor.java` | ソース | 旧データ形式の検出 |
| Descriptor.java | `core/src/main/java/hudson/model/Descriptor.java` | ソース | 設定可能オブジェクトの基底クラス |
