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