001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015-2016 ForgeRock AS.
015 */
016package org.forgerock.audit.events;
017
018import static org.forgerock.json.JsonValue.*;
019
020import org.forgerock.audit.util.DateUtil;
021import org.forgerock.json.JsonValue;
022import org.forgerock.services.context.Context;
023import org.forgerock.services.context.TransactionIdContext;
024import org.forgerock.util.Reject;
025
026import java.util.LinkedHashSet;
027import java.util.Set;
028
029/**
030 * Root builder for all audit events.
031 *
032 * @param <T> the type of the builder
033 */
034public abstract class AuditEventBuilder<T extends AuditEventBuilder<T>> {
035
036    /** Event name event payload field name. */
037    public static final String EVENT_NAME = "eventName";
038    /** Timestamp event payload field name. */
039    public static final String TIMESTAMP = "timestamp";
040    /** Transaction ID event payload field name. */
041    public static final String TRANSACTION_ID = "transactionId";
042    /** User ID event payload field name. */
043    public static final String USER_ID = "userId";
044    /** Tracking IDs event payload field name. */
045    public static final String TRACKING_IDS = "trackingIds";
046
047    /** Represents the event as a JSON value. */
048    protected JsonValue jsonValue = json(object());
049
050    /** Flag used to ensure super class implementations of validate() get called by subclasses. */
051    private boolean superValidateCalled = false;
052
053    /** Flag used to ensure super class implementations of setDefaults() get called by subclasses. */
054    private boolean superSetDefaultsCalled = false;
055
056    /** Accumulates trackingId entries. */
057    private final Set<String> trackingIdEntries = new LinkedHashSet<>();
058
059    /**
060     * Creates the builder.
061     */
062    protected AuditEventBuilder() {
063        // Reduce visibility of the default constructor
064    }
065
066    /**
067     * Returns this object, as its actual type.
068     *
069     * @return this object
070     */
071    @SuppressWarnings("unchecked")
072    protected final T self() {
073        return (T) this;
074    }
075
076    /**
077     * Generates the audit event.
078     *
079     * As a side-effect of calling this method, this builder is reset to its starting state.
080     *
081     * @return the audit event
082     */
083    public final AuditEvent toEvent() {
084
085        superSetDefaultsCalled = false;
086        setDefaults();
087        if (!superSetDefaultsCalled) {
088            throw new IllegalStateException("Subclasses overriding setDefaults() must call super.setDefaults()");
089        }
090
091        superValidateCalled = false;
092        validate();
093        if (!superValidateCalled) {
094            throw new IllegalStateException("Subclasses overriding validate() must call super.validate()");
095        }
096
097        AuditEvent auditEvent = new AuditEvent(jsonValue);
098        jsonValue = json(object());
099        return auditEvent;
100    }
101
102    /**
103     * Called by {@link #toEvent()} to allow any unset fields to be given their default value.
104     *
105     * When overriding this method, the super class implementation must be called.
106     */
107    protected void setDefaults() {
108        if (!jsonValue.isDefined(TIMESTAMP)) {
109            timestamp(System.currentTimeMillis());
110        }
111        if (!trackingIdEntries.isEmpty()) {
112            jsonValue.put(TRACKING_IDS, trackingIdEntries);
113        }
114        superSetDefaultsCalled = true;
115    }
116
117    /**
118     * Called by {@link #toEvent()} to ensure that the audit event will be created in a valid state.
119     *
120     * When overriding this method, the super class implementation must be called.
121     *
122     * @throws IllegalStateException if a required field has not been populated.
123     */
124    protected void validate() {
125        requireField(EVENT_NAME);
126        requireField(TRANSACTION_ID);
127        superValidateCalled = true;
128    }
129
130    /**
131     * Helper method to be used when overriding {@link #validate()}.
132     *
133     * @param rootFieldName The name of the field that must be populated.
134     * @throws IllegalStateException if the required field has not been populated.
135     */
136    protected void requireField(String rootFieldName) {
137        if (!jsonValue.isDefined(rootFieldName)) {
138            throw new IllegalStateException("The field " + rootFieldName + " is mandatory.");
139        }
140    }
141
142    /**
143     * Sets the provided name for the event.
144     *
145     * An event's name will usually be of the form {product}-{component}-{operation}. For example,
146     * AM-SESSION-CREATED, AM-CREST-SUCCESSFUL, etc.
147     *
148     * @param name the event's name.
149     * @return this builder
150     */
151    public final T eventName(String name) {
152        jsonValue.put(EVENT_NAME, name);
153        return self();
154    }
155
156    /**
157     * Sets the provided time stamp for the event.
158     *
159     * @param timestamp the time stamp.
160     * @return this builder
161     */
162    public final T timestamp(long timestamp) {
163        Reject.ifTrue(timestamp <= 0, "The timestamp has to be greater than 0.");
164        jsonValue.put(TIMESTAMP, DateUtil.getDateUtil("UTC").formatDateTime(timestamp));
165        return self();
166    }
167
168    /**
169     * Sets the provided transactionId for the event.
170     *
171     * @param id the transaction id.
172     * @return this builder
173     */
174    public final T transactionId(String id) {
175        Reject.ifNull(id);
176        jsonValue.put(TRANSACTION_ID, id);
177        return self();
178    }
179
180    /**
181     * Sets the provided userId for the event.
182     *
183     * @param id the user id.
184     * @return this builder
185     */
186    public final T userId(String id) {
187        jsonValue.put(USER_ID, id);
188        return self();
189    }
190
191    /**
192     * Adds an entry to trackingIds for the event.
193     *
194     * @param trackingIdValue the unique value associated with the object being tracked.
195     * @return this builder
196     */
197    public final T trackingId(String trackingIdValue) {
198        Reject.ifNull(trackingIdValue, "trackingId value cannot be null");
199        trackingIdEntries.add(trackingIdValue);
200        return self();
201    }
202
203    /**
204     * Adds the specified entries to trackingIds for the event.
205     *
206     * @param trackingIdValues the set of trackingId entries to be recorded (see {@link #trackingId}.
207     * @return this builder
208     */
209    public final T trackingIds(Set<String> trackingIdValues) {
210        // iterate the entries so that each can be validated
211        for (String trackingIdValue : trackingIdValues) {
212            trackingId(trackingIdValue);
213        }
214        return self();
215    }
216
217    /**
218     * Sets transactionId from ID of {@link TransactionIdContext}, if the provided
219     * <code>Context</code> contains a <code>TransactionIdContext</code>.
220     *
221     * @param context The CREST context.
222     * @return this builder
223     */
224    public final T transactionIdFromContext(Context context) {
225        if (context.containsContext(TransactionIdContext.class)) {
226            TransactionIdContext transactionIdContext = context.asContext(TransactionIdContext.class);
227            transactionId(transactionIdContext.getTransactionId().getValue());
228        }
229        return self();
230    }
231}