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