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}