bernard
authored
2013-04-04 13:08:24 +0000
1
package pl . itcrowd . youtrack . api ;
bernard
authored
2011-12-05 08:00:02 +0000
2
bernard
authored
2012-11-05 09:56:36 +0000
3
4
import org.apache.commons.logging.Log ;
import org.apache.commons.logging.LogFactory ;
bernard
authored
2012-07-02 21:19:19 +0000
5
6
7
8
9
import org.apache.http.Header ;
import org.apache.http.HttpEntity ;
import org.apache.http.HttpHeaders ;
import org.apache.http.HttpResponse ;
import org.apache.http.HttpStatus ;
bernard
authored
2012-10-31 14:56:42 +0000
10
import org.apache.http.NameValuePair ;
bernard
authored
2012-07-02 21:19:19 +0000
11
12
13
14
15
import org.apache.http.StatusLine ;
import org.apache.http.client.ClientProtocolException ;
import org.apache.http.client.HttpClient ;
import org.apache.http.client.HttpResponseException ;
import org.apache.http.client.entity.UrlEncodedFormEntity ;
bernard
authored
2012-11-04 20:40:52 +0000
16
import org.apache.http.client.methods.HttpDelete ;
bernard
authored
2012-10-31 14:56:42 +0000
17
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase ;
bernard
authored
2012-07-02 21:19:19 +0000
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.apache.http.client.methods.HttpGet ;
import org.apache.http.client.methods.HttpPost ;
import org.apache.http.client.methods.HttpPut ;
import org.apache.http.client.methods.HttpUriRequest ;
import org.apache.http.conn.ClientConnectionManager ;
import org.apache.http.conn.scheme.Scheme ;
import org.apache.http.conn.scheme.SchemeRegistry ;
import org.apache.http.conn.ssl.SSLSocketFactory ;
import org.apache.http.impl.client.DefaultHttpClient ;
import org.apache.http.impl.conn.PoolingClientConnectionManager ;
import org.apache.http.impl.conn.SchemeRegistryFactory ;
import org.apache.http.message.BasicNameValuePair ;
import org.apache.http.util.EntityUtils ;
bernard
authored
2013-04-04 13:08:24 +0000
31
32
33
34
35
36
37
import pl.itcrowd.youtrack.api.defaults.Fields ;
import pl.itcrowd.youtrack.api.exceptions.NoResultFoundException ;
import pl.itcrowd.youtrack.api.exceptions.YoutrackAPIException ;
import pl.itcrowd.youtrack.api.exceptions.YoutrackErrorException ;
import pl.itcrowd.youtrack.api.rest.AssigneeList ;
import pl.itcrowd.youtrack.api.rest.Enumeration ;
import pl.itcrowd.youtrack.api.rest.Issue ;
38
import pl.itcrowd.youtrack.api.rest.IssueCompacts ;
bernard
authored
2013-04-04 13:08:24 +0000
39
40
41
import pl.itcrowd.youtrack.api.rest.Issues ;
import pl.itcrowd.youtrack.api.rest.User ;
import pl.itcrowd.youtrack.api.rest.UserRefs ;
bernard
authored
2011-12-05 08:00:02 +0000
42
bernard
authored
2012-07-02 21:19:19 +0000
43
44
45
46
import javax.net.ssl.SSLContext ;
import javax.net.ssl.TrustManager ;
import javax.net.ssl.X509TrustManager ;
import javax.xml.bind.JAXBElement ;
bernard
authored
2011-12-05 08:00:02 +0000
47
import javax.xml.bind.JAXBException ;
bernard
authored
2012-07-03 10:50:59 +0000
48
import javax.xml.namespace.QName ;
bernard
authored
2011-12-05 08:00:02 +0000
49
import java.io.IOException ;
bernard
authored
2012-10-30 10:07:05 +0000
50
import java.io.UnsupportedEncodingException ;
bernard
authored
2012-07-02 21:19:19 +0000
51
52
53
54
55
56
57
import java.net.URI ;
import java.net.URISyntaxException ;
import java.security.KeyManagementException ;
import java.security.NoSuchAlgorithmException ;
import java.security.SecureRandom ;
import java.security.cert.CertificateException ;
import java.security.cert.X509Certificate ;
bernard
authored
2011-12-05 08:00:02 +0000
58
import java.util.ArrayList ;
bernard
authored
2012-07-02 21:19:19 +0000
59
import java.util.Arrays ;
bernard
authored
2012-10-31 14:56:42 +0000
60
import java.util.Collections ;
61
import java.util.HashMap ;
bernard
authored
2011-12-16 08:51:04 +0000
62
import java.util.List ;
63
import java.util.Map ;
bernard
authored
2012-07-02 21:19:19 +0000
64
65
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
bernard
authored
2011-12-05 08:00:02 +0000
66
bernard
authored
2013-04-04 13:08:24 +0000
67
import static pl . itcrowd . youtrack . api . URIUtils . buildURI ;
bernard
authored
2012-11-05 09:56:36 +0000
68
bernard
authored
2011-12-05 08:00:02 +0000
69
70
public class YoutrackAPI {
bernard
authored
2013-04-04 13:12:45 +0000
71
private final static QName Enumeration_QNAME = new QName ( "" , "enumeration" );
bernard
authored
2012-10-31 16:56:49 +0000
72
bernard
authored
2013-04-04 13:12:45 +0000
73
private final static QName Issue_QNAME = new QName ( "" , "issue" );
bernard
authored
2012-07-03 10:50:59 +0000
74
bernard
authored
2013-04-04 13:12:45 +0000
75
private static Log LOG = LogFactory . getLog ( YoutrackAPI . class );
bernard
authored
2012-11-05 09:56:36 +0000
76
bernard
authored
2013-04-04 13:12:45 +0000
77
private HttpClient httpClient ;
bernard
authored
2012-07-02 21:19:19 +0000
78
bernard
authored
2013-04-04 13:12:45 +0000
79
private String serviceLocation ;
bernard
authored
2011-12-05 08:00:02 +0000
80
bernard
authored
2013-04-04 13:12:45 +0000
81
private URI serviceLocationURI ;
bernard
authored
2012-11-05 09:56:36 +0000
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public YoutrackAPI ( String serviceLocation )
{
this ( serviceLocation , null );
}
public YoutrackAPI ( String serviceLocation , HttpClient httpClient )
{
if ( serviceLocation == null ) {
throw new IllegalArgumentException ( "serviceLocation cannot be null" );
}
this . serviceLocation = serviceLocation ;
try {
serviceLocationURI = new URI ( this . serviceLocation );
} catch ( URISyntaxException e ) {
throw new RuntimeException ( e );
}
this . httpClient = httpClient == null ? getDefaultHttpClient () : httpClient ;
}
public YoutrackAPI ( String serviceLocation , String username , String password ) throws IOException , JAXBException
{
this ( serviceLocation , null , username , password );
}
public YoutrackAPI ( String serviceLocation , HttpClient httpClient , String username , String password ) throws IOException , JAXBException
{
this ( serviceLocation , httpClient );
login ( username , password );
}
bernard
authored
2013-04-04 13:12:45 +0000
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
private static HttpClient getDefaultHttpClient ()
{
SSLContext sslContext ;
try {
sslContext = SSLContext . getInstance ( "SSL" );
} catch ( NoSuchAlgorithmException e ) {
throw new RuntimeException ( e );
}
// set up a TrustManager that trusts everything
try {
sslContext . init ( null , new TrustManager []{ new X509TrustManager () {
public void checkClientTrusted ( X509Certificate [] x509Certificates , String s ) throws CertificateException
{
}
public void checkServerTrusted ( X509Certificate [] x509Certificates , String s ) throws CertificateException
{
}
public X509Certificate [] getAcceptedIssuers ()
{
return new X509Certificate [ 0 ];
}
}}, new SecureRandom ());
} catch ( KeyManagementException e ) {
throw new RuntimeException ( e );
}
SSLSocketFactory sf = new SSLSocketFactory ( sslContext );
Scheme httpsScheme = new Scheme ( "https" , 443 , sf );
SchemeRegistry schemeRegistry = SchemeRegistryFactory . createDefault ();
schemeRegistry . register ( schemeRegistry . unregister ( "https" ));
schemeRegistry . register ( httpsScheme );
bernard
authored
2012-07-02 21:19:19 +0000
146
bernard
authored
2013-04-04 13:12:45 +0000
147
148
ClientConnectionManager cm = new PoolingClientConnectionManager ( schemeRegistry );
return new DefaultHttpClient ( cm );
bernard
authored
2012-11-04 20:40:52 +0000
149
}
bernard
authored
2012-07-02 21:19:19 +0000
150
bernard
authored
2013-04-04 13:12:45 +0000
151
152
153
154
155
private static boolean isBlank ( String str )
{
int strLen ;
if ( str == null || ( strLen = str . length ()) == 0 ) {
return true ;
bernard
authored
2012-11-04 20:40:52 +0000
156
}
bernard
authored
2013-04-04 13:12:45 +0000
157
158
159
160
for ( int i = 0 ; i < strLen ; i ++) {
if ((! Character . isWhitespace ( str . charAt ( i )))) {
return false ;
}
bernard
authored
2012-11-04 20:40:52 +0000
161
}
bernard
authored
2013-04-04 13:12:45 +0000
162
return true ;
bernard
authored
2012-07-02 21:19:19 +0000
163
}
bernard
authored
2011-12-05 08:00:02 +0000
164
165
public AssigneeList getAssignees ( String project ) throws IOException , JAXBException
bernard
authored
2013-04-04 13:12:45 +0000
166
{
167
168
169
170
171
172
173
final String responseString = execute ( new HttpGet ( buildURI ( serviceLocationURI , "/rest/admin/project/" + project + "/assignee" )));
final Object result = YoutrackUnmarshaller . unmarshall ( responseString );
if ( result instanceof AssigneeList ) {
return ( AssigneeList ) result ;
} else {
throw new YoutrackAPIException ( "Unexpected type: " + result );
}
bernard
authored
2012-11-04 20:40:52 +0000
174
}
bernard
authored
2013-04-04 13:12:45 +0000
175
176
public Enumeration getBundle ( String customField ) throws IOException , JAXBException
bernard
authored
2013-04-04 13:12:45 +0000
177
{
178
179
180
181
182
183
184
185
186
187
188
189
190
final Object result = YoutrackUnmarshaller . unmarshall (
execute ( new HttpGet ( buildURI ( serviceLocationURI , "/rest/admin/customfield/bundle/" + customField ))));
if ( result instanceof Enumeration ) {
return ( Enumeration ) result ;
} else if ( result instanceof JAXBElement ) {
final JAXBElement jaxbElement = ( JAXBElement ) result ;
if ( Enumeration_QNAME . equals ( jaxbElement . getName ())) {
return ( Enumeration ) (( JAXBElement ) result ). getValue ();
} else {
throw new YoutrackAPIException ( "Unexpected type: " + jaxbElement . getValue ());
}
} else {
throw new YoutrackAPIException ( "Unexpected type: " + result );
bernard
authored
2013-04-04 13:12:45 +0000
191
}
bernard
authored
2012-07-03 10:19:33 +0000
192
193
}
194
public List < User > getIndividualAssignees ( String project ) throws IOException , JAXBException
bernard
authored
2013-04-04 13:12:45 +0000
195
{
196
197
198
199
200
201
202
final String responseString = execute ( new HttpGet ( buildURI ( serviceLocationURI , "/rest/admin/project/" + project + "/assignee/individual" )));
final Object result = YoutrackUnmarshaller . unmarshall ( responseString );
if ( result instanceof UserRefs ) {
return (( UserRefs ) result ). getUsers ();
} else {
throw new YoutrackAPIException ( "Unexpected type: " + result );
}
bernard
authored
2013-04-04 13:12:45 +0000
203
}
bernard
authored
2011-12-05 08:00:02 +0000
204
205
public IssueWrapper getIssue ( String issueId ) throws IOException , JAXBException
bernard
authored
2013-04-04 13:12:45 +0000
206
{
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
final String responseString ;
try {
responseString = execute ( new HttpGet ( buildURI ( serviceLocationURI , "/rest/issue/" + issueId )));
} catch ( YoutrackErrorException e ) {
if ( e . getStatusCode () == HttpStatus . SC_NOT_FOUND ) {
throw new NoResultFoundException ( e . getMessage (), e );
} else {
throw e ;
}
}
final Object result = YoutrackUnmarshaller . unmarshall ( responseString );
if ( result instanceof pl . itcrowd . youtrack . api . rest . Issue ) {
return new IssueWrapper (( Issue ) result );
} else if ( result instanceof JAXBElement ) {
final JAXBElement jaxbElement = ( JAXBElement ) result ;
if ( Issue_QNAME . equals ( jaxbElement . getName ())) {
return new IssueWrapper (( Issue ) jaxbElement . getValue ());
} else {
throw new YoutrackAPIException ( "Unexpected type: " + jaxbElement . getValue ());
}
} else {
throw new YoutrackAPIException ( "Unexpected type " + result );
}
bernard
authored
2013-04-04 13:12:45 +0000
230
}
bernard
authored
2012-07-02 21:19:19 +0000
231
bernard
authored
2013-04-04 13:12:45 +0000
232
233
234
public String getServiceLocation ()
{
return serviceLocation ;
bernard
authored
2012-11-05 09:56:36 +0000
235
}
bernard
authored
2013-04-04 13:12:45 +0000
236
237
238
239
public void command ( String issueId , String command ) throws IOException
{
command ( issueId , command , null , null , null , null );
bernard
authored
2012-11-05 09:56:36 +0000
240
}
bernard
authored
2013-04-04 13:12:45 +0000
241
242
243
244
public void command ( String issueId , Command command ) throws IOException
{
command ( issueId , command . toString ());
bernard
authored
2012-07-03 10:19:33 +0000
245
}
bernard
authored
2013-04-04 13:12:45 +0000
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
public void command ( String issueId , String command , String comment , String group , Boolean disableNotifications , String runAs ) throws IOException
{
final HttpPost request = new HttpPost ( buildURI ( serviceLocationURI , "/rest/issue/" + issueId + "/execute" ));
final List < BasicNameValuePair > parameters = new ArrayList < BasicNameValuePair >();
parameters . add ( new BasicNameValuePair ( "command" , command ));
if (! isBlank ( comment )) {
parameters . add ( new BasicNameValuePair ( "comment" , comment ));
}
if (! isBlank ( group )) {
parameters . add ( new BasicNameValuePair ( "group" , group ));
}
if ( disableNotifications != null ) {
parameters . add ( new BasicNameValuePair ( "disableNotifications" , disableNotifications . toString ()));
}
if (! isBlank ( runAs )) {
parameters . add ( new BasicNameValuePair ( "runAs" , runAs ));
}
request . setEntity ( new UrlEncodedFormEntity ( parameters ));
execute ( request );
bernard
authored
2012-07-03 10:19:33 +0000
266
}
bernard
authored
2013-04-04 13:12:45 +0000
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/**
* Creates new issue on Youtrack.
*
* @param project project to create issue in
* @param summary summary of the issue
* @param description longer description of the issue
*
* @return issue id of created issue
*
* @throws IOException in case of communication problems
*/
public String createIssue ( String project , String summary , String description ) throws IOException
{
final HttpPut request = createPutRequest ( buildURI ( serviceLocationURI , "/rest/issue" ), new BasicNameValuePair ( "project" , project ),
new BasicNameValuePair ( "summary" , summary ), new BasicNameValuePair ( "description" , description ));
final HttpResponse response = httpClient . execute ( request );
final StatusLine statusLine = response . getStatusLine ();
final HttpEntity entity = response . getEntity ();
final String responseText = entity == null ? null : EntityUtils . toString ( entity );
throwExceptionsIfNeeded ( statusLine , responseText );
final Header header = response . getFirstHeader ( HttpHeaders . LOCATION );
if ( header == null ) {
throw new YoutrackAPIException ( "Missing location header despite positive status code: " + statusLine . getStatusCode ());
}
final String issueURL = header . getValue ();
final Matcher matcher = Pattern . compile ( ".*(" + project + "-\\d+)" ). matcher ( issueURL );
if (! matcher . find () || matcher . groupCount () < 1 ) {
throw new YoutrackAPIException ( "Cannot extract issue id from issue URL: " + issueURL );
}
return matcher . group ( 1 );
bernard
authored
2012-07-03 10:19:33 +0000
299
}
bernard
authored
2013-04-04 13:12:45 +0000
300
301
302
303
public void deleteIssue ( String issueId ) throws IOException
{
execute ( new HttpDelete ( buildURI ( serviceLocationURI , "/rest/issue/" + issueId )));
bernard
authored
2012-07-02 21:19:19 +0000
304
}
bernard
authored
2013-04-04 13:12:45 +0000
305
306
public void login ( String username , String password ) throws IOException , JAXBException
bernard
authored
2013-04-04 13:12:45 +0000
307
{
308
309
310
final HttpPost request = new HttpPost ( buildURI ( serviceLocationURI , "/rest/user/login" ));
request . setEntity ( new UrlEncodedFormEntity ( Arrays . asList ( new BasicNameValuePair ( "login" , username ), new BasicNameValuePair ( "password" , password ))));
execute ( request );
bernard
authored
2011-12-05 08:00:02 +0000
311
}
bernard
authored
2013-04-04 13:12:45 +0000
312
313
public List < IssueWrapper > searchIssues ( Object filter , Integer maxResults , Integer after ) throws JAXBException , IOException
bernard
authored
2013-04-04 13:12:45 +0000
314
{
315
316
317
318
319
320
321
final Map < String , Object > params = new HashMap < String , Object >();
params . put ( "filter" , filter );
params . put ( "max" , maxResults );
params . put ( "after" , after );
final Object result = YoutrackUnmarshaller . unmarshall ( execute ( new HttpGet ( buildURI ( serviceLocationURI , "/rest/issue" , params ))));
if (!( result instanceof IssueCompacts )) {
throw new YoutrackAPIException ( "Unmarshalling problem. Expected Issues, received: " + result . getClass () + " " + result );
bernard
authored
2013-04-04 13:12:45 +0000
322
}
323
324
325
326
List < Issue > issues = (( IssueCompacts ) result ). getIssues ();
List < IssueWrapper > wrappedIssues = new ArrayList < IssueWrapper >();
for ( Issue issue : issues ) {
wrappedIssues . add ( new IssueWrapper ( issue ));
bernard
authored
2013-04-04 13:12:45 +0000
327
}
328
return wrappedIssues ;
bernard
authored
2012-07-02 21:19:19 +0000
329
}
bernard
authored
2013-04-04 13:12:45 +0000
330
331
332
333
334
335
336
337
338
339
340
341
342
343
public List < IssueWrapper > searchIssuesByProject ( String project , Object filter ) throws JAXBException , IOException
{
final Object result = YoutrackUnmarshaller . unmarshall (
execute ( new HttpGet ( buildURI ( serviceLocationURI , "/rest/issue/byproject/" + project , "filter=" + ( filter == null ? "" : filter )))));
if (!( result instanceof Issues )) {
throw new YoutrackAPIException ( "Unmarshalling problem. Expected Issues, received: " + result . getClass () + " " + result );
}
List < Issue > issues = (( Issues ) result ). getIssues ();
List < IssueWrapper > wrappedIssues = new ArrayList < IssueWrapper >();
for ( Issue issue : issues ) {
wrappedIssues . add ( new IssueWrapper ( issue ));
}
return wrappedIssues ;
bernard
authored
2012-11-04 20:40:52 +0000
344
}
bernard
authored
2013-04-04 13:12:45 +0000
345
346
347
348
349
350
351
352
353
354
public void updateIssue ( String issueId , String summary , String description ) throws IOException
{
final HttpPost request = createPostRequest ( buildURI ( serviceLocationURI , "/rest/issue/" + issueId ),
new BasicNameValuePair ( Fields . summary . name (), summary ), new BasicNameValuePair ( Fields . description . name (), description ));
final HttpResponse response = httpClient . execute ( request );
final StatusLine statusLine = response . getStatusLine ();
final HttpEntity entity = response . getEntity ();
final String responseText = entity == null ? null : EntityUtils . toString ( entity );
throwExceptionsIfNeeded ( statusLine , responseText );
bernard
authored
2012-11-04 20:40:52 +0000
355
}
bernard
authored
2013-04-04 13:12:45 +0000
356
357
358
359
private HttpPost createPostRequest ( URI uri , NameValuePair ... nameValuePair ) throws UnsupportedEncodingException
{
return setEntity ( new HttpPost ( uri ), nameValuePair );
bernard
authored
2012-11-04 20:40:52 +0000
360
}
bernard
authored
2013-04-04 13:12:45 +0000
361
362
363
364
private HttpPut createPutRequest ( URI uri , NameValuePair ... nameValuePair ) throws UnsupportedEncodingException
{
return setEntity ( new HttpPut ( uri ), nameValuePair );
bernard
authored
2012-11-04 20:40:52 +0000
365
}
bernard
authored
2013-04-04 13:12:45 +0000
366
367
368
private String execute ( HttpUriRequest request ) throws IOException
{
369
request . addHeader ( "Accept" , "application/xml" );
bernard
authored
2013-04-04 13:12:45 +0000
370
371
372
373
374
375
final HttpResponse response = httpClient . execute ( request );
final StatusLine statusLine = response . getStatusLine ();
final HttpEntity entity = response . getEntity ();
String responseText = entity == null ? null : EntityUtils . toString ( entity );
if ( statusLine . getStatusCode () >= 300 ) {
try {
bernard
authored
2013-05-07 10:25:46 +0000
376
final String error = YoutrackUnmarshaller . unmarshalError ( responseText );
bernard
authored
2013-04-04 13:12:45 +0000
377
378
379
380
381
382
383
384
385
386
throw new YoutrackErrorException ( error , statusLine . getStatusCode ());
} catch ( JAXBException e ) {
LOG . error ( "Cannot unmarshal following response text:\n" + responseText , e );
throw new HttpResponseException ( statusLine . getStatusCode (), responseText );
}
}
if ( entity == null ) {
throw new ClientProtocolException ( "Response contains no content" );
}
return responseText ;
bernard
authored
2012-11-04 20:40:52 +0000
387
}
bernard
authored
2013-04-04 13:12:45 +0000
388
389
390
391
392
393
394
private < T extends HttpEntityEnclosingRequestBase > T setEntity ( T request , NameValuePair [] nameValuePair ) throws UnsupportedEncodingException
{
final ArrayList < NameValuePair > list = new ArrayList < NameValuePair >();
Collections . addAll ( list , nameValuePair );
request . setEntity ( new UrlEncodedFormEntity ( list ));
return request ;
bernard
authored
2012-11-04 20:40:52 +0000
395
}
bernard
authored
2013-04-04 13:12:45 +0000
396
397
398
399
400
401
private void throwExceptionsIfNeeded ( StatusLine statusLine , String responseText ) throws IOException
{
if ( statusLine . getStatusCode () >= 300 ) {
throw new HttpResponseException ( statusLine . getStatusCode (), responseText );
}
402
403
}
}