View Javadoc

1   /*
2    * Copyright 2008-2010 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package de.smartics.maven.issues;
18  
19  import java.io.File;
20  import java.lang.reflect.Constructor;
21  import java.util.List;
22  import java.util.Locale;
23  
24  import org.apache.maven.artifact.versioning.ArtifactVersion;
25  import org.apache.maven.doxia.sink.Sink;
26  import org.apache.maven.plugin.logging.Log;
27  import org.apache.maven.reporting.MavenReportException;
28  import org.apache.maven.reporting.MavenReportRenderer;
29  import org.codehaus.plexus.util.StringUtils;
30  import org.eclipse.mylyn.tasks.core.data.TaskData;
31  
32  import de.smartics.maven.issues.util.ReportReference;
33  import de.smartics.maven.issues.util.ReportReferenceExtractor;
34  import de.smartics.maven.issues.util.Utils;
35  
36  /**
37   * Mojo base implementation to render release reports.
38   *
39   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
40   * @version $Revision:591 $
41   */
42  public abstract class AbstractIssuesReportMojo extends
43      AbstractIssuesConnectionMojo
44  {
45    // ********************************* Fields *********************************
46  
47    // --- constants ------------------------------------------------------------
48  
49    // --- members --------------------------------------------------------------
50  
51    /**
52     * The title to be set in the configuration to be used instead of the one
53     * found in the localized files. This property is used to specify the title
54     * from the configuration and is useful if the user wants to select a specific
55     * set of information retrieved by a specific query and now wants to set a
56     * specific title.
57     * <p>
58     * This value may be omitted in which case the renderer retrieves a default
59     * value (probably assuming that a release notes report is rendered).
60     * </p>
61     *
62     * @parameter default-value=""
63     * @since 1.0
64     */
65    private String title;
66  
67    /**
68     * The description to be set in the configuration to be used instead of the
69     * one found in the localized files. This property is used to specify the
70     * description from the configuration and is useful if the user wants to
71     * select a specific set of information retrieved by a specific query and now
72     * wants to set a specific description.
73     * <p>
74     * This value may be omitted in which case the renderer retrieves a default
75     * value (probably assuming that a release notes report is rendered).
76     * </p>
77     *
78     * @parameter default-value=""
79     * @since 1.0
80     */
81    protected String description;
82  
83    /**
84     * The description file is a <a
85     * href="http://maven.apache.org/doxia/references/xdoc-format.html">XDoc</a>
86     * file to be included as-is into the generated report. It is rendered between
87     * the main header and the report table where the description is normally
88     * written. The description is left out if a description file is given.
89     * <p>
90     * If no description file is explicitly specified, the system looks at the
91     * default location. If the file exists, it is used.
92     * </p>
93     * <p>
94     * Please note that any <code>-SNAPSHOT</code> element in front of the
95     * extension is removed. <code>$${outputName}</code> is replaced by
96     * {@link #getOutputName()}.
97     * </p>
98     *
99     * @parameter default-value=
100    *            "src/site/relnotes/$${outputName}-${project.version}.xml"
101    * @since 1.0
102    */
103   protected String descriptionFile;
104 
105   /**
106    * The description to be set in the configuration if no issue matches the
107    * query to be used instead of the one found in the localized files. This
108    * property is used to specify the description from the configuration and is
109    * useful if the user wants to select a specific set of information retrieved
110    * by a specific query and now wants to set a specific description.
111    * <p>
112    * This value may be omitted in which case the renderer retrieves a default
113    * value (probably assuming that a release notes report is rendered).
114    * </p>
115    *
116    * @parameter default-value=""
117    * @since 1.0
118    */
119   private String noResultsDescription;
120 
121   /**
122    * The raw text for the footer. May contain any valid HTML code. The string is
123    * not checked to be valid. If you want to remove the link to the plugin's
124    * homepage, simple add a non breaking space (e.g. <code>&amp;amp;nbsp;</code>
125    * ).
126    *
127    * @parameter
128    * @since 1.0
129    */
130   protected String footerText =
131       "<div style='text-align:center;font-size:x-small;'>generated by "
132           + "<a href='" + "http://project.smartics.de/maven-issues-plugin'>"
133           + "maven-issues-plugin</a></div>";
134 
135   /**
136    * The type (probably but not necessarily a user type) that specifies the
137    * information in the issue that is used to group the issues in sections.
138    * <p>
139    * For example a user type <code>ct_type</code> may be defined to specify the
140    * type of an issue. Valid types could be <code>Bug</code>,
141    * <code>New Feature</code>, <code>Improvement</code>, and <code>Task</code>.
142    * </p>
143    *
144    * @parameter default-value="cf_type"
145    * @since 1.0
146    */
147   private String sectionType;
148 
149   /**
150    * The order of the values specified for the section type. Only values
151    * specified in this list will be rendered at all.
152    * <p>
153    * Regarding the example given for "section type", this list could define
154    * <code>New Feature</code>, <code>Bug</code>, <code>Improvement</code> . This
155    * would render issues tagged as new features in the first, issues tagged as
156    * bugs in the second and issues tagged as improvements in the last section.
157    * Issues tagged as tasks will not be rendered.
158    * </p>
159    * <p>
160    * The values are separated by comma.
161    * </p>
162    *
163    * @parameter default-value="New Feature,Change Request,Improvement,Bug"
164    * @since 1.0
165    */
166   private String sections;
167 
168   /**
169    * The value of the flag that indicates whether or not eMail addresses should
170    * be rendered. Rendering eMail addresses may be useful for intranet sites.
171    * Due to spamming it might not be wise to render an eMail address on an
172    * internet server.
173    * <p>
174    * A value of <code>true</code> indicates that the eMail address should be
175    * rendered (e.g. as a mailto anchor in HTML for an assignee name),
176    * <code>false</code> if no eMail address information should be written.
177    * </p>
178    *
179    * @parameter default-value="false"
180    * @since 1.0
181    */
182   private boolean renderEmailAdresses;
183 
184   /**
185    * Specifies the index (zero bases) of the column at which the
186    * {@link #getComponent() component information} is to rendered. This option
187    * is only taken into account if the {@link #getComponent() component
188    * property} does not specify exactly one component. Please note that this
189    * cannot be used in named queries.
190    * <p>
191    * This is a handy option for running the report on a multi project. Sub
192    * projects specify exactly one component while the parent project specifies
193    * none. The result with setting an index of e.g. <code>1</code> is that in
194    * each sub project the component is not mentioned while it is on the parent
195    * project (listing all issues of all projects) it is rendered at the second
196    * column.
197    * </p>
198    *
199    * @parameter default-value="1"
200    * @since 1.0
201    */
202   private int includeComponentAtIndex;
203 
204   /**
205    * Specifies the column width for the {@link #getIncludeComponentAtIndex()
206    * includeComponentAtIndex} property. If that property is not set, this
207    * property value is ignored.
208    *
209    * @parameter default-value="0"
210    * @since 1.0
211    */
212   private int includeComponentAtIndexColumnWidth;
213 
214   /**
215    * Specifies if previous versions of this report should be referenced.
216    *
217    * @parameter default-value="true"
218    * @since 1.0
219    */
220   private boolean referencePreviousReports;
221 
222   // ****************************** Initializer *******************************
223 
224   // ****************************** Constructors ******************************
225 
226   // ****************************** Inner Classes *****************************
227 
228   // ********************************* Methods ********************************
229 
230   // --- init -----------------------------------------------------------------
231 
232   // --- get&set --------------------------------------------------------------
233 
234   /**
235    * Returns the title to be set in the configuration to be used instead of the
236    * one found in the localized files. This property is used to specify the
237    * title from the configuration and is useful if the user wants to select a
238    * specific set of information retrieved by a specific query and now wants to
239    * set a specific title.
240    * <p>
241    * This value may be omitted in which case the renderer retrieves a default
242    * value (probably assuming that a release notes report is rendered).
243    * </p>
244    *
245    * @return the title to be set in the configuration to be used instead of the
246    *         one found in the localized files.
247    */
248   public String getTitle()
249   {
250     return title;
251   }
252 
253   /**
254    * Returns the description to be set in the configuration to be used instead
255    * of the one found in the localized files. This property is used to specify
256    * the description from the configuration and is useful if the user wants to
257    * select a specific set of information retrieved by a specific query and now
258    * wants to set a specific description.
259    * <p>
260    * This value may be omitted in which case the renderer retrieves a default
261    * value (probably assuming that a release notes report is rendered).
262    * </p>
263    *
264    * @return the description to be set in the configuration to be used instead
265    *         of the one found in the localized files.
266    */
267   public String getDescription()
268   {
269     return description;
270   }
271 
272   /**
273    * Returns the description file is a <a
274    * href="http://maven.apache.org/doxia/references/xdoc-format.html">XDoc</a>
275    * file to be included as-is into the generated report. It is rendered between
276    * the main header and the report table where the description is normally
277    * written. The description is left out if a description file is given.
278    *
279    * @return the description file is a <a
280    *         href="http://maven.apache.org/doxia/references/xdoc-format.html"
281    *         >XDoc</a> file to be included as-is into the generated report.
282    */
283   public String getDescriptionFile()
284   {
285     return descriptionFile;
286   }
287 
288   /**
289    * Returns the description to be set in the configuration if no issue matches
290    * the query to be used instead of the one found in the localized files. This
291    * property is used to specify the description from the configuration and is
292    * useful if the user wants to select a specific set of information retrieved
293    * by a specific query and now wants to set a specific description.
294    * <p>
295    * This value may be omitted in which case the renderer retrieves a default
296    * value (probably assuming that a release notes report is rendered).
297    * </p>
298    *
299    * @return the description to be set in the configuration if no issue matches
300    *         the query to be used instead of the one found in the localized
301    *         files.
302    */
303   public String getNoResultsDescription()
304   {
305     return noResultsDescription;
306   }
307 
308   /**
309    * Returns the name of the class that runs the rendering of the report page.
310    *
311    * @return the name of the class that runs the rendering of the report page.
312    */
313   protected abstract String getReportRenderer();
314 
315   /**
316    * Returns the value for columns.
317    * <p>
318    * Lists the columns to be rendered. Each element of this list is a property
319    * of an issue. The identifiers given here must match the ones defined in the
320    * referenced issue management system. E.g. for Bugzilla these are defined in
321    * <code>org.eclipse.mylyn.internal.bugzilla.core.BugzillaAttribute</code>.
322    * <p>
323    * The values are separated by comma.
324    * </p>
325    *
326    * @return the value for columns.
327    */
328   protected abstract String getColumns();
329 
330   /**
331    * Returns the value for columnWidths.
332    * <p>
333    * Lists the column width to be used to set to the columns. If the value is
334    * <code>0</code> (zero) no width will be set explicitly for that column.
335    * </p>
336    *
337    * @return the value for columnWidths.
338    */
339   protected abstract String getColumnWidths();
340 
341   /**
342    * Returns the value for includeOnSamePageAll.
343    * <p>
344    * On the same page all of the given version type are rendered. For instance
345    * if this value refers to the major version, all versions having the same
346    * major version are rendered. A value of micro implies that only the current
347    * version is to be rendered.
348    * <p>
349    * Defaults to {@link VersionType#MAJOR}.
350    * </p>
351    *
352    * @return the value for includeOnSamePageAll.
353    */
354   public abstract VersionType getIncludeOnSamePageAllOfVersion();
355 
356   /**
357    * Returns the value for component.
358    * <p>
359    * Sets the component(s) that you want to limit your report to include.
360    * Multiple components can be separated by commas. If this is set to empty -
361    * that means all components.
362    *
363    * @return the value for component.
364    */
365   public abstract String getComponent();
366 
367   /**
368    * Returns the name of the query to execute. If the query name is specified
369    * none of the other query properties is taken into account.
370    *
371    * @return the name of the query to execute.
372    */
373   public abstract String getQueryName();
374 
375   /**
376    * Returns the value for includeComponentAtIndex.
377    * <p>
378    * Specifies the index (zero bases) of the column at which the
379    * {@link #getComponent() component information} is to rendered. This option
380    * is only taken into account if the {@link #getComponent() component
381    * property} does not specify exactly one component. Please note that this
382    * cannot be used in named queries.
383    * </p>
384    * <p>
385    * This is a handy option for running the report on a multi project. Sub
386    * projects specify exactly one component while the parent project specifies
387    * none. The result with setting an index of e.g. <code>1</code> is that in
388    * each sub project the component is not mentioned while it is on the parent
389    * project (listing all issues of all projects) it is rendered at the second
390    * column.
391    * </p>
392    *
393    * @return the value for includeComponentAtIndex.
394    */
395   public int getIncludeComponentAtIndex()
396   {
397     return includeComponentAtIndex;
398   }
399 
400   /**
401    * Returns the value for includeComponentAtIndexColumnWidth.
402    * <p>
403    * Specifies the column width for the {@link #getIncludeComponentAtIndex()
404    * includeComponentAtIndex} property. If that property is not set, this
405    * property value is ignored.
406    * </p>
407    *
408    * @return the value for includeComponentAtIndexColumnWidth.
409    */
410   public int getIncludeComponentAtIndexColumnWidth()
411   {
412     return includeComponentAtIndexColumnWidth;
413   }
414 
415   // --- business -------------------------------------------------------------
416 
417   /**
418    * Creates the renderer to use for report rendering.
419    *
420    * @param locale the locale to select the resource bundle that provides labels
421    *          for the generated reports.
422    * @param issues the issues to render.
423    * @return the created renderer instance.
424    * @throws MavenReportException if a problem prevents the creation of the
425    *           report renderer.
426    */
427   @Override
428   @SuppressWarnings("unchecked")
429   protected MavenReportRenderer createRenderer(final Locale locale,
430       final VersionFactory versionFactoryInstance,
431       final ArtifactVersionRange versionRange, final List<TaskData> issues)
432     throws MavenReportException
433   {
434     final Log log = getLog();
435     final String reportRendererName = getReportRenderer();
436     try
437     {
438       final Class<MavenReportRenderer> clazz =
439           (Class<MavenReportRenderer>) Class.forName(reportRendererName);
440       final Constructor<MavenReportRenderer> constructor =
441           clazz.getConstructor(RendererConfig.class, Sink.class, List.class);
442 
443       final RendererConfig config =
444           createRenderConfig(locale, versionFactoryInstance, versionRange);
445       if (log.isDebugEnabled())
446       {
447         log.debug("Created configuration: " + config);
448       }
449 
450       final MavenReportRenderer renderer =
451           constructor.newInstance(config, getSink(), issues);
452       return renderer;
453     }
454     catch (final Exception e)
455     {
456       final String message =
457           "Cannot create renderer for class '" + reportRendererName + "'.";
458       if (log.isWarnEnabled())
459       {
460         log.warn(message, e);
461       }
462       throw new MavenReportException(message, e);
463     }
464   }
465 
466   /**
467    * Creates the renderer configuration from the information set to the Mojo.
468    *
469    * @param locale the locale to select the resource bundle that provides labels
470    *          for the generated reports.
471    * @return the configuration instance.
472    * @throws IllegalArgumentException if the information is not valid to create
473    *           an instance.
474    */
475   private RendererConfig createRenderConfig(final Locale locale,
476       final VersionFactory versionFactoryInstance,
477       final ArtifactVersionRange versionRange) throws IllegalArgumentException
478   {
479     final RendererConfig.Builder builder = new RendererConfig.Builder();
480     builder.setBundle(getBundle(locale));
481 
482     builder.setComponent(getComponent());
483     builder.setSectionType(sectionType);
484     builder.setSections(Utils.splitToList(sections));
485 
486     builder.setColumns(Utils.splitToList(getColumns()));
487     builder.setColumnWidths(Utils.splitToList(getColumnWidths()));
488     builder.setIncludeComponentAtIndex(getIncludeComponentAtIndex());
489     builder
490         .setIncludeComponentAtIndexColumnWidth(getIncludeComponentAtIndexColumnWidth());
491 
492     builder.setRenderEmailAdresses(renderEmailAdresses);
493 
494     builder.setQueryName(getQueryName());
495 
496     final ArtifactVersion version =
497         addVersionInformation(versionFactoryInstance, versionRange, builder);
498 
499     setTitle(builder);
500     setDescription(builder);
501     setDescriptionFile(builder);
502     setNoResultDescription(builder);
503     setFooterText(builder);
504 
505     if (referencePreviousReports)
506     {
507       final ReportReferenceExtractor extractor =
508           new ReportReferenceExtractor(getOutputName(), version,
509               project.getReporting());
510       final List<ReportReference> references = extractor.readReportReferences();
511       builder.setReportReferences(references);
512     }
513 
514     return builder.build();
515   }
516 
517   /**
518    * Adds version information to the builder.
519    *
520    * @param builder the configuration builder to add to.
521    * @return the project's version.
522    * @throws IllegalArgumentException if the version specification cannot be
523    *           parsed.
524    */
525   private ArtifactVersion addVersionInformation(
526       final VersionFactory versionFactoryInstance,
527       final ArtifactVersionRange versionRange,
528       final RendererConfig.Builder builder) throws IllegalArgumentException
529   {
530     builder.setVersionFactory(versionFactoryInstance);
531     builder.setVersionRange(versionRange);
532     builder
533         .setIncludeOnSamePageAllOfVersion(getIncludeOnSamePageAllOfVersion());
534     final ArtifactVersion artifactVersion =
535         versionFactoryInstance.createVersion(project.getVersion());
536     builder.setCurrentReleaseVersion(artifactVersion);
537     return artifactVersion;
538   }
539 
540   /**
541    * Sets the description to render if the query yielded no result.
542    *
543    * @param builder the builder to add the information.
544    */
545   private void setNoResultDescription(final RendererConfig.Builder builder)
546   {
547     if (StringUtils.isNotBlank(noResultsDescription))
548     {
549       builder.setNoResultsDescription(noResultsDescription);
550     }
551   }
552 
553   /**
554    * Sets the title to the builder.
555    *
556    * @param builder the builder to add the information.
557    */
558   private void setTitle(final RendererConfig.Builder builder)
559   {
560     if (StringUtils.isNotBlank(getTitle()))
561     {
562       builder.setTitle(getTitle());
563     }
564   }
565 
566   /**
567    * Sets the description to the builder.
568    *
569    * @param builder the builder to add the information.
570    */
571   private void setDescription(final RendererConfig.Builder builder)
572   {
573     if (StringUtils.isNotBlank(description))
574     {
575       builder.setDescription(description);
576     }
577   }
578 
579   /**
580    * Sets the description file to the builder. If the file does not exist, the
581    * fact is logged at debug level and no value is set to the configuration
582    * builder.
583    *
584    * @param builder the builder to add the information.
585    */
586   private void setDescriptionFile(final RendererConfig.Builder builder)
587   {
588     final Log log = getLog();
589     if (log.isDebugEnabled())
590     {
591       log.debug("Checking description file: " + descriptionFile);
592     }
593 
594     if (StringUtils.isNotBlank(descriptionFile))
595     {
596       final File file = determineDescriptionFile();
597       if (file.canRead())
598       {
599         builder.setDescriptionFile(file);
600       }
601       else
602       {
603         if (log.isDebugEnabled())
604         {
605           log.debug("Cannot read description file '" + file.getAbsolutePath()
606                     + "'.");
607         }
608       }
609     }
610   }
611 
612   /**
613    * Determines the location to search for the description file.
614    *
615    * @return the file representation to check.
616    */
617   private File determineDescriptionFile()
618   {
619     final String normalizedFileName =
620         descriptionFile.replace("-SNAPSHOT.xml", ".xml").replace(
621             "${outputName}", getOutputName());
622     return new File(project.getBasedir(), normalizedFileName);
623   }
624 
625   /**
626    * Sets the footer text to the builder.
627    *
628    * @param builder the builder to add the information.
629    */
630   private void setFooterText(final RendererConfig.Builder builder)
631   {
632     if (StringUtils.isNotBlank(footerText))
633     {
634       builder.setFooterText(footerText);
635     }
636   }
637 
638   // --- object basics --------------------------------------------------------
639 
640 }