Skip to content

Commit 63021bd

Browse files
committed
Support for bulk user and event processing
1 parent cc45caf commit 63021bd

21 files changed

+795
-45
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*/**/SpikeMain.java
1+
*/**/Spike*.java
22
.gradle
33
*.class
44
out/*

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ UserCollection users = User.list();
138138
while(users.hasNext()) {
139139
System.out.println(users.next().getUserId());
140140
}
141+
142+
// Bulk submit users
143+
final List<JobItem<User>> items = Lists.newArrayList();
144+
items.add(new JobItem<User>("post", user1));
145+
items.add(new JobItem<User>("post", user2));
146+
items.add(new JobItem<User>("delete", user3));
147+
final Job job = User.submit(items);
148+
System.out.println(job.getID());
149+
150+
// Bulk submit, add to an existing job
151+
final List<JobItem<User>> moreItems = Lists.newArrayList();
152+
items.add(new JobItem<User>("post", user4));
153+
items.add(new JobItem<User>("delete", user5));
154+
User.submit(moreItems, job);
141155
```
142156

143157
### Contacts
@@ -244,6 +258,20 @@ Event event = new Event().setEventName("bought-hat")
244258
.putMetadata("found_date", System.currentTimeMillis())
245259
.putMetadata("new_signup", true);
246260
Event.create(event);
261+
262+
// Bulk submit events
263+
final List<JobItem<Event>> items = Lists.newArrayList();
264+
items.add(new JobItem<Event>("post", event1));
265+
items.add(new JobItem<Event>("post", event2));
266+
items.add(new JobItem<Event>("post", event3));
267+
final Job job = Event.submit(items);
268+
System.out.println(job.getID());
269+
270+
// Bulk submit, add to an existing job
271+
final List<JobItem<Event>> moreItems = Lists.newArrayList();
272+
items.add(new JobItem<Event>("post", event4));
273+
items.add(new JobItem<Event>("delete", event5));
274+
Event.submit(moreItems, job);
247275
```
248276
249277
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.intercom.api;
2+
3+
import com.google.common.collect.Lists;
4+
5+
class Conditions {
6+
7+
/**
8+
* Ensures that an object reference passed as a parameter to the calling
9+
* method is not null. Variant of Guava's Preconditions that returns an
10+
* InvalidException containing an ErrorCollection
11+
*
12+
* @param reference an object reference
13+
* @param errorMessage the exception message to use if the check fails
14+
* @return the non-null reference that was validated
15+
* @throws InvalidException if {@code reference} is null
16+
*/
17+
public static <T> T checkNotNull(T reference, String errorMessage) {
18+
if (reference == null) {
19+
throw new InvalidException(
20+
new ErrorCollection(
21+
Lists.newArrayList(
22+
new Error("invalid", "item method must be supplied"))));
23+
}
24+
return reference;
25+
}
26+
}

