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