001package com.streamconverter.command;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.io.OutputStream;
006import java.util.Objects;
007import org.apache.commons.io.input.TeeInputStream;
008
009/**
010 * Abstract class for commands that consume an input stream and produce an output stream.
011 *
012 * <p>This class provides a template method pattern for executing the command, ensuring that the
013 * input and output streams are not null. It also provides a method to consume the input stream,
014 * which must be implemented by subclasses.
015 */
016public abstract class ConsumerCommand extends AbstractStreamCommand {
017
018  /**
019   * Default constructor.
020   *
021   * <p>Initializes the command with default settings.
022   */
023  public ConsumerCommand() {
024    super();
025  }
026
027  /**
028   * Executes the command on the provided input stream and writes the result to the output stream.
029   *
030   * <p><strong>Note on data integrity:</strong> This method uses {@link TeeInputStream} to copy
031   * input bytes to {@code outputStream} as they are read by {@link #consume(InputStream)}. If
032   * {@code consume()} throws an exception, partial data may already have been written to {@code
033   * outputStream}. To prevent this, place a {@link
034   * com.streamconverter.command.impl.FileBufferCommand} immediately before this command in the
035   * pipeline:
036   *
037   * <pre>{@code
038   * StreamConverter.create(
039   *     new MyValidateCommand(),
040   *     FileBufferCommand.create(),
041   *     new MyConsumerCommand()  // ConsumerCommand subclass
042   * );
043   * }</pre>
044   *
045   * @param inputStream The input stream to read data from.
046   * @param outputStream The output stream to write data to.
047   * @throws IOException If an I/O error occurs during the execution of the command.
048   */
049  @Override
050  public void execute(InputStream inputStream, OutputStream outputStream) throws IOException {
051    Objects.requireNonNull(inputStream);
052    Objects.requireNonNull(outputStream);
053
054    try (InputStream teeInputStream = new TeeInputStream(inputStream, outputStream); ) {
055      this.consume(teeInputStream);
056    } catch (IOException e) {
057      throw new IOException("Error while consuming input stream", e);
058    }
059  }
060
061  /**
062   * Abstract method to be implemented by subclasses for consuming the input stream.
063   *
064   * @param inputStream The input stream to read data from.
065   * @throws IOException If an I/O error occurs during the execution of the command.
066   */
067  public abstract void consume(InputStream inputStream) throws IOException;
068}