001package com.streamconverter.command; 002 003import com.streamconverter.context.ExecutionContext; 004import java.io.IOException; 005import java.io.InputStream; 006import java.io.OutputStream; 007import java.util.Objects; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010import org.slf4j.MDC; 011 012/** 013 * 既存のIStreamCommandをコンテキスト対応にするデコレータ 014 * 015 * <p>このデコレータは既存のコマンドを変更することなく、 ExecutionContextの適用とMDCの設定を自動化します。 016 */ 017public class ContextPropagatingDecorator implements IStreamCommand { 018 019 private static final Logger logger = LoggerFactory.getLogger(ContextPropagatingDecorator.class); 020 021 private final IStreamCommand wrappedCommand; 022 private final String commandName; 023 024 /** 025 * デコレータを作成 026 * 027 * @param command ラップするコマンド 028 */ 029 public ContextPropagatingDecorator(IStreamCommand command) { 030 this.wrappedCommand = Objects.requireNonNull(command, "command cannot be null"); 031 this.commandName = command.getClass().getSimpleName(); 032 } 033 034 /** 035 * デコレータを作成(カスタムコマンド名付き) 036 * 037 * @param command ラップするコマンド 038 * @param commandName ログ用のコマンド名 039 */ 040 public ContextPropagatingDecorator(IStreamCommand command, String commandName) { 041 this.wrappedCommand = Objects.requireNonNull(command, "command cannot be null"); 042 this.commandName = Objects.requireNonNull(commandName, "commandName cannot be null"); 043 } 044 045 @Override 046 public void execute(InputStream inputStream, OutputStream outputStream) throws IOException { 047 // Default implementation creates a new ExecutionContext 048 ExecutionContext context = ExecutionContext.create(); 049 execute(inputStream, outputStream, context); 050 } 051 052 @Override 053 public void execute(InputStream inputStream, OutputStream outputStream, ExecutionContext context) 054 throws IOException { 055 056 // 実行前の準備 057 int sequence = context.getNextCommandSequence(); 058 String stageName = commandName + "-" + sequence; 059 060 // 既存のMDCコンテキストを保存 061 String previousExecutionId = MDC.get(ExecutionContext.EXECUTION_ID_KEY); 062 String previousStage = MDC.get(ExecutionContext.STAGE_KEY); 063 064 try { 065 // ExecutionContextをMDCに適用 066 context.applyToMDCWithStage(stageName); 067 068 // コンテキスト情報をユーザーコンテキストに保存(必要に応じて) 069 context.setUserContext("currentCommand", commandName); 070 context.setUserContext("currentSequence", String.valueOf(sequence)); 071 072 logger.info("Starting command execution: {} (sequence: {})", commandName, sequence); 073 074 // ラップしたコマンドの実行 075 // 統合されたIStreamCommandインターフェースを使用 076 wrappedCommand.execute(inputStream, outputStream, context); 077 078 logger.info("Completed command execution: {} (sequence: {})", commandName, sequence); 079 080 } catch (IOException e) { 081 logger.error( 082 "Command execution failed: {} (sequence: {}) - {}", 083 commandName, 084 sequence, 085 e.getMessage(), 086 e); 087 throw e; 088 } catch (Exception e) { 089 logger.error( 090 "Unexpected error in command execution: {} (sequence: {}) - {}", 091 commandName, 092 sequence, 093 e.getMessage(), 094 e); 095 throw new IOException("Command execution failed: " + commandName, e); 096 } finally { 097 // MDCの復元(必要に応じて) 098 restoreMDCContext(previousExecutionId, previousStage); 099 100 // ユーザーコンテキストのクリーンアップ 101 context.setUserContext("currentCommand", null); 102 context.setUserContext("currentSequence", null); 103 } 104 } 105 106 /** MDCコンテキストを復元 */ 107 private void restoreMDCContext(String previousExecutionId, String previousStage) { 108 if (previousExecutionId != null) { 109 MDC.put(ExecutionContext.EXECUTION_ID_KEY, previousExecutionId); 110 } else { 111 MDC.remove(ExecutionContext.EXECUTION_ID_KEY); 112 } 113 114 if (previousStage != null) { 115 MDC.put(ExecutionContext.STAGE_KEY, previousStage); 116 } else { 117 MDC.remove(ExecutionContext.STAGE_KEY); 118 } 119 } 120 121 /** 122 * ラップされたコマンドを取得 123 * 124 * @return ラップされたコマンド 125 */ 126 public IStreamCommand getWrappedCommand() { 127 return wrappedCommand; 128 } 129 130 /** 131 * コマンド名を取得 132 * 133 * @return コマンド名 134 */ 135 public String getCommandName() { 136 return commandName; 137 } 138 139 @Override 140 public String toString() { 141 return String.format( 142 "ContextPropagatingDecorator{commandName='%s', wrappedCommand=%s}", 143 commandName, wrappedCommand.getClass().getSimpleName()); 144 } 145}