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   * Get the associated message.
066   *
067   * @return the message or {@code null}
068   */
069  @Nullable
070  protected abstract String getMessage();
071
072  /**
073   * Determines the appropriate LogBuilder based on the exit code status. For
074   * non-positive exit codes (success/info), returns an INFO level builder. For
075   * positive exit codes (errors), returns an ERROR level builder.
076   *
077   * @return the appropriate LogBuilder based on exit status, or {@code null} if
078   *         logging is disabled at the determined level
079   */
080  @Nullable
081  private LogBuilder getLogBuilder() {
082    LogBuilder logBuilder = null;
083    if (getExitCode().getStatusCode() <= 0) {
084      if (LOGGER.isInfoEnabled()) {
085        logBuilder = LOGGER.atInfo();
086      }
087    } else if (LOGGER.isErrorEnabled()) {
088      logBuilder = LOGGER.atError();
089    }
090    return logBuilder;
091  }
092
093  /**
094   * Generates and logs a message based on the current exit status. The message is
095   * logged at either INFO level (for success/info status) or ERROR level (for
096   * error status).
097   *
098   * @param showStackTrace
099   *          if {@code true} and a throwable is present, includes the stack trace
100   *          in the log
101   */
102  @Override
103  public void generateMessage(boolean showStackTrace) {
104    LogBuilder logBuilder = getLogBuilder();
105    if (logBuilder == null) {
106      return;
107    }
108
109    boolean useStackTrace = showStackTrace && throwable != null;
110    if (useStackTrace) {
111      logBuilder.withThrowable(throwable);
112    }
113
114    String message = getMessage();
115    if (throwable != null && message == null) {
116      message = throwable.getLocalizedMessage();
117    }
118
119    if (message != null && !message.isEmpty()) {
120      logBuilder.log(message);
121    } else if (useStackTrace) {
122      // log the throwable
123      logBuilder.log();
124    } // else avoid an empty log line
125  }
126
127}