1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.cli.processor;
7   
8   import org.apache.logging.log4j.LogBuilder;
9   import org.apache.logging.log4j.LogManager;
10  import org.apache.logging.log4j.Logger;
11  
12  import edu.umd.cs.findbugs.annotations.NonNull;
13  import edu.umd.cs.findbugs.annotations.Nullable;
14  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
15  
16  /**
17   * Records information about the exit status of a CLI command.
18   * <p>
19   * This abstract class provides base functionality for handling CLI command exit
20   * statuses, including error logging and throwable management. Implementing
21   * classes must provide the {@link #getMessage()} implementation to define the
22   * status message content.
23   */
24  public abstract class AbstractExitStatus implements ExitStatus {
25    private static final Logger LOGGER = LogManager.getLogger(AbstractExitStatus.class);
26  
27    @NonNull
28    private final ExitCode exitCode;
29  
30    private Throwable throwable;
31  
32    /**
33     * Construct a new exit status based on the provided {@code exitCode}.
34     *
35     * @param exitCode
36     *          the exit code
37     */
38    public AbstractExitStatus(@NonNull ExitCode exitCode) {
39      this.exitCode = exitCode;
40    }
41  
42    @Override
43    public ExitCode getExitCode() {
44      return exitCode;
45    }
46  
47    /**
48     * Get the associated throwable.
49     *
50     * @return the throwable or {@code null}
51     */
52    @Override
53    public Throwable getThrowable() {
54      return throwable;
55    }
56  
57    @Override
58    @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "intended as a exposed property")
59    public ExitStatus withThrowable(@NonNull Throwable throwable) {
60      this.throwable = throwable;
61      return this;
62    }
63  
64    /**
65     * Determines the appropriate LogBuilder based on the exit code status. For
66     * non-positive exit codes (success/info), returns an INFO level builder. For
67     * positive exit codes (errors), returns an ERROR level builder.
68     *
69     * @return the appropriate LogBuilder based on exit status, or {@code null} if
70     *         logging is disabled at the determined level
71     */
72    @Nullable
73    private LogBuilder getLogBuilder() {
74      LogBuilder logBuilder = null;
75      if (getExitCode().getStatusCode() <= 0) {
76        if (LOGGER.isInfoEnabled()) {
77          logBuilder = LOGGER.atInfo();
78        }
79      } else if (LOGGER.isErrorEnabled()) {
80        logBuilder = LOGGER.atError();
81      }
82      return logBuilder;
83    }
84  
85    /**
86     * Generates and logs a message based on the current exit status. The message is
87     * logged at either INFO level (for success/info status) or ERROR level (for
88     * error status).
89     *
90     * @param showStackTrace
91     *          if {@code true} and a throwable is present, includes the stack trace
92     *          in the log
93     */
94    @Override
95    public void generateMessage(boolean showStackTrace) {
96      LogBuilder logBuilder = getLogBuilder();
97      if (logBuilder == null) {
98        return;
99      }
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 }