1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.maven.plugin.jira;
21
22 import com.fasterxml.jackson.core.JsonFactory;
23 import com.fasterxml.jackson.core.JsonGenerator;
24 import com.fasterxml.jackson.core.JsonParser;
25 import com.fasterxml.jackson.databind.JsonNode;
26 import com.fasterxml.jackson.databind.MappingJsonFactory;
27 import org.apache.cxf.configuration.security.AuthorizationPolicy;
28 import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
29 import org.apache.cxf.interceptor.LoggingInInterceptor;
30 import org.apache.cxf.interceptor.LoggingOutInterceptor;
31 import org.apache.cxf.jaxrs.client.ClientConfiguration;
32 import org.apache.cxf.jaxrs.client.WebClient;
33 import org.apache.cxf.transport.http.HTTPConduit;
34 import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
35 import org.apache.cxf.transports.http.configuration.ProxyServerType;
36 import org.apache.maven.plugin.MojoExecutionException;
37 import org.apache.maven.plugin.MojoFailureException;
38 import org.apache.maven.plugin.issues.Issue;
39
40 import javax.ws.rs.core.HttpHeaders;
41 import javax.ws.rs.core.MediaType;
42 import javax.ws.rs.core.Response;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.StringWriter;
46 import java.text.ParseException;
47 import java.text.SimpleDateFormat;
48 import java.util.ArrayList;
49 import java.util.Date;
50 import java.util.List;
51 import java.util.Map;
52
53
54
55
56
57
58
59
60 public class RestJiraDownloader extends AbstractJiraDownloader
61 {
62 private List<Issue> issueList;
63 private JsonFactory jsonFactory;
64 private SimpleDateFormat dateFormat;
65
66 private List<String> resolvedFixVersionIds;
67 private List<String> resolvedStatusIds;
68 private List<String> resolvedComponentIds;
69 private List<String> resolvedTypeIds;
70 private List<String> resolvedResolutionIds;
71 private List<String> resolvedPriorityIds;
72
73 private String jiraProject;
74
75 public static class NoRest extends Exception {
76 public NoRest( )
77 {
78
79 }
80 public NoRest( String message )
81 {
82 super( message );
83 }
84 }
85
86 public RestJiraDownloader() {
87 jsonFactory = new MappingJsonFactory( );
88
89 dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" );
90 resolvedFixVersionIds = new ArrayList<String>( );
91 resolvedStatusIds = new ArrayList<String>( );
92 resolvedComponentIds = new ArrayList<String>( );
93 resolvedTypeIds = new ArrayList<String>( );
94 resolvedResolutionIds = new ArrayList<String>( );
95 resolvedPriorityIds = new ArrayList<String>( );
96 }
97
98 public void doExecute()
99 throws Exception
100 {
101
102 Map<String, String> urlMap = JiraHelper.getJiraUrlAndProjectName( project.getIssueManagement().getUrl() );
103 String jiraUrl = urlMap.get( "url" );
104 jiraProject = urlMap.get( "project" );
105
106
107 ClassLoader ccl = Thread.currentThread().getContextClassLoader();
108 try
109 {
110 Thread.currentThread().setContextClassLoader( WebClient.class.getClassLoader( ) );
111 WebClient client = setupWebClient( jiraUrl );
112
113
114
115
116
117 client.replacePath( "/rest/api/2/serverInfo" );
118 client.accept( MediaType.APPLICATION_JSON );
119 Response siResponse = client.get();
120 if ( siResponse.getStatus() != Response.Status.OK.getStatusCode() )
121 {
122 throw new NoRest(
123 "This JIRA server does not support version 2 of the REST API, which maven-changes-plugin requires." );
124 }
125
126 doSessionAuth( client );
127
128 resolveIds( client, jiraProject );
129
130 String jqlQuery = new JqlQueryBuilder( log ).urlEncode( false ).project( jiraProject ).fixVersion(
131 getFixFor() ).fixVersionIds( resolvedFixVersionIds ).statusIds( resolvedStatusIds ).priorityIds(
132 resolvedPriorityIds ).resolutionIds( resolvedResolutionIds ).components( resolvedComponentIds ).typeIds(
133 resolvedTypeIds ).sortColumnNames( sortColumnNames ).build();
134
135 StringWriter searchParamStringWriter = new StringWriter();
136 JsonGenerator gen = jsonFactory.createGenerator( searchParamStringWriter );
137 gen.writeStartObject();
138 gen.writeStringField( "jql", jqlQuery );
139 gen.writeNumberField( "maxResults", nbEntriesMax );
140 gen.writeArrayFieldStart( "fields" );
141
142 gen.writeString( "*all" );
143 gen.writeEndArray();
144 gen.writeEndObject();
145 gen.close();
146 client.replacePath( "/rest/api/2/search" );
147 client.type( MediaType.APPLICATION_JSON_TYPE );
148 client.accept( MediaType.APPLICATION_JSON_TYPE );
149 Response searchResponse = client.post( searchParamStringWriter.toString() );
150 if ( searchResponse.getStatus() != Response.Status.OK.getStatusCode() )
151 {
152 reportErrors( searchResponse );
153 }
154
155 JsonNode issueTree = getResponseTree( searchResponse );
156 assert issueTree.isObject();
157 JsonNode issuesNode = issueTree.get( "issues" );
158 assert issuesNode.isArray();
159 buildIssues( issuesNode, jiraUrl, jiraProject );
160 }
161 finally
162 {
163 Thread.currentThread().setContextClassLoader( ccl );
164 }
165 }
166
167 private JsonNode getResponseTree( Response response )
168 throws IOException
169 {
170 JsonParser jsonParser = jsonFactory.createJsonParser( (InputStream) response.getEntity() );
171 return (JsonNode) jsonParser.readValueAsTree();
172 }
173
174 private void reportErrors( Response resp )
175 throws IOException, MojoExecutionException
176 {
177 if ( MediaType.APPLICATION_JSON_TYPE.getType().equals( getResponseMediaType( resp ).getType() ) )
178 {
179 JsonNode errorTree = getResponseTree( resp );
180 assert errorTree.isObject();
181 JsonNode messages = errorTree.get( "errorMessages" );
182 if ( messages != null )
183 {
184 for ( int mx = 0; mx < messages.size(); mx ++ )
185 {
186 getLog().error( messages.get( mx ).asText() );
187 }
188 }
189 else
190 {
191 JsonNode message = errorTree.get( "message" );
192 if ( message != null )
193 {
194 getLog().error( message.asText() );
195 }
196 }
197 }
198 throw new MojoExecutionException( String.format( "Failed to query issues; response %d", resp.getStatus() ) );
199 }
200
201 private void resolveIds( WebClient client, String jiraProject )
202 throws IOException, MojoExecutionException, MojoFailureException
203 {
204 resolveList( resolvedComponentIds, client, "components", component, "/rest/api/2/project/{key}/components", jiraProject );
205 resolveList( resolvedFixVersionIds, client, "fixVersions", fixVersionIds, "/rest/api/2/project/{key}/versions", jiraProject );
206 resolveList( resolvedStatusIds, client, "status", statusIds, "/rest/api/2/status" );
207 resolveList( resolvedResolutionIds, client, "resolution", resolutionIds, "/rest/api/2/resolution" );
208 resolveList( resolvedTypeIds, client, "type", typeIds, "/rest/api/2/issuetype" );
209 resolveList( resolvedPriorityIds, client, "priority", priorityIds, "/rest/api/2/priority" );
210 }
211
212 private void resolveList( List<String> targetList, WebClient client, String what, String input,
213 String listRestUrlPattern, String... listUrlArgs )
214 throws IOException, MojoExecutionException, MojoFailureException
215 {
216 if ( input == null || input.length() == 0 )
217 {
218 return;
219 }
220 if ( listUrlArgs != null && listUrlArgs.length != 0)
221 {
222 client.replacePath( "/" );
223 client.path( listRestUrlPattern, listUrlArgs );
224 }
225 else
226 {
227 client.replacePath( listRestUrlPattern );
228 }
229 client.accept( MediaType.APPLICATION_JSON );
230 Response resp = client.get();
231 if ( resp.getStatus() != 200 )
232 {
233 getLog().error( String.format( "Could not get %s list from %s", what, listRestUrlPattern ) );
234 reportErrors( resp );
235 }
236
237 JsonNode items = getResponseTree( resp );
238 String[] pieces = input.split( "," );
239 for (String item : pieces ) {
240 targetList.add( resolveOneItem( items, what, item ) );
241 }
242 }
243
244 private String resolveOneItem( JsonNode items, String what, String nameOrId )
245 throws IOException, MojoExecutionException, MojoFailureException
246 {
247 for ( int cx = 0; cx < items.size(); cx ++ )
248 {
249 JsonNode item = items.get( cx );
250 if ( nameOrId.equals( item.get( "id" ).asText() ) )
251 {
252 return nameOrId;
253 }
254 else if ( nameOrId.equals( item.get( "name" ).asText() ) )
255 {
256 return item.get( "id" ).asText();
257 }
258 }
259 throw new MojoFailureException( String.format("Could not find %s %s.", what, nameOrId ) );
260 }
261
262 private MediaType getResponseMediaType( Response response )
263 {
264 String header = (String) response.getMetadata().getFirst( HttpHeaders.CONTENT_TYPE ) ;
265 return header == null ? null : MediaType.valueOf( header );
266 }
267
268 private void buildIssues( JsonNode issuesNode, String jiraUrl, String jiraProject )
269 {
270 issueList = new ArrayList<Issue>( );
271 for ( int ix = 0; ix < issuesNode.size(); ix++ )
272 {
273 JsonNode issueNode = issuesNode.get( ix );
274 assert issueNode.isObject();
275 Issue issue = new Issue();
276 JsonNode val;
277
278 val = issueNode.get( "id" );
279 if ( val != null )
280 {
281 issue.setId( val.asText() );
282 }
283
284 val = issueNode.get( "key" );
285 if ( val != null )
286 {
287 issue.setKey( val.asText() );
288 issue.setLink( String.format( "%s/browse/%s", jiraUrl, val.asText()) );
289 }
290
291
292 JsonNode fieldsNode = issueNode.get( "fields" );
293
294 val = fieldsNode.get( "assignee" );
295 processAssignee( issue, val );
296
297 val = fieldsNode.get( "created" );
298 processCreated( issue, val );
299
300 val = fieldsNode.get( "comment" );
301 processComments( issue, val );
302
303 val = fieldsNode.get( "fixVersions" );
304 processFixVersions( issue, val );
305
306
307 val = fieldsNode.get( "priority" );
308 processPriority( issue, val );
309
310 val = fieldsNode.get( "reporter" );
311 processReporter( issue, val );
312
313 val = fieldsNode.get( "resolution" );
314 processResolution( issue, val );
315
316 val = fieldsNode.get( "status" );
317 processStatus( issue, val );
318
319 val = fieldsNode.get( "summary" );
320 if ( val != null )
321 {
322 issue.setSummary( val.asText() );
323 }
324
325 val = issueNode.get( "title" );
326 if ( val != null )
327 {
328 issue.setTitle( val.asText() );
329 }
330
331 val = issueNode.get( "updated" );
332 processUpdated( issue, val );
333
334 val = issueNode.get( "versions" );
335 processVersions( issue, val );
336
337
338 issueList.add( issue );
339 }
340 }
341
342 private void processVersions( Issue issue, JsonNode val )
343 {
344 StringBuilder sb = new StringBuilder( );
345 if ( val != null )
346 {
347 for ( int vx = 0; vx < val.size(); vx ++ )
348 {
349 sb.append( val.get( vx ).get( "name" ).asText() );
350 sb.append( ", " );
351 }
352 }
353 if ( sb.length() > 0 )
354 {
355
356 issue.setVersion( sb.substring( 0, sb.length() - 2 ) );
357 }
358 }
359
360 private void processStatus( Issue issue, JsonNode val )
361 {
362 if (val != null )
363 {
364 issue.setStatus( val.get( "name" ).asText() );
365 }
366 }
367
368 private void processPriority( Issue issue, JsonNode val )
369 {
370 if (val != null )
371 {
372 issue.setPriority( val.get( "name" ).asText() );
373 }
374 }
375
376 private void processResolution( Issue issue, JsonNode val )
377 {
378 if (val != null )
379 {
380 issue.setResolution( val.get( "name" ).asText() );
381 }
382 }
383
384 private String getPerson( JsonNode val )
385 {
386 JsonNode nameNode = val.get( "displayName" );
387 if ( nameNode == null )
388 {
389 nameNode = val.get( "name" );
390 }
391 if ( nameNode != null )
392 {
393 return nameNode.asText();
394 }
395 else
396 {
397 return null;
398 }
399 }
400
401 private void processAssignee( Issue issue, JsonNode val )
402 {
403 if ( val != null )
404 {
405 String text = getPerson( val );
406 if ( text != null )
407 {
408 issue.setAssignee( text );
409 }
410 }
411 }
412
413 private void processReporter( Issue issue, JsonNode val )
414 {
415 if ( val != null )
416 {
417 String text = getPerson( val );
418 if ( text != null )
419 {
420 issue.setReporter( text );
421 }
422 }
423 }
424
425 private void processCreated( Issue issue, JsonNode val )
426 {
427 if ( val != null )
428 {
429 try
430 {
431 issue.setCreated( parseDate( val ) );
432 }
433 catch ( ParseException e )
434 {
435 getLog().warn( "Invalid created date " + val.asText() );
436 }
437 }
438 }
439
440 private void processUpdated( Issue issue, JsonNode val )
441 {
442 if ( val != null )
443 {
444 try
445 {
446 issue.setUpdated( parseDate( val ) );
447 }
448 catch ( ParseException e )
449 {
450 getLog().warn( "Invalid created date " + val.asText() );
451 }
452 }
453 }
454
455 private Date parseDate( JsonNode val )
456 throws ParseException
457 {
458 return dateFormat.parse( val.asText() );
459 }
460
461 private void processFixVersions( Issue issue, JsonNode val )
462 {
463 if (val != null)
464 {
465 assert val.isArray();
466 for ( int vx = 0; vx < val.size(); vx++ )
467 {
468 JsonNode fvNode = val.get( vx );
469 issue.addFixVersion( fvNode.get( "name" ).asText() );
470 }
471 }
472 }
473
474 private void processComments( Issue issue, JsonNode val )
475 {
476 if ( val != null )
477 {
478 JsonNode commentsArray = val.get( "comments" );
479 for ( int cx = 0; cx < commentsArray.size(); cx++ )
480 {
481 JsonNode cnode = commentsArray.get( cx );
482 issue.addComment( cnode.get( "body" ).asText() );
483 }
484 }
485 }
486
487 private void doSessionAuth( WebClient client )
488 throws IOException, MojoExecutionException, NoRest
489 {
490
491 if ( jiraUser != null )
492 {
493 client.replacePath( "/rest/auth/1/session" );
494 client.type( MediaType.APPLICATION_JSON_TYPE );
495 StringWriter jsWriter = new StringWriter( );
496 JsonGenerator gen = jsonFactory.createGenerator( jsWriter );
497 gen.writeStartObject();
498 gen.writeStringField( "username", jiraUser );
499 gen.writeStringField( "password", jiraPassword );
500 gen.writeEndObject();
501 gen.close();
502 Response authRes = client.post( jsWriter.toString() );
503 if ( authRes.getStatus() != Response.Status.OK.getStatusCode() )
504 {
505 if ( authRes.getStatus() != 401 && authRes.getStatus() != 403 )
506 {
507
508 throw new NoRest();
509 }
510 throw new MojoExecutionException( String.format( "Authentication failure status %d.", authRes.getStatus() ) );
511 }
512 }
513 }
514
515 private WebClient setupWebClient( String jiraUrl )
516 {
517 WebClient client = WebClient.create( jiraUrl );
518
519 ClientConfiguration clientConfiguration = WebClient.getConfig( client );
520 HTTPConduit http = clientConfiguration.getHttpConduit();
521
522 if ( getLog().isDebugEnabled() )
523 {
524 clientConfiguration.getInInterceptors().add( new LoggingInInterceptor( ) );
525 clientConfiguration.getOutInterceptors().add( new LoggingOutInterceptor( ) );
526 }
527
528 HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
529
530 httpClientPolicy.setConnectionTimeout(36000);
531 httpClientPolicy.setAllowChunking(false);
532 httpClientPolicy.setReceiveTimeout(32000);
533
534 if ( proxyHost != null )
535 {
536 getLog().debug( "Using proxy: " + proxyHost + " at port " + proxyPort );
537 httpClientPolicy.setProxyServer( proxyHost );
538 httpClientPolicy.setProxyServerPort( proxyPort );
539 httpClientPolicy.setProxyServerType( ProxyServerType.HTTP );
540 if ( proxyUser != null )
541 {
542 ProxyAuthorizationPolicy proxyAuthorizationPolicy = new ProxyAuthorizationPolicy();
543 proxyAuthorizationPolicy.setAuthorizationType( "Basic" );
544 proxyAuthorizationPolicy.setUserName( proxyUser );
545 proxyAuthorizationPolicy.setPassword( proxyPass );
546 http.setProxyAuthorization( proxyAuthorizationPolicy );
547 }
548 }
549
550 if ( webUser != null )
551 {
552 AuthorizationPolicy authPolicy = new AuthorizationPolicy();
553 authPolicy.setAuthorizationType( "Basic" );
554 authPolicy.setUserName( webUser );
555 authPolicy.setPassword( webPassword );
556 http.setAuthorization( authPolicy );
557 }
558
559 http.setClient(httpClientPolicy);
560 return client;
561 }
562
563 public List<Issue> getIssueList() throws MojoExecutionException
564 {
565 return issueList;
566 }
567 }