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 if (request.getAfter() != null) { 257 queryBuilder.appendParam("created_after", BoxDateFormat.format(request.getAfter())); 258 } 259 if (request.getBefore() != null) { 260 queryBuilder.appendParam("created_before", BoxDateFormat.format(request.getBefore())); 261 } 262 if (request.getPosition() != null) { 263 queryBuilder.appendParam("stream_position", request.getPosition()); 264 } 265 if (request.getLimit() != ENTERPRISE_LIMIT) { 266 queryBuilder.appendParam("limit", request.getLimit()); 267 } 268 if (request.getTypes().size() > 0) { 269 StringBuilder filterBuilder = new StringBuilder(); 270 for (BoxEvent.EventType filterType : request.getTypes()) { 271 filterBuilder.append(filterType.name()); 272 filterBuilder.append(','); 273 } 274 filterBuilder.deleteCharAt(filterBuilder.length() - 1); 275 queryBuilder.appendParam("event_type", filterBuilder.toString()); 276 } 277 } 278 279 /** 280 * Returns an iterator over the events in this log. 281 * 282 * @return an iterator over the events in this log. 283 */ 284 @Override 285 public Iterator<BoxEvent> iterator() { 286 return this.events.iterator(); 287 } 288 289 /** 290 * Gets the date of the earliest event in this log. 291 * 292 * <p>The value returned by this method corresponds to the <code>created_after</code> URL parameter that was used 293 * when retrieving the events in this EventLog.</p> 294 * 295 * @return the date of the earliest event in this log. 296 */ 297 public Date getStartDate() { 298 return this.startDate; 299 } 300 301 void setStartDate(Date startDate) { 302 this.startDate = startDate; 303 } 304 305 /** 306 * Gets the date of the latest event in this log. 307 * 308 * <p>The value returned by this method corresponds to the <code>created_before</code> URL parameter that was used 309 * when retrieving the events in this EventLog.</p> 310 * 311 * @return the date of the latest event in this log. 312 */ 313 public Date getEndDate() { 314 return this.endDate; 315 } 316 317 void setEndDate(Date endDate) { 318 this.endDate = endDate; 319 } 320 321 /** 322 * Gets the maximum number of events that this event log could contain given its start date, end date, and stream 323 * position. 324 * 325 * <p>The value returned by this method corresponds to the <code>limit</code> URL parameter that was used when 326 * retrieving the events in this EventLog.</p> 327 * 328 * @return the maximum number of events. 329 */ 330 public int getLimit() { 331 return this.limit; 332 } 333 334 /** 335 * Gets the starting position of the events in this log within the event stream. 336 * 337 * <p>The value returned by this method corresponds to the <code>stream_position</code> URL parameter that was used 338 * when retrieving the events in this EventLog.</p> 339 * 340 * @return the starting position within the event stream. 341 */ 342 public String getStreamPosition() { 343 return this.streamPosition; 344 } 345 346 /** 347 * Gets the next position within the event stream for retrieving subsequent events. 348 * 349 * <p>The value returned by this method corresponds to the <code>next_stream_position</code> field returned by the 350 * API's events endpoint.</p> 351 * 352 * @return the next position within the event stream. 353 */ 354 public String getNextStreamPosition() { 355 return this.nextStreamPosition; 356 } 357 358 /** 359 * Gets the number of events in this log, including duplicate events. 360 * 361 * <p>The chunk size may not be representative of the number of events returned by this EventLog's iterator because 362 * the iterator will automatically ignore duplicate events.</p> 363 * 364 * <p>The value returned by this method corresponds to the <code>chunk_size</code> field returned by the API's 365 * events endpoint.</p> 366 * 367 * @return the number of events, including duplicates. 368 */ 369 public int getChunkSize() { 370 return this.chunkSize; 371 } 372 373 /** 374 * Gets the number of events in this list, excluding duplicate events. 375 * 376 * <p>The size is guaranteed to be representative of the number of events returned by this EventLog's iterator.</p> 377 * 378 * @return the number of events, excluding duplicates. 379 */ 380 public int getSize() { 381 return this.events.size(); 382 } 383 384 private static final class EventLogRequest { 385 private final Date before; 386 private final Date after; 387 private final String position; 388 private final Integer limit; 389 private final Collection<BoxEvent.EventType> types; 390 391 private EventLogRequest( 392 Date before, 393 Date after, 394 String position, 395 Integer limit, 396 Collection<BoxEvent.EventType> types 397 ) { 398 this.before = before; 399 this.after = after; 400 this.position = position; 401 this.limit = limit; 402 this.types = types; 403 } 404 405 private Date getBefore() { 406 return before; 407 } 408 409 private Date getAfter() { 410 return after; 411 } 412 413 private String getPosition() { 414 return position; 415 } 416 417 private Integer getLimit() { 418 return limit; 419 } 420 421 private Collection<BoxEvent.EventType> getTypes() { 422 return types; 423 } 424 } 425}