001package com.box.sdk; 002 003import com.eclipsesource.json.Json; 004import com.eclipsesource.json.JsonArray; 005import com.eclipsesource.json.JsonObject; 006import com.eclipsesource.json.JsonValue; 007import java.net.MalformedURLException; 008import java.net.URL; 009import java.util.Collection; 010import java.util.Date; 011import java.util.Iterator; 012import java.util.LinkedHashSet; 013import java.util.Set; 014 015/** 016 * A log of events that were retrieved from the events endpoint. 017 * 018 * <p>An EventLog cannot be instantiated directly. Instead, use one of the static methods to retrieve a log of events. 019 * Unlike the {@link EventStream} class, EventLog doesn't support retrieving events in real-time. 020 * </p> 021 */ 022public class EventLog implements Iterable<BoxEvent> { 023 024 static final int ENTERPRISE_LIMIT = 500; 025 /** 026 * Enterprise Event URL Template. 027 */ 028 public static final URLTemplate ENTERPRISE_EVENT_URL_TEMPLATE = new URLTemplate("events?stream_type=admin_logs&" 029 + "limit=" + ENTERPRISE_LIMIT); 030 private final int chunkSize; 031 private final int limit; 032 private final String nextStreamPosition; 033 private final String streamPosition; 034 private final Set<BoxEvent> events; 035 036 private Date startDate; 037 private Date endDate; 038 039 EventLog(BoxAPIConnection api, JsonObject json, String streamPosition, int limit) { 040 this.streamPosition = streamPosition; 041 this.limit = limit; 042 JsonValue nextStreamPosition = json.get("next_stream_position"); 043 if (nextStreamPosition.isString()) { 044 this.nextStreamPosition = nextStreamPosition.asString(); 045 } else { 046 this.nextStreamPosition = nextStreamPosition.toString(); 047 } 048 this.chunkSize = json.get("chunk_size").asInt(); 049 050 this.events = new LinkedHashSet<>(this.chunkSize); 051 JsonArray entries = json.get("entries").asArray(); 052 for (JsonValue entry : entries) { 053 this.events.add(new BoxEvent(api, entry.asObject())); 054 } 055 } 056 057 /** 058 * Method reads from the `admin-logs` stream and returns {@link BoxEvent} {@link Iterator}. 059 * The emphasis for this stream is on completeness over latency, 060 * which means that Box will deliver admin events in chronological order and without duplicates, 061 * but with higher latency. You can specify start and end time/dates. This method 062 * will only work with an API connection for an enterprise admin account 063 * or service account with manage enterprise properties. 064 * You can specify a date range to limit when events occured, starting from a given position within the 065 * event stream, set limit or specify event types that should be filtered. 066 * Example: 067 * <pre> 068 * {@code 069 * EnterpriseEventsRequest request = new EnterpriseEventsRequest() 070 * .after(after) // The lower bound on the timestamp of the events returned. 071 * .before(before) // The upper bound on the timestamp of the events returned. 072 * .limit(20) // The number of entries to be returned in the response. 073 * .position(position) // The starting position of the event stream. 074 * .types(EventType.LOGIN, EventType.FAILED_LOGIN); // List of event types to filter by. 075 * EventLog.getEnterpriseEvents(api, request); 076 * } 077 * </pre> 078 * 079 * @param api the API connection to use. 080 * @param enterpriseEventsRequest request to get events. 081 * @return a log of all the events that met the given criteria. 082 */ 083 public static EventLog getEnterpriseEvents(BoxAPIConnection api, EnterpriseEventsRequest enterpriseEventsRequest) { 084 EventLogRequest request = new EventLogRequest( 085 enterpriseEventsRequest.getBefore(), 086 enterpriseEventsRequest.getAfter(), 087 enterpriseEventsRequest.getPosition(), 088 enterpriseEventsRequest.getLimit(), 089 enterpriseEventsRequest.getTypes() 090 ); 091 return getEnterpriseEventsForStreamType(api, enterpriseEventsRequest.getStreamType(), request); 092 } 093 094 /** 095 * Method reads from the `admin-logs-streaming` stream and returns {@link BoxEvent} {@link Iterator} 096 * The emphasis for this feed is on low latency rather than chronological accuracy, which means that Box may return 097 * events more than once and out of chronological order. Events are returned via the API around 12 seconds after they 098 * are processed by Box (the 12 seconds buffer ensures that new events are not written after your cursor position). 099 * Only two weeks of events are available via this feed, and you cannot set start and end time/dates. This method 100 * will only work with an API connection for an enterprise admin account 101 * or service account with manage enterprise properties. 102 * You can specify a starting from a given position within the event stream, 103 * set limit or specify event types that should be filtered. 104 * Example: 105 * <pre> 106 * {@code 107 * EnterpriseEventsStreamRequest request = new EnterpriseEventsStreamRequest() 108 * .limit(200) // The number of entries to be returned in the response. 109 * .position(position) // The starting position of the event stream. 110 * .types(EventType.LOGIN, EventType.FAILED_LOGIN); // List of event types to filter by. 111 * EventLog.getEnterpriseEventsStream(api, request); 112 * } 113 * </pre> 114 * 115 * @param api the API connection to use. 116 * @param enterpriseEventsStreamRequest request to get events. 117 * @return a log of all the events that met the given criteria. 118 */ 119 public static EventLog getEnterpriseEventsStream( 120 BoxAPIConnection api, EnterpriseEventsStreamRequest enterpriseEventsStreamRequest 121 ) { 122 EventLogRequest request = new EventLogRequest( 123 null, 124 null, 125 enterpriseEventsStreamRequest.getPosition(), 126 enterpriseEventsStreamRequest.getLimit(), 127 enterpriseEventsStreamRequest.getTypes() 128 ); 129 return getEnterpriseEventsForStreamType(api, enterpriseEventsStreamRequest.getStreamType(), request); 130 } 131 132 private static EventLog getEnterpriseEventsForStreamType( 133 BoxAPIConnection api, String streamType, EventLogRequest request 134 ) { 135 URL url = new URLTemplate("events?").build(api.getBaseURL()); 136 QueryStringBuilder queryBuilder = new QueryStringBuilder(url.getQuery()); 137 queryBuilder.appendParam("stream_type", streamType); 138 addParamsToQuery(request, queryBuilder); 139 140 try { 141 url = queryBuilder.addToURL(url); 142 } catch (MalformedURLException e) { 143 throw new BoxAPIException("Couldn't append a query string to the provided URL."); 144 } 145 146 BoxJSONRequest apiRequest = new BoxJSONRequest(api, url, "GET"); 147 try (BoxJSONResponse response = apiRequest.send()) { 148 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 149 EventLog log = new EventLog(api, responseJSON, request.getPosition(), request.getLimit()); 150 log.setStartDate(request.getAfter()); 151 log.setEndDate(request.getBefore()); 152 return log; 153 } 154 } 155 156 private static void addParamsToQuery(EventLogRequest request, QueryStringBuilder queryBuilder) { 157 queryBuilder.appendParam("limit", request.getLimit()); 158 159 if (request.getAfter() != null) { 160 queryBuilder.appendParam("created_after", BoxDateFormat.format(request.getAfter())); 161 } 162 if (request.getBefore() != null) { 163 queryBuilder.appendParam("created_before", BoxDateFormat.format(request.getBefore())); 164 } 165 if (request.getPosition() != null) { 166 queryBuilder.appendParam("stream_position", request.getPosition()); 167 } 168 if (request.getTypes().size() > 0) { 169 String types = String.join(",", request.getTypes()); 170 queryBuilder.appendParam("event_type", types); 171 } 172 } 173 174 /** 175 * Returns an iterator over the events in this log. 176 * 177 * @return an iterator over the events in this log. 178 */ 179 @Override 180 public Iterator<BoxEvent> iterator() { 181 return this.events.iterator(); 182 } 183 184 /** 185 * Gets the date of the earliest event in this log. 186 * 187 * <p>The value returned by this method corresponds to the <code>created_after</code> URL parameter that was used 188 * when retrieving the events in this EventLog.</p> 189 * 190 * @return the date of the earliest event in this log. 191 */ 192 public Date getStartDate() { 193 return this.startDate; 194 } 195 196 void setStartDate(Date startDate) { 197 this.startDate = startDate; 198 } 199 200 /** 201 * Gets the date of the latest event in this log. 202 * 203 * <p>The value returned by this method corresponds to the <code>created_before</code> URL parameter that was used 204 * when retrieving the events in this EventLog.</p> 205 * 206 * @return the date of the latest event in this log. 207 */ 208 public Date getEndDate() { 209 return this.endDate; 210 } 211 212 void setEndDate(Date endDate) { 213 this.endDate = endDate; 214 } 215 216 /** 217 * Gets the maximum number of events that this event log could contain given its start date, end date, and stream 218 * position. 219 * 220 * <p>The value returned by this method corresponds to the <code>limit</code> URL parameter that was used when 221 * retrieving the events in this EventLog.</p> 222 * 223 * @return the maximum number of events. 224 */ 225 public int getLimit() { 226 return this.limit; 227 } 228 229 /** 230 * Gets the starting position of the events in this log within the event stream. 231 * 232 * <p>The value returned by this method corresponds to the <code>stream_position</code> URL parameter that was used 233 * when retrieving the events in this EventLog.</p> 234 * 235 * @return the starting position within the event stream. 236 */ 237 public String getStreamPosition() { 238 return this.streamPosition; 239 } 240 241 /** 242 * Gets the next position within the event stream for retrieving subsequent events. 243 * 244 * <p>The value returned by this method corresponds to the <code>next_stream_position</code> field returned by the 245 * API's events endpoint.</p> 246 * 247 * @return the next position within the event stream. 248 */ 249 public String getNextStreamPosition() { 250 return this.nextStreamPosition; 251 } 252 253 /** 254 * Gets the number of events in this log, including duplicate events. 255 * 256 * <p>The chunk size may not be representative of the number of events returned by this EventLog's iterator because 257 * the iterator will automatically ignore duplicate events.</p> 258 * 259 * <p>The value returned by this method corresponds to the <code>chunk_size</code> field returned by the API's 260 * events endpoint.</p> 261 * 262 * @return the number of events, including duplicates. 263 */ 264 public int getChunkSize() { 265 return this.chunkSize; 266 } 267 268 /** 269 * Gets the number of events in this list, excluding duplicate events. 270 * 271 * <p>The size is guaranteed to be representative of the number of events returned by this EventLog's iterator.</p> 272 * 273 * @return the number of events, excluding duplicates. 274 */ 275 public int getSize() { 276 return this.events.size(); 277 } 278 279 private static final class EventLogRequest { 280 private final Date before; 281 private final Date after; 282 private final String position; 283 private final Integer limit; 284 private final Collection<String> types; 285 286 private EventLogRequest( 287 Date before, 288 Date after, 289 String position, 290 Integer limit, 291 Collection<String> types 292 ) { 293 this.before = before; 294 this.after = after; 295 this.position = position; 296 this.limit = limit; 297 this.types = types; 298 } 299 300 private Date getBefore() { 301 return before; 302 } 303 304 private Date getAfter() { 305 return after; 306 } 307 308 private String getPosition() { 309 return position; 310 } 311 312 private Integer getLimit() { 313 return limit; 314 } 315 316 private Collection<String> getTypes() { 317 return types; 318 } 319 } 320}