001package com.identityworksllc.iiq.common.vo;
002
003import com.fasterxml.jackson.annotation.*;
004import com.identityworksllc.iiq.common.logging.SLogger;
005import sailpoint.object.Configuration;
006import sailpoint.tools.GeneralException;
007import sailpoint.tools.Util;
008
009import java.util.ArrayList;
010import java.util.List;
011import java.util.Map;
012import java.util.function.Supplier;
013
014/**
015 * Represents a value along with a list of stamped log messages, supporting various log levels.
016 *
017 * This class is designed to encapsulate a result value of type {@code T} and a list of {@link StampedMessage}
018 * objects that record messages at different log levels (INFO, DEBUG, ERROR, TRACE, WARN). It provides methods
019 * to add messages at each log level, check if a log level is enabled, and retrieve the value or messages.
020 *
021 * The log level can be set to control which messages are recorded. Messages are only added if the current
022 * log level is at least as high as the level of the message being added.
023 *
024 * @param <T> The type of the result value
025 */
026@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
027@JsonInclude(JsonInclude.Include.NON_EMPTY)
028public class ResultValue<T> implements Supplier<T> {
029    /**
030     * The current log level for this result. Controls which messages are recorded.
031     * This field is transient and ignored by Jackson serialization.
032     */
033    @JsonIgnore
034    private transient LogLevel logLevel;
035    /**
036     * The list of stamped messages associated with this result.
037     */
038    private final List<StampedMessage> messages;
039    /**
040     * The result value.
041     */
042    private T value;
043    /**
044     * Flag indicating whether the value has been set. The value can be set
045     * to null, so this flag is used to track if it was explicitly set.
046     */
047    private boolean valueSet;
048
049    /**
050     * Default constructor. Initializes with an empty message list and INFO log level.
051     */
052    public ResultValue() {
053        this.messages = new ArrayList<>();
054        this.logLevel = LogLevel.INFO;
055    }
056
057    /**
058     * Jackson constructor for deserialization.
059     *
060     * @param value    The result value
061     * @param messages The list of stamped messages
062     * @param valueSet Flag indicating if the value has been set
063     */
064    @JsonCreator
065    public ResultValue(@JsonProperty("value") T value, @JsonProperty("messages") List<StampedMessage> messages, @JsonProperty("valueSet") boolean valueSet) {
066        this.value = value;
067        this.messages = messages != null ? new ArrayList<>(messages) : new ArrayList<>();
068        this.logLevel = LogLevel.INFO;
069        this.valueSet = valueSet;
070    }
071
072    /**
073     * Adds a DEBUG level message using a supplier, if DEBUG is enabled.
074     *
075     * @param value The message supplier
076     */
077    public void debug(Supplier<String> value) {
078        if (logLevel.atLeast(LogLevel.DEBUG)) {
079            messages.add(new StampedMessage(LogLevel.DEBUG, value.get()));
080        }
081    }
082
083    /**
084     * Adds a DEBUG level message with formatting, if DEBUG is enabled.
085     *
086     * @param message The message format string
087     * @param args    The arguments for formatting
088     */
089    public void debug(String message, Object... args) {
090        debug(() -> {
091            Object[] formattedArgs = SLogger.format(args);
092            return String.format(message, formattedArgs);
093        });
094    }
095
096    /**
097     * Adds an ERROR level message using a supplier, if ERROR is enabled.
098     *
099     * @param value The message supplier
100     */
101    public void error(Supplier<String> value) {
102        if (logLevel.atLeast(LogLevel.ERROR)) {
103            messages.add(new StampedMessage(LogLevel.ERROR, value.get()));
104        }
105    }
106
107    /**
108     * Adds an ERROR level message with formatting, if ERROR is enabled.
109     *
110     * @param message The message format string
111     * @param args    The arguments for formatting
112     */
113    public void error(String message, Object... args) {
114        error(() -> {
115            Object[] formattedArgs = SLogger.format(args);
116            return String.format(message, formattedArgs);
117        });
118    }
119
120    /**
121     * Returns the result value.
122     * @return The value
123     */
124    public T get() {
125        return getValue();
126    }
127
128    /**
129     * Returns the list of stamped messages.
130     *
131     * @return The list of messages
132     */
133    public List<StampedMessage> getMessages() {
134        return messages;
135    }
136
137    /**
138     * Returns the result value.
139     *
140     * @return The value
141     */
142    public T getValue() {
143        return value;
144    }
145
146    /**
147     * Returns true if there are any messages.
148     *
149     * @return True if messages exist, false otherwise
150     */
151    public boolean hasMessages() {
152        return !messages.isEmpty();
153    }
154
155    /**
156     * Adds an INFO level message using a supplier, if INFO is enabled.
157     *
158     * @param value The message supplier
159     */
160    public void info(Supplier<String> value) {
161        if (logLevel.atLeast(LogLevel.INFO)) {
162            messages.add(new StampedMessage(LogLevel.INFO, value.get()));
163        }
164    }
165
166    /**
167     * Adds an INFO level message with formatting, if INFO is enabled.
168     *
169     * @param message The message format string
170     * @param args    The arguments for formatting
171     */
172    public void info(String message, Object... args) {
173        info(() -> {
174            Object[] formattedArgs = SLogger.format(args);
175            return String.format(message, formattedArgs);
176        });
177    }
178
179    /**
180     * Returns true if DEBUG level messages are enabled.
181     *
182     * @return True if DEBUG is enabled
183     */
184    public boolean isDebugEnabled() {
185        return logLevel.atLeast(LogLevel.DEBUG);
186    }
187
188    /**
189     * Returns true if ERROR level messages are enabled.
190     *
191     * @return True if ERROR is enabled
192     */
193    public boolean isErrorEnabled() {
194        return logLevel.atLeast(LogLevel.ERROR);
195    }
196
197    /**
198     * Returns true if INFO level messages are enabled.
199     *
200     * @return True if INFO is enabled
201     */
202    public boolean isInfoEnabled() {
203        return logLevel.atLeast(LogLevel.INFO);
204    }
205
206    /**
207     * Returns true if TRACE level messages are enabled.
208     *
209     * @return True if TRACE is enabled
210     */
211    public boolean isTraceEnabled() {
212        return logLevel.atLeast(LogLevel.TRACE);
213    }
214
215    /**
216     * Returns true if the result value has been set. This allows us to distinguish
217     * between a value that is unset and a value that is explicitly set to null.
218     *
219     * @return True if the value is set, false otherwise
220     */
221    public boolean isValueSet() {
222        return valueSet;
223    }
224
225    /**
226     * Returns true if WARN level messages are enabled.
227     *
228     * @return True if WARN is enabled
229     */
230    public boolean isWarnEnabled() {
231        return logLevel.atLeast(LogLevel.WARN);
232    }
233
234    /**
235     * Returns the result value, or the provided alternative if the value is not set.
236     * @param other The alternative value to return if the result value is not set
237     * @return The result value or the alternative
238     */
239    public T orElse(T other) {
240        return valueSet ? value : other;
241    }
242
243    /**
244     * Returns the result value, or throws the provided exception if the value is not set.
245     * @param e The exception to throw if the result value is not set
246     * @return The result value
247     * @throws GeneralException if the result value is not set
248     */
249    public T orElseThrow(GeneralException e) throws GeneralException {
250        if (valueSet) {
251            return value;
252        } else {
253            throw e;
254        }
255    }
256
257    /**
258     * Sets the log level for this result.
259     *
260     * @param logLevel The log level to set
261     */
262    public final void setLogLevel(LogLevel logLevel) {
263        this.logLevel = logLevel;
264    }
265
266    /**
267     * Sets the log level based on a configuration key.
268     * @param logLevelConfig The configuration key for the log level
269     */
270    public final void setLogLevel(String logLevelConfig) {
271        Configuration systemConfig = Configuration.getSystemConfig();
272        Map<String, Object> logLevels = Util.otom(systemConfig.get("IIQCommon.ResultValue.LogLevels"));
273
274        if (logLevels != null && logLevels.containsKey(logLevelConfig)) {
275            String levelStr = (String) logLevels.get(logLevelConfig);
276            this.logLevel = LogLevel.valueOf(levelStr.toUpperCase());
277        } else {
278            this.logLevel = LogLevel.INFO;
279        }
280    }
281
282    /**
283     * Sets the result value.
284     *
285     * @param value The value to set
286     */
287    public final void setValue(T value) {
288        this.value = value;
289        this.valueSet = true;
290    }
291
292    /**
293     * Adds a TRACE level message using a supplier, if TRACE is enabled.
294     *
295     * @param value The message supplier
296     */
297    public void trace(Supplier<String> value) {
298        if (logLevel.atLeast(LogLevel.TRACE)) {
299            messages.add(new StampedMessage(LogLevel.TRACE, value.get()));
300        }
301    }
302
303    /**
304     * Adds a TRACE level message with formatting, if TRACE is enabled.
305     *
306     * @param message The message format string
307     * @param args    The arguments for formatting
308     */
309    public void trace(String message, Object... args) {
310        trace(() -> {
311            Object[] formattedArgs = SLogger.format(args);
312            return String.format(message, formattedArgs);
313        });
314    }
315
316    /**
317     * Adds a WARN level message using a supplier, if WARN is enabled.
318     *
319     * @param value The message supplier
320     */
321    public void warn(Supplier<String> value) {
322        if (logLevel.atLeast(LogLevel.WARN)) {
323            messages.add(new StampedMessage(LogLevel.WARN, value.get()));
324        }
325    }
326
327    /**
328     * Adds a WARN level message with formatting, if WARN is enabled.
329     *
330     * @param message The message format string
331     * @param args    The arguments for formatting
332     */
333    public void warn(String message, Object... args) {
334        warn(() -> {
335            Object[] formattedArgs = SLogger.format(args);
336            return String.format(message, formattedArgs);
337        });
338    }
339}