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