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 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    public static final String EVENT_NAME = "eventName";
037    public static final String TIMESTAMP = "timestamp";
038    public static final String TRANSACTION_ID = "transactionId";
039    public static final String USER_ID = "userId";
040    public static final String TRACKING_IDS = "trackingIds";
041
042    /** Represents the event as a JSON value. */
043    protected JsonValue jsonValue = json(object());
044
045    /** Flag used to ensure super class implementations of validate() get called by subclasses. */
046    private boolean superValidateCalled = false;
047
048    /** Flag used to ensure super class implementations of setDefaults() get called by subclasses. */
049    private boolean superSetDefaultsCalled = false;
050
051    /** Accumulates trackingId entries. */
052    private final Set<String> trackingIdEntries = new LinkedHashSet<>();
053
054    /**
055     * Creates the builder.
056     */
057    protected AuditEventBuilder() {
058        // Reduce visibility of the default constructor
059    }
060
061    /**
062     * Returns this object, as its actual type.
063     *
064     * @return this object
065     */
066    @SuppressWarnings("unchecked")
067    protected final T self() {
068        return (T) this;
069    }
070
071    /**
072     * Generates the audit event.
073     *
074     * As a side-effect of calling this method, this builder is reset to its starting state.
075     *
076     * @return the audit event
077     */
078    public final AuditEvent toEvent() {
079
080        superSetDefaultsCalled = false;
081        setDefaults();
082        if (!superSetDefaultsCalled) {
083            throw new IllegalStateException("Subclasses overriding setDefaults() must call super.setDefaults()");
084        }
085
086        superValidateCalled = false;
087        validate();
088        if (!superValidateCalled) {
089            throw new IllegalStateException("Subclasses overriding validate() must call super.validate()");
090        }
091
092        AuditEvent auditEvent = new AuditEvent(jsonValue);
093        jsonValue = json(object());
094        return auditEvent;
095    }
096
097    /**
098     * Called by {@link #toEvent()} to allow any unset fields to be given their default value.
099     *
100     * When overriding this method, the super class implementation must be called.
101     */
102    protected void setDefaults() {
103        if (!jsonValue.isDefined(TIMESTAMP)) {
104            timestamp(System.currentTimeMillis());
105        }
106        if (!trackingIdEntries.isEmpty()) {
107            jsonValue.put(TRACKING_IDS, trackingIdEntries);
108        }
109        superSetDefaultsCalled = true;
110    }
111
112    /**
113     * Called by {@link #toEvent()} to ensure that the audit event will be created in a valid state.
114     *
115     * When overriding this method, the super class implementation must be called.
116     *
117     * @throws IllegalStateException if a required field has not been populated.
118     */
119    protected void validate() {
120        requireField(EVENT_NAME);
121        requireField(TRANSACTION_ID);
122        superValidateCalled = true;
123    }
124
125    /**
126     * Helper method to be used when overriding {@link #validate()}.
127     *
128     * @param rootFieldName The name of the field that must be populated.
129     * @throws IllegalStateException if the required field has not been populated.
130     */
131    protected void requireField(String rootFieldName) {
132        if (!jsonValue.isDefined(rootFieldName)) {
133            throw new IllegalStateException("The field " + rootFieldName + " is mandatory.");
134        }
135    }
136
137    /**
138     * Sets the provided name for the event.
139     *
140     * An event's name will usually be of the form {product}-{component}-{operation}. For example,
141     * AM-SESSION-CREATED, AM-CREST-SUCCESSFUL, etc.
142     *
143     * @param name the event's name.
144     * @return this builder
145     */
146    public final T eventName(String name) {
147        jsonValue.put(EVENT_NAME, name);
148        return self();
149    }
150
151    /**
152     * Sets the provided time stamp for the event.
153     *
154     * @param timestamp the time stamp.
155     * @return this builder
156     */
157    public final T timestamp(long timestamp) {
158        Reject.ifTrue(timestamp <= 0, "The timestamp has to be greater than 0.");
159        jsonValue.put(TIMESTAMP, DateUtil.getDateUtil("UTC").formatDateTime(timestamp));
160        return self();
161    }
162
163    /**
164     * Sets the provided transactionId for the event.
165     *
166     * @param id the transaction id.
167     * @return this builder
168     */
169    public final T transactionId(String id) {
170        Reject.ifNull(id);
171        jsonValue.put(TRANSACTION_ID, id);
172        return self();
173    }
174
175    /**
176     * Sets the provided userId for the event.
177     *
178     * @param id the user id.
179     * @return this builder
180     */
181    public final T userId(String id) {
182        jsonValue.put(USER_ID, id);
183        return self();
184    }
185
186    /**
187     * Adds an entry to trackingIds for the event.
188     *
189     * @param trackingIdValue the unique value associated with the object being tracked.
190     * @return this builder
191     */
192    public final T trackingId(String trackingIdValue) {
193        Reject.ifNull(trackingIdValue, "trackingId value cannot be null");
194        trackingIdEntries.add(trackingIdValue);
195        return self();
196    }
197
198    /**
199     * Adds the specified entries to trackingIds for the event.
200     *
201     * @param trackingIdValues the set of trackingId entries to be recorded (see {@link #trackingId}.
202     * @return this builder
203     */
204    public final T trackingIds(Set<String> trackingIdValues) {
205        // iterate the entries so that each can be validated
206        for (String trackingIdValue : trackingIdValues) {
207            trackingId(trackingIdValue);
208        }
209        return self();
210    }
211
212    /**
213     * Sets transactionId from ID of {@link TransactionIdContext}, if the provided
214     * <code>Context</code> contains a <code>TransactionIdContext</code>.
215     *
216     * @param context The CREST context.
217     * @return this builder
218     */
219    public final T transactionIdFromContext(Context context) {
220        if (context.containsContext(TransactionIdContext.class)) {
221            TransactionIdContext transactionIdContext = context.asContext(TransactionIdContext.class);
222            transactionId(transactionIdContext.getTransactionId().getValue());
223        }
224        return self();
225    }
226}