View Javadoc

1   /*
2    * Copyright 2006-2012 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.plugin.buildmetadata;
17  
18  import java.io.BufferedInputStream;
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.Enumeration;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.MissingResourceException;
27  import java.util.Properties;
28  import java.util.ResourceBundle;
29  import java.util.Set;
30  import java.util.StringTokenizer;
31  
32  import org.apache.commons.io.IOUtils;
33  import org.apache.maven.doxia.sink.Sink;
34  import org.apache.maven.reporting.MavenReportException;
35  import org.codehaus.plexus.util.StringUtils;
36  
37  import de.smartics.maven.plugin.buildmetadata.common.Constant;
38  import de.smartics.maven.plugin.buildmetadata.common.Property;
39  import de.smartics.maven.plugin.buildmetadata.common.Constant.Section;
40  
41  /**
42   * Renders the build report.
43   *
44   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
45   * @version $Revision:591 $
46   */
47  public final class BuildReportRenderer
48  { // NOPMD
49    // ********************************* Fields *********************************
50  
51    // --- constants ------------------------------------------------------------
52  
53    // --- members --------------------------------------------------------------
54  
55    /**
56     * The sink to write to.
57     */
58    private final Sink sink;
59  
60    /**
61     * The resource bundle to access localized messages.
62     */
63    private final ResourceBundle messages;
64  
65    /**
66     * The properties file to read the build information from.
67     */
68    private final File buildMetaDataPropertiesFile;
69  
70    /**
71     * The list of a system properties or environment variables to be selected by
72     * the user to include into the build meta data properties.
73     * <p>
74     * The name is the name of the property, the section is relevant for placing
75     * the property in one of the following sections:
76     * </p>
77     * <ul>
78     * <li><code>build.scm</code></li>
79     * <li><code>build.dateAndVersion</code></li>
80     * <li><code>build.runtime</code></li>
81     * <li><code>build.java</code></li>
82     * <li><code>build.maven</code></li>
83     * <li><code>build.misc</code></li>
84     * </ul>
85     * <p>
86     * If no valid section is given, the property is silently rendered in the
87     * <code>build.misc</code> section.
88     * </p>
89     *
90     * @parameter
91     */
92    private final List<Property> properties;
93  
94    // ****************************** Initializer *******************************
95  
96    // ****************************** Constructors ******************************
97  
98    /**
99     * Default constructor.
100    *
101    * @param messages the resource bundle to access localized messages.
102    * @param sink the sink to write to.
103    * @param buildMetaDataPropertiesFile the properties file to read the build
104    *          information from.
105    * @param properties the list of a system properties or environment variables
106    *          to be selected by the user to include into the build meta data
107    *          properties.
108    */
109   public BuildReportRenderer(final ResourceBundle messages, final Sink sink,
110       final File buildMetaDataPropertiesFile, final List<Property> properties)
111   {
112     this.sink = sink;
113     this.messages = messages;
114     this.buildMetaDataPropertiesFile = buildMetaDataPropertiesFile;
115     this.properties = properties;
116   }
117 
118   // ****************************** Inner Classes *****************************
119 
120   // ********************************* Methods ********************************
121 
122   // --- init -----------------------------------------------------------------
123 
124   // --- get&set --------------------------------------------------------------
125 
126   // --- business -------------------------------------------------------------
127 
128   /**
129    * Renders the report to the instance's sink.
130    *
131    * @throws MavenReportException if the report cannot be rendered.
132    */
133   public void renderReport() throws MavenReportException
134   {
135     sink.head();
136     sink.title();
137     sink.text(messages.getString("report.name"));
138     sink.title_();
139     sink.head_();
140 
141     sink.body();
142     renderBody();
143     sink.body_();
144     sink.flush();
145     sink.close();
146   }
147 
148   /**
149    * Renders the body of the report.
150    *
151    * @throws MavenReportException if the report cannot be rendered.
152    */
153   private void renderBody() throws MavenReportException
154   {
155     sink.section1();
156 
157     sink.sectionTitle1();
158     sink.text(messages.getString("report.name"));
159     sink.sectionTitle1_();
160 
161     sink.paragraph();
162     sink.text(messages.getString("report.description"));
163     sink.paragraph_();
164 
165     final Properties buildMetaDataProperties = readBuildMetaDataProperties();
166 
167     renderSections(buildMetaDataProperties);
168 
169     renderFooter();
170     sink.section1_();
171   }
172 
173   private void renderSections(final Properties buildMetaDataProperties)
174   {
175     for (final Section section : Constant.REPORT_PROPERTIES)
176     {
177       final List<String> properties = section.getProperties();
178       if (hasPropertiesProvided(buildMetaDataProperties, properties))
179       {
180         final String sectionKey = section.getTitleKey();
181         sink.sectionTitle2();
182         sink.text(messages.getString(sectionKey));
183         sink.sectionTitle2_();
184         renderTableStart();
185         for (final String key : properties)
186         {
187           renderCell(buildMetaDataProperties, key);
188         }
189         renderSelectedPropertiesForSection(buildMetaDataProperties, sectionKey);
190         renderTableEnd();
191       }
192     }
193 
194     renderNonStandardProperties(buildMetaDataProperties);
195   }
196 
197   private boolean hasPropertiesProvided(
198       final Properties buildMetaDataProperties, final List<String> properties)
199   {
200     for (final String key : properties)
201     {
202       final Object value = buildMetaDataProperties.get(key);
203       if (value != null && StringUtils.isNotBlank(String.valueOf(value)))
204       {
205         return true;
206       }
207     }
208 
209     final Set<String> selectedProperties = createSelectedProperties();
210     for (final String key : selectedProperties)
211     {
212       final Object value = buildMetaDataProperties.get(key);
213       if (value != null && StringUtils.isNotBlank(String.valueOf(value)))
214       {
215         return true;
216       }
217     }
218 
219     return false;
220   }
221 
222   private void renderSelectedPropertiesForSection(
223       final Properties buildMetaDataProperties, final String sectionKey)
224   {
225     if (properties != null && !properties.isEmpty())
226     {
227       for (final Property property : properties)
228       {
229         if (sectionKey.equals(property.getSection()))
230         {
231           final String key = property.getName();
232           renderCell(buildMetaDataProperties, key);
233         }
234       }
235     }
236   }
237 
238   private void renderNonStandardProperties(
239       final Properties buildMetaDataProperties)
240   {
241     final Properties nonStandardProperties =
242         Constant.calcNonStandardProperties(buildMetaDataProperties, properties);
243     if (!nonStandardProperties.isEmpty())
244     {
245       sink.sectionTitle2();
246       sink.text(messages.getString(Constant.SECTION_BUILD_MISC));
247       sink.sectionTitle2_();
248       renderTableStart();
249       for (final Enumeration<Object> en = nonStandardProperties.keys(); en
250           .hasMoreElements();)
251       {
252         final String key = String.valueOf(en.nextElement());
253         if (Constant.isIntendedForMiscSection(key))
254         {
255           renderCell(nonStandardProperties, key);
256         }
257       }
258       renderTableEnd();
259     }
260   }
261 
262   private Set<String> createSelectedProperties()
263   {
264     final Set<String> selectedProperties = new HashSet<String>();
265 
266     if (properties != null)
267     {
268       for (final Property property : properties)
269       {
270         selectedProperties.add(property.getName());
271       }
272     }
273 
274     return selectedProperties;
275   }
276 
277   private void renderTableEnd()
278   {
279     sink.table_();
280   }
281 
282   private void renderTableStart()
283   {
284     sink.table();
285     sink.tableRow();
286     sink.tableHeaderCell("200");
287     final String topicLabel = messages.getString("report.table.header.topic");
288     sink.text(topicLabel);
289     sink.tableHeaderCell_();
290     sink.tableHeaderCell();
291     final String valueLabel = messages.getString("report.table.header.value");
292     sink.text(valueLabel);
293     sink.tableHeaderCell_();
294     sink.tableRow_();
295   }
296 
297   /**
298    * Renders a single cell of the table.
299    *
300    * @param buildMetaDataProperties build meta data properties to access the
301    *          data to be rendered.
302    * @param key the key to the data to be rendered.
303    */
304   private void renderCell(final Properties buildMetaDataProperties,
305       final String key)
306   {
307     final Object value = buildMetaDataProperties.get(key);
308     if (value != null)
309     {
310       sink.tableRow();
311       sink.tableCell();
312       sink.text(getLabel(key));
313       sink.tableCell_();
314       sink.tableCell();
315       if (Constant.PROP_NAME_MAVEN_ACTIVE_PROFILES.equals(key))
316       {
317         renderMultiTupleValue(buildMetaDataProperties, value,
318             Constant.MAVEN_ACTIVE_PROFILE_PREFIX);
319       }
320       else if (Constant.PROP_NAME_SCM_LOCALLY_MODIFIED_FILES.equals(key))
321       {
322         final String filesValue = Constant.prettifyFilesValue(value);
323         renderMultiValue(filesValue);
324       }
325       else if (Constant.PROP_NAME_MAVEN_GOALS.equals(key))
326       {
327         renderMultiValue(value);
328       }
329       else if (Constant.PROP_NAME_MAVEN_FILTERS.equals(key))
330       {
331         renderMultiValue(value);
332       }
333       else
334       {
335         renderSingleValue(value);
336       }
337       sink.tableCell_();
338       sink.tableRow_();
339     }
340   }
341 
342   private void renderSingleValue(final Object value)
343   {
344     final String stringValue = String.valueOf(value);
345     if (stringValue != null && !isLink(stringValue))
346     {
347       sink.text(stringValue);
348     }
349     else
350     {
351       sink.link(stringValue);
352       sink.text(stringValue);
353       sink.link_();
354     }
355   }
356 
357   private boolean isLink(final String input)
358   {
359     return (input.startsWith("http://") || input.startsWith("https://"));
360   }
361 
362   private void renderMultiTupleValue(final Properties buildMetaDataProperties,
363       final Object value, final String subKeyPrefix)
364   {
365     final String stringValue = Constant.prettify((String) value);
366     if (hasMultipleValues(stringValue))
367     {
368       final StringTokenizer tokenizer = new StringTokenizer(stringValue, ",");
369       sink.numberedList(Sink.NUMBERING_DECIMAL);
370       while (tokenizer.hasMoreTokens())
371       {
372         final String profileName = tokenizer.nextToken().trim();
373         final String subKey = subKeyPrefix + '.' + profileName;
374         final Object subValue = buildMetaDataProperties.get(subKey);
375         final String item = profileName + ':' + subValue;
376         sink.listItem();
377         sink.text(item);
378         sink.listItem_();
379       }
380       sink.numberedList_();
381     }
382     else
383     {
384       sink.text(String.valueOf(value));
385     }
386   }
387 
388   private void renderMultiValue(final Object value)
389   {
390     final String stringValue = Constant.prettify((String) value);
391     if (hasMultipleValues(stringValue))
392     {
393       final StringTokenizer tokenizer = new StringTokenizer(stringValue, ",");
394       sink.numberedList(Sink.NUMBERING_DECIMAL);
395       while (tokenizer.hasMoreTokens())
396       {
397         final String subValue = tokenizer.nextToken();
398         sink.listItem();
399         sink.text(String.valueOf(subValue));
400         sink.listItem_();
401       }
402       sink.numberedList_();
403     }
404     else
405     {
406       sink.text(stringValue);
407     }
408   }
409 
410   private boolean hasMultipleValues(final String stringValue)
411   {
412     return stringValue.indexOf(',') != -1;
413   }
414 
415   private String getLabel(final String key)
416   {
417     try
418     {
419       return messages.getString(key);
420     }
421     catch (final MissingResourceException e)
422     {
423       if (properties != null)
424       {
425         for (final Property property : properties)
426         {
427           final String label = property.getLabel();
428           if (StringUtils.isNotBlank(label)
429               && key.equals(property.getMappedName()))
430           {
431             return label;
432           }
433         }
434       }
435       return key;
436     }
437   }
438 
439   /**
440    * Renders the footer text.
441    */
442   private void renderFooter()
443   {
444     final String footerText = messages.getString("report.footer");
445     if (StringUtils.isNotBlank(footerText))
446     {
447       sink.rawText(footerText);
448     }
449   }
450 
451   /**
452    * Reads the build meta data properties from the well known location.
453    *
454    * @return the read properties.
455    * @throws MavenReportException if the properties cannot be read.
456    */
457   private Properties readBuildMetaDataProperties() throws MavenReportException
458   {
459     final Properties buildMetaDataProperties = new Properties();
460     InputStream inStream = null;
461     try
462     {
463       inStream =
464           new BufferedInputStream(new FileInputStream(
465               this.buildMetaDataPropertiesFile));
466       buildMetaDataProperties.load(inStream);
467     }
468     catch (final IOException e)
469     {
470       throw new MavenReportException("Cannot read build properties file '"
471                                      + this.buildMetaDataPropertiesFile + "'.",
472           e);
473     }
474     finally
475     {
476       IOUtils.closeQuietly(inStream);
477     }
478     return buildMetaDataProperties;
479   }
480 
481   // --- object basics --------------------------------------------------------
482 
483 }