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.io;
17  
18  import java.io.IOException;
19  import java.text.DateFormat;
20  import java.text.ParseException;
21  import java.text.SimpleDateFormat;
22  import java.util.Date;
23  import java.util.Enumeration;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.StringTokenizer;
29  
30  import org.apache.commons.lang.time.DateFormatUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.codehaus.plexus.util.StringUtils;
34  import org.w3c.dom.DOMException;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Text;
38  
39  import de.smartics.maven.plugin.buildmetadata.common.Constant;
40  import de.smartics.maven.plugin.buildmetadata.common.Property;
41  import de.smartics.maven.plugin.buildmetadata.common.SortedProperties;
42  
43  /**
44   * Creates an XML report with the build meta data. The report contains the same
45   * information as the <code>build.properties</code> file. It is useful for use
46   * cases where the build meta data information will be further processed by XSL
47   * transformations which require XML documents as input.
48   *
49   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
50   * @version $Revision:591 $
51   */
52  public final class SdocBuilder
53  { // NOPMD
54    // ********************************* Fields *********************************
55  
56    // --- constants ------------------------------------------------------------
57  
58    /**
59     * The URI of the XML schema instance.
60     * <p>
61     * The value of this constant is {@value}.
62     * </p>
63     */
64    private static final String XML_SCHEMA_INSTANCE =
65        "http://www.w3.org/2001/XMLSchema-instance";
66  
67    /**
68     * The URI of the code doctype.
69     * <p>
70     * The value of this constant is {@value}.
71     * </p>
72     */
73    private static final String CODE_URI =
74        "http://www.smartics.de/project/process/implementation/buildmetadata";
75  
76    /**
77     * The generic identifier of the element name containing a version
78     * information.
79     * <p>
80     * The value of this constant is {@value}.
81     * </p>
82     */
83    private static final String GI_VERSION = "version";
84  
85    /**
86     * The generic identifier of the element name containing a name information.
87     * <p>
88     * The value of this constant is {@value}.
89     * </p>
90     */
91    private static final String GI_NAME = "name";
92  
93    /**
94     * Reference to the logger for this class.
95     */
96    private static final Log LOG = LogFactory.getLog(SdocBuilder.class);
97  
98    // --- members --------------------------------------------------------------
99  
100   /**
101    * The empty document to write to.
102    */
103   private final Document document;
104 
105   /**
106    * The properties to write to the XML report.
107    */
108   private final Properties buildMetaDataProperties;
109 
110   /**
111    * The list of a system properties or environment variables to be selected by
112    * the user to include into the build meta data properties.
113    * <p>
114    * The name is the name of the property, the section is relevant for placing
115    * the property in one of the following sections:
116    * </p>
117    * <ul>
118    * <li><code>build.scm</code></li>
119    * <li><code>build.dateAndVersion</code></li>
120    * <li><code>build.runtime</code></li>
121    * <li><code>build.java</code></li>
122    * <li><code>build.maven</code></li>
123    * <li><code>build.misc</code></li>
124    * </ul>
125    * <p>
126    * If no valid section is given, the property is silently rendered in the
127    * <code>build.misc</code> section.
128    * </p>
129    */
130   private final List<Property> selectedProperties;
131 
132   // ****************************** Initializer *******************************
133 
134   // ****************************** Constructors ******************************
135 
136   /**
137    * Default constructor.
138    *
139    * @param document the empty document to write to.
140    * @param buildMetaDataProperties the properties to write to the XML report.
141    * @param selectedProperties the list of a system properties or environment
142    *          variables to be selected by the user to include into the build
143    *          meta data properties.
144    */
145   public SdocBuilder(final Document document,
146       final Properties buildMetaDataProperties,
147       final List<Property> selectedProperties)
148   {
149     this.document = document;
150     this.buildMetaDataProperties = buildMetaDataProperties;
151     this.selectedProperties = selectedProperties;
152   }
153 
154   // ****************************** Inner Classes *****************************
155 
156   // ********************************* Methods ********************************
157 
158   // --- init -----------------------------------------------------------------
159 
160   // --- get&set --------------------------------------------------------------
161 
162   // --- business -------------------------------------------------------------
163 
164   /**
165    * Writes the content to the document.
166    *
167    * @return the written XML document.
168    * @throws IOException on any problem writing to the XML document.
169    */
170   public Document writeDocumentContent() throws IOException
171   {
172     final Element docRoot = createDocRoot();
173 
174     createContentElement(GI_NAME, Constant.PROP_NAME_FULL_VERSION, docRoot);
175     createContentElement(GI_VERSION, Constant.PROP_NAME_VERSION, docRoot);
176     createContentElement("groupId", Constant.PROP_NAME_GROUP_ID, docRoot);
177     createContentElement("artifactId", Constant.PROP_NAME_ARTIFACT_ID, docRoot);
178     final String date = formatDate(Constant.PROP_NAME_BUILD_DATE);
179     createValueElement("date", date, docRoot);
180     createContentElement("timestamp", Constant.PROP_NAME_BUILD_TIMESTAMP,
181         docRoot);
182 
183     createScmElement(docRoot);
184     createRuntimeElement(docRoot);
185     createMiscElement(docRoot);
186 
187     return document;
188   }
189 
190   private String formatDate(final String datePropertyKey)
191   {
192     final String originalDateString =
193         buildMetaDataProperties.getProperty(datePropertyKey);
194     if (StringUtils.isNotBlank(originalDateString))
195     {
196       try
197       {
198         final String originalPattern =
199             buildMetaDataProperties
200                 .getProperty(Constant.PROP_NAME_BUILD_DATE_PATTERN);
201         final DateFormat format =
202             new SimpleDateFormat(originalPattern, Locale.ENGLISH);
203         final Date date = format.parse(originalDateString);
204         final String dateString =
205             DateFormatUtils.ISO_DATETIME_FORMAT.format(date);
206         return dateString;
207       }
208       catch (final ParseException e)
209       {
210         if (LOG.isDebugEnabled())
211         {
212           LOG.debug("Cannot parse date of property '" + datePropertyKey + "': "
213                     + originalDateString + ". Skipping...");
214         }
215         return null;
216       }
217     }
218     return null;
219   }
220 
221   private void createScmElement(final Element docRoot)
222   {
223     final Element parent = document.createElement("scm");
224     createContentElement("revision", Constant.PROP_NAME_SCM_REVISION_ID, parent);
225     final String date = formatDate(Constant.PROP_NAME_SCM_REVISION_DATE);
226     createValueElement("revision-date", date, parent);
227     createContentElement("url", Constant.PROP_NAME_SCM_URL, parent);
228     createLocallyModifiedFiles(parent);
229     docRoot.appendChild(parent);
230   }
231 
232   private void createLocallyModifiedFiles(final Element scm)
233   {
234     final String value =
235         buildMetaDataProperties
236             .getProperty(Constant.PROP_NAME_SCM_LOCALLY_MODIFIED_FILES);
237 
238     if (StringUtils.isNotBlank(value))
239     {
240       final Element parent = document.createElement("locally-modified-files");
241 
242       final String filesValue = Constant.prettifyFilesValue(value);
243       renderFiles(parent, filesValue);
244       scm.appendChild(parent);
245     }
246   }
247 
248   private void renderFiles(final Element lmf, final String value)
249   {
250     final String stringValue = Constant.prettify(value);
251     final StringTokenizer tokenizer = new StringTokenizer(stringValue, ",");
252     while (tokenizer.hasMoreTokens())
253     {
254       final String subValue = tokenizer.nextToken();
255       final int colonIndex = subValue.indexOf(':');
256       if (colonIndex > -1)
257       {
258         final String filePath = subValue.substring(0, colonIndex);
259         final Element file = createValueElement("file", filePath, lmf);
260         if (file != null && colonIndex < subValue.length() - 1)
261         {
262           final String modType = subValue.substring(colonIndex + 1).trim();
263           file.setAttribute("modtype", modType);
264         }
265       }
266     }
267   }
268 
269   private void createRuntimeElement(final Element docRoot)
270   {
271     final Element parent = document.createElement("runtime");
272 
273     createContentElement("build-server", Constant.PROP_NAME_HOSTNAME, parent);
274     createContentElement("build-user", Constant.PROP_NAME_BUILD_USER, parent);
275 
276     createOsElement(parent);
277     createJavaElement(parent);
278     createMavenElement(parent);
279     createEnvElement(parent);
280 
281     docRoot.appendChild(parent);
282   }
283 
284   private void createOsElement(final Element runtime)
285   {
286     final Element parent = document.createElement("os");
287     createContentElement("arch", Constant.PROP_NAME_OS_ARCH, parent);
288     createContentElement(GI_NAME, Constant.PROP_NAME_OS_NAME, parent);
289     createContentElement(GI_VERSION, Constant.PROP_NAME_OS_VERSION, parent);
290     if (parent.hasChildNodes())
291     {
292       runtime.appendChild(parent);
293     }
294   }
295 
296   private void createJavaElement(final Element runtime)
297   {
298     final Element parent = document.createElement("java");
299     createContentElement(GI_NAME, Constant.PROP_NAME_JAVA_RUNTIME_NAME, parent);
300     createContentElement(GI_VERSION, Constant.PROP_NAME_JAVA_RUNTIME_VERSION,
301         parent);
302     createContentElement("vendor", Constant.PROP_NAME_JAVA_VENDOR, parent);
303     createContentElement("vm", Constant.PROP_NAME_JAVA_VM, parent);
304     createContentElement("compiler", Constant.PROP_NAME_JAVA_COMPILER, parent);
305     createContentElement("options", Constant.PROP_NAME_JAVA_OPTS, parent);
306     if (parent.hasChildNodes())
307     {
308       runtime.appendChild(parent);
309     }
310   }
311 
312   private void createMavenElement(final Element runtime)
313   {
314     final Element parent = document.createElement("maven");
315     createContentElement(GI_VERSION, Constant.PROP_NAME_MAVEN_VERSION, parent);
316 
317     final Element goals = document.createElement("goals");
318     final String goalsString =
319         buildMetaDataProperties.getProperty(Constant.PROP_NAME_MAVEN_GOALS);
320     renderGoals(goals, goalsString);
321     parent.appendChild(goals);
322 
323     final Element profiles = document.createElement("profiles");
324     final String profilesString =
325         buildMetaDataProperties
326             .getProperty(Constant.PROP_NAME_MAVEN_ACTIVE_PROFILES);
327     if (StringUtils.isNotBlank(profilesString))
328     {
329       renderProfiles(profiles, profilesString);
330       parent.appendChild(profiles);
331     }
332 
333     createContentElement("options", Constant.PROP_NAME_MAVEN_OPTS, parent);
334     if (parent.hasChildNodes())
335     {
336       runtime.appendChild(parent);
337     }
338   }
339 
340   private void renderGoals(final Element goals, final String value)
341   {
342     if (StringUtils.isNotBlank(value))
343     {
344       final String stringValue = Constant.prettify(value);
345       final StringTokenizer tokenizer = new StringTokenizer(stringValue, ",");
346       while (tokenizer.hasMoreTokens())
347       {
348         final String goal = tokenizer.nextToken();
349         createValueElement("goal", goal.trim(), goals);
350       }
351     }
352   }
353 
354   private void renderProfiles(final Element profiles, final String value)
355   {
356     final String stringValue = Constant.prettify(value);
357     final StringTokenizer tokenizer = new StringTokenizer(stringValue, ",");
358     while (tokenizer.hasMoreTokens())
359     {
360       final String profileName = tokenizer.nextToken().trim();
361       final Element profile =
362           createValueElement("profile", profileName, profiles);
363       if (profile != null)
364       {
365         final String profileSourceKey =
366             Constant.MAVEN_ACTIVE_PROFILE_PREFIX + '.' + profileName;
367         final String source =
368             buildMetaDataProperties.getProperty(profileSourceKey);
369         profile.setAttribute("source", source);
370       }
371     }
372   }
373 
374   private void createEnvElement(final Element runtime)
375   {
376     final Element parent = document.createElement("env");
377 
378     final Properties sorted =
379         SortedProperties.createSorted(buildMetaDataProperties);
380     final String matchPrefix = Constant.MAVEN_EXECUTION_PROPERTIES_PREFIX + '.';
381     for (final Map.Entry<Object, Object> entry : sorted.entrySet())
382     {
383       final String key = String.valueOf(entry.getKey());
384       if (key.startsWith(matchPrefix))
385       {
386         final String value = String.valueOf(entry.getValue());
387         final Element env = createValueElement("var", value, parent);
388         if (env != null)
389         {
390           env.setAttribute(GI_NAME, key);
391         }
392       }
393     }
394 
395     if (parent.hasChildNodes())
396     {
397       runtime.appendChild(parent);
398     }
399   }
400 
401   private void createMiscElement(final Element docRoot)
402   {
403     final Properties nonStandardProperties =
404         Constant.calcNonStandardProperties(buildMetaDataProperties,
405             selectedProperties);
406     if (!nonStandardProperties.isEmpty())
407     {
408       final Element parent = document.createElement("misc");
409 
410       for (final Enumeration<Object> en = nonStandardProperties.keys(); en
411           .hasMoreElements();)
412       {
413         final String key = String.valueOf(en.nextElement());
414         createMetaDataElement(parent, key);
415       }
416       docRoot.appendChild(parent);
417     }
418   }
419 
420   private void createMetaDataElement(final Element parent, final String key)
421   {
422     if (Constant.isIntendedForMiscSection(key))
423     {
424       final Element metadata = createContentElement("metadata", key, parent);
425       if (metadata != null)
426       {
427         metadata.setAttribute(GI_NAME, key);
428       }
429     }
430   }
431 
432   private Element createDocRoot() throws DOMException
433   {
434     final Element docRoot = document.createElement("buildmetadata");
435     docRoot.setAttribute("xmlns:xsi", XML_SCHEMA_INSTANCE);
436     docRoot.setAttribute("xmlns", CODE_URI);
437     docRoot.setAttribute("xsi:schemaLocation", CODE_URI + ' ' + CODE_URI);
438     document.appendChild(docRoot);
439     return docRoot;
440   }
441 
442   private Element createContentElement(final String gi,
443       final String propertyKey, final Element parent)
444   {
445     final String content = buildMetaDataProperties.getProperty(propertyKey);
446     return createValueElement(gi, content, parent);
447   }
448 
449   private Element createValueElement(final String gi, final String value,
450       final Element parent)
451   {
452     if (value != null)
453     {
454       final Element element = document.createElement(gi);
455       final Text text = document.createTextNode(value);
456       element.appendChild(text);
457       parent.appendChild(element);
458       return element;
459     }
460     return null;
461   }
462 
463   // --- object basics --------------------------------------------------------
464 
465 }