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}