Coverage Report - org.apache.maven.plugin.jira.RestJiraDownloader
 
Classes in this File Line Coverage Branch Coverage Complexity
RestJiraDownloader
0%
0/239
0%
0/114
3.231
RestJiraDownloader$NoRest
0%
0/4
N/A
3.231
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 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  
  * Use the JIRA REST API to implement the download.
 55  
  *
 56  
  * This class assumes that the URL points to a copy of JIRA that implements the REST API.
 57  
  * A static function may be forthcoming in here to probe and see if a given URL supports it.
 58  
  *
 59  
  */
 60  0
 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  0
         {
 78  
             // blank on purpose.
 79  0
         }
 80  
         public NoRest( String message )
 81  
         {
 82  0
             super( message );
 83  0
         }
 84  
     }
 85  
 
 86  0
     public RestJiraDownloader() {
 87  0
         jsonFactory = new MappingJsonFactory(  );
 88  
         //2012-07-17T06:26:47.723-0500
 89  0
         dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" );
 90  0
         resolvedFixVersionIds = new ArrayList<String>(  );
 91  0
         resolvedStatusIds = new ArrayList<String>(  );
 92  0
         resolvedComponentIds = new ArrayList<String>(  );
 93  0
         resolvedTypeIds = new ArrayList<String>(  );
 94  0
         resolvedResolutionIds = new ArrayList<String>(  );
 95  0
         resolvedPriorityIds = new ArrayList<String>(  );
 96  0
     }
 97  
 
 98  
     public void doExecute()
 99  
         throws Exception
 100  
     {
 101  
 
 102  0
         Map<String, String> urlMap = JiraHelper.getJiraUrlAndProjectName( project.getIssueManagement().getUrl() );
 103  0
         String jiraUrl = urlMap.get( "url" );
 104  0
         jiraProject = urlMap.get( "project" );
 105  
 
 106  
         // This classloader juggling is a workaround for a classic Maven 2 class loader management bug.
 107  0
         ClassLoader ccl = Thread.currentThread().getContextClassLoader();
 108  
         try
 109  
         {
 110  0
             Thread.currentThread().setContextClassLoader( WebClient.class.getClassLoader( ) );
 111  0
             WebClient client = setupWebClient( jiraUrl );
 112  
 
 113  
             // We use version 2 of the REST API, that first appeared in JIRA 5
 114  
             // Check if version 2 of the REST API is supported
 115  
             // http://docs.atlassian.com/jira/REST/5.0/
 116  
             // Note that serverInfo can always be accessed without authentication
 117  0
             client.replacePath( "/rest/api/2/serverInfo" );
 118  0
             client.accept( MediaType.APPLICATION_JSON );
 119  0
             Response siResponse = client.get();
 120  0
             if ( siResponse.getStatus() != Response.Status.OK.getStatusCode() )
 121  
             {
 122  0
                 throw new NoRest(
 123  
                     "This JIRA server does not support version 2 of the REST API, which maven-changes-plugin requires." );
 124  
             }
 125  
 
 126  0
             doSessionAuth( client );
 127  
 
 128  0
             resolveIds( client, jiraProject );
 129  
 
 130  0
             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  0
             StringWriter searchParamStringWriter = new StringWriter();
 136  0
             JsonGenerator gen = jsonFactory.createGenerator( searchParamStringWriter );
 137  0
             gen.writeStartObject();
 138  0
             gen.writeStringField( "jql", jqlQuery );
 139  0
             gen.writeNumberField( "maxResults", nbEntriesMax );
 140  0
             gen.writeArrayFieldStart( "fields" );
 141  
             // Retrieve all fields. If that seems slow, we can reconsider.
 142  0
             gen.writeString( "*all" );
 143  0
             gen.writeEndArray();
 144  0
             gen.writeEndObject();
 145  0
             gen.close();
 146  0
             client.replacePath( "/rest/api/2/search" );
 147  0
             client.type( MediaType.APPLICATION_JSON_TYPE );
 148  0
             client.accept( MediaType.APPLICATION_JSON_TYPE );
 149  0
             Response searchResponse = client.post( searchParamStringWriter.toString() );
 150  0
             if ( searchResponse.getStatus() != Response.Status.OK.getStatusCode() )
 151  
             {
 152  0
                 reportErrors( searchResponse );
 153  
             }
 154  
 
 155  0
             JsonNode issueTree = getResponseTree( searchResponse );
 156  0
             assert issueTree.isObject();
 157  0
             JsonNode issuesNode = issueTree.get( "issues" );
 158  0
             assert issuesNode.isArray();
 159  0
             buildIssues( issuesNode, jiraUrl, jiraProject );
 160  
         }
 161  
         finally
 162  
         {
 163  0
             Thread.currentThread().setContextClassLoader( ccl );
 164  0
         }
 165  0
     }
 166  
 
 167  
     private JsonNode getResponseTree( Response response )
 168  
         throws IOException
 169  
     {
 170  0
         JsonParser jsonParser = jsonFactory.createJsonParser( (InputStream) response.getEntity() );
 171  0
         return (JsonNode) jsonParser.readValueAsTree();
 172  
     }
 173  
 
 174  
     private void reportErrors( Response resp )
 175  
         throws IOException, MojoExecutionException
 176  
     {
 177  0
         if ( MediaType.APPLICATION_JSON_TYPE.getType().equals( getResponseMediaType( resp ).getType() ) )
 178  
         {
 179  0
             JsonNode errorTree = getResponseTree( resp );
 180  0
             assert errorTree.isObject();
 181  0
             JsonNode messages = errorTree.get( "errorMessages" );
 182  0
             if ( messages != null )
 183  
             {
 184  0
                 for ( int mx = 0; mx < messages.size(); mx ++ )
 185  
                 {
 186  0
                     getLog().error( messages.get( mx ).asText() );
 187  
                 }
 188  
             }
 189  
             else
 190  
             {
 191  0
                 JsonNode message = errorTree.get( "message" );
 192  0
                 if ( message != null )
 193  
                 {
 194  0
                     getLog().error( message.asText() );
 195  
                 }
 196  
             }
 197  
         }
 198  0
         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  0
         resolveList( resolvedComponentIds, client, "components",  component, "/rest/api/2/project/{key}/components", jiraProject );
 205  0
         resolveList( resolvedFixVersionIds, client, "fixVersions", fixVersionIds, "/rest/api/2/project/{key}/versions", jiraProject );
 206  0
         resolveList( resolvedStatusIds, client, "status", statusIds, "/rest/api/2/status" );
 207  0
         resolveList( resolvedResolutionIds, client, "resolution", resolutionIds, "/rest/api/2/resolution" );
 208  0
         resolveList( resolvedTypeIds, client, "type", typeIds, "/rest/api/2/issuetype" );
 209  0
         resolveList( resolvedPriorityIds, client, "priority", priorityIds, "/rest/api/2/priority" );
 210  0
     }
 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  0
         if ( input == null || input.length() == 0 )
 217  
         {
 218  0
             return;
 219  
         }
 220  0
         if ( listUrlArgs != null && listUrlArgs.length != 0)
 221  
         {
 222  0
             client.replacePath( "/" );
 223  0
             client.path( listRestUrlPattern, listUrlArgs );
 224  
         }
 225  
         else
 226  
         {
 227  0
             client.replacePath( listRestUrlPattern );
 228  
         }
 229  0
         client.accept( MediaType.APPLICATION_JSON );
 230  0
         Response resp = client.get();
 231  0
         if ( resp.getStatus() != 200 )
 232  
         {
 233  0
             getLog().error( String.format( "Could not get %s list from %s", what, listRestUrlPattern ) );
 234  0
             reportErrors( resp );
 235  
         }
 236  
 
 237  0
         JsonNode items = getResponseTree( resp );
 238  0
         String[] pieces = input.split( "," );
 239  0
         for (String item : pieces ) {
 240  0
             targetList.add( resolveOneItem( items, what, item ) );
 241  
         }
 242  0
     }
 243  
 
 244  
     private String resolveOneItem( JsonNode items, String what, String nameOrId )
 245  
         throws IOException, MojoExecutionException, MojoFailureException
 246  
     {
 247  0
         for ( int cx = 0; cx < items.size(); cx ++ )
 248  
         {
 249  0
             JsonNode item = items.get( cx );
 250  0
             if ( nameOrId.equals( item.get( "id" ).asText() ) )
 251  
             {
 252  0
                 return nameOrId;
 253  
             }
 254  0
             else if ( nameOrId.equals( item.get( "name" ).asText() ) )
 255  
             {
 256  0
                 return item.get( "id" ).asText();
 257  
             }
 258  
         }
 259  0
         throw new MojoFailureException( String.format("Could not find %s %s.", what, nameOrId ) );
 260  
     }
 261  
 
 262  
     private MediaType getResponseMediaType( Response response )
 263  
     {
 264  0
         String header = (String) response.getMetadata().getFirst( HttpHeaders.CONTENT_TYPE ) ;
 265  0
         return header == null ? null : MediaType.valueOf( header );
 266  
     }
 267  
 
 268  
     private void buildIssues( JsonNode issuesNode, String jiraUrl, String jiraProject )
 269  
     {
 270  0
         issueList = new ArrayList<Issue>(  );
 271  0
         for ( int ix = 0; ix < issuesNode.size(); ix++ )
 272  
         {
 273  0
             JsonNode issueNode = issuesNode.get( ix );
 274  0
             assert issueNode.isObject();
 275  0
             Issue issue = new Issue();
 276  
             JsonNode val;
 277  
 
 278  0
             val = issueNode.get( "id" );
 279  0
             if ( val != null )
 280  
             {
 281  0
                 issue.setId( val.asText() );
 282  
             }
 283  
 
 284  0
             val = issueNode.get( "key" );
 285  0
             if ( val != null )
 286  
             {
 287  0
                 issue.setKey( val.asText() );
 288  0
                 issue.setLink( String.format( "%s/browse/%s", jiraUrl, val.asText()) );
 289  
             }
 290  
 
 291  
             // much of what we want is in here.
 292  0
             JsonNode fieldsNode = issueNode.get( "fields" );
 293  
 
 294  0
             val = fieldsNode.get( "assignee" );
 295  0
             processAssignee( issue, val );
 296  
 
 297  0
             val = fieldsNode.get( "created" );
 298  0
             processCreated( issue, val );
 299  
 
 300  0
             val = fieldsNode.get( "comment" );
 301  0
             processComments( issue, val );
 302  
 
 303  0
             val = fieldsNode.get( "fixVersions" );
 304  0
             processFixVersions( issue, val );
 305  
 
 306  
 
 307  0
             val = fieldsNode.get( "priority" );
 308  0
             processPriority( issue, val );
 309  
 
 310  0
             val = fieldsNode.get( "reporter" );
 311  0
             processReporter( issue, val );
 312  
 
 313  0
             val = fieldsNode.get( "resolution" );
 314  0
             processResolution( issue, val );
 315  
 
 316  0
             val = fieldsNode.get( "status" );
 317  0
             processStatus( issue, val );
 318  
 
 319  0
             val = fieldsNode.get( "summary" );
 320  0
             if ( val != null )
 321  
             {
 322  0
                 issue.setSummary( val.asText() );
 323  
             }
 324  
 
 325  0
             val = issueNode.get( "title" );
 326  0
             if ( val != null )
 327  
             {
 328  0
                 issue.setTitle( val.asText() );
 329  
             }
 330  
 
 331  0
             val = issueNode.get( "updated" );
 332  0
             processUpdated( issue, val );
 333  
 
 334  0
             val = issueNode.get( "versions" );
 335  0
             processVersions( issue, val );
 336  
 
 337  
 
 338  0
             issueList.add( issue );
 339  
         }
 340  0
     }
 341  
 
 342  
     private void processVersions( Issue issue, JsonNode val )
 343  
     {
 344  0
         StringBuilder sb = new StringBuilder( );
 345  0
         if ( val != null )
 346  
         {
 347  0
             for ( int vx = 0; vx < val.size(); vx ++ )
 348  
             {
 349  0
                 sb.append( val.get( vx ).get( "name" ).asText() );
 350  0
                 sb.append( ", " );
 351  
             }
 352  
         }
 353  0
         if ( sb.length() > 0 )
 354  
         {
 355  
             // remove last ", "
 356  0
             issue.setVersion( sb.substring( 0, sb.length() - 2 ) );
 357  
         }
 358  0
     }
 359  
 
 360  
     private void processStatus( Issue issue, JsonNode val )
 361  
     {
 362  0
         if (val != null )
 363  
         {
 364  0
             issue.setStatus( val.get( "name" ).asText() );
 365  
         }
 366  0
     }
 367  
 
 368  
     private void processPriority( Issue issue, JsonNode val )
 369  
     {
 370  0
         if (val != null )
 371  
         {
 372  0
             issue.setPriority( val.get( "name" ).asText() );
 373  
         }
 374  0
     }
 375  
 
 376  
     private void processResolution( Issue issue, JsonNode val )
 377  
     {
 378  0
         if (val != null )
 379  
         {
 380  0
             issue.setResolution( val.get( "name" ).asText() );
 381  
         }
 382  0
     }
 383  
 
 384  
     private String getPerson( JsonNode val )
 385  
     {
 386  0
         JsonNode nameNode = val.get( "displayName" );
 387  0
         if ( nameNode == null )
 388  
         {
 389  0
             nameNode = val.get( "name" );
 390  
         }
 391  0
         if ( nameNode != null )
 392  
         {
 393  0
             return nameNode.asText();
 394  
         }
 395  
         else
 396  
         {
 397  0
             return null;
 398  
         }
 399  
     }
 400  
 
 401  
     private void processAssignee( Issue issue, JsonNode val )
 402  
     {
 403  0
         if ( val != null )
 404  
         {
 405  0
             String text = getPerson( val );
 406  0
             if ( text != null )
 407  
             {
 408  0
                 issue.setAssignee( text );
 409  
             }
 410  
         }
 411  0
     }
 412  
 
 413  
     private void processReporter( Issue issue, JsonNode val )
 414  
     {
 415  0
         if ( val != null )
 416  
         {
 417  0
             String text = getPerson( val );
 418  0
             if ( text != null )
 419  
             {
 420  0
                 issue.setReporter( text );
 421  
             }
 422  
         }
 423  0
     }
 424  
 
 425  
     private void processCreated( Issue issue, JsonNode val )
 426  
     {
 427  0
         if ( val != null )
 428  
         {
 429  
             try
 430  
             {
 431  0
                 issue.setCreated( parseDate( val ) );
 432  
             }
 433  0
             catch ( ParseException e )
 434  
             {
 435  0
                 getLog().warn( "Invalid created date " + val.asText() );
 436  0
             }
 437  
         }
 438  0
     }
 439  
 
 440  
     private void processUpdated( Issue issue, JsonNode val )
 441  
     {
 442  0
         if ( val != null )
 443  
         {
 444  
             try
 445  
             {
 446  0
                 issue.setUpdated( parseDate( val ) );
 447  
             }
 448  0
             catch ( ParseException e )
 449  
             {
 450  0
                 getLog().warn( "Invalid created date " + val.asText() );
 451  0
             }
 452  
         }
 453  0
     }
 454  
 
 455  
     private Date parseDate( JsonNode val )
 456  
         throws ParseException
 457  
     {
 458  0
         return dateFormat.parse( val.asText() );
 459  
     }
 460  
 
 461  
     private void processFixVersions( Issue issue, JsonNode val )
 462  
     {
 463  0
         if (val != null)
 464  
         {
 465  0
             assert val.isArray();
 466  0
             for ( int vx = 0; vx < val.size(); vx++ )
 467  
             {
 468  0
                 JsonNode fvNode = val.get( vx );
 469  0
                 issue.addFixVersion( fvNode.get( "name" ).asText() );
 470  
             }
 471  
         }
 472  0
     }
 473  
 
 474  
     private void processComments( Issue issue, JsonNode val )
 475  
     {
 476  0
         if ( val != null )
 477  
         {
 478  0
             JsonNode commentsArray = val.get( "comments" );
 479  0
             for ( int cx = 0; cx < commentsArray.size(); cx++ )
 480  
             {
 481  0
                 JsonNode cnode = commentsArray.get( cx );
 482  0
                 issue.addComment( cnode.get( "body" ).asText() );
 483  
             }
 484  
         }
 485  0
     }
 486  
 
 487  
     private void doSessionAuth( WebClient client )
 488  
         throws IOException, MojoExecutionException, NoRest
 489  
     {
 490  
         /* if JiraUser is specified instead of WebUser, we need to make a session. */
 491  0
         if ( jiraUser != null )
 492  
         {
 493  0
             client.replacePath( "/rest/auth/1/session" );
 494  0
             client.type( MediaType.APPLICATION_JSON_TYPE );
 495  0
             StringWriter jsWriter = new StringWriter( );
 496  0
             JsonGenerator gen = jsonFactory.createGenerator( jsWriter );
 497  0
             gen.writeStartObject();
 498  0
             gen.writeStringField( "username", jiraUser );
 499  0
             gen.writeStringField( "password", jiraPassword );
 500  0
             gen.writeEndObject();
 501  0
             gen.close();
 502  0
             Response authRes = client.post( jsWriter.toString() );
 503  0
             if ( authRes.getStatus() != Response.Status.OK.getStatusCode() )
 504  
             {
 505  0
                 if ( authRes.getStatus() != 401 && authRes.getStatus() != 403 )
 506  
                 {
 507  
                     // if not one of the documented failures, assume that there's no rest in there in the first place.
 508  0
                     throw new NoRest();
 509  
                 }
 510  0
                 throw new MojoExecutionException( String.format( "Authentication failure status %d.", authRes.getStatus() ) );
 511  
             }
 512  
         }
 513  0
     }
 514  
 
 515  
     private WebClient setupWebClient( String jiraUrl )
 516  
     {
 517  0
         WebClient client = WebClient.create( jiraUrl );
 518  
 
 519  0
         ClientConfiguration clientConfiguration = WebClient.getConfig( client );
 520  0
         HTTPConduit http = clientConfiguration.getHttpConduit();
 521  
 
 522  0
         if ( getLog().isDebugEnabled() )
 523  
         {
 524  0
             clientConfiguration.getInInterceptors().add( new LoggingInInterceptor(  ) );
 525  0
             clientConfiguration.getOutInterceptors().add( new LoggingOutInterceptor(  ) );
 526  
         }
 527  
 
 528  0
         HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
 529  
 
 530  0
         httpClientPolicy.setConnectionTimeout(36000);
 531  0
         httpClientPolicy.setAllowChunking(false);
 532  0
         httpClientPolicy.setReceiveTimeout(32000);
 533  
 
 534  0
         if ( proxyHost != null )
 535  
         {
 536  0
             getLog().debug( "Using proxy: " + proxyHost + " at port " + proxyPort );
 537  0
             httpClientPolicy.setProxyServer( proxyHost );
 538  0
             httpClientPolicy.setProxyServerPort( proxyPort );
 539  0
             httpClientPolicy.setProxyServerType( ProxyServerType.HTTP );
 540  0
             if ( proxyUser != null )
 541  
             {
 542  0
                 ProxyAuthorizationPolicy proxyAuthorizationPolicy = new ProxyAuthorizationPolicy();
 543  0
                 proxyAuthorizationPolicy.setAuthorizationType( "Basic" );
 544  0
                 proxyAuthorizationPolicy.setUserName( proxyUser );
 545  0
                 proxyAuthorizationPolicy.setPassword( proxyPass );
 546  0
                 http.setProxyAuthorization( proxyAuthorizationPolicy );
 547  
             }
 548  
         }
 549  
 
 550  0
         if ( webUser != null )
 551  
         {
 552  0
             AuthorizationPolicy authPolicy = new AuthorizationPolicy();
 553  0
             authPolicy.setAuthorizationType( "Basic" );
 554  0
             authPolicy.setUserName( webUser );
 555  0
             authPolicy.setPassword( webPassword );
 556  0
             http.setAuthorization( authPolicy );
 557  
         }
 558  
 
 559  0
         http.setClient(httpClientPolicy);
 560  0
         return client;
 561  
     }
 562  
 
 563  
     public List<Issue> getIssueList() throws MojoExecutionException
 564  
     {
 565  0
         return issueList;
 566  
     }
 567  
 }