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}