OutputImpl.java

/*
 *
 * MIT License
 *
 * Copyright (c) [2016] [Saptarshi Debnath]
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.saptarshidebnath.lib.processrunner.output;

import com.saptarshidebnath.lib.processrunner.configuration.Configuration;
import com.saptarshidebnath.lib.processrunner.configuration.Configuration.ConfigBuilder;
import com.saptarshidebnath.lib.processrunner.constants.OutputSourceType;
import com.saptarshidebnath.lib.processrunner.constants.ProcessRunnerConstants;
import com.saptarshidebnath.lib.processrunner.exception.ProcessConfigurationException;
import com.saptarshidebnath.lib.processrunner.model.OutputRecord;
import com.saptarshidebnath.lib.processrunner.utilities.fileutils.GrepFile;
import com.saptarshidebnath.lib.processrunner.utilities.fileutils.LogWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default Implementation of {@link Output}.
 *
 * <p>If the masterlog file is configured, the class gives the ability to :-
 *
 * <ul>
 *   <li>Save the {@link OutputSourceType#SYSOUT} to a file.
 *   <li>Save the {@link OutputSourceType#SYSERR} to a file.
 *   <li>Save both {@link OutputSourceType#ALL} to file.
 *   <li>Search the master log file for a regular expression pattern.
 *   <li>get the return code and the actual log file if saved while executing the program.
 * </ul>
 */
class OutputImpl implements Output {
  private static Logger logger = LoggerFactory.getLogger(Output.class);
  private final Configuration configuration;
  private final int returnCode;

  /**
   * Accepts {@link Configuration} and return code to create a {@link Output} object.
   *
   * @param configuration a valid {@link Configuration} object.
   * @param returnCode a {@link Integer} value typically ranging from 0 - 255
   */
  OutputImpl(final Configuration configuration, final int returnCode) {
    this.configuration = configuration;
    this.returnCode = returnCode;
  }

  @Override
  public String toString() {
    return "OutputImpl{" + "configuration=" + configuration + ", returnCode=" + returnCode + '}';
  }

  /**
   * Prints the {@link OutputRecord#getOutputText()} of type {@link OutputSourceType#SYSOUT} to the
   * {@link File} supplied.
   *
   * @param sysOut A {@link File} object where the log is going to be written.
   * @return A {@link File} object where the log has been written. May return null if master log
   *     file was not configured. Please see {@link ConfigBuilder#setMasterLogFile(File, boolean)}
   *     and {@link ConfigBuilder#setMasterLogFile(File, boolean, Charset)} for more details on how
   *     to set Master log file.
   * @throws IOException In case of any IO Error while writing to disk.
   * @throws ProcessConfigurationException if master log is accessed without configuring the same.
   */
  @Override
  public File saveSysOut(final File sysOut) throws ProcessConfigurationException, IOException {
    File response;
    if (configuration.getMasterLogFile() == null) {
      throw new ProcessConfigurationException(
          ProcessRunnerConstants.STRING_CONSTANT_EXCEPTION_MASTER_LOG_FILE_NOT_CONFIGURED
              + configuration);
    }
    logger.trace("Saving sys out to {}", sysOut.getAbsolutePath());
    response = new LogWriter().writeLog(this.configuration, sysOut, OutputSourceType.SYSOUT);
    return response;
  }

  /**
   * Prints the {@link OutputRecord#getOutputText()} of type {@link OutputSourceType#SYSERR} to the
   * {@link File} supplied.
   *
   * @param sysError A {@link File} object where the log is going to be written.
   * @return A {@link File} object where the log has been written. May return null if master log
   *     file was not configured. Please see {@link ConfigBuilder#setMasterLogFile(File, boolean)}
   *     and {@link ConfigBuilder#setMasterLogFile(File, boolean, Charset)} for more details on how
   *     to set Master log file.
   * @throws IOException In case of any IO Error while writing to disk.
   * @throws ProcessConfigurationException if master log is accessed without configuring the same.
   */
  @Override
  public File saveSysError(final File sysError) throws ProcessConfigurationException, IOException {
    File response;
    if (configuration.getMasterLogFile() == null) {
      throw new ProcessConfigurationException(
          ProcessRunnerConstants.STRING_CONSTANT_EXCEPTION_MASTER_LOG_FILE_NOT_CONFIGURED
              + configuration);
    } else {
      logger.trace("Saving sys error to : {}", sysError.getAbsolutePath());
      response = new LogWriter().writeLog(this.configuration, sysError, OutputSourceType.SYSERR);
    }
    return response;
  }

