001package com.streamconverter.context;
002
003import java.util.Collections;
004import java.util.Map;
005import java.util.concurrent.ConcurrentHashMap;
006import org.slf4j.MDC;
007
008/**
009 * パイプライン内でコマンド間の共有値を管理するコンテキスト。
010 *
011 * <p>パイプライン実行中に、あるコマンドが抽出した値(例: XMLヘッダのorderId)を 他のコマンドのログ出力に自動反映するための仕組みを提供する。
012 *
013 * <p>共有値は {@link com.streamconverter.logging.PipelineContextTurboFilter} により、
014 * ログ出力直前にMDCへ自動的にマージされる。
015 *
016 * <p>使用例:
017 *
018 * <pre>{@code
019 * // コマンド内でストリームデータから値を抽出してMDCに伝搬
020 * PipelineContext.putShared("orderId", extractedOrderId);
021 *
022 * // 他のコマンドのログ出力時に自動的にMDCに反映される
023 * }</pre>
024 *
025 * <p><b>MDC 関連クラスの全体像:</b>
026 *
027 * <ul>
028 *   <li>{@link com.streamconverter.command.rule.MdcPropagatingRule} — ストリームから抽出した値を このコンテキスト経由で MDC
029 *       に伝搬する Rule 実装。コマンドから MDC へ値を書き込む際の推奨手段。
030 *   <li>{@link com.streamconverter.logging.PipelineContextTurboFilter} — ログ出力直前に {@link
031 *       #syncToMDC()} を呼び出し、共有値を MDC へ自動反映する Logback TurboFilter。
032 *   <li>{@link com.streamconverter.logging.MDCInitializer} — {@code InheritableMDCAdapter} を
033 *       インストールし、MDC コンテキストを子スレッドへ自動継承させる。アプリ起動時に一度呼ぶ。
034 * </ul>
035 */
036public final class PipelineContext {
037
038  /** パイプラインコンテキストを新規作成する。 */
039  public PipelineContext() {}
040
041  private static final ThreadLocal<PipelineContext> HOLDER = new ThreadLocal<>();
042
043  private final ConcurrentHashMap<String, String> sharedValues = new ConcurrentHashMap<>();
044
045  /**
046   * 共有値を設定し、呼び出しスレッドのMDCにも即座に反映する。
047   *
048   * <p>PipelineContextが未設定のスレッドから呼ばれた場合は何もしない。
049   *
050   * @param key MDCキー名
051   * @param value 値。nullの場合はキーを削除する
052   * @throws IllegalArgumentException keyがnullの場合
053   */
054  public static void putShared(String key, String value) {
055    if (key == null) {
056      throw new IllegalArgumentException("key must not be null");
057    }
058    PipelineContext ctx = HOLDER.get();
059    if (ctx == null) {
060      return;
061    }
062    if (value != null) {
063      ctx.sharedValues.put(key, value);
064      MDC.put(key, value);
065    } else {
066      ctx.sharedValues.remove(key);
067      MDC.remove(key);
068    }
069  }
070
071  /**
072   * 共有値を取得する。
073   *
074   * @param key MDCキー名
075   * @return 値。キーが存在しない場合またはPipelineContext未設定の場合はnull
076   */
077  public static String getShared(String key) {
078    PipelineContext ctx = HOLDER.get();
079    if (ctx == null) {
080      return null;
081    }
082    return ctx.sharedValues.get(key);
083  }
084
085  /**
086   * 全共有値を呼び出しスレッドのMDCにマージする。
087   *
088   * <p>PipelineContextが未設定の場合は何もしない。
089   */
090  public static void syncToMDC() {
091    PipelineContext ctx = HOLDER.get();
092    if (ctx == null) {
093      return;
094    }
095    ctx.sharedValues.forEach(MDC::put);
096  }
097
098  /**
099   * 現在のスレッドに紐づくPipelineContextを取得する。
100   *
101   * @return PipelineContext、未設定の場合はnull
102   */
103  static PipelineContext current() {
104    return HOLDER.get();
105  }
106
107  /**
108   * 共有値のスナップショットを返す。
109   *
110   * @return 共有値の不変ビュー
111   */
112  Map<String, String> getSharedValues() {
113    return Collections.unmodifiableMap(sharedValues);
114  }
115
116  /**
117   * 現在のスレッドにPipelineContextを設定する。
118   *
119   * <p>StreamConverterがパイプラインの各コマンドスレッドで呼び出す。
120   *
121   * @param context 設定するPipelineContext
122   */
123  public static void set(PipelineContext context) {
124    HOLDER.set(context);
125  }
126
127  /** 現在のスレッドからPipelineContextをクリアする。 */
128  public static void clear() {
129    HOLDER.remove();
130  }
131}