001package com.streamconverter.context; 002 003import java.time.Instant; 004import java.util.Collections; 005import java.util.HashMap; 006import java.util.Map; 007import java.util.Objects; 008import java.util.UUID; 009import java.util.concurrent.atomic.AtomicInteger; 010import org.slf4j.MDC; 011 012/** 013 * 実行コンテキストを管理するクラス 014 * 015 * <p>StreamConverterの実行全体を通じて一意の識別子とコンテキスト情報を提供し、 マルチスレッド環境でのMDC伝播を支援します。 016 */ 017public class ExecutionContext { 018 019 private final String executionId; 020 private final Instant startTime; 021 private final AtomicInteger commandSequence; 022 private final Map<String, String> globalContext; 023 private final Map<String, String> userContext; 024 025 // 標準的なコンテキストキー 026 /** 実行ID用のMDCキー */ 027 public static final String EXECUTION_ID_KEY = "executionId"; 028 029 /** 開始時刻用のMDCキー */ 030 public static final String START_TIME_KEY = "startTime"; 031 032 /** コマンドシーケンス用のMDCキー */ 033 public static final String COMMAND_SEQUENCE_KEY = "commandSequence"; 034 035 /** スレッド名用のMDCキー */ 036 public static final String THREAD_NAME_KEY = "threadName"; 037 038 /** ステージ用のMDCキー */ 039 public static final String STAGE_KEY = "stage"; 040 041 private ExecutionContext(Builder builder) { 042 this.executionId = builder.executionId; 043 this.startTime = builder.startTime; 044 this.commandSequence = new AtomicInteger(0); 045 this.globalContext = Collections.unmodifiableMap(new HashMap<>(builder.globalContext)); 046 this.userContext = new HashMap<>(builder.userContext); 047 } 048 049 /** 050 * 新しい実行コンテキストを生成 051 * 052 * @return 新しいExecutionContextインスタンス 053 */ 054 public static ExecutionContext create() { 055 return new Builder().build(); 056 } 057 058 /** 059 * カスタム設定でExecutionContextを作成するためのビルダーを取得 060 * 061 * @return Builderインスタンス 062 */ 063 public static Builder builder() { 064 return new Builder(); 065 } 066 067 /** 068 * 実行ID(一意識別子)を取得 069 * 070 * @return 実行ID 071 */ 072 public String getExecutionId() { 073 return executionId; 074 } 075 076 /** 077 * 実行開始時刻を取得 078 * 079 * @return 開始時刻 080 */ 081 public Instant getStartTime() { 082 return startTime; 083 } 084 085 /** 086 * 次のコマンドシーケンス番号を取得(自動インクリメント) 087 * 088 * @return シーケンス番号 089 */ 090 public int getNextCommandSequence() { 091 return commandSequence.incrementAndGet(); 092 } 093 094 /** 095 * 現在のコマンドシーケンス番号を取得 096 * 097 * @return 現在のシーケンス番号 098 */ 099 public int getCurrentCommandSequence() { 100 return commandSequence.get(); 101 } 102 103 /** 104 * グローバルコンテキスト値を取得 105 * 106 * @param key キー 107 * @return 値(存在しない場合はnull) 108 */ 109 public String getGlobalContext(String key) { 110 return globalContext.get(key); 111 } 112 113 /** 114 * 全てのグローバルコンテキストを取得 115 * 116 * @return 読み取り専用のコンテキストマップ 117 */ 118 public Map<String, String> getAllGlobalContext() { 119 return globalContext; 120 } 121 122 /** 123 * ユーザーコンテキスト値を取得 124 * 125 * @param key キー 126 * @return 値(存在しない場合はnull) 127 */ 128 public String getUserContext(String key) { 129 return userContext.get(key); 130 } 131 132 /** 133 * ユーザーコンテキスト値を設定 134 * 135 * @param key キー 136 * @param value 値 137 */ 138 public void setUserContext(String key, String value) { 139 if (value == null) { 140 userContext.remove(key); 141 } else { 142 userContext.put(key, value); 143 } 144 } 145 146 /** 147 * 全てのユーザーコンテキストを取得 148 * 149 * @return ユーザーコンテキストマップ 150 */ 151 public Map<String, String> getAllUserContext() { 152 return new HashMap<>(userContext); 153 } 154 155 /** 156 * 現在のコンテキストをMDCに設定 157 * 158 * <p>このメソッドを呼び出すことで、実行コンテキストの情報が 現在のスレッドのMDCに設定されます。 159 */ 160 public void applyToMDC() { 161 // 基本的なコンテキスト情報をMDCに設定 162 MDC.put(EXECUTION_ID_KEY, executionId); 163 MDC.put(START_TIME_KEY, startTime.toString()); 164 MDC.put(COMMAND_SEQUENCE_KEY, String.valueOf(getCurrentCommandSequence())); 165 MDC.put(THREAD_NAME_KEY, Thread.currentThread().getName()); 166 167 // グローバルコンテキストをMDCに設定 168 globalContext.forEach(MDC::put); 169 170 // ユーザーコンテキストをMDCに設定 171 userContext.forEach(MDC::put); 172 } 173 174 /** 175 * 特定のステージでのMDC設定 176 * 177 * @param stageName ステージ名 178 */ 179 public void applyToMDCWithStage(String stageName) { 180 applyToMDC(); 181 MDC.put(STAGE_KEY, stageName); 182 } 183 184 /** 185 * コンテキストのコピーを作成 ユーザーコンテキストは複製されますが、グローバルコンテキストは共有されます。 コピー時はコマンドシーケンスは0にリセットされます。 186 * 187 * @return コンテキストのコピー 188 */ 189 public ExecutionContext copy() { 190 return new Builder() 191 .executionId(this.executionId) 192 .startTime(this.startTime) 193 .globalContext(this.globalContext) 194 .userContext(this.userContext) 195 .build(); 196 // コマンドシーケンスは新しいコピーとして0から開始 197 } 198 199 /** ExecutionContext作成用のBuilderクラス */ 200 public static class Builder { 201 private String executionId = generateExecutionId(); 202 private Instant startTime = Instant.now(); 203 private Map<String, String> globalContext = new HashMap<>(); 204 private Map<String, String> userContext = new HashMap<>(); 205 206 /** Creates a new builder. */ 207 public Builder() {} 208 209 /** 210 * 実行IDを設定 211 * 212 * @param executionId 実行ID 213 * @return Builderインスタンス 214 */ 215 public Builder executionId(String executionId) { 216 this.executionId = Objects.requireNonNull(executionId, "executionId cannot be null"); 217 return this; 218 } 219 220 /** 221 * 開始時刻を設定 222 * 223 * @param startTime 開始時刻 224 * @return Builderインスタンス 225 */ 226 public Builder startTime(Instant startTime) { 227 this.startTime = Objects.requireNonNull(startTime, "startTime cannot be null"); 228 return this; 229 } 230 231 /** 232 * グローバルコンテキスト値を設定 233 * 234 * @param key キー 235 * @param value 値 236 * @return Builderインスタンス 237 */ 238 public Builder globalContext(String key, String value) { 239 Objects.requireNonNull(key, "key cannot be null"); 240 if (value == null) { 241 this.globalContext.remove(key); 242 } else { 243 this.globalContext.put(key, value); 244 } 245 return this; 246 } 247 248 /** 249 * グローバルコンテキストを一括設定 250 * 251 * @param context コンテキストマップ 252 * @return Builderインスタンス 253 */ 254 public Builder globalContext(Map<String, String> context) { 255 if (context != null) { 256 this.globalContext.putAll(context); 257 } 258 return this; 259 } 260 261 /** 262 * ユーザーコンテキスト値を設定 263 * 264 * @param key キー 265 * @param value 値 266 * @return Builderインスタンス 267 */ 268 public Builder userContext(String key, String value) { 269 Objects.requireNonNull(key, "key cannot be null"); 270 if (value == null) { 271 this.userContext.remove(key); 272 } else { 273 this.userContext.put(key, value); 274 } 275 return this; 276 } 277 278 /** 279 * ユーザーコンテキストを一括設定 280 * 281 * @param context コンテキストマップ 282 * @return Builderインスタンス 283 */ 284 public Builder userContext(Map<String, String> context) { 285 if (context != null) { 286 this.userContext.putAll(context); 287 } 288 return this; 289 } 290 291 /** 292 * ExecutionContextインスタンスを生成 293 * 294 * @return ExecutionContextインスタンス 295 */ 296 public ExecutionContext build() { 297 return new ExecutionContext(this); 298 } 299 300 private static String generateExecutionId() { 301 return "EXEC-" 302 + UUID.randomUUID().toString().substring(0, 8) 303 + "-" 304 + System.currentTimeMillis(); 305 } 306 } 307 308 @Override 309 public String toString() { 310 return String.format( 311 "ExecutionContext{executionId='%s', startTime=%s, commandSequence=%d}", 312 executionId, startTime, getCurrentCommandSequence()); 313 } 314 315 @Override 316 public boolean equals(Object obj) { 317 if (this == obj) return true; 318 if (obj == null || getClass() != obj.getClass()) return false; 319 ExecutionContext that = (ExecutionContext) obj; 320 return Objects.equals(executionId, that.executionId); 321 } 322 323 @Override 324 public int hashCode() { 325 return Objects.hash(executionId); 326 } 327}