1 package org.apache.maven.plugin.changes;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.net.URL;
25 import java.text.SimpleDateFormat;
26 import java.util.Collections;
27 import java.util.Date;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.ResourceBundle;
34
35 import org.apache.commons.collections.map.CaseInsensitiveMap;
36 import org.apache.maven.execution.MavenSession;
37 import org.apache.maven.plugins.annotations.Component;
38 import org.apache.maven.plugins.annotations.Mojo;
39 import org.apache.maven.plugins.annotations.Parameter;
40 import org.apache.maven.project.MavenProject;
41 import org.apache.maven.reporting.MavenReportException;
42 import org.apache.maven.shared.filtering.MavenFileFilter;
43 import org.apache.maven.shared.filtering.MavenFileFilterRequest;
44 import org.apache.maven.shared.filtering.MavenFilteringException;
45 import org.codehaus.plexus.util.FileUtils;
46 import org.codehaus.plexus.util.IOUtil;
47 import org.codehaus.plexus.util.ReaderFactory;
48 import org.codehaus.plexus.util.StringUtils;
49 import org.codehaus.plexus.util.xml.XmlStreamReader;
50
51
52
53
54
55
56
57 @Mojo( name = "changes-report", threadSafe = true )
58 public class ChangesMojo
59 extends AbstractChangesReport
60 {
61
62
63
64
65
66
67 @Parameter( defaultValue = "false" )
68 private boolean aggregated;
69
70
71
72
73
74
75
76 @Parameter( property = "changes.addActionDate", defaultValue = "false" )
77 private boolean addActionDate;
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 @Parameter( defaultValue = "true" )
97 private boolean escapeHTML;
98
99
100
101
102
103
104 @Parameter( defaultValue = "${project.build.directory}/changes", required = true, readonly = true )
105 private File filteredOutputDirectory;
106
107
108
109
110
111
112 @Parameter( defaultValue = "false" )
113 private boolean filteringChanges;
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 @Parameter( property = "changes.issueLinkTemplate", defaultValue = "%URL%/ViewIssue.jspa?key=%ISSUE%" )
129 private String issueLinkTemplate;
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 @Parameter
153 private Map issueLinkTemplatePerSystem;
154
155
156
157
158 @Component
159 private MavenFileFilter mavenFileFilter;
160
161
162
163
164
165
166
167 @Parameter( defaultValue = "yyyy-MM-dd" )
168 private String publishDateFormat;
169
170
171
172
173
174
175
176 @Parameter( defaultValue = "en" )
177 private String publishDateLocale;
178
179
180
181
182 @Component
183 protected MavenSession session;
184
185
186
187
188 @Parameter( defaultValue = "${project.issueManagement.system}", readonly = true )
189 private String system;
190
191
192
193
194
195
196
197 @Parameter( defaultValue = "team-list.html" )
198 private String teamlist;
199
200
201
202 @Parameter( defaultValue = "${project.issueManagement.url}", readonly = true )
203 private String url;
204
205
206
207
208 @Parameter( property = "changes.xmlPath", defaultValue = "src/changes/changes.xml" )
209 private File xmlPath;
210
211 private ReleaseUtils releaseUtils = new ReleaseUtils( getLog() );
212
213 private CaseInsensitiveMap caseInsensitiveIssueLinkTemplatePerSystem;
214
215
216
217
218
219 public boolean canGenerateReport()
220 {
221 return xmlPath.isFile();
222 }
223
224 public void executeReport( Locale locale )
225 throws MavenReportException
226 {
227 Date now = new Date();
228 SimpleDateFormat simpleDateFormat =
229 new SimpleDateFormat(publishDateFormat, new Locale(publishDateLocale));
230 Properties additionalProperties = new Properties();
231 additionalProperties.put("publishDate", simpleDateFormat.format(now));
232
233 ChangesXML changesXml = getChangesFromFile( xmlPath, project, additionalProperties);
234 if ( changesXml == null ) return;
235
236 if ( aggregated )
237 {
238 final String basePath = project.getBasedir().getAbsolutePath();
239 final String absolutePath = xmlPath.getAbsolutePath();
240 if ( !absolutePath.startsWith( basePath ) )
241 {
242 getLog().warn( "xmlPath should be within the project dir for aggregated changes report." );
243 return;
244 }
245 final String relativePath = absolutePath.substring( basePath.length() );
246
247 List releaseList = changesXml.getReleaseList();
248 for ( Iterator iterator = project.getCollectedProjects().iterator(); iterator.hasNext(); )
249 {
250 final MavenProject childProject = (MavenProject) iterator.next();
251 final File changesFile = new File( childProject.getBasedir(), relativePath );
252 final ChangesXML childXml = getChangesFromFile( changesFile, childProject, additionalProperties );
253 if ( childXml != null )
254 {
255 releaseList = releaseUtils.mergeReleases( releaseList, childProject.getName(), childXml.getReleaseList() );
256 }
257 }
258 changesXml.setReleaseList( releaseList );
259 }
260
261 ChangesReportGenerator report = new ChangesReportGenerator( changesXml.getReleaseList() );
262
263 report.setAuthor( changesXml.getAuthor() );
264 report.setTitle( changesXml.getTitle() );
265
266 report.setEscapeHTML ( escapeHTML );
267
268
269
270 if ( issueLinkTemplatePerSystem == null )
271 {
272 caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
273 }
274 else
275 {
276 caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap( issueLinkTemplatePerSystem );
277 }
278
279
280
281 addIssueLinkTemplate( ChangesReportGenerator.DEFAULT_ISSUE_SYSTEM_KEY, issueLinkTemplate );
282 addIssueLinkTemplate( "Bitbucket", "%URL%/issue/%ISSUE%" );
283 addIssueLinkTemplate( "Bugzilla", "%URL%/show_bug.cgi?id=%ISSUE%" );
284 addIssueLinkTemplate( "GitHub", "%URL%/%ISSUE%" );
285 addIssueLinkTemplate( "GoogleCode", "%URL%/detail?id=%ISSUE%" );
286 addIssueLinkTemplate( "JIRA", "%URL%/%ISSUE%" );
287 addIssueLinkTemplate( "Mantis", "%URL%/view.php?id=%ISSUE%" );
288 addIssueLinkTemplate( "MKS", "%URL%/viewissue?selection=%ISSUE%" );
289 addIssueLinkTemplate( "Redmine", "%URL%/issues/show/%ISSUE%" );
290 addIssueLinkTemplate( "Scarab", "%URL%/issues/id/%ISSUE%" );
291 addIssueLinkTemplate( "SourceForge", "http://sourceforge.net/support/tracker.php?aid=%ISSUE%" );
292 addIssueLinkTemplate( "SourceForge2", "%URL%/%ISSUE%" );
293 addIssueLinkTemplate( "Trac", "%URL%/ticket/%ISSUE%" );
294 addIssueLinkTemplate( "Trackplus", "%URL%/printItem.action?key=%ISSUE%" );
295 addIssueLinkTemplate( "YouTrack", "%URL%/issue/%ISSUE%" );
296
297
298
299
300 logIssueLinkTemplatePerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
301
302 report.setIssueLinksPerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
303
304 report.setSystem( system );
305
306 report.setTeamlist ( teamlist );
307
308 report.setUrl( url );
309
310 report.setAddActionDate( addActionDate );
311
312 if ( StringUtils.isEmpty( url ) )
313 {
314 getLog().warn( "No issue management URL defined in POM. Links to your issues will not work correctly." );
315 }
316
317 report.doGenerateReport( getBundle( locale ), getSink() );
318
319
320 copyStaticResources();
321 }
322
323 public String getDescription( Locale locale )
324 {
325 return getBundle( locale ).getString( "report.issues.description" );
326 }
327
328 public String getName( Locale locale )
329 {
330 return getBundle( locale ).getString( "report.issues.name" );
331 }
332
333 public String getOutputName()
334 {
335 return "changes-report";
336 }
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352 private ChangesXML getChangesFromFile( File changesXml, MavenProject project, Properties additionalProperties )
353 throws MavenReportException
354 {
355 if ( !changesXml.exists() )
356 {
357 getLog().warn( "changes.xml file " + changesXml.getAbsolutePath() + " does not exist." );
358 return null;
359 }
360
361 if ( filteringChanges )
362 {
363 if ( !filteredOutputDirectory.exists() )
364 {
365 filteredOutputDirectory.mkdirs();
366 }
367 XmlStreamReader xmlStreamReader = null;
368 try
369 {
370
371 xmlStreamReader = ReaderFactory.newXmlReader( changesXml );
372 String encoding = xmlStreamReader.getEncoding();
373 File resultFile = new File( filteredOutputDirectory, project.getGroupId() + "." + project.getArtifactId() + "-changes.xml" );
374
375 final MavenFileFilterRequest mavenFileFilterRequest =
376 new MavenFileFilterRequest( changesXml, resultFile, true, project, Collections.EMPTY_LIST, false,
377 encoding, session, additionalProperties );
378 mavenFileFilter.copyFile( mavenFileFilterRequest );
379 changesXml = resultFile;
380 }
381 catch ( IOException e )
382 {
383 throw new MavenReportException( "Exception during filtering changes file : " + e.getMessage(), e );
384 }
385 catch ( MavenFilteringException e )
386 {
387 throw new MavenReportException( "Exception during filtering changes file : " + e.getMessage(), e );
388 }
389 finally
390 {
391 if ( xmlStreamReader != null )
392 {
393 IOUtil.close( xmlStreamReader );
394 }
395 }
396
397 }
398 return new ChangesXML( changesXml, getLog() );
399 }
400
401
402
403
404
405
406
407
408
409 private void addIssueLinkTemplate( String system, String issueLinkTemplate )
410 {
411 if ( caseInsensitiveIssueLinkTemplatePerSystem == null )
412 {
413 caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
414 }
415 if ( !caseInsensitiveIssueLinkTemplatePerSystem.containsKey( system ) )
416 {
417 caseInsensitiveIssueLinkTemplatePerSystem.put( system, issueLinkTemplate );
418 }
419 }
420
421 private void copyStaticResources()
422 throws MavenReportException
423 {
424 final String pluginResourcesBase = "org/apache/maven/plugin/changes";
425 String resourceNames[] = {
426 "images/add.gif",
427 "images/fix.gif",
428 "images/icon_help_sml.gif",
429 "images/remove.gif",
430 "images/rss.png",
431 "images/update.gif" };
432 try
433 {
434 getLog().debug( "Copying static resources." );
435 for ( int i = 0; i < resourceNames.length; i++ )
436 {
437 URL url = this.getClass().getClassLoader().getResource( pluginResourcesBase + "/" + resourceNames[i] );
438 FileUtils.copyURLToFile( url, new File( getReportOutputDirectory(), resourceNames[i] ) );
439 }
440 }
441 catch ( IOException e )
442 {
443 throw new MavenReportException( "Unable to copy static resources." );
444 }
445 }
446
447 private ResourceBundle getBundle( Locale locale )
448 {
449 return ResourceBundle.getBundle( "changes-report", locale, this.getClass().getClassLoader() );
450 }
451
452 protected String getTeamlist()
453 {
454 return teamlist;
455 }
456
457 private void logIssueLinkTemplatePerSystem( Map issueLinkTemplatePerSystem )
458 {
459 if ( getLog().isDebugEnabled() )
460 {
461 if ( issueLinkTemplatePerSystem == null )
462 {
463 getLog().debug( "No issueLinkTemplatePerSystem configuration was found" );
464 }
465 else
466 {
467 Iterator iterator = issueLinkTemplatePerSystem.entrySet().iterator();
468 while ( iterator.hasNext() )
469 {
470 Map.Entry entry = (Map.Entry) iterator.next();
471 getLog().debug( "issueLinkTemplatePerSystem[" + entry.getKey() + "] = " + entry.getValue() );
472 }
473 }
474 }
475 }
476 }