1 package org.apache.maven.plugin.jira; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import java.io.File; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Locale; 26 import java.util.Map; 27 import java.util.ResourceBundle; 28 29 import org.apache.commons.lang.StringUtils; 30 import org.apache.maven.plugin.changes.AbstractChangesReport; 31 import org.apache.maven.plugin.changes.ProjectUtils; 32 import org.apache.maven.plugin.issues.Issue; 33 import org.apache.maven.plugin.issues.IssueUtils; 34 import org.apache.maven.plugin.issues.IssuesReportGenerator; 35 import org.apache.maven.plugin.issues.IssuesReportHelper; 36 import org.apache.maven.reporting.MavenReportException; 37 import org.apache.maven.settings.Settings; 38 39 /** 40 * Goal which downloads issues from the Issue Tracking System and generates a report. 41 * 42 * @goal jira-report 43 * @author <a href="mailto:jruiz@exist.com">Johnny R. Ruiz III</a> 44 * @version $Id: JiraMojo.html 816603 2012-05-08 12:53:30Z hboutemy $ 45 * @threadSafe 46 */ 47 public class JiraMojo 48 extends AbstractChangesReport 49 { 50 /** 51 * Valid JIRA columns. 52 */ 53 private static final Map<String,Integer> JIRA_COLUMNS = new HashMap<String,Integer>( 16 ); 54 55 static 56 { 57 JIRA_COLUMNS.put( "Assignee", new Integer( IssuesReportHelper.COLUMN_ASSIGNEE ) ); 58 JIRA_COLUMNS.put( "Component", new Integer( IssuesReportHelper.COLUMN_COMPONENT ) ); 59 JIRA_COLUMNS.put( "Created", new Integer( IssuesReportHelper.COLUMN_CREATED ) ); 60 JIRA_COLUMNS.put( "Fix Version", new Integer( IssuesReportHelper.COLUMN_FIX_VERSION ) ); 61 JIRA_COLUMNS.put( "Id", new Integer( IssuesReportHelper.COLUMN_ID ) ); 62 JIRA_COLUMNS.put( "Key", new Integer( IssuesReportHelper.COLUMN_KEY ) ); 63 JIRA_COLUMNS.put( "Priority", new Integer( IssuesReportHelper.COLUMN_PRIORITY ) ); 64 JIRA_COLUMNS.put( "Reporter", new Integer( IssuesReportHelper.COLUMN_REPORTER ) ); 65 JIRA_COLUMNS.put( "Resolution", new Integer( IssuesReportHelper.COLUMN_RESOLUTION ) ); 66 JIRA_COLUMNS.put( "Status", new Integer( IssuesReportHelper.COLUMN_STATUS ) ); 67 JIRA_COLUMNS.put( "Summary", new Integer( IssuesReportHelper.COLUMN_SUMMARY ) ); 68 JIRA_COLUMNS.put( "Type", new Integer( IssuesReportHelper.COLUMN_TYPE ) ); 69 JIRA_COLUMNS.put( "Updated", new Integer( IssuesReportHelper.COLUMN_UPDATED ) ); 70 JIRA_COLUMNS.put( "Version", new Integer( IssuesReportHelper.COLUMN_VERSION ) ); 71 } 72 73 /** 74 * Sets the names of the columns that you want in the report. The columns 75 * will appear in the report in the same order as you specify them here. 76 * Multiple values can be separated by commas. 77 * <p> 78 * Valid columns are: <code>Assignee</code>, <code>Component</code>, 79 * <code>Created</code>, <code>Fix Version</code>, <code>Id</code>, 80 * <code>Key</code>, <code>Priority</code>, <code>Reporter</code>, 81 * <code>Resolution</code>, <code>Status</code>, <code>Summary</code>, 82 * <code>Type</code>, <code>Updated</code> and <code>Version</code>. 83 * </p> 84 * 85 * @parameter default-value="Key,Summary,Status,Resolution,Assignee" 86 * @since 2.0 87 */ 88 private String columnNames; 89 90 /** 91 * Sets the component(s) that you want to limit your report to include. 92 * Multiple values can be separated by commas (such as 10011,10012). 93 * If this is set to empty - that means all components will be included. 94 * 95 * @parameter default-value="" 96 */ 97 private String component; 98 99 /** 100 * Defines the filter parameters to restrict which issues are retrieved 101 * from JIRA. The filter parameter uses the same format of url 102 * parameters that is used in a JIRA search. 103 * 104 * @parameter default-value="" 105 */ 106 private String filter; 107 108 /** 109 * Sets the fix version id(s) that you want to limit your report to include. 110 * These are JIRA's internal version ids, <b>NOT</b> the human readable display ones. 111 * Multiple fix versions can be separated by commas. 112 * If this is set to empty - that means all fix versions will be included. 113 * 114 * @parameter default-value="" 115 * @since 2.0 116 */ 117 private String fixVersionIds; 118 119 /** 120 * The pattern used by dates in the JIRA XML-file. This is used to parse 121 * the Created and Updated fields. 122 * 123 * @parameter default-value="EEE, d MMM yyyy HH:mm:ss Z" 124 * @since 2.4 125 */ 126 private String jiraDatePattern; 127 128 /** 129 * Defines the JIRA password for authentication into a private JIRA installation. 130 * 131 * @parameter default-value="" 132 */ 133 private String jiraPassword; 134 135 /** 136 * Defines the JIRA username for authentication into a private JIRA installation. 137 * 138 * @parameter default-value="" 139 */ 140 private String jiraUser; 141 142 /** 143 * Path to the JIRA XML file, which will be parsed. 144 * 145 * @parameter expression="${project.build.directory}/jira-results.xml" 146 * @required 147 * @readonly 148 */ 149 private File jiraXmlPath; 150 151 /** 152 * Maximum number of entries to be fetched from JIRA. 153 * 154 * @parameter default-value=100 155 * 156 */ 157 private int maxEntries; 158 159 /** 160 * If you only want to show issues for the current version in the report. 161 * The current version being used is <code>${project.version}</code> minus 162 * any "-SNAPSHOT" suffix. 163 * 164 * @parameter default-value="false" 165 * @since 2.0 166 */ 167 private boolean onlyCurrentVersion; 168 169 /** 170 * Sets the priority(s) that you want to limit your report to include. 171 * Valid statuses are <code>Blocker</code>, <code>Critical</code>, 172 * <code>Major</code>, <code>Minor</code> and <code>Trivial</code>. 173 * Multiple values can be separated by commas. 174 * If this is set to empty - that means all priorities will be included. 175 * 176 * @parameter default-value="" 177 */ 178 private String priorityIds; 179 180 /** 181 * Sets the resolution(s) that you want to fetch from JIRA. 182 * Valid resolutions are: <code>Unresolved</code>, <code>Fixed</code>, 183 * <code>Won't Fix</code>, <code>Duplicate</code>, <code>Incomplete</code> 184 * and <code>Cannot Reproduce</code>. 185 * Multiple values can be separated by commas. 186 * <p> 187 * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter had no 188 * default value. 189 * </p> 190 * 191 * @parameter default-value="Fixed" 192 */ 193 private String resolutionIds; 194 195 /** 196 * Settings XML configuration. 197 * 198 * @parameter expression="${settings}" 199 * @required 200 * @readonly 201 */ 202 private Settings settings; 203 204 /** 205 * Sets the column names that you want to sort the report by. Add 206 * <code>DESC</code> following the column name 207 * to specify <i>descending</i> sequence. For 208 * example <code>Fix Version DESC, Type</code> sorts first by 209 * the Fix Version in descending order and then by Type in 210 * ascending order. By default sorting is done in ascending order, but is 211 * possible to specify <code>ASC</code> for consistency. The previous 212 * example would then become <code>Fix Version DESC, Type ASC</code>. 213 * <p> 214 * Valid columns are: <code>Assignee</code>, <code>Component</code>, 215 * <code>Created</code>, <code>Fix Version</code>, <code>Id</code>, 216 * <code>Key</code>, <code>Priority</code>, <code>Reporter</code>, 217 * <code>Resolution</code>, <code>Status</code>, <code>Summary</code>, 218 * <code>Type</code>, <code>Updated</code> and <code>Version</code>. 219 * </p> 220 * <p> 221 * <strong>Note:</strong> If you are using JIRA 4 you need to put your 222 * sort column names in the reverse order. The handling of this changed 223 * between JIRA 3 and JIRA 4. The current default value is suitable for 224 * JIRA 3. This may change in the future, so please configure your sort 225 * column names in an order that works for your own JIRA version. 226 * </p> 227 * 228 * @parameter default-value="Priority DESC, Created DESC" 229 * @since 2.0 230 */ 231 private String sortColumnNames; 232 233 /** 234 * Sets the status(es) that you want to fetch from JIRA. 235 * Valid statuses are: <code>Open</code>, <code>In Progress</code>, 236 * <code>Reopened</code>, <code>Resolved</code> and <code>Closed</code>. 237 * Multiple values can be separated by commas. 238 * <p> 239 * If your installation of JIRA uses custom status IDs, you can reference 240 * them here by their numeric values. 241 * You can obtain them on the Statuses page 242 * (in 4.0.2 it's under Administration > Issue Settings > Statuses) 243 * - just hover over the Edit link for the status you want and 244 * you'll see something like 245 * <your JIRA URL>/secure/admin/EditStatus!default.jspa?id=12345; 246 * in this case the value is 12345. 247 * </p> 248 * <p> 249 * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter had no 250 * default value. 251 * </p> 252 * 253 * @parameter default-value="Closed" 254 */ 255 private String statusIds; 256 257 /** 258 * Sets the types(s) that you want to limit your report to include. 259 * Valid types are: <code>Bug</code>, <code>New Feature</code>, 260 * <code>Task</code>, <code>Improvement</code>, <code>Wish</code>, 261 * <code>Test</code> and <code>Sub-task</code>. 262 * Multiple values can be separated by commas. 263 * If this is set to empty - that means all types will be included. 264 * 265 * @parameter default-value="" 266 * @since 2.0 267 */ 268 private String typeIds; 269 270 /** 271 * The prefix used when naming versions in JIRA. 272 * <p> 273 * If you have a project in JIRA with several components that have different 274 * release cycles, it is an often used pattern to prefix the version with 275 * the name of the component, e.g. maven-filtering-1.0 etc. To fetch issues 276 * from JIRA for a release of the "maven-filtering" component you would need 277 * to set this parameter to "maven-filtering-". 278 * </p> 279 * 280 * @parameter default-value="" 281 * @since 2.4 282 */ 283 private String versionPrefix; 284 285 /** 286 * Defines the http password for basic authentication into the JIRA webserver. 287 * 288 * @parameter default-value="" 289 */ 290 private String webPassword; 291 292 /** 293 * Defines the http user for basic authentication into the JIRA webserver. 294 * 295 * @parameter default-value="" 296 */ 297 private String webUser; 298 299 /* 300 * Used for tests. 301 */ 302 private AbstractJiraDownloader mockDownloader; 303 304 /* --------------------------------------------------------------------- */ 305 /* Public methods */ 306 /* --------------------------------------------------------------------- */ 307 308 /** 309 * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport() 310 */ 311 public boolean canGenerateReport() 312 { 313 if ( mockDownloader != null ) 314 { 315 return true; 316 } 317 return ProjectUtils.validateIfIssueManagementComplete( project, "JIRA", "JIRA Report", getLog() ); 318 } 319 320 public void executeReport( Locale locale ) 321 throws MavenReportException 322 { 323 // Validate parameters 324 List<Integer> columnIds = IssuesReportHelper.getColumnIds( columnNames, JIRA_COLUMNS ); 325 if ( columnIds.isEmpty() ) 326 { 327 // This can happen if the user has configured column names and they are all invalid 328 throw new MavenReportException( 329 "maven-changes-plugin: None of the configured columnNames '" + columnNames + "' are valid." ); 330 } 331 332 try 333 { 334 // Download issues 335 AbstractJiraDownloader issueDownloader; 336 if ( mockDownloader != null ) 337 { 338 issueDownloader = mockDownloader; 339 } 340 else 341 { 342 issueDownloader = new JiraDownloader(); 343 } 344 configureIssueDownloader( issueDownloader ); 345 issueDownloader.doExecute(); 346 347 List<Issue> issueList = issueDownloader.getIssueList(); 348 349 if ( StringUtils.isNotEmpty( versionPrefix ) ) 350 { 351 int originalNumberOfIssues = issueList.size(); 352 issueList = IssueUtils.filterIssuesWithVersionPrefix( issueList, versionPrefix ); 353 getLog().debug( "Filtered out " + issueList.size() + " issues of " + originalNumberOfIssues 354 + " that matched the versionPrefix '" + versionPrefix + "'." ); 355 } 356 357 if ( onlyCurrentVersion ) 358 { 359 String version = ( versionPrefix == null ? "" : versionPrefix ) + project.getVersion(); 360 issueList = IssueUtils.getIssuesForVersion( issueList, version ); 361 getLog().info( "The JIRA Report will contain issues only for the current version." ); 362 } 363 364 // Generate the report 365 IssuesReportGenerator report = new IssuesReportGenerator( IssuesReportHelper.toIntArray( columnIds ) ); 366 367 if ( issueList.isEmpty() ) 368 { 369 report.doGenerateEmptyReport( getBundle( locale ), getSink() ); 370 } 371 else 372 { 373 report.doGenerateReport( getBundle( locale ), getSink(), issueList ); 374 } 375 } 376 catch ( Exception e ) 377 { 378 getLog().warn( e ); 379 } 380 } 381 382 public String getDescription( Locale locale ) 383 { 384 return getBundle( locale ).getString( "report.issues.description" ); 385 } 386 387 public String getName( Locale locale ) 388 { 389 return getBundle( locale ).getString( "report.issues.name" ); 390 } 391 392 public String getOutputName() 393 { 394 return "jira-report"; 395 } 396 397 /* --------------------------------------------------------------------- */ 398 /* Private methods */ 399 /* --------------------------------------------------------------------- */ 400 401 private ResourceBundle getBundle( Locale locale ) 402 { 403 return ResourceBundle.getBundle( "jira-report", locale, this.getClass().getClassLoader() ); 404 } 405 406 private void configureIssueDownloader( AbstractJiraDownloader issueDownloader ) 407 { 408 issueDownloader.setLog( getLog() ); 409 410 issueDownloader.setMavenProject( project ); 411 412 issueDownloader.setOutput( jiraXmlPath ); 413 414 issueDownloader.setNbEntries( maxEntries ); 415 416 issueDownloader.setComponent( component ); 417 418 issueDownloader.setFixVersionIds( fixVersionIds ); 419 420 issueDownloader.setStatusIds( statusIds ); 421 422 issueDownloader.setResolutionIds( resolutionIds ); 423 424 issueDownloader.setPriorityIds( priorityIds ); 425 426 issueDownloader.setSortColumnNames( sortColumnNames ); 427 428 issueDownloader.setFilter( filter ); 429 430 issueDownloader.setJiraDatePattern( jiraDatePattern ); 431 432 issueDownloader.setJiraUser( jiraUser ); 433 434 issueDownloader.setJiraPassword( jiraPassword ); 435 436 issueDownloader.setTypeIds( typeIds ); 437 438 issueDownloader.setWebUser( webUser ); 439 440 issueDownloader.setWebPassword( webPassword ); 441 442 issueDownloader.setSettings( settings ); 443 } 444 445 public void setMockDownloader( AbstractJiraDownloader mockDownloader ) 446 { 447 this.mockDownloader = mockDownloader; 448 } 449 450 public AbstractJiraDownloader getMockDownloader() 451 { 452 return mockDownloader; 453 } 454 }