View Javadoc

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 816601 2012-05-08 12:50:18Z 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>&lt;issueManagement&gt;/&lt;url&gt;</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>&lt;issueManagement&gt;/&lt;url&gt;</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( "Bitbucket", "%URL%/issue/%ISSUE%" );
290         addIssueLinkTemplate( "Bugzilla", "%URL%/show_bug.cgi?id=%ISSUE%" );
291         addIssueLinkTemplate( "GitHub", "%URL%/%ISSUE%" );
292         addIssueLinkTemplate( "GoogleCode", "%URL%/detail?id=%ISSUE%" );
293         addIssueLinkTemplate( "JIRA", "%URL%/%ISSUE%" );
294         addIssueLinkTemplate( "Mantis", "%URL%/view.php?id=%ISSUE%" );
295         addIssueLinkTemplate( "MKS", "%URL%/viewissue?selection=%ISSUE%" );
296         addIssueLinkTemplate( "Redmine", "%URL%/issues/show/%ISSUE%" );
297         addIssueLinkTemplate( "Scarab", "%URL%/issues/id/%ISSUE%" );
298         addIssueLinkTemplate( "SourceForge", "http://sourceforge.net/support/tracker.php?aid=%ISSUE%" );
299         addIssueLinkTemplate( "Trac", "%URL%/ticket/%ISSUE%" );
300         addIssueLinkTemplate( "YouTrack", "%URL%/issue/%ISSUE%" );
301         // @todo Add more issue management systems here
302 
303         // Show the current issueLinkTemplatePerSystem configuration
304         logIssueLinkTemplatePerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
305 
306         report.setIssueLinksPerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
307 
308         report.setSystem( system );
309 
310         report.setTeamlist ( teamlist );
311 
312         report.setUrl( url );
313 
314         report.setAddActionDate( addActionDate );
315 
316         if ( StringUtils.isEmpty( url ) )
317         {
318             getLog().warn( "No issue management URL defined in POM. Links to your issues will not work correctly." );
319         }
320 
321         report.doGenerateReport( getBundle( locale ), getSink() );
322 
323         // Copy the images
324         copyStaticResources();
325     }
326 
327     public String getDescription( Locale locale )
328     {
329         return getBundle( locale ).getString( "report.issues.description" );
330     }
331 
332     public String getName( Locale locale )
333     {
334         return getBundle( locale ).getString( "report.issues.name" );
335     }
336 
337     public String getOutputName()
338     {
339         return "changes-report";
340     }
341 
342     /* --------------------------------------------------------------------- */
343     /* Private methods                                                       */
344     /* --------------------------------------------------------------------- */
345 
346     /**
347      * Add the issue link template for the given issue management system,
348      * but only if it has not already been configured.
349      *
350      * @param system The issue management system
351      * @param issueLinkTemplate The issue link template to use
352      * @since 2.4
353      */
354     private void addIssueLinkTemplate( String system, String issueLinkTemplate )
355     {
356         if ( caseInsensitiveIssueLinkTemplatePerSystem == null )
357         {
358             caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
359         }
360         if ( !caseInsensitiveIssueLinkTemplatePerSystem.containsKey( system ) )
361         {
362             caseInsensitiveIssueLinkTemplatePerSystem.put( system, issueLinkTemplate );
363         }
364     }
365 
366     private void copyStaticResources()
367         throws MavenReportException
368     {
369         final String pluginResourcesBase = "org/apache/maven/plugin/changes";
370         String resourceNames[] = {
371             "images/add.gif",
372             "images/fix.gif",
373             "images/icon_help_sml.gif",
374             "images/remove.gif",
375             "images/rss.png",
376             "images/update.gif" };
377         try
378         {
379             getLog().debug( "Copying static resources." );
380             for ( int i = 0; i < resourceNames.length; i++ )
381             {
382                 URL url = this.getClass().getClassLoader().getResource( pluginResourcesBase + "/" + resourceNames[i] );
383                 FileUtils.copyURLToFile( url, new File( getReportOutputDirectory(), resourceNames[i] ) );
384             }
385         }
386         catch ( IOException e )
387         {
388             throw new MavenReportException( "Unable to copy static resources." );
389         }
390     }
391 
392     private ResourceBundle getBundle( Locale locale )
393     {
394         return ResourceBundle.getBundle( "changes-report", locale, this.getClass().getClassLoader() );
395     }
396 
397     protected String getTeamlist()
398     {
399         return teamlist;
400     }
401 
402     private void logIssueLinkTemplatePerSystem( Map issueLinkTemplatePerSystem )
403     {
404         if ( getLog().isDebugEnabled() )
405         {
406             if ( issueLinkTemplatePerSystem == null )
407             {
408                 getLog().debug( "No issueLinkTemplatePerSystem configuration was found" );
409             }
410             else
411             {
412                 Iterator iterator = issueLinkTemplatePerSystem.entrySet().iterator();
413                 while ( iterator.hasNext() )
414                 {
415                     Map.Entry entry = (Map.Entry) iterator.next();
416                     getLog().debug( "issueLinkTemplatePerSystem[" + entry.getKey() + "] = " + entry.getValue() );
417                 }
418             }
419         }
420     }
421 }