intercom-java/src/main/java/io/intercom/api/Contact.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ void checkValidConvertUser(HashMap<String, String> convertContact) {
140140
@SuppressWarnings("UnusedDeclaration")
141141
@JsonIgnoreProperties(ignoreUnknown = true)
142142
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
143-
static class ContactConvert extends TypedData {
143+
static class ContactConvert {
144144

145145
@JsonProperty("contact")
146146
private Map<String, String> contact;

intercom-java/src/main/java/io/intercom/api/DataResource.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.intercom.api;
22

33
import java.net.URI;
4+
import java.util.List;
45
import java.util.Map;
56

67
abstract class DataResource {
@@ -21,6 +22,11 @@ public static <T, R> R create(T entity, String collectionPath, Class<R> response
2122
return resource.post(response, entity);
2223
}
2324

25+
public static <T, R> R create(T entity, List<String> paths, Class<R> response) {
26+
final HttpClient resource = new HttpClient(UriBuilder.newBuilder().path(paths).build());
27+
return resource.post(response, entity);
28+
}
29+
2430
public static <T, R> R update(T entity, String collectionPath, Class<R> response) {
2531
final HttpClient resource = new HttpClient(UriBuilder.newBuilder().path(collectionPath).build());
2632
return resource.post(response, entity);

intercom-java/src/main/java/io/intercom/api/Event.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@
88
import com.google.common.collect.Lists;
99
import com.google.common.collect.Maps;
1010

11+
import java.util.ArrayList;
12+
import java.util.List;
1113
import java.util.Map;
1214

1315
@SuppressWarnings("UnusedDeclaration")
1416
@JsonIgnoreProperties(ignoreUnknown = true)
1517
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
1618
public class Event extends TypedData {
1719

20+
private static final List<String> BULK_METHODS = Lists.newArrayList("post");
21+
private static final ArrayList<String> BULK_PATHS = Lists.newArrayListWithExpectedSize(2);
22+
23+
static {
24+
BULK_PATHS.add("bulk");
25+
BULK_PATHS.add("events");
26+
}
27+
1828
public static void create(Event event) throws InvalidException, AuthorizationException {
1929

2030
validateCreateEvent(event);
@@ -25,6 +35,32 @@ public static void create(Event event) throws InvalidException, AuthorizationExc
2535
DataResource.create(event, "events", Void.class);
2636
}
2737

38+
public static Job submit(List<JobItem<Event>> items)
39+
throws AuthorizationException, ClientException, ServerException, InvalidException, RateLimitException {
40+
return submit(items, null);
41+
}
42+
43+
public static Job submit(List<JobItem<Event>> items, Job job)
44+
throws AuthorizationException, ClientException, ServerException, InvalidException, RateLimitException {
45+
return Job.submit(validateJobItems(items), job, BULK_PATHS);
46+
}
47+
48+
public static JobItemCollection<Event> listJobErrorFeed(String jobID)
49+
throws AuthorizationException, ClientException, ServerException, InvalidException, RateLimitException {
50+
return Job.listJobErrorFeed(jobID, Event.class);
51+
}
52+
53+
@VisibleForTesting
54+
static List<JobItem<Event>> validateJobItems(List<JobItem<Event>> items) {
55+
final JobSupport jobSupport = new JobSupport();
56+
for (JobItem<Event> item : items) {
57+
jobSupport.validateJobItem(item, BULK_METHODS);
58+
validateCreateEvent(item.getData());
59+
}
60+
61+
return items;
62+
}
63+
2864
private static final ErrorCollection INVALID_NAME = new ErrorCollection(
2965
Lists.newArrayList(
3066
new Error("invalid", "an event must supply an event name")));

intercom-java/src/main/java/io/intercom/api/HttpClient.java

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.intercom.api;
22

3+
import com.fasterxml.jackson.databind.JavaType;
34
import com.fasterxml.jackson.databind.ObjectMapper;
45
import com.fasterxml.jackson.databind.SerializationFeature;
56
import com.google.common.collect.Lists;
@@ -73,57 +74,46 @@ private HttpClient(URI uri, Map<String, String> headers) {
7374
}
7475

7576
public <T> T get(Class<T> reqres) throws IntercomException {
76-
HttpURLConnection conn = null;
77-
try {
78-
conn = initializeConnection(uri, "GET");
79-
return runRequest(uri, reqres, conn);
80-
} catch (IOException e) {
81-
return throwLocalException(e);
82-
} finally {
83-
IOUtils.disconnectQuietly(conn);
84-
}
77+
return get(getJavaType(reqres));
78+
}
79+
80+
<T> T get(JavaType responseType) throws IntercomException {
81+
return executeHttpMethod("GET", null, responseType);
82+
}
83+
84+
public <T> T delete(Class<T> reqres) {
85+
return executeHttpMethod("DELETE", null, getJavaType(reqres));
8586
}
8687

8788
public <T, E> T put(Class<T> reqres, E entity) {
8889
headers.put("Content-Type", APPLICATION_JSON);
89-
HttpURLConnection conn = null;
90-
try {
91-
conn = initializeConnection(uri, "PUT");
92-
prepareRequestEntity(entity, conn);
93-
return runRequest(uri, reqres, conn);
94-
} catch (IOException e) {
95-
return throwLocalException(e);
96-
} finally {
97-
IOUtils.disconnectQuietly(conn);
98-
}
90+
return executeHttpMethod("PUT", (E) entity, getJavaType(reqres));
9991
}
10092

10193
public <T, E> T post(Class<T> reqres, E entity) {
10294
headers.put("Content-Type", APPLICATION_JSON);
103-
HttpURLConnection conn = null;
104-
try {
105-
conn = initializeConnection(uri, "POST");
106-
prepareRequestEntity(entity, conn);
107-
return runRequest(uri, reqres, conn);
108-
} catch (IOException e) {
109-
return throwLocalException(e);
110-
} finally {
111-
IOUtils.disconnectQuietly(conn);
112-
}
95+
return executeHttpMethod("POST", entity, getJavaType(reqres));
11396
}
11497

115-
public <T> T delete(Class<T> reqres) {
98+
private <T, E> T executeHttpMethod(String method, E entity, JavaType responseType) {
11699
HttpURLConnection conn = null;
117100
try {
118-
conn = initializeConnection(uri, "DELETE");
119-
return runRequest(uri, reqres, conn);
101+
conn = initializeConnection(uri, method);
102+
if(entity != null) {
103+
prepareRequestEntity(entity, conn);
104+
}
105+
return runRequest(uri, responseType, conn);
120106
} catch (IOException e) {
121107
return throwLocalException(e);
122108
} finally {
123109
IOUtils.disconnectQuietly(conn);
124110
}
125111
}
126112

113+
private <T> JavaType getJavaType(Class<T> reqres) {
114+
return objectMapper.getTypeFactory().constructType(reqres);
115+
}
116+
127117
// trick java with a dummy return
128118
private <T> T throwLocalException(IOException e) {
129119
throw new IntercomException(String.format("Local exception calling [%s]. Check connectivity and settings. [%s]", uri.toASCIIString(), e.getMessage()), e);
@@ -151,13 +141,12 @@ private HttpURLConnection initializeConnection(URI uri, String method) throws IO
151141
return conn;
152142
}
153143

154-
private <T> T runRequest(URI uri, Class<T> response, HttpURLConnection conn) throws IOException {
144+
private <T> T runRequest(URI uri, JavaType javaType, HttpURLConnection conn) throws IOException {
155145
conn.connect();
156146
final int responseCode = conn.getResponseCode();
157147
if (responseCode >= 200 && responseCode < 300) {
158-
return handleSuccess(response, conn, responseCode);
148+
return handleSuccess(javaType, conn, responseCode);
159149
} else {
160-
// errors are redirects for now
161150
return handleError(uri, conn, responseCode);
162151
}
163152
}
@@ -175,23 +164,27 @@ private <T> T handleError(URI uri, HttpURLConnection conn, int responseCode) thr
175164
return throwException(responseCode, errors);
176165
}
177166

178-
private <T> T handleSuccess(Class<T> response, HttpURLConnection conn, int responseCode) throws IOException {
179-
if (responseCode == 202 || responseCode == 204 || Void.class.equals(response)) {
167+
private <T> T handleSuccess(JavaType javaType, HttpURLConnection conn, int responseCode) throws IOException {
168+
if (shouldSkipResponseEntity(javaType, conn, responseCode)) {
180169
return null;
181170
} else {
182-
return readEntity(conn, responseCode, response);
171+
return readEntity(conn, responseCode, javaType);
183172
}
184173
}
185174

186-
private <T> T readEntity(HttpURLConnection conn, int responseCode, Class<T> entityType) throws IOException {
175+
private boolean shouldSkipResponseEntity(JavaType javaType, HttpURLConnection conn, int responseCode) {
176+
return responseCode == 202 || responseCode == 204 || Void.class.equals(javaType.getRawClass()) || "DELETE".equals(conn.getRequestMethod());
177+
}
178+
179+
private <T> T readEntity(HttpURLConnection conn, int responseCode, JavaType javaType) throws IOException {
187180
final InputStream entityStream = conn.getInputStream();
188181
try {
189182
if (logger.isDebugEnabled()) {
190183
final String text = CharStreams.toString(new InputStreamReader(entityStream));
191184
logger.debug("api server response status[{}] --\n{}\n-- ", responseCode, text);
192-
return objectMapper.readValue(text, entityType);
185+
return objectMapper.readValue(text, javaType);
193186
} else {
194-
return objectMapper.readValue(entityStream, entityType);
187+
return objectMapper.readValue(entityStream, javaType);
195188
}
196189
} finally {
197190
IOUtils.closeQuietly(entityStream);

intercom-java/src/main/java/io/intercom/api/Intercom.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public class Intercom {
3030

3131
private static volatile HttpConnectorSupplier httpConnectorSupplier = HttpConnectorSupplier.defaultSupplier;
3232

33+
public static long currentTimestamp() {
34+
return System.currentTimeMillis()/1000;
35+
}
36+
3337
public static int getConnectionTimeout() {
3438
return connectionTimeout;
3539
}

0 commit comments

Comments
 (0)