View Javadoc

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