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}