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
38
39
40
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 ;
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
41
bernard
authored
2012-07-02 21:19:19 +0000
42
43
44
45
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
46
import javax.xml.bind.JAXBException ;
bernard
authored
2012-07-03 10:50:59 +0000
47
import javax.xml.namespace.QName ;
bernard
authored
2011-12-05 08:00:02 +0000
48
import java.io.IOException ;
bernard
authored
2012-10-30 10:07:05 +0000
49
import java.io.UnsupportedEncodingException ;
bernard
authored
2012-07-02 21:19:19 +0000
50
51
52
53
54
55
56
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
57
import java.util.ArrayList ;
bernard
authored
2012-07-02 21:19:19 +0000
58
import java.util.Arrays ;
bernard
authored
2012-10-31 14:56:42 +0000
59
import java.util.Collections ;
bernard
authored
2011-12-16 08:51:04 +0000
60
import java.util.List ;
bernard
authored
2012-07-02 21:19:19 +0000
61
62
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
bernard
authored
2011-12-05 08:00:02 +0000
63
bernard
authored
2013-04-04 13:08:24 +0000
64
import static pl . itcrowd . youtrack . api . URIUtils . buildURI ;
bernard
authored
2012-11-05 09:56:36 +0000
65
bernard
authored
2011-12-05 08:00:02 +0000
66
67
public class YoutrackAPI {
bernard
authored
2013-04-04 13:12:45 +0000
68
private final static QName Enumeration_QNAME = new QName ( "" , "enumeration" );
bernard
authored
2012-10-31 16:56:49 +0000
69
bernard
authored
2013-04-04 13:12:45 +0000
70
private final static QName Issue_QNAME = new QName ( "" , "issue" );
bernard
authored
2012-07-03 10:50:59 +0000
71
bernard
authored
2013-04-04 13:12:45 +0000
72
private static Log LOG = LogFactory . getLog ( YoutrackAPI . class );
bernard
authored
2012-11-05 09:56:36 +0000
73
bernard
authored
2013-04-04 13:12:45 +0000
74
private HttpClient httpClient ;
bernard
authored
2012-07-02 21:19:19 +0000
75
bernard
authored
2013-04-04 13:12:45 +0000
76
private String serviceLocation ;
bernard
authored
2011-12-05 08:00:02 +0000
77
bernard
authored
2013-04-04 13:12:45 +0000
78
private URI serviceLocationURI ;
bernard
authored
2012-11-05 09:56:36 +0000
79
bernard
authored
2013-04-04 13:12:45 +0000
80
81
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
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
113
bernard
authored
2013-04-04 13:12:45 +0000
114
115
ClientConnectionManager cm = new PoolingClientConnectionManager ( schemeRegistry );
return new DefaultHttpClient ( cm );
bernard
authored
2012-11-04 20:40:52 +0000
116
}
bernard
authored
2012-07-02 21:19:19 +0000
117
bernard
authored
2013-04-04 13:12:45 +0000
118
119
120
121
122
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
123
}
bernard
authored
2013-04-04 13:12:45 +0000
124
125
126
127
for ( int i = 0 ; i < strLen ; i ++) {
if ((! Character . isWhitespace ( str . charAt ( i )))) {
return false ;
}
bernard
authored
2012-11-04 20:40:52 +0000
128
}
bernard
authored
2013-04-04 13:12:45 +0000
129
return true ;
bernard
authored
2012-07-02 21:19:19 +0000
130
}
bernard
authored
2011-12-05 08:00:02 +0000
131
bernard
authored
2013-04-04 13:12:45 +0000
132
133
134
public YoutrackAPI ( String serviceLocation )
{
this ( serviceLocation , null );
bernard
authored
2012-11-04 20:40:52 +0000
135
}
bernard
authored
2013-04-04 13:12:45 +0000
136
137
138
139
140
141
142
143
144
145
146
147
148
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 ;
bernard
authored
2012-07-03 10:19:33 +0000
149
150
}
bernard
authored
2013-04-04 13:12:45 +0000
151
152
153
154
public YoutrackAPI ( String serviceLocation , String username , String password ) throws IOException , JAXBException
{
this ( serviceLocation , null , username , password );
}
bernard
authored
2011-12-05 08:00:02 +0000
155
bernard
authored
2013-04-04 13:12:45 +0000
156
157
158
159
160
public YoutrackAPI ( String serviceLocation , HttpClient httpClient , String username , String password ) throws IOException , JAXBException
{
this ( serviceLocation , httpClient );
login ( username , password );
}
bernard
authored
2012-07-02 21:19:19 +0000
161
bernard
authored
2013-04-04 13:12:45 +0000
162
163
164
public String getServiceLocation ()
{
return serviceLocation ;
bernard
authored
2012-11-05 09:56:36 +0000
165
}
bernard
authored
2013-04-04 13:12:45 +0000
166
167
168
169
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
170
}
bernard
authored
2013-04-04 13:12:45 +0000
171
172
173
174
public void command ( String issueId , Command command ) throws IOException
{
command ( issueId , command . toString ());
bernard
authored
2012-07-03 10:19:33 +0000
175
}
bernard
authored
2013-04-04 13:12:45 +0000
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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
196
}
bernard
authored
2013-04-04 13:12:45 +0000
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/**
* 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
229
}
bernard
authored
2013-04-04 13:12:45 +0000
230
231
232
233
public void deleteIssue ( String issueId ) throws IOException
{
execute ( new HttpDelete ( buildURI ( serviceLocationURI , "/rest/issue/" + issueId )));
bernard
authored
2012-07-02 21:19:19 +0000
234
}
bernard
authored
2013-04-04 13:12:45 +0000
235
236
237
238
239
240
241
242
243
244
public AssigneeList getAssignees ( String project ) throws IOException , JAXBException
{
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-07-05 16:25:04 +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
public Enumeration getBundle ( String customField ) throws IOException , JAXBException
{
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
2012-07-03 10:50:59 +0000
263
}
bernard
authored
2013-04-04 13:12:45 +0000
264
265
266
267
268
269
270
271
272
273
public List < User > getIndividualAssignees ( String project ) throws IOException , JAXBException
{
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
2011-12-05 08:00:02 +0000
274
}
bernard
authored
2013-04-04 13:12:45 +0000
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
public IssueWrapper getIssue ( String issueId ) throws IOException , JAXBException
{
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
2012-10-31 14:56:42 +0000
301
}
bernard
authored
2013-04-04 13:12:45 +0000
302
303
304
305
306
307
public void login ( String username , String password ) throws IOException , JAXBException
{
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
2012-07-02 21:19:19 +0000
308
}
bernard
authored
2013-04-04 13:12:45 +0000
309
310
311
312
313
314
315
316
317
318
319
320
321
322
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
323
}
bernard
authored
2013-04-04 13:12:45 +0000
324
325
326
327
328
329
330
331
332
333
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
334
}
bernard
authored
2013-04-04 13:12:45 +0000
335
336
337
338
private HttpPost createPostRequest ( URI uri , NameValuePair ... nameValuePair ) throws UnsupportedEncodingException
{
return setEntity ( new HttpPost ( uri ), nameValuePair );
bernard
authored
2012-11-04 20:40:52 +0000
339
}
bernard
authored
2013-04-04 13:12:45 +0000
340
341
342
343
private HttpPut createPutRequest ( URI uri , NameValuePair ... nameValuePair ) throws UnsupportedEncodingException
{
return setEntity ( new HttpPut ( uri ), nameValuePair );
bernard
authored
2012-11-04 20:40:52 +0000
344
}
bernard
authored
2013-04-04 13:12:45 +0000
345
346
347
private String execute ( HttpUriRequest request ) throws IOException
{
bernard
authored
2013-05-07 10:25:46 +0000
348
request . addHeader ( "Accept" , "application/xml" );
bernard
authored
2013-04-04 13:12:45 +0000
349
350
351
352
353
354
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
355
final String error = YoutrackUnmarshaller . unmarshalError ( responseText );
bernard
authored
2013-04-04 13:12:45 +0000
356
357
358
359
360
361
362
363
364
365
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
366
}
bernard
authored
2013-04-04 13:12:45 +0000
367
368
369
370
371
372
373
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
374
}
bernard
authored
2013-04-04 13:12:45 +0000
375
376
377
378
379
380
private void throwExceptionsIfNeeded ( StatusLine statusLine , String responseText ) throws IOException
{
if ( statusLine . getStatusCode () >= 300 ) {
throw new HttpResponseException ( statusLine . getStatusCode (), responseText );
}
381
382
}
}