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 * Gets all the enterprise events that occurred within a specified date range, starting from a given position 059 * within the event stream. 060 * 061 * @param api the API connection to use. 062 * @param position the starting position of the event stream. 063 * @param after the lower bound on the timestamp of the events returned. 064 * @param before the upper bound on the timestamp of the events returned. 065 * @param types an optional list of event types to filter by. 066 * @return a log of all the events that met the given criteria. 067 * @deprecated Use {@link #getEnterpriseEvents(BoxAPIConnection, EnterpriseEventsRequest)} 068 */ 069 @Deprecated 070 public static EventLog getEnterpriseEvents(BoxAPIConnection api, String position, Date after, Date before, 071 BoxEvent.Type... types) { 072 return getEnterpriseEvents(api, position, after, before, ENTERPRISE_LIMIT, types); 073 } 074 075 /** 076 * Gets all the enterprise events that occurred within a specified date range. 077 * 078 * @param api the API connection to use. 079 * @param after the lower bound on the timestamp of the events returned. 080 * @param before the upper bound on the timestamp of the events returned. 081 * @param types an optional list of event types to filter by. 082 * @return a log of all the events that met the given criteria. 083 * @deprecated Use {@link #getEnterpriseEvents(BoxAPIConnection, EnterpriseEventsRequest)} 084 */ 085 @Deprecated 086 public static EventLog getEnterpriseEvents(BoxAPIConnection api, Date after, Date before, BoxEvent.Type... types) { 087 return getEnterpriseEvents(api, null, after, before, ENTERPRISE_LIMIT, types); 088 } 089 090 /** 091 * Gets all the enterprise events that occurred within a specified date range, starting from a given position 092 * within the event stream. 093 * 094 * @param api the API connection to use. 095 * @param position the starting position of the event stream. 096 * @param after the lower bound on the timestamp of the events returned. 097 * @param before the upper bound on the timestamp of the events returned. 098 * @param limit the number of entries to be returned in the response. 099 * @param types an optional list of event types to filter by. 100 * @return a log of all the events that met the given criteria. 101 * @deprecated Use {@link #getEnterpriseEvents(BoxAPIConnection, EnterpriseEventsRequest)} 102 */ 103 @Deprecated 104 public static EventLog getEnterpriseEvents(BoxAPIConnection api, String position, Date after, Date before, 105 int limit, BoxEvent.Type... types) { 106 107 URL url = ENTERPRISE_EVENT_URL_TEMPLATE.build(api.getBaseURL()); 108 109 if (position != null || types.length > 0 || after != null 110 || before != null || limit != ENTERPRISE_LIMIT) { 111 QueryStringBuilder queryBuilder = new QueryStringBuilder(url.getQuery()); 112 113 if (after != null) { 114 queryBuilder.appendParam("created_after", 115 BoxDateFormat.format(after)); 116 } 117 118 if (before != null) { 119 queryBuilder.appendParam("created_before", 120 BoxDateFormat.format(before)); 121 } 122 123 if (position != null) { 124 queryBuilder.appendParam("stream_position", position); 125 } 126 127 if (limit != ENTERPRISE_LIMIT) { 128 queryBuilder.appendParam("limit", limit); 129 } 130 131 if (types.length > 0) { 132 StringBuilder filterBuilder = new StringBuilder(); 133 for (BoxEvent.Type filterType : types) { 134 filterBuilder.append(filterType.name()); 135 filterBuilder.append(','); 136 } 137 filterBuilder.deleteCharAt(filterBuilder.length() - 1); 138 queryBuilder.appendParam("event_type", filterBuilder.toString()); 139 } 140 141 try { 142 url = queryBuilder.addToURL(url); 143 } catch (MalformedURLException e) { 144 throw new BoxAPIException("Couldn't append a query string to the provided URL."); 145 } 146 } 147 148 BoxAPIRequest request = new BoxAPIRequest(api, url, "GET"); 149 BoxJSONResponse response = (BoxJSONResponse) request.send(); 150 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 151 EventLog log = new EventLog(api, responseJSON, position, limit); 152 log.setStartDate(after); 153 log.setEndDate(before); 154 return log; 155 } 156 157 /** 158 * Method reads from the `admin-logs` stream and returns {@link BoxEvent} {@link Iterator}. 159 * The emphasis for this stream is on completeness over latency, 160 * which means that Box will deliver admin events in chronological order and without duplicates, 161 * but with higher latency. You can specify start and end time/dates. This method 162 * will only work with an API connection for an enterprise admin account 163 * or service account with manage enterprise properties. 164 * You can specify a date range to limit when events occured, starting from a given position within the 165 * event stream, set limit or specify event types that should be filtered. 166 * Example: 167 * <pre> 168 * {@code 169 * EnterpriseEventsRequest request = new EnterpriseEventsRequest() 170 * .after(after) // The lower bound on the timestamp of the events returned. 171 * .before(before) // The upper bound on the timestamp of the events returned. 172 * .limit(20) // The number of entries to be returned in the response. 173 * .position(position) // The starting position of the event stream. 174 * .types(EventType.LOGIN, EventType.FAILED_LOGIN); // List of event types to filter by. 175 * EventLog.getEnterpriseEvents(api, request); 176 * } 177 * </pre> 178 * 179 * @param api the API connection to use. 180 * @param enterpriseEventsRequest request to get events. 181 * @return a log of all the events that met the given criteria. 182 */ 183 public static EventLog getEnterpriseEvents(BoxAPIConnection api, EnterpriseEventsRequest enterpriseEventsRequest) { 184 EventLogRequest request = new EventLogRequest( 185 enterpriseEventsRequest.getBefore(), 186 enterpriseEventsRequest.getAfter(), 187 enterpriseEventsRequest.getPosition(), 188 enterpriseEventsRequest.getLimit(), 189 enterpriseEventsRequest.getTypes() 190 ); 191 return getEnterpriseEventsForStreamType(api, enterpriseEventsRequest.getStreamType(), request); 192 } 193 194 /** 195 * Method reads from the `admin-logs-streaming` stream and returns {@link BoxEvent} {@link Iterator} 196 * The emphasis for this feed is on low latency rather than chronological accuracy, which means that Box may return 197 * events more than once and out of chronological order. Events are returned via the API around 12 seconds after they 198 * are processed by Box (the 12 seconds buffer ensures that new events are not written after your cursor position). 199 * Only two weeks of events are available via this feed, and you cannot set start and end time/dates. This method 200 * will only work with an API connection for an enterprise admin account 201 * or service account with manage enterprise properties. 202 * You can specify a starting from a given position within the event stream, 203 * set limit or specify event types that should be filtered. 204 * Example: 205 * <pre> 206 * {@code 207 * EnterpriseEventsStreamRequest request = new EnterpriseEventsStreamRequest() 208 * .limit(200) // The number of entries to be returned in the response. 209 * .position(position) // The starting position of the event stream. 210 * .types(EventType.LOGIN, EventType.FAILED_LOGIN); // List of event types to filter by. 211 * EventLog.getEnterpriseEventsStream(api, request); 212 * } 213 * </pre> 214 * 215 * @param api the API connection to use. 216 * @param enterpriseEventsStreamRequest request to get events. 217 * @return a log of all the events that met the given criteria. 218 */ 219 public static EventLog getEnterpriseEventsStream( 220 BoxAPIConnection api, EnterpriseEventsStreamRequest enterpriseEventsStreamRequest 221 ) { 222 EventLogRequest request = new EventLogRequest( 223 null, 224 null, 225 enterpriseEventsStreamRequest.getPosition(), 226 enterpriseEventsStreamRequest.getLimit(), 227 enterpriseEventsStreamRequest.getTypes() 228 ); 229 return getEnterpriseEventsForStreamType(api, enterpriseEventsStreamRequest.getStreamType(), request); 230 } 231 232 private static EventLog getEnterpriseEventsForStreamType( 233 BoxAPIConnection api, String streamType, EventLogRequest request 234 ) { 235 URL url = new URLTemplate("events?").build(api.getBaseURL()); 236 QueryStringBuilder queryBuilder = new QueryStringBuilder(url.getQuery()); 237 queryBuilder.appendParam("stream_type", streamType); 238 addParamsToQuery(request, queryBuilder); 239 240 try { 241 url = queryBuilder.addToURL(url); 242 } catch (MalformedURLException e) { 243 throw new BoxAPIException("Couldn't append a query string to the provided URL."); 244 } 245 246 BoxAPIRequest apiRequest = new BoxAPIRequest(api, url, "GET"); 247 BoxJSONResponse response = (BoxJSONResponse) apiRequest.send(); 248 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 249 EventLog log = new EventLog(api, responseJSON, request.getPosition(), request.getLimit()); 250 log.setStartDate(request.getAfter()); 251 log.setEndDate(request.getBefore()); 252 return log; 253 } 254 255 private static void addParamsToQuery(EventLogRequest request, QueryStringBuilder queryBuilder) { 256 queryBuilder.appendParam("limit", request.getLimit()); 257 258 if (request.getAfter() != null) { 259 queryBuilder.appendParam("created_after", BoxDateFormat.format(request.getAfter())); 260 } 261 if (request.getBefore() != null) { 262 queryBuilder.appendParam("created_before", BoxDateFormat.format(request.getBefore())); 263 } 264 if (request.getPosition() != null) { 265 queryBuilder.appendParam("stream_position", request.getPosition()); 266 } 267 if (request.getTypes().size() > 0) { 268 String types = String.join(",", request.getTypes()); 269 queryBuilder.appendParam("event_type", types); 270 } 271 } 272 273 /** 274 * Returns an iterator over the events in this log. 275 * 276 * @return an iterator over the events in this log. 277 */ 278 @Override 279 public Iterator<BoxEvent> iterator() { 280 return this.events.iterator(); 281 } 282 283 /** 284 * Gets the date of the earliest event in this log. 285 * 286 * <p>The value returned by this method corresponds to the <code>created_after</code> URL parameter that was used 287 * when retrieving the events in this EventLog.</p> 288 * 289 * @return the date of the earliest event in this log. 290 */ 291 public Date getStartDate() { 292 return this.startDate; 293 } 294 295 void setStartDate(Date startDate) { 296 this.startDate = startDate; 297 } 298 299 /** 300 * Gets the date of the latest event in this log. 301 * 302 * <p>The value returned by this method corresponds to the <code>created_before</code> URL parameter that was used 303 * when retrieving the events in this EventLog.</p> 304 * 305 * @return the date of the latest event in this log. 306 */ 307 public Date getEndDate() { 308 return this.endDate; 309 } 310 311 void setEndDate(Date endDate) { 312 this.endDate = endDate; 313 } 314 315 /** 316 * Gets the maximum number of events that this event log could contain given its start date, end date, and stream 317 * position. 318 * 319 * <p>The value returned by this method corresponds to the <code>limit</code> URL parameter that was used when 320 * retrieving the events in this EventLog.</p> 321 * 322 * @return the maximum number of events. 323 */ 324 public int getLimit() { 325 return this.limit; 326 } 327 328 /** 329 * Gets the starting position of the events in this log within the event stream. 330 * 331 * <p>The value returned by this method corresponds to the <code>stream_position</code> URL parameter that was used 332 * when retrieving the events in this EventLog.</p> 333 * 334 * @return the starting position within the event stream. 335 */ 336 public String getStreamPosition() { 337 return this.streamPosition; 338 } 339 340 /** 341 * Gets the next position within the event stream for retrieving subsequent events. 342 * 343 * <p>The value returned by this method corresponds to the <code>next_stream_position</code> field returned by the 344 * API's events endpoint.</p> 345 * 346 * @return the next position within the event stream. 347 */ 348 public String getNextStreamPosition() { 349 return this.nextStreamPosition; 350 } 351 352 /** 353 * Gets the number of events in this log, including duplicate events. 354 * 355 * <p>The chunk size may not be representative of the number of events returned by this EventLog's iterator because 356 * the iterator will automatically ignore duplicate events.</p> 357 * 358 * <p>The value returned by this method corresponds to the <code>chunk_size</code> field returned by the API's 359 * events endpoint.</p> 360 * 361 * @return the number of events, including duplicates. 362 */ 363 public int getChunkSize() { 364 return this.chunkSize; 365 } 366 367 /** 368 * Gets the number of events in this list, excluding duplicate events. 369 * 370 * <p>The size is guaranteed to be representative of the number of events returned by this EventLog's iterator.</p> 371 * 372 * @return the number of events, excluding duplicates. 373 */ 374 public int getSize() { 375 return this.events.size(); 376 } 377 378 private static final class EventLogRequest { 379 private final Date before; 380 private final Date after; 381 private final String position; 382 private final Integer limit; 383 private final Collection<String> types; 384 385 private EventLogRequest( 386 Date before, 387 Date after, 388 String position, 389 Integer limit, 390 Collection<String> types 391 ) { 392 this.before = before; 393 this.after = after; 394 this.position = position; 395 this.limit = limit; 396 this.types = types; 397 } 398 399 private Date getBefore() { 400 return before; 401 } 402 403 private Date getAfter() { 404 return after; 405 } 406 407 private String getPosition() { 408 return position; 409 } 410 411 private Integer getLimit() { 412 return limit; 413 } 414 415 private Collection<String> getTypes() { 416 return types; 417 } 418 } 419}