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