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