001package com.streamconverter.command; 002 003import com.streamconverter.context.ExecutionContextHolder; 004import com.streamconverter.util.MeasuredInputStream; 005import com.streamconverter.util.MeasuredOutputStream; 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.OutputStream; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Abstract class for stream commands. 014 * 015 * <p>This class provides a template for executing commands on input streams and writing the results 016 * to output streams. 017 */ 018public abstract class AbstractStreamCommand implements IStreamCommand { 019 /** Logger instance for this command. Uses the actual subclass name for better traceability. */ 020 protected final Logger log; 021 022 /** 023 * Default constructor. 024 * 025 * <p>Initializes the command with default settings and creates a logger using the actual command 026 * class name. 027 */ 028 public AbstractStreamCommand() { 029 super(); 030 this.log = LoggerFactory.getLogger(getClass()); 031 } 032 033 /** 034 * Executes the command with ExecutionContext support. 035 * 036 * <p>This override ensures that MDC is synchronized with the latest shared context values before 037 * logging, enabling cross-thread MDC propagation in parallel execution environments. 038 * 039 * @param inputStream The input stream to read data from. 040 * @param outputStream The output stream to write data to. 041 * @param context The execution context for traceability and logging enhancement. 042 * @throws IOException If an I/O error occurs during the execution of the command. 043 */ 044 @Override 045 public final void execute( 046 InputStream inputStream, 047 OutputStream outputStream, 048 com.streamconverter.context.ExecutionContext context) 049 throws IOException { 050 // Store context in ThreadLocal for TurboFilter to access on every log output 051 try { 052 ExecutionContextHolder.set(context); 053 // Delegate to the basic execute method 054 execute(inputStream, outputStream); 055 } finally { 056 // Clean up ThreadLocal to prevent memory leaks 057 ExecutionContextHolder.clear(); 058 } 059 } 060 061 /** 062 * Executes the command on the provided input stream and writes the result to the output stream. 063 * 064 * <p>This method automatically logs execution details including: - Command name and execution 065 * time - Input/output data sizes - Exception details if execution fails - Performance metrics 066 * 067 * @param inputStream The input stream to read data from. 068 * @param outputStream The output stream to write data to. 069 * @throws IOException If an I/O error occurs during the execution of the command. 070 */ 071 @Override 072 public final void execute(InputStream inputStream, OutputStream outputStream) throws IOException { 073 String commandName = this.getClass().getSimpleName(); 074 long startTime = System.currentTimeMillis(); 075 long startMemory = getUsedMemory(); 076 077 // 実行開始ログ 078 if (log.isInfoEnabled()) { 079 log.info("Starting command execution: {}", commandName); 080 } 081 if (log.isDebugEnabled()) { 082 log.debug("Command details: {}", getCommandDetails()); 083 } 084 085 // データサイズ測定用のストリームでラップ(try-with-resourcesでリソース管理) 086 long inputBytes = 0; 087 long outputBytes = 0; 088 try (MeasuredInputStream measuredInput = new MeasuredInputStream(inputStream); 089 MeasuredOutputStream measuredOutput = new MeasuredOutputStream(outputStream)) { 090 091 // 実際の処理実行 092 executeInternal(measuredInput, measuredOutput); 093 094 // データサイズ測定値を取得 095 inputBytes = measuredInput.getBytesRead(); 096 outputBytes = measuredOutput.getBytesWritten(); 097 098 // 成功時のログ出力 099 long duration = System.currentTimeMillis() - startTime; 100 long memoryUsed = getUsedMemory() - startMemory; 101 102 if (log.isInfoEnabled()) { 103 log.info( 104 "Command execution completed: {} ({}ms, input: {}bytes, output: {}bytes, memory: {}MB)", 105 commandName, 106 duration, 107 inputBytes, 108 outputBytes, 109 memoryUsed / 1024 / 1024); 110 } 111 112 // パフォーマンス警告 113 if (duration > 5000) { // 5秒以上 114 log.warn("Command {} took longer than expected: {}ms", commandName, duration); 115 } 116 117 } catch (Exception e) { 118 // 例外発生時のログ出力 119 long duration = System.currentTimeMillis() - startTime; 120 long memoryUsed = getUsedMemory() - startMemory; 121 122 if (log.isErrorEnabled()) { 123 log.error( 124 "Command execution failed: {} ({}ms, input: {}bytes, output: {}bytes, memory: {}MB) - {}", 125 commandName, 126 duration, 127 inputBytes, 128 outputBytes, 129 memoryUsed / 1024 / 1024, 130 e.getMessage(), 131 e); 132 } 133 134 // NullPointerExceptionをIOExceptionでラップ 135 if (e instanceof NullPointerException) { 136 throw new IOException("Invalid null stream parameter", e); 137 } 138 139 throw e; 140 } 141 } 142 143 /** 144 * Abstract method to be implemented by subclasses for executing the command. 145 * 146 * @param inputStream The input stream to read data from. 147 * @param outputStream The output stream to write data to. 148 * @throws IOException If an I/O error occurs during the execution of the command. 149 */ 150 protected abstract void executeInternal(InputStream inputStream, OutputStream outputStream) 151 throws IOException; 152 153 /** 154 * コマンドの詳細情報を取得します。 155 * 156 * <p>サブクラスでオーバーライドして、コマンド固有の設定や状態を返すことができます。 この情報はデバッグログに出力され、問題の診断に役立ちます。 157 * 158 * @return コマンドの詳細情報 159 */ 160 protected String getCommandDetails() { 161 return this.getClass().getSimpleName() + " (default implementation)"; 162 } 163 164 /** 165 * 現在の使用メモリ量を取得します。 166 * 167 * @return 使用メモリ量(バイト) 168 */ 169 private long getUsedMemory() { 170 Runtime runtime = Runtime.getRuntime(); 171 return runtime.totalMemory() - runtime.freeMemory(); 172 } 173}