001package com.streamconverter.command.rule;
002
003import com.streamconverter.context.PipelineContext;
004
005/**
006 * 変換結果をMDC共有値として伝搬するルール。
007 *
008 * <p>ストリームデータから抽出された値を、パイプライン内の全コマンドのログ出力に 自動反映するために使用する。値はパススルーされ、変換は行わない。
009 *
010 * <p>{@code MDC.put} だけのルールとの違い:
011 *
012 * <ol>
013 *   <li><b>後続コマンドへの伝搬</b> — 値を {@link com.streamconverter.context.PipelineContext}
014 *       の共有値として格納し、{@link com.streamconverter.logging.PipelineContextTurboFilter
015 *       PipelineContextTurboFilter} が他コマンドのログ出力直前に {@code syncToMDC()} で自動反映する。{@code MDC.put}
016 *       だけでは呼び出しスレッド内しか効かない。
017 *   <li><b>実行タイミング非依存</b> — 子スレッド起動後に値を書いても {@code PipelineContext} 経由なら {@code TurboFilter}
018 *       が毎ログ前に同期するため順序非依存。
019 *   <li><b>スレッドからのコンテキスト切り離し</b> — {@code PipelineContext.clear()} により、このスレッドとの関連付けを解除できる。
020 * </ol>
021 *
022 * <p>使用例:
023 *
024 * <pre>{@code
025 * // XMLの orderId を抽出してMDCに伝搬
026 * XmlNavigateCommand command = XmlNavigateCommand.create(
027 *     TreePath.fromXPath("/order/@id"),
028 *     MdcPropagatingRule.create("orderId")
029 * );
030 * }</pre>
031 */
032public final class MdcPropagatingRule implements IRule {
033
034  private final String mdcKey;
035
036  private MdcPropagatingRule(String mdcKey) {
037    this.mdcKey = mdcKey;
038  }
039
040  /**
041   * 指定されたキーにストリーム値を伝搬するルールを作成します。
042   *
043   * <p>使用例:
044   *
045   * <pre>{@code
046   * XmlNavigateCommand.create(
047   *     TreePath.fromXml("request/userId"),
048   *     MdcPropagatingRule.create("userId"));
049   * }</pre>
050   *
051   * @param mdcKey MDC/PipelineContext に格納するキー名
052   * @return 伝搬ルールのインスタンス
053   * @throws IllegalArgumentException mdcKey が null または空の場合
054   */
055  public static MdcPropagatingRule create(String mdcKey) {
056    if (mdcKey == null || mdcKey.isEmpty()) {
057      throw new IllegalArgumentException("mdcKey cannot be null or empty");
058    }
059    return new MdcPropagatingRule(mdcKey);
060  }
061
062  /**
063   * 入力値をMDC共有値として設定し、そのまま返す。
064   *
065   * @param input 変換対象の文字列
066   * @return 入力値をそのまま返す
067   * @throws IllegalArgumentException inputがnullの場合
068   */
069  @Override
070  public String apply(String input) {
071    if (input == null) {
072      throw new IllegalArgumentException("Input cannot be null");
073    }
074    PipelineContext.putShared(mdcKey, input);
075    return input;
076  }
077
078  @Override
079  public String toString() {
080    return "MdcPropagatingRule{mdcKey='" + mdcKey + "'}";
081  }
082
083  @Override
084  public boolean equals(Object obj) {
085    if (this == obj) return true;
086    if (!(obj instanceof MdcPropagatingRule other)) return false;
087    return mdcKey.equals(other.mdcKey);
088  }
089
090  @Override
091  public int hashCode() {
092    return mdcKey.hashCode();
093  }
094}