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