1 package org.apache.maven.plugin.changes;
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.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.Locale;
30 import java.util.Map;
31 import java.util.Properties;
32 import java.util.ResourceBundle;
33
34 import org.apache.commons.collections.map.CaseInsensitiveMap;
35 import org.apache.maven.execution.MavenSession;
36 import org.apache.maven.reporting.MavenReportException;
37 import org.apache.maven.shared.filtering.MavenFileFilter;
38 import org.apache.maven.shared.filtering.MavenFileFilterRequest;
39 import org.apache.maven.shared.filtering.MavenFilteringException;
40 import org.codehaus.plexus.util.FileUtils;
41 import org.codehaus.plexus.util.IOUtil;
42 import org.codehaus.plexus.util.ReaderFactory;
43 import org.codehaus.plexus.util.StringUtils;
44 import org.codehaus.plexus.util.xml.XmlStreamReader;
45
46 /**
47 * Goal which creates a nicely formatted Changes Report in html format from a changes.xml file.
48 *
49 * @goal changes-report
50 * @author <a href="mailto:jruiz@exist.com">Johnny R. Ruiz III</a>
51 * @version $Id: ChangesMojo.html 816598 2012-05-08 12:46:49Z hboutemy $
52 * @threadSafe
53 */
54 public class ChangesMojo
55 extends AbstractChangesReport
56 {
57 /**
58 * A flag whether the report should also include the dates of individual actions. If set to <code>false</code>, only
59 * the dates of releases will be written to the report.
60 *
61 * @parameter expression="${changes.addActionDate}" default-value="false"
62 * @since 2.1
63 */
64 private boolean addActionDate;
65
66 /**
67 * Whether HTML code within an action should be escaped. By changing this to
68 * <code>false</code> you can restore the behavior that was in version 2.2
69 * of this plugin, allowing you to use HTML code to format the content of an
70 * action.
71 * <p>
72 * <strong>Note:</strong> If you use HTML code in an action you need to
73 * place it inside a CDATA section.
74 * </p>
75 * <strong>Note:</strong> Putting any kind of markup inside a CDATA section
76 * might mess up the Changes Report or other generated documents, such as
77 * PDFs, that are based on your <code>changes.xml</code> file if you are not
78 * careful.
79 *
80 * @parameter default-value="true"
81 * @since 2.4
82 * @deprecated using markup inside CDATA sections does not work for all output formats!
83 */
84 private boolean escapeHTML;
85
86 /**
87 * The directory for interpolated changes.xml.
88 *
89 * @parameter expression="${project.build.directory}/changes"
90 * @required
91 * @readonly
92 * @since 2.2
93 */
94 private File filteredOutputDirectory;
95
96 /**
97 * applying filtering filtering "a la" resources plugin
98 *
99 * @parameter default-value="false"
100 * @since 2.2
101 */
102 private boolean filteringChanges;
103
104 /**
105 * Template string that is used to discover the URL to use to display an issue report.
106 * There are 2 template tokens you can use. <code>%URL%</code>: this is computed by getting the
107 * <code><issueManagement>/<url></code> value from the POM, and removing the last '/'
108 * and everything that comes after it. <code>%ISSUE%</code>: this is the issue number.
109 * <p>
110 * <strong>Note:</strong> In versions of this plugin prior to 2.0-beta-2 this parameter was called
111 * <code>link_template</code>.
112 * </p>
113 *
114 * @parameter expression="${changes.issueLinkTemplate}" default-value="%URL%/ViewIssue.jspa?key=%ISSUE%"
115 * @since 2.0-beta-2
116 * @deprecated As of 2.1 use issueLinkTemplatePerSystem : this one will be with system default
117 */
118 private String issueLinkTemplate;
119
120 /**
121 * Template strings per system that is used to discover the URL to use to display an issue report. Each key in this
122 * map denotes the (case-insensitive) identifier of the issue tracking system and its value gives the URL template.
123 * <p>
124 * There are 2 template tokens you can use. <code>%URL%</code>: this is computed by getting the
125 * <code><issueManagement>/<url></code> value from the POM, and removing the last '/'
126 * and everything that comes after it. <code>%ISSUE%</code>: this is the issue number.
127 * </p>
128 * <p>
129 * <strong>Note:</strong> The deprecated issueLinkTemplate will be used for a system called "default".
130 * </p>
131 * <p>
132 * <strong>Note:</strong> Starting with version 2.4 you usually don't need
133 * to specify this, unless you need to link to an issue management system in
134 * your Changes report that isn't supported out of the box. See the
135 * <a href="./usage.html">Usage page</a> for more
136 * information.
137 * </p>
138 *
139 * @parameter
140 * @since 2.1
141 */
142 private Map issueLinkTemplatePerSystem;
143
144 /**
145 * @component
146 * @since 2.2
147 */
148 private MavenFileFilter mavenFileFilter;
149
150 /**
151 * Format to use for publishDate. The value will be available with the following expression ${publishDate}
152 *
153 * @see SimpleDateFormat
154 * @parameter default-value="yyyy-MM-dd"
155 * @since 2.2
156 */
157 private String publishDateFormat;
158
159 /**
160 * Locale to use for publishDate when formatting
161 *
162 * @see Locale
163 * @parameter default-value="en"
164 * @since 2.2
165 */
166 private String publishDateLocale;
167
168 /**
169 * @parameter expression="${session}"
170 * @readonly
171 * @required
172 * @since 2.2
173 */
174 protected MavenSession session;
175
176 /**
177 * @parameter default-value="${project.issueManagement.system}"
178 * @readonly
179 * @since 2.4
180 */
181 private String system;
182
183 /**
184 * The URI of a file containing all the team members. If this is set to the
185 * special value "none", no links will be generated for the team members.
186 *
187 * @parameter default-value="team-list.html"
188 * @since 2.4
189 */
190 private String teamlist;
191
192 /**
193 * @parameter default-value="${project.issueManagement.url}"
194 * @readonly
195 */
196 private String url;
197
198 /**
199 * The path of the <code>changes.xml</code> file that will be converted into an HTML report.
200 *
201 * @parameter expression="${changes.xmlPath}" default-value="src/changes/changes.xml"
202 */
203 private File xmlPath;
204
205 private CaseInsensitiveMap caseInsensitiveIssueLinkTemplatePerSystem;
206
207 /* --------------------------------------------------------------------- */
208 /* Public methods */
209 /* --------------------------------------------------------------------- */
210
211 public boolean canGenerateReport()
212 {
213 return xmlPath.isFile();
214 }
215
216 public void executeReport( Locale locale )
217 throws MavenReportException
218 {
219
220 if ( !xmlPath.exists() )
221 {
222 getLog().warn( "changes.xml file " + xmlPath.getAbsolutePath() + " does not exist." );
223 return;
224 }
225 if ( filteringChanges )
226 {
227 if ( !filteredOutputDirectory.exists() )
228 {
229 filteredOutputDirectory.mkdirs();
230 }
231 XmlStreamReader xmlStreamReader = null;
232 try
233 {
234 // so we get encoding from the file itself
235 xmlStreamReader = ReaderFactory.newXmlReader( xmlPath );
236 String encoding = xmlStreamReader.getEncoding();
237 File resultFile = new File( filteredOutputDirectory, "changes.xml" );
238 Date now = new Date();
239 SimpleDateFormat simpleDateFormat =
240 new SimpleDateFormat( publishDateFormat, new Locale( publishDateLocale ) );
241 Properties additionalProperties = new Properties();
242 additionalProperties.put( "publishDate", simpleDateFormat.format( now ) );
243 MavenFileFilterRequest mavenFileFilterRequest =
244 new MavenFileFilterRequest( xmlPath, resultFile, true, project, Collections.EMPTY_LIST, false,
245 encoding, session, additionalProperties );
246 mavenFileFilter.copyFile( mavenFileFilterRequest );
247 xmlPath = resultFile;
248 }
249 catch ( IOException e )
250 {
251 throw new MavenReportException( "Exception during filtering changes file : " + e.getMessage(), e );
252 }
253 catch ( MavenFilteringException e )
254 {
255 throw new MavenReportException( "Exception during filtering changes file : " + e.getMessage(), e );
256 }
257 finally
258 {
259 if ( xmlStreamReader != null )
260 {
261 IOUtil.close( xmlStreamReader );
262 }
263 }
264
265 }
266
267 ChangesXML changesXml = new ChangesXML( xmlPath, getLog() );
268 ChangesReportGenerator report = new ChangesReportGenerator( changesXml.getReleaseList() );
269
270 report.setAuthor( changesXml.getAuthor() );
271 report.setTitle( changesXml.getTitle() );
272
273 report.setEscapeHTML ( escapeHTML );
274
275 // Create a case insensitive version of issueLinkTemplatePerSystem
276 // We need something case insensitive to maintain backward compatibility
277 if ( issueLinkTemplatePerSystem == null )
278 {
279 caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
280 }
281 else
282 {
283 caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap( issueLinkTemplatePerSystem );
284 }
285
286 // Set good default values for issue management systems here, but only
287 // if they have not been configured already by the user
288 addIssueLinkTemplate( ChangesReportGenerator.DEFAULT_ISSUE_SYSTEM_KEY, issueLinkTemplate );
289 addIssueLinkTemplate( "Bugzilla", "%URL%/show_bug.cgi?id=%ISSUE%" );
290 addIssueLinkTemplate( "GoogleCode", "%URL%/detail?id=%ISSUE%" );
291 addIssueLinkTemplate( "JIRA", "%URL%/%ISSUE%" );
292 addIssueLinkTemplate( "Mantis", "%URL%/view.php?id=%ISSUE%" );
293 addIssueLinkTemplate( "Redmine", "%URL%/issues/show/%ISSUE%" );
294 addIssueLinkTemplate( "Scarab", "%URL%/issues/id/%ISSUE%" );
295 addIssueLinkTemplate( "SourceForge", "http://sourceforge.net/support/tracker.php?aid=%ISSUE%" );
296 addIssueLinkTemplate( "Trac", "%URL%/ticket/%ISSUE%" );
297 addIssueLinkTemplate( "YouTrack", "%URL%/issue/%ISSUE%" );
298 // @todo Add more issue management systems here
299
300 // Show the current issueLinkTemplatePerSystem configuration
301 logIssueLinkTemplatePerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
302
303 report.setIssueLinksPerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
304
305 report.setSystem( system );
306
307 report.setTeamlist ( teamlist );
308
309 report.setUrl( url );
310
311 report.setAddActionDate( addActionDate );
312
313 if ( StringUtils.isEmpty( url ) )
314 {
315 getLog().warn( "No issue management URL defined in POM. Links to your issues will not work correctly." );
316 }
317
318 report.doGenerateReport( getBundle( locale ), getSink() );
319
320 // Copy the images
321 copyStaticResources();
322 }
323
324 public String getDescription( Locale locale )
325 {
326 return getBundle( locale ).getString( "report.issues.description" );
327 }
328
329 public String getName( Locale locale )
330 {
331 return getBundle( locale ).getString( "report.issues.name" );
332 }
333
334 public String getOutputName()
335 {
336 return "changes-report";
337 }
338
339 /* --------------------------------------------------------------------- */
340 /* Private methods */
341 /* --------------------------------------------------------------------- */
342
343 /**
344 * Add the issue link template for the given issue management system,
345 * but only if it has not already been configured.
346 *
347 * @param system The issue management system
348 * @param issueLinkTemplate The issue link template to use
349 * @since 2.4
350 */
351 private void addIssueLinkTemplate( String system, String issueLinkTemplate )
352 {
353 if ( caseInsensitiveIssueLinkTemplatePerSystem == null )
354 {
355 caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
356 }
357 if ( !caseInsensitiveIssueLinkTemplatePerSystem.containsKey( system ) )
358 {
359 caseInsensitiveIssueLinkTemplatePerSystem.put( system, issueLinkTemplate );
360 }
361 }
362
363 private void copyStaticResources()
364 throws MavenReportException
365 {
366 final String pluginResourcesBase = "org/apache/maven/plugin/changes";
367 String resourceNames[] = {
368 "images/add.gif",
369 "images/fix.gif",
370 "images/icon_help_sml.gif",
371 "images/remove.gif",
372 "images/rss.png",
373 "images/update.gif" };
374 try
375 {
376 getLog().debug( "Copying static resources." );
377 for ( int i = 0; i < resourceNames.length; i++ )
378 {
379 URL url = this.getClass().getClassLoader().getResource( pluginResourcesBase + "/" + resourceNames[i] );
380 FileUtils.copyURLToFile( url, new File( getReportOutputDirectory(), resourceNames[i] ) );
381 }
382 }
383 catch ( IOException e )
384 {
385 throw new MavenReportException( "Unable to copy static resources." );
386 }
387 }
388
389 private ResourceBundle getBundle( Locale locale )
390 {
391 return ResourceBundle.getBundle( "changes-report", locale, this.getClass().getClassLoader() );
392 }
393
394 protected String getTeamlist()
395 {
396 return teamlist;
397 }
398
399 private void logIssueLinkTemplatePerSystem( Map issueLinkTemplatePerSystem )
400 {
401 if ( getLog().isDebugEnabled() )
402 {
403 if ( issueLinkTemplatePerSystem == null )
404 {
405 getLog().debug( "No issueLinkTemplatePerSystem configuration was found" );
406 }
407 else
408 {
409 Iterator iterator = issueLinkTemplatePerSystem.entrySet().iterator();
410 while ( iterator.hasNext() )
411 {
412 Map.Entry entry = (Map.Entry) iterator.next();
413 getLog().debug( "issueLinkTemplatePerSystem[" + entry.getKey() + "] = " + entry.getValue() );
414 }
415 }
416 }
417 }
418 }