001package com.identityworksllc.iiq.common.vo; 002 003import com.fasterxml.jackson.annotation.JsonAutoDetect; 004import com.fasterxml.jackson.annotation.JsonInclude; 005import com.google.common.base.Objects; 006import org.apache.commons.lang3.builder.ToStringBuilder; 007import sailpoint.tools.Message; 008 009import java.io.Serializable; 010import java.util.ArrayList; 011import java.util.List; 012import java.util.StringJoiner; 013 014/** 015 * A generic class to represent (and perhaps log) some operation outcome. 016 * It contains fields for generic start/stop tracking, as well as fields 017 * for a slew of output and logging indicators. 018 * 019 * This class is not intended to be used in any particular way. It is used 020 * for various purposes throughout Instrumental ID's codebase. 021 * 022 * This class implements {@link AutoCloseable} so that it can be used in the 023 * following sort of structure: 024 * 025 * ``` 026 * Outcome outcome; 027 * 028 * try(outcome = Outcome.start()) { 029 * // Do things, recording the outcome 030 * } 031 * 032 * // Your outcome will have a proper start/stop time for that block here 033 * ``` 034 */ 035@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 036@JsonInclude(JsonInclude.Include.NON_NULL) 037public class Outcome implements AutoCloseable, Serializable { 038 039 /** 040 * Create and start a new Outcome object. This is intended to be used 041 * in conjunction with the try-with-resources and AutoClosable function 042 * to start/stop your Outcome along with its operation. 043 * @return A started outcome 044 */ 045 public static Outcome start() { 046 Outcome outcome = new Outcome(); 047 outcome.startTimeMillis = outcome.created; 048 return outcome; 049 } 050 051 /** 052 * The name of an application associated with this outcome 053 */ 054 private String applicationName; 055 056 /** 057 * The name of an attribute associated with this outcome 058 */ 059 private String attribute; 060 061 /** 062 * The millisecond timestamp at which this object was created 063 */ 064 private final long created; 065 066 /** 067 * The name of an identity associated with this outcome 068 */ 069 private String identityName; 070 071 /** 072 * The list of timestampped messages logged for this outcome 073 */ 074 private final List<StampedMessage> messages; 075 076 /** 077 * A native identity associated with this outcome 078 */ 079 private String nativeIdentity; 080 081 /** 082 * An arbitrary object ID associated with this outcome 083 */ 084 private String objectId; 085 086 /** 087 * An arbitrary object name associated with this outcome 088 */ 089 private String objectName; 090 091 /** 092 * The type of the object ID or name above 093 */ 094 private String objectType; 095 096 /** 097 * The provisioning transaction associated with this outcome 098 */ 099 private String provisioningTransaction; 100 101 /** 102 * An indicator that something has been refreshed 103 */ 104 private Boolean refreshed; 105 106 /** 107 * A response code, intended when this is used for reporting an HTTP response 108 */ 109 @JsonInclude(JsonInclude.Include.NON_DEFAULT) 110 private Integer responseCode; 111 112 /** 113 * The start time in milliseconds 114 */ 115 @JsonInclude(JsonInclude.Include.NON_DEFAULT) 116 private long startTimeMillis; 117 118 /** 119 * The status of this operation 120 */ 121 private OutcomeType status; 122 123 /** 124 * The stop time in milliseconds 125 */ 126 @JsonInclude(JsonInclude.Include.NON_DEFAULT) 127 private long stopTimeMillis; 128 129 /** 130 * Arbitrary text associated with this operation 131 */ 132 private String text; 133 134 /** 135 * A flag indicating that something was updated 136 */ 137 private Boolean updated; 138 139 /** 140 * Arbitrary value associated with this operation (presumably of the named attribute) 141 */ 142 private String value; 143 144 /** 145 * Basic constructor, also used by Jackson to figure out what the 'default' for each 146 * item in the class is. 147 */ 148 public Outcome() { 149 this.created = System.currentTimeMillis(); 150 this.stopTimeMillis = -1L; 151 this.startTimeMillis = -1L; 152 this.messages = new ArrayList<>(); 153 } 154 155 /** 156 * Adds a timestamped error to this outcome 157 * @param input A string message to use with the error 158 * @param err The error itself 159 */ 160 public void addError(String input, Throwable err) { 161 this.messages.add(new StampedMessage(input, err)); 162 } 163 164 /** 165 * Adds a timestamped IIQ message to this outcome 166 * @param input The IIQ outcome 167 */ 168 public void addMessage(Message input) { 169 this.messages.add(new StampedMessage(input)); 170 } 171 172 /** 173 * Adds a timestamped log message of INFO level to this outcome 174 * @param input The log message 175 */ 176 public void addMessage(String input) { 177 this.messages.add(new StampedMessage(input)); 178 } 179 180 /** 181 * If the outcome has not yet had its stop timestamp set, sets it to the current time 182 */ 183 @Override 184 public void close() { 185 if (this.stopTimeMillis < 0) { 186 this.stopTimeMillis = System.currentTimeMillis(); 187 } 188 } 189 190 public String getApplicationName() { 191 return applicationName; 192 } 193 194 public String getAttribute() { 195 return attribute; 196 } 197 198 public long getCreated() { 199 return created; 200 } 201 202 public String getIdentityName() { 203 return identityName; 204 } 205 206 public List<StampedMessage> getMessages() { 207 return messages; 208 } 209 210 public String getNativeIdentity() { 211 return nativeIdentity; 212 } 213 214 public String getObjectId() { 215 return objectId; 216 } 217 218 public String getObjectName() { 219 return objectName; 220 } 221 222 public String getObjectType() { 223 return objectType; 224 } 225 226 public String getProvisioningTransaction() { 227 return provisioningTransaction; 228 } 229 230 public int getResponseCode() { 231 return responseCode != null ? responseCode : 0; 232 } 233 234 public long getStartTimeMillis() { 235 return startTimeMillis; 236 } 237 238 /** 239 * Gets the status, or calculates it based on other fields. 240 * 241 * If there is no status explicitly set and the stop time is not set, returns null. 242 * 243 * @return The status, or null if the outcome is not yet stopped 244 */ 245 public OutcomeType getStatus() { 246 if (status != null) { 247 return status; 248 } else if (this.stopTimeMillis < 0) { 249 return null; 250 } else { 251 boolean hasError = this.messages.stream().anyMatch(msg -> msg.getLevel() == LogLevel.ERROR); 252 if (hasError) { 253 return OutcomeType.Failure; 254 } 255 boolean hasWarning = this.messages.stream().anyMatch(msg -> msg.getLevel() == LogLevel.WARN); 256 if (hasWarning) { 257 return OutcomeType.Warning; 258 } 259 return OutcomeType.Success; 260 } 261 } 262 263 public long getStopTimeMillis() { 264 return stopTimeMillis; 265 } 266 267 public String getText() { 268 return text; 269 } 270 271 public String getValue() { 272 return value; 273 } 274 275 public boolean isRefreshed() { 276 return refreshed != null && refreshed; 277 } 278 279 public boolean isUpdated() { 280 return updated != null && updated; 281 } 282 283 public void setApplicationName(String applicationName) { 284 this.applicationName = applicationName; 285 } 286 287 public void setAttribute(String attribute) { 288 this.attribute = attribute; 289 } 290 291 public void setIdentityName(String identityName) { 292 this.identityName = identityName; 293 } 294 295 public void setNativeIdentity(String nativeIdentity) { 296 this.nativeIdentity = nativeIdentity; 297 } 298 299 public void setObjectId(String objectId) { 300 this.objectId = objectId; 301 } 302 303 public void setObjectName(String objectName) { 304 this.objectName = objectName; 305 } 306 307 public void setObjectType(String objectType) { 308 this.objectType = objectType; 309 } 310 311 public void setProvisioningTransaction(String provisioningTransaction) { 312 this.provisioningTransaction = provisioningTransaction; 313 } 314 315 public void setRefreshed(Boolean refreshed) { 316 this.refreshed = refreshed; 317 } 318 319 public void setResponseCode(Integer responseCode) { 320 this.responseCode = responseCode; 321 } 322 323 public void setStartTimeMillis(long startTimeMillis) { 324 this.startTimeMillis = startTimeMillis; 325 } 326 327 public void setStatus(OutcomeType status) { 328 this.status = status; 329 } 330 331 public void setStopTimeMillis(long stopTimeMillis) { 332 this.stopTimeMillis = stopTimeMillis; 333 } 334 335 public void setText(String text) { 336 this.text = text; 337 } 338 339 public void setUpdated(Boolean updated) { 340 this.updated = updated; 341 } 342 343 public void setValue(String value) { 344 this.value = value; 345 } 346 347 /** 348 * Sets the status to Terminated and the stop time to the current timestamp 349 */ 350 public void terminate() { 351 this.status = OutcomeType.Terminated; 352 this.stopTimeMillis = System.currentTimeMillis(); 353 } 354 355 @Override 356 public String toString() { 357 StringJoiner joiner = new StringJoiner(", ", Outcome.class.getSimpleName() + "[", "]"); 358 if ((applicationName) != null) { 359 joiner.add("applicationName='" + applicationName + "'"); 360 } 361 if ((attribute) != null) { 362 joiner.add("attribute='" + attribute + "'"); 363 } 364 joiner.add("created=" + created); 365 if ((identityName) != null) { 366 joiner.add("identityName='" + identityName + "'"); 367 } 368 if ((messages) != null) { 369 joiner.add("messages=" + messages); 370 } 371 if ((nativeIdentity) != null) { 372 joiner.add("nativeIdentity='" + nativeIdentity + "'"); 373 } 374 if ((objectId) != null) { 375 joiner.add("objectId='" + objectId + "'"); 376 } 377 if ((objectName) != null) { 378 joiner.add("objectName='" + objectName + "'"); 379 } 380 if ((objectType) != null) { 381 joiner.add("objectType='" + objectType + "'"); 382 } 383 if ((provisioningTransaction) != null) { 384 joiner.add("provisioningTransaction='" + provisioningTransaction + "'"); 385 } 386 joiner.add("refreshed=" + refreshed); 387 if (responseCode != null && responseCode > 0) { 388 joiner.add("responseCode=" + responseCode); 389 } 390 joiner.add("startTimeMillis=" + startTimeMillis); 391 if ((status) != null) { 392 joiner.add("status=" + status); 393 } 394 joiner.add("stopTimeMillis=" + stopTimeMillis); 395 if ((text) != null) { 396 joiner.add("text='" + text + "'"); 397 } 398 joiner.add("updated=" + updated); 399 if ((value) != null) { 400 joiner.add("value='" + value + "'"); 401 } 402 return joiner.toString(); 403 } 404}