001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.cli.processor;
007
008import org.apache.logging.log4j.LogBuilder;
009import org.apache.logging.log4j.LogManager;
010import org.apache.logging.log4j.Logger;
011
012import edu.umd.cs.findbugs.annotations.NonNull;
013import edu.umd.cs.findbugs.annotations.Nullable;
014import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
015
016/**
017 * Records information about the exit status of a CLI command.
018 * <p>
019 * This abstract class provides base functionality for handling CLI command exit
020 * statuses, including error logging and throwable management. Implementing
021 * classes must provide the {@link #getMessage()} implementation to define the
022 * status message content.
023 */
024public abstract class AbstractExitStatus implements ExitStatus {
025  private static final Logger LOGGER = LogManager.getLogger(AbstractExitStatus.class);
026
027  @NonNull
028  private final ExitCode exitCode;
029
030  private Throwable throwable;
031
032  /**
033   * Construct a new exit status based on the provided {@code exitCode}.
034   *
035   * @param exitCode
036   *          the exit code
037   */
038  public AbstractExitStatus(@NonNull ExitCode exitCode) {
039    this.exitCode = exitCode;
040  }
041
042  @Override
043  public ExitCode getExitCode() {
044    return exitCode;
045  }
046
047  /**
048   * Get the associated throwable.
049   *
050   * @return the throwable or {@code null}
051   */
052  @Override
053  public Throwable getThrowable() {
054    return throwable;
055  }
056
057  @Override
058  @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "intended as a exposed property")
059  public ExitStatus withThrowable(@NonNull Throwable throwable) {
060    this.throwable = throwable;
061    return this;
062  }
063
064  /**
065   * Determines the appropriate LogBuilder based on the exit code status. For
066   * non-positive exit codes (success/info), returns an INFO level builder. For
067   * positive exit codes (errors), returns an ERROR level builder.
068   *
069   * @return the appropriate LogBuilder based on exit status, or {@code null} if
070   *         logging is disabled at the determined level
071   */
072  @Nullable
073  private LogBuilder getLogBuilder() {
074    LogBuilder logBuilder = null;
075    if (getExitCode().getStatusCode() <= 0) {
076      if (LOGGER.isInfoEnabled()) {
077        logBuilder = LOGGER.atInfo();
078      }
079    } else if (LOGGER.isErrorEnabled()) {
080      logBuilder = LOGGER.atError();
081    }
082    return logBuilder;
083  }
084
085  /**
086   * Generates and logs a message based on the current exit status. The message is
087   * logged at either INFO level (for success/info status) or ERROR level (for
088   * error status).
089   *
090   * @param showStackTrace
091   *          if {@code true} and a throwable is present, includes the stack trace
092   *          in the log
093   */
094  @Override
095  public void generateMessage(boolean showStackTrace) {
096    LogBuilder logBuilder = getLogBuilder();
097    if (logBuilder == null) {
098      return;
099    }
100
101    boolean useStackTrace = showStackTrace && throwable != null;
102    if (useStackTrace) {
103      logBuilder.withThrowable(throwable);
104    }
105
106    String message = getMessage();
107    if (throwable != null && message == null) {
108      message = throwable.getLocalizedMessage();
109    }
110
111    if (message != null && !message.isEmpty()) {
112      logBuilder.log(message);
113    } else if (useStackTrace) {
114      // log the throwable
115      logBuilder.log();
116    } // else avoid an empty log line
117  }
118
119}