package org.apache.maven.plugins.jira; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import org.apache.commons.lang.StringUtils; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.changes.AbstractChangesReport; import org.apache.maven.plugins.changes.ProjectUtils; import org.apache.maven.plugins.issues.Issue; import org.apache.maven.plugins.issues.IssueUtils; import org.apache.maven.plugins.issues.IssuesReportGenerator; import org.apache.maven.plugins.issues.IssuesReportHelper; import org.apache.maven.reporting.MavenReportException; import org.apache.maven.settings.Settings; /** * Goal which downloads issues from the Issue Tracking System and generates a report. * * @author <a href="mailto:jruiz@exist.com">Johnny R. Ruiz III</a> * @version $Id$ */ @Mojo( name = "jira-report", threadSafe = true ) public class JiraMojo extends AbstractChangesReport { /** * Valid JIRA columns. */ private static final Map<String, Integer> JIRA_COLUMNS = new HashMap<String, Integer>( 16 ); static { JIRA_COLUMNS.put( "Assignee", IssuesReportHelper.COLUMN_ASSIGNEE ); JIRA_COLUMNS.put( "Component", IssuesReportHelper.COLUMN_COMPONENT ); JIRA_COLUMNS.put( "Created", IssuesReportHelper.COLUMN_CREATED ); JIRA_COLUMNS.put( "Fix Version", IssuesReportHelper.COLUMN_FIX_VERSION ); JIRA_COLUMNS.put( "Id", IssuesReportHelper.COLUMN_ID ); JIRA_COLUMNS.put( "Key", IssuesReportHelper.COLUMN_KEY ); JIRA_COLUMNS.put( "Priority", IssuesReportHelper.COLUMN_PRIORITY ); JIRA_COLUMNS.put( "Reporter", IssuesReportHelper.COLUMN_REPORTER ); JIRA_COLUMNS.put( "Resolution", IssuesReportHelper.COLUMN_RESOLUTION ); JIRA_COLUMNS.put( "Status", IssuesReportHelper.COLUMN_STATUS ); JIRA_COLUMNS.put( "Summary", IssuesReportHelper.COLUMN_SUMMARY ); JIRA_COLUMNS.put( "Type", IssuesReportHelper.COLUMN_TYPE ); JIRA_COLUMNS.put( "Updated", IssuesReportHelper.COLUMN_UPDATED ); JIRA_COLUMNS.put( "Version", IssuesReportHelper.COLUMN_VERSION ); } /** * Sets the names of the columns that you want in the report. The columns will appear in the report in the same * order as you specify them here. Multiple values can be separated by commas. * <p> * Valid columns are: <code>Assignee</code>, <code>Component</code>, <code>Created</code>, <code>Fix Version</code>, * <code>Id</code>, <code>Key</code>, <code>Priority</code>, <code>Reporter</code>, <code>Resolution</code>, * <code>Status</code>, <code>Summary</code>, <code>Type</code>, <code>Updated</code> and <code>Version</code>. * </p> * * @since 2.0 */ @Parameter( defaultValue = "Key,Summary,Status,Resolution,Assignee" ) private String columnNames; /** * Use the JIRA query language instead of the JIRA query based on HTTP parameters. From JIRA 5.1 and up only JQL is * supported. JIRA 4.4 supports both JQL and URL parameter based queries. From 5.1.1 this is obsolete, since REST * queries only use JQL. * * @since 2.8 */ @Parameter( property = "changes.useJql", defaultValue = "false" ) private boolean useJql; /** * Since JIRA 5.1.1, it is no longer possible to construct a URL that downloads RSS. Meanwhile JIRA added a REST API * in 4.2. By default, this plugin uses the REST API if available. Setting this parameter to true forces it to * attempt to use RSS. * * @since 2.9 */ @Parameter( defaultValue = "false" ) private boolean forceRss; /** * Sets the component(s) that you want to limit your report to include. Multiple values can be separated by commas * (such as 10011,10012). If this is set to empty - that means all components will be included. */ @Parameter( defaultValue = "" ) private String component; /** * Defines the filter parameters to restrict which issues are retrieved from JIRA. The filter parameter uses the * same format of url parameters that is used in a JIRA search. */ @Parameter( defaultValue = "" ) private String filter; /** * Sets the fix version id(s) that you want to limit your report to include. These are JIRA's internal version ids, * <b>NOT</b> the human readable display ones. Multiple fix versions can be separated by commas. If this is set to * empty - that means all fix versions will be included. * * @since 2.0 */ @Parameter( defaultValue = "" ) private String fixVersionIds; /** * The pattern used by dates in the JIRA XML-file. This is used to parse the Created and Updated fields. * * @since 2.4 */ @Parameter( defaultValue = "EEE, d MMM yyyy HH:mm:ss Z" ) private String jiraDatePattern; /** * Defines the JIRA password for authentication into a private JIRA installation. */ @Parameter( defaultValue = "" ) private String jiraPassword; /** * Defines the JIRA username for authentication into a private JIRA installation. */ @Parameter( defaultValue = "" ) private String jiraUser; /** * Path to the JIRA XML file, which will be parsed. */ @Parameter( defaultValue = "${project.build.directory}/jira-results.xml", required = true, readonly = true ) private File jiraXmlPath; /** * Maximum number of entries to be fetched from JIRA. */ @Parameter( defaultValue = "100" ) private int maxEntries; /** * If you only want to show issues for the current version in the report. The current version being used is * <code>${project.version}</code> minus any "-SNAPSHOT" suffix. * * @since 2.0 */ @Parameter( defaultValue = "false" ) private boolean onlyCurrentVersion; /** * Sets the priority(s) that you want to limit your report to include. Valid statuses are <code>Blocker</code>, * <code>Critical</code>, <code>Major</code>, <code>Minor</code> and <code>Trivial</code>. Multiple values can be * separated by commas. If this is set to empty - that means all priorities will be included. */ @Parameter( defaultValue = "" ) private String priorityIds; /** * Sets the resolution(s) that you want to fetch from JIRA. Valid resolutions are: <code>Unresolved</code>, * <code>Fixed</code>, <code>Won't Fix</code>, <code>Duplicate</code>, <code>Incomplete</code> and * <code>Cannot Reproduce</code>. Multiple values can be separated by commas. * <p> * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter had no default value. * </p> */ @Parameter( defaultValue = "Fixed" ) private String resolutionIds; /** * Settings XML configuration. */ @Parameter( defaultValue = "${settings}", readonly = true, required = true ) private Settings settings; /** * If set to <code>true</code>, then the JIRA report will not be generated. * * @since 2.8 */ @Parameter( property = "changes.jira.skip", defaultValue = "false" ) private boolean skip; /** * Sets the column names that you want to sort the report by. Add <code>DESC</code> following the column name to * specify <i>descending</i> sequence. For example <code>Fix Version DESC, Type</code> sorts first by the Fix * Version in descending order and then by Type in ascending order. By default sorting is done in ascending order, * but is possible to specify <code>ASC</code> for consistency. The previous example would then become * <code>Fix Version DESC, Type ASC</code>. * <p> * Valid columns are: <code>Assignee</code>, <code>Component</code>, <code>Created</code>, <code>Fix Version</code>, * <code>Id</code>, <code>Key</code>, <code>Priority</code>, <code>Reporter</code>, <code>Resolution</code>, * <code>Status</code>, <code>Summary</code>, <code>Type</code>, <code>Updated</code> and <code>Version</code>. * </p> * <p> * <strong>Note:</strong> If you are using JIRA 4 you need to put your sort column names in the reverse order. The * handling of this changed between JIRA 3 and JIRA 4. The current default value is suitable for JIRA 3. This may * change in the future, so please configure your sort column names in an order that works for your own JIRA * version. If you use JQL, by setting the <code>useJql</code> parameter to <code>true</code>, then the order of the * fields are in normal order again. Starting with JIRA 5.1 you have to use JQL. * </p> * * @since 2.0 */ @Parameter( defaultValue = "Priority DESC, Created DESC" ) private String sortColumnNames; /** * Sets the status(es) that you want to fetch from JIRA. Valid statuses are: <code>Open</code>, * <code>In Progress</code>, <code>Reopened</code>, <code>Resolved</code> and <code>Closed</code>. Multiple values * can be separated by commas. * <p> * If your installation of JIRA uses custom status IDs, you can reference them here by their numeric values. You can * obtain them on the Statuses page (in 4.0.2 it's under Administration > Issue Settings > Statuses) - just hover * over the Edit link for the status you want and you'll see something like <your JIRA * URL>/secure/admin/EditStatus!default.jspa?id=12345; in this case the value is 12345. * </p> * <p> * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter had no default value. * </p> */ @Parameter( defaultValue = "Closed" ) private String statusIds; /** * Sets the types(s) that you want to limit your report to include. Valid types are: <code>Bug</code>, * <code>New Feature</code>, <code>Task</code>, <code>Improvement</code>, <code>Wish</code>, <code>Test</code> and * <code>Sub-task</code>. Multiple values can be separated by commas. If this is set to empty - that means all types * will be included. * * @since 2.0 */ @Parameter( defaultValue = "" ) private String typeIds; /** * The prefix used when naming versions in JIRA. * <p> * If you have a project in JIRA with several components that have different release cycles, it is an often used * pattern to prefix the version with the name of the component, e.g. maven-filtering-1.0 etc. To fetch issues from * JIRA for a release of the "maven-filtering" component you would need to set this parameter to "maven-filtering-". * </p> * * @since 2.4 */ @Parameter( defaultValue = "" ) private String versionPrefix; /** * Defines the http password for basic authentication into the JIRA webserver. */ @Parameter( defaultValue = "" ) private String webPassword; /** * Defines the http user for basic authentication into the JIRA webserver. */ @Parameter( defaultValue = "" ) private String webUser; /* * Used for tests. */ private AbstractJiraDownloader mockDownloader; /* --------------------------------------------------------------------- */ /* Public methods */ /* --------------------------------------------------------------------- */ /** * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport() */ public boolean canGenerateReport() { // Run only at the execution root if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() ) { getLog().info( "Skipping the JIRA Report in this project because it's not the Execution Root" ); return false; } if ( skip ) { return false; } if ( mockDownloader != null ) { return true; } String message = ProjectUtils.validateIssueManagement( project, "JIRA", "JIRA Report" ); if ( message != null ) { getLog().warn( message ); } return message == null; } public void executeReport( Locale locale ) throws MavenReportException { // Validate parameters List<Integer> columnIds = IssuesReportHelper.getColumnIds( columnNames, JIRA_COLUMNS ); if ( columnIds.isEmpty() ) { // This can happen if the user has configured column names and they are all invalid throw new MavenReportException( "maven-changes-plugin: None of the configured columnNames '" + columnNames + "' are valid." ); } try { // Download issues AbstractJiraDownloader issueDownloader; if ( mockDownloader != null ) { issueDownloader = mockDownloader; } else { AdaptiveJiraDownloader downloader = new AdaptiveJiraDownloader(); downloader.setForceClassic( forceRss ); issueDownloader = downloader; } configureIssueDownloader( issueDownloader ); issueDownloader.doExecute(); List<Issue> issueList = issueDownloader.getIssueList(); if ( StringUtils.isNotEmpty( versionPrefix ) ) { int originalNumberOfIssues = issueList.size(); issueList = IssueUtils.filterIssuesWithVersionPrefix( issueList, versionPrefix ); getLog().debug( "Filtered out " + issueList.size() + " issues of " + originalNumberOfIssues + " that matched the versionPrefix '" + versionPrefix + "'." ); } if ( onlyCurrentVersion ) { String version = ( versionPrefix == null ? "" : versionPrefix ) + project.getVersion(); issueList = IssueUtils.getIssuesForVersion( issueList, version ); getLog().info( "The JIRA Report will contain issues only for the current version." ); } // Generate the report IssuesReportGenerator report = new IssuesReportGenerator( IssuesReportHelper.toIntArray( columnIds ) ); if ( issueList.isEmpty() ) { report.doGenerateEmptyReport( getBundle( locale ), getSink() ); } else { report.doGenerateReport( getBundle( locale ), getSink(), issueList ); } } catch ( Exception e ) { getLog().warn( e ); } } public String getDescription( Locale locale ) { return getBundle( locale ).getString( "report.issues.description" ); } public String getName( Locale locale ) { return getBundle( locale ).getString( "report.issues.name" ); } public String getOutputName() { return "jira-report"; } /* --------------------------------------------------------------------- */ /* Private methods */ /* --------------------------------------------------------------------- */ private ResourceBundle getBundle( Locale locale ) { return ResourceBundle.getBundle( "jira-report", locale, this.getClass().getClassLoader() ); } private void configureIssueDownloader( AbstractJiraDownloader issueDownloader ) { issueDownloader.setLog( getLog() ); issueDownloader.setMavenProject( project ); issueDownloader.setOutput( jiraXmlPath ); issueDownloader.setNbEntries( maxEntries ); issueDownloader.setComponent( component ); issueDownloader.setFixVersionIds( fixVersionIds ); issueDownloader.setStatusIds( statusIds ); issueDownloader.setResolutionIds( resolutionIds ); issueDownloader.setPriorityIds( priorityIds ); issueDownloader.setSortColumnNames( sortColumnNames ); issueDownloader.setFilter( filter ); issueDownloader.setJiraDatePattern( jiraDatePattern ); issueDownloader.setJiraUser( jiraUser ); issueDownloader.setJiraPassword( jiraPassword ); issueDownloader.setTypeIds( typeIds ); issueDownloader.setWebUser( webUser ); issueDownloader.setWebPassword( webPassword ); issueDownloader.setSettings( settings ); issueDownloader.setUseJql( useJql ); issueDownloader.setOnlyCurrentVersion( onlyCurrentVersion ); issueDownloader.setVersionPrefix( versionPrefix ); } public void setMockDownloader( AbstractJiraDownloader mockDownloader ) { this.mockDownloader = mockDownloader; } public AbstractJiraDownloader getMockDownloader() { return mockDownloader; } }