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}