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