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}