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     * Get the associated message.
66     *
67     * @return the message or {@code null}
68     */
69    @Nullable
70    protected abstract String getMessage();
71  
72    /**
73     * Determines the appropriate LogBuilder based on the exit code status. For
74     * non-positive exit codes (success/info), returns an INFO level builder. For
75     * positive exit codes (errors), returns an ERROR level builder.
76     *
77     * @return the appropriate LogBuilder based on exit status, or {@code null} if
78     *         logging is disabled at the determined level
79     */
80    @Nullable
81    private LogBuilder getLogBuilder() {
82      LogBuilder logBuilder = null;
83      if (getExitCode().getStatusCode() <= 0) {
84        if (LOGGER.isInfoEnabled()) {
85          logBuilder = LOGGER.atInfo();
86        }
87      } else if (LOGGER.isErrorEnabled()) {
88        logBuilder = LOGGER.atError();
89      }
90      return logBuilder;
91    }
92  
93    /**
94     * Generates and logs a message based on the current exit status. The message is
95     * logged at either INFO level (for success/info status) or ERROR level (for
96     * error status).
97     *
98     * @param showStackTrace
99     *          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 }