  /**
   * Returns the master log file originally captured while executing the Process. Its an Json Array
   * of type {@link OutputRecord}.
   *
   * @return A {@link File} master pointing to the Master log file. May return null if master log
   *     file was not configured. Please see {@link ConfigBuilder#setMasterLogFile(File, boolean)}
   *     and {@link ConfigBuilder#setMasterLogFile(File, boolean, Charset)} for more details on how
   *     to set Master log file.
   * @throws ProcessConfigurationException if master log is accessed without configuring the same.
   */
  @Override
  public File getMasterLogAsJson() throws ProcessConfigurationException {
    if (this.configuration.getMasterLogFile() == null) {
      throw new ProcessConfigurationException(
          ProcessRunnerConstants.STRING_CONSTANT_EXCEPTION_MASTER_LOG_FILE_NOT_CONFIGURED
              + configuration);
    }
    return this.configuration.getMasterLogFile();
  }

  /**
   * Save the log of the process executed as a text file.
   *
   * @param log A {@link File} object where the log is going to be written.
   * @return A {@link File} the log not in json format. Please see {@link
   *     ConfigBuilder#setMasterLogFile(File, boolean)} and {@link
   *     ConfigBuilder#setMasterLogFile(File, boolean, Charset)} for more details on how to set
   *     Master log file.
   * @throws IOException when there are problems with IO
   * @throws ProcessConfigurationException if master log is accessed without configuring the same.
   */
  @Override
  public File saveLog(File log) throws IOException, ProcessConfigurationException {
    if (configuration.getMasterLogFile() == null) {
      throw new ProcessConfigurationException(
          ProcessRunnerConstants.STRING_CONSTANT_EXCEPTION_MASTER_LOG_FILE_NOT_CONFIGURED
              + configuration);
    }
    return new LogWriter().writeLog(this.configuration, log, OutputSourceType.ALL);
  }

  /**
   * Search the content of the {@link Configuration#getMasterLogFile()} for a particular regex. The
   * search is done line by line.
   *
   * @param regex a proper Regular Expression that need to be searched for.
   * @return a {@link Boolean#TRUE} or {@link Boolean#FALSE} depending upon if the search is
   *     positive or negative. Please see {@link ConfigBuilder#setMasterLogFile(File, boolean)} and
   *     {@link ConfigBuilder#setMasterLogFile(File, boolean, Charset)} for more details on how to
   *     set Master log file.
   * @throws IOException In case of any IO error.
   * @throws ProcessConfigurationException if master log is accessed without configuring the same.
   */
  @Override
  public boolean searchMasterLog(final String regex)
      throws IOException, ProcessConfigurationException {
    Boolean response;
    if (configuration.getMasterLogFile() == null) {
      String message =
          ProcessRunnerConstants.STRING_CONSTANT_EXCEPTION_MASTER_LOG_FILE_NOT_CONFIGURED
              + configuration;
      throw new ProcessConfigurationException(message);
    } else {
      response =
          this.searchFile(configuration.getMasterLogFile(), regex, this.configuration.getCharset());
    }
    return response;
  }

  /**
   * Searches the file for the regular expression and returns a {@link List} of type {@link String}
   * containing all the matching lines.
   *
   * @param regex accepts a {@link String} object to search for
   * @return a {@link List} of {@link OutputRecord} containing all the lines in the output that have
   *     matched the regex
   * @throws IOException if there are any error reading the master log file.
   * @throws ProcessConfigurationException if master log file not configured to be saved when the
   *     process is started and later user is trying to grep for non existent file.
   */
  @Override
  public List<OutputRecord> grepForRegex(String regex)
      throws IOException, ProcessConfigurationException {
    if (configuration.getMasterLogFile() == null) {
      String message =
          ProcessRunnerConstants.STRING_CONSTANT_EXCEPTION_MASTER_LOG_FILE_NOT_CONFIGURED
              + configuration;
      throw new ProcessConfigurationException(message);
    }
    return new GrepFile().grepFile(regex, configuration);
  }

  /**
   * Returns the process exit / return code.
   *
   * @return return the exit code as an integer value from 0 - 255
   */
  @Override
  public int getReturnCode() {
    return this.returnCode;
  }

  private boolean searchFile(File fileToRead, final String regex, Charset charset)
      throws IOException {
    logger.trace("Searching for regular expression : {}", regex);
    try (Stream<String> stream = Files.lines(Paths.get(fileToRead.getCanonicalPath()), charset)) {
      return stream
          .parallel()
          .map(currentLine -> ProcessRunnerConstants.GSON.fromJson(currentLine, OutputRecord.class))
          .anyMatch(outputRecord -> outputRecord.getOutputText().matches(regex));
    }
  }
}