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