1
1
package org .zendesk .client .v2 ;
2
+
3
+ import java .util .concurrent .ExecutionException ;
4
+ import java .util .concurrent .atomic .AtomicInteger ;
5
+
6
+ import org .zendesk .client .v2 .model .*;
7
+ import org .zendesk .client .v2 .model .targets .*;
2
8
3
9
import com .fasterxml .jackson .annotation .JsonInclude ;
4
10
import com .fasterxml .jackson .core .JsonProcessingException ;
5
- import com .fasterxml .jackson .databind .DeserializationFeature ;
6
- import com .fasterxml .jackson .databind .JsonNode ;
7
- import com .fasterxml .jackson .databind .ObjectMapper ;
8
- import com .fasterxml .jackson .databind .SerializationFeature ;
9
- import com .ning .http .client .AsyncCompletionHandler ;
10
- import com .ning .http .client .AsyncHttpClient ;
11
- import com .ning .http .client .ListenableFuture ;
12
- import com .ning .http .client .Realm ;
11
+ import com .fasterxml .jackson .databind .*;
12
+ import com .ning .http .client .*;
13
13
import com .ning .http .client .Request ;
14
+
14
15
import com .ning .http .client .RequestBuilder ;
15
16
import com .ning .http .client .Response ;
16
17
43
44
import java .util .Iterator ;
44
45
import java .util .List ;
45
46
import java .util .Map ;
46
- import java .util .NoSuchElementException ;
47
- import java .util .concurrent .ExecutionException ;
48
-
47
+ import java .util .NoSuchElementException ;
48
+
49
49
50
50
/**
51
51
* @author stephenc
@@ -59,22 +59,41 @@ public class Zendesk implements Closeable {
59
59
private final String url ;
60
60
private final String oauthToken ;
61
61
private final ObjectMapper mapper ;
62
- private final Logger logger ;
62
+ private final Logger logger ;
63
63
private boolean closed = false ;
64
64
private static final Map <String , Class <? extends SearchResultEntity >> searchResultTypes = searchResultTypes ();
65
+ private static final Map <String , Class <? extends Target >> targetTypes = targetTypes ();
65
66
66
67
private static Map <String , Class <? extends SearchResultEntity >> searchResultTypes () {
67
- Map <String , Class <? extends SearchResultEntity >> result = new HashMap <String , Class <? extends
68
- SearchResultEntity >>();
69
- result .put ("ticket" , Ticket .class );
70
- result .put ("user" , User .class );
71
- result .put ("group" , Group .class );
72
- result .put ("organization" , Organization .class );
73
- result .put ("topic" , Topic .class );
74
- return Collections .unmodifiableMap (result );
68
+ Map <String , Class <? extends SearchResultEntity >> result = new HashMap <String , Class <? extends
69
+ SearchResultEntity >>();
70
+ result .put ("ticket" , Ticket .class );
71
+ result .put ("user" , User .class );
72
+ result .put ("group" , Group .class );
73
+ result .put ("organization" , Organization .class );
74
+ result .put ("topic" , Topic .class );
75
+ return Collections .unmodifiableMap (result );
75
76
}
76
-
77
- private Zendesk (AsyncHttpClient client , String url , String username , String password ) {
77
+
78
+ private static Map <String , Class <? extends Target >> targetTypes () {
79
+ Map <String , Class <? extends Target >> result = new HashMap <String , Class <? extends Target >>();
80
+ result .put ("url_target" , UrlTarget .class );
81
+ result .put ("email_target" ,EmailTarget .class );
82
+ result .put ("basecamp_target" , BasecampTarget .class );
83
+ result .put ("campfire_target" , CampfireTarget .class );
84
+ result .put ("pivotal_target" , PivotalTarget .class );
85
+ result .put ("twitter_target" , TwitterTarget .class );
86
+
87
+ // TODO: Implement other Target types
88
+ //result.put("clickatell_target", ClickatellTarget.class);
89
+ //result.put("flowdock_target", FlowdockTarget.class);
90
+ //result.put("get_satisfaction_target", GetSatisfactionTarget.class);
91
+ //result.put("yammer_target", YammerTarget.class);
92
+
93
+ return Collections .unmodifiableMap (result );
94
+ }
95
+
96
+ private Zendesk (AsyncHttpClient client , String url , String username , String password ) {
78
97
this .logger = LoggerFactory .getLogger (Zendesk .class );
79
98
this .closeClient = client == null ;
80
99
this .oauthToken = null ;
@@ -92,7 +111,7 @@ private Zendesk(AsyncHttpClient client, String url, String username, String pass
92
111
throw new IllegalStateException ("Cannot specify token or password without specifying username" );
93
112
}
94
113
this .realm = null ;
95
- }
114
+ }
96
115
this .mapper = createMapper ();
97
116
}
98
117
@@ -360,6 +379,47 @@ public void deleteAttachment(long id) {
360
379
complete (submit (req ("DELETE" , tmpl ("/attachments/{id}.json" ).set ("id" , id )), handleStatus ()));
361
380
}
362
381
382
+ public Iterable <Target > getTargets () {
383
+ return new PagedIterable <Target >(cnst ("/targets.json" ), handleTargetList ("targets" ));
384
+ }
385
+
386
+ public Target getTarget (long id ) {
387
+ return complete (submit (req ("GET" , tmpl ("/targets/{id}.json" ).set ("id" , id )), handle (Target .class , "target" )));
388
+ }
389
+
390
+ public Target createTarget (Target target ) {
391
+ return complete (submit (req ("POST" , cnst ("/targets.json" ), JSON , json (Collections .singletonMap ("target" , target ))),
392
+ handle (Target .class , "target" )));
393
+ }
394
+
395
+ public void deleteTarget (long targetId ) {
396
+ complete (submit (req ("DELETE" , tmpl ("/targets/{id}.json" ).set ("id" , targetId )), handleStatus ()));
397
+ }
398
+
399
+ public Iterable <Trigger > getTriggers () {
400
+ return new PagedIterable <Trigger >(cnst ("/triggers.json" ), handleList (Trigger .class , "triggers" ));
401
+ }
402
+
403
+ public Trigger getTrigger (long id ) {
404
+ return complete (submit (req ("GET" , tmpl ("/triggers/{id}.json" ).set ("id" , id )), handle (Trigger .class , "trigger" )));
405
+ }
406
+
407
+ public Trigger createTrigger (Trigger trigger ) {
408
+ return complete (submit (req ("POST" , cnst ("/triggers.json" ), JSON , json (Collections .singletonMap ("trigger" , trigger ))),
409
+ handle (Trigger .class , "trigger" )));
410
+ }
411
+
412
+ public void deleteTrigger (long triggerId ) {
413
+ complete (submit (req ("DELETE" , tmpl ("/triggers/{id}.json" ).set ("id" , triggerId )), handleStatus ()));
414
+ }
415
+
416
+
417
+ public Iterable <TwitterMonitor > getTwitterMonitors () {
418
+ return new PagedIterable <TwitterMonitor >(cnst ("/channels/twitter/monitored_twitter_handles.json" ),
419
+ handleList (TwitterMonitor .class , "monitored_twitter_handles" ));
420
+ }
421
+
422
+
363
423
public Iterable <User > getUsers () {
364
424
return new PagedIterable <User >(cnst ("/users.json" ), handleList (User .class , "users" ));
365
425
}
@@ -626,6 +686,24 @@ public Comment getRequestComment(long requestId, long commentId) {
626
686
handle (Comment .class , "comment" )));
627
687
}
628
688
689
+ public Ticket createComment (long ticketId , Comment comment ) {
690
+ Ticket ticket = new Ticket ();
691
+ ticket .setComment (comment );
692
+ return complete (submit (req ("PUT" , tmpl ("/tickets/{id}.json" ).set ("id" , ticketId ), JSON ,
693
+ json (Collections .singletonMap ("ticket" , ticket ))),
694
+ handle (Ticket .class , "ticket" )));
695
+ }
696
+
697
+ public Ticket createTicketFromTweet (long tweetId , long monitorId ) {
698
+ Map <String ,Object > map = new HashMap <String ,Object >();
699
+ map .put ("twitter_status_message_id" , tweetId );
700
+ map .put ("monitored_twitter_handle_id" , monitorId );
701
+
702
+ return complete (submit (req ("POST" , cnst ("/channels/twitter/tickets.json" ), JSON ,
703
+ json (Collections .singletonMap ("ticket" , map ))),
704
+ handle (Ticket .class , "ticket" )));
705
+ }
706
+
629
707
public Iterable <Organization > getOrganizations () {
630
708
return new PagedIterable <Organization >(cnst ("/organizations.json" ),
631
709
handleList (Organization .class , "organizations" ));
@@ -912,6 +990,14 @@ public <T extends SearchResultEntity> Iterable<T> getSearchResults(Class<T> type
912
990
.set ("params" , params ),
913
991
handleList (type , "results" ));
914
992
}
993
+
994
+ public void notifyApp (String json ) {
995
+ complete (submit (req ("POST" , cnst ("/apps/notify.json" ), JSON , json .getBytes ()), handleStatus ()));
996
+ }
997
+
998
+ public void updateInstallation (int id , String json ) {
999
+ complete (submit (req ("PUT" , tmpl ("/apps/installations/{id}.json" ).set ("id" , id ), JSON , json .getBytes ()), handleStatus ()));
1000
+ }
915
1001
916
1002
// TODO search with sort order
917
1003
// TODO search with query building API
@@ -950,7 +1036,7 @@ private Request req(String method, Uri template) {
950
1036
builder .setRealm (realm );
951
1037
} else {
952
1038
builder .addHeader ("Authorization" , "Bearer " + oauthToken );
953
- }
1039
+ }
954
1040
builder .setUrl (template .toString ());
955
1041
return builder .build ();
956
1042
}
@@ -961,7 +1047,7 @@ private Request req(String method, Uri template, String contentType, byte[] body
961
1047
builder .setRealm (realm );
962
1048
} else {
963
1049
builder .addHeader ("Authorization" , "Bearer " + oauthToken );
964
- }
1050
+ }
965
1051
builder .setUrl (template .toString ());
966
1052
builder .addHeader ("Content-type" , contentType );
967
1053
builder .setBody (body );
@@ -979,6 +1065,8 @@ private Request req(String method, Uri template, int page) {
979
1065
builder .setUrl (template .toString ().replace ("%2B" , "+" )); //replace out %2B with + due to API restriction
980
1066
return builder .build ();
981
1067
}
1068
+
1069
+
982
1070
983
1071
protected AsyncCompletionHandler <Void > handleStatus () {
984
1072
return new AsyncCompletionHandler <Void >() {
@@ -1041,25 +1129,33 @@ public List<T> onCompleted(Response response) throws Exception {
1041
1129
throw new ZendeskResponseException (response );
1042
1130
}
1043
1131
};
1044
- }
1045
-
1132
+ }
1133
+
1046
1134
protected <T > AsyncCompletionHandler <List <T >> handleList (final Class <T > clazz , final String name ) {
1047
- return new AsyncCompletionHandler <List <T >>() {
1048
- @ Override
1049
- public List <T > onCompleted (Response response ) throws Exception {
1050
- logResponse (response );
1051
- if (isStatus2xx (response )) {
1052
- List <T > values = new ArrayList <T >();
1053
- for (JsonNode node : mapper .readTree (response .getResponseBodyAsStream ()).get (name )) {
1054
- values .add (mapper .convertValue (node , clazz ));
1055
- }
1056
- return values ;
1057
- }
1058
- throw new ZendeskResponseException (response );
1059
- }
1060
- };
1061
- }
1062
-
1135
+ final AtomicInteger readCount = new AtomicInteger (0 );
1136
+ return new AsyncCompletionHandler <List <T >>() {
1137
+ @ Override
1138
+ public List <T > onCompleted (Response response ) throws Exception {
1139
+ logResponse (response );
1140
+ if (isStatus2xx (response )) {
1141
+ JsonNode responseNode = mapper .readTree (response .getResponseBodyAsBytes ());
1142
+ int count = responseNode .get ("count" ).asInt ();
1143
+ if (count > readCount .get ()) {
1144
+ List <T > values = new ArrayList <T >();
1145
+ for (JsonNode node : responseNode .get (name )) {
1146
+ values .add (mapper .convertValue (node , clazz ));
1147
+ readCount .incrementAndGet ();
1148
+ }
1149
+ return values ;
1150
+ } else {
1151
+ return null ;
1152
+ }
1153
+ }
1154
+ throw new ZendeskResponseException (response );
1155
+ }
1156
+ };
1157
+ }
1158
+
1063
1159
protected AsyncCompletionHandler <List <SearchResultEntity >> handleSearchList (final String name ) {
1064
1160
return new AsyncCompletionHandler <List <SearchResultEntity >>() {
1065
1161
@ Override
@@ -1080,6 +1176,35 @@ public List<SearchResultEntity> onCompleted(Response response) throws Exception
1080
1176
};
1081
1177
}
1082
1178
1179
+ protected AsyncCompletionHandler <List <Target >> handleTargetList (final String name ) {
1180
+ final AtomicInteger readCount = new AtomicInteger (0 );
1181
+ return new AsyncCompletionHandler <List <Target >>() {
1182
+ @ Override
1183
+ public List <Target > onCompleted (Response response ) throws Exception {
1184
+ logResponse (response );
1185
+ if (isStatus2xx (response )) {
1186
+ JsonNode responseNode = mapper .readTree (response .getResponseBodyAsBytes ());
1187
+ int count = responseNode .get ("count" ).asInt ();
1188
+ if (count > readCount .get ()) {
1189
+ List <Target > values = new ArrayList <Target >();
1190
+ for (JsonNode node : responseNode .get (name )) {
1191
+ Class <? extends Target > clazz = targetTypes .get (node .get ("type" ).asText ());
1192
+ if (clazz != null ) {
1193
+ values .add (mapper .convertValue (node , clazz ));
1194
+ }
1195
+ readCount .incrementAndGet ();
1196
+ }
1197
+ return values ;
1198
+ } else {
1199
+ return null ;
1200
+ }
1201
+ }
1202
+ throw new ZendeskResponseException (response );
1203
+ }
1204
+ };
1205
+ }
1206
+
1207
+
1083
1208
private TemplateUri tmpl (String template ) {
1084
1209
return new TemplateUri (url + template );
1085
1210
}
@@ -1223,7 +1348,7 @@ public static ObjectMapper createMapper() {
1223
1348
mapper .enable (SerializationFeature .WRITE_ENUMS_USING_TO_STRING );
1224
1349
mapper .enable (DeserializationFeature .READ_ENUMS_USING_TO_STRING );
1225
1350
mapper .disable (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES );
1226
- mapper .setSerializationInclusion (JsonInclude .Include .NON_NULL );
1351
+ mapper .setSerializationInclusion (JsonInclude .Include .NON_NULL );
1227
1352
return mapper ;
1228
1353
}
1229
1354
@@ -1236,6 +1361,7 @@ private class PagedIterable<T> implements Iterable<T> {
1236
1361
private final Uri url ;
1237
1362
private final AsyncCompletionHandler <List <T >> handler ;
1238
1363
private final int initialPage ;
1364
+ private int size = 0 ;
1239
1365
1240
1366
private PagedIterable (Uri url , AsyncCompletionHandler <List <T >> handler ) {
1241
1367
this (url , handler , 1 );
@@ -1262,12 +1388,16 @@ private PagedIterator(int page) {
1262
1388
1263
1389
public boolean hasNext () {
1264
1390
if (current == null || !current .hasNext ()) {
1265
- if (page > 0 ) {
1391
+ if (page > 0 ) {
1266
1392
List <T > values = complete (submit (req ("GET" , url , page ++), handler ));
1267
- if (values .isEmpty ()) {
1393
+ if (values == null || values .isEmpty ()) {
1268
1394
page = -1 ;
1395
+ } else {
1396
+ synchronized (this ) {
1397
+ size += values .size ();
1398
+ }
1399
+ current = values .iterator ();
1269
1400
}
1270
- current = values .iterator ();
1271
1401
} else {
1272
1402
return false ;
1273
1403
}
@@ -1329,6 +1459,7 @@ public Builder setToken(String token) {
1329
1459
return this ;
1330
1460
}
1331
1461
1462
+
1332
1463
public Builder setOauthToken (String oauthToken ) {
1333
1464
this .oauthToken = oauthToken ;
1334
1465
if (oauthToken != null ) {
@@ -1352,4 +1483,5 @@ public Zendesk build() {
1352
1483
return new Zendesk (client , url , username , password );
1353
1484
}
1354
1485
}
1486
+
1355
1487
}
0 commit comments