View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.rat.report.xml;
20  
21  import java.io.IOException;
22  import java.time.Instant;
23  import java.time.ZoneOffset;
24  import java.time.format.DateTimeFormatter;
25  import java.util.Locale;
26  
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.rat.VersionInfo;
29  import org.apache.rat.api.Document;
30  import org.apache.rat.api.MetaData;
31  import org.apache.rat.api.RatException;
32  import org.apache.rat.config.results.ClaimValidator;
33  import org.apache.rat.license.ILicense;
34  import org.apache.rat.report.claim.ClaimStatistic;
35  import org.apache.rat.report.xml.writer.XmlWriter;
36  import org.apache.rat.utils.CasedString;
37  
38  /**
39   * Creates the elements in the XML report.
40   */
41  public final class XmlElements {
42  
43      /**
44       * Format to be used to output the current date,
45       * adapted from commons-lang3's ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT
46       * in order to be compliant with the JDK's java.time-API.
47       * Prefix adapted due to timezone-specific changes and Z in case of UTC.
48       */
49      private static final DateTimeFormatter ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT =
50              DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssxxx").withZone(ZoneOffset.UTC);
51  
52      private XmlElements() {
53          // do not instantiate
54      }
55  
56      /**
57       * Converts an enum name to pascal case.
58       *
59       * @param name the attribute to normalize
60       * @return a pascal cased name.
61       */
62      private static String normalizeName(final String name) {
63          CasedString casedName = new CasedString(CasedString.StringCase.SNAKE, name.toLowerCase(Locale.ROOT));
64          return casedName.toCase(CasedString.StringCase.PASCAL);
65      }
66  
67      /**
68       * Create the RAT report element. Includes the timestamp and the version element.
69       * Does not close the report.
70       *
71       * @throws RatException on error
72       */
73      public static void ratReport(final XmlWriter writer) throws RatException {
74          try {
75              writer.startElement(Elements.RAT_REPORT.elementName)
76                      .attribute(Attributes.TIMESTAMP.attributeName(),
77                              ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(Instant.now()));
78              version(writer);
79          } catch (IOException e) {
80              throw new RatException(e);
81          }
82      }
83  
84      /**
85       * Creates the version element with all version attributes populated. Closes the version element.
86       *
87       * @throws RatException on error
88       */
89      public static void version(final XmlWriter writer) throws RatException {
90          VersionInfo versionInfo = new VersionInfo();
91          try {
92              writer.startElement(Elements.VERSION.elementName)
93                      .attribute(Attributes.PRODUCT.attributeName(), versionInfo.getTitle())
94                      .attribute(Attributes.VENDOR.attributeName(), versionInfo.getVendor())
95                      .attribute(Attributes.VERSION.attributeName(), versionInfo.getVersion())
96                      .closeElement();
97          } catch (IOException e) {
98              throw new RatException(e);
99          }
100     }
101 
102     /**
103      * Creates a license element. Closes the element before exit.
104      *
105      * @param license  the license for the element.
106      * @param approved {@code true} if the license is approved.
107      * @throws RatException on error.
108      */
109     public static void license(final XmlWriter writer, final ILicense license, final boolean approved) throws RatException {
110         try {
111             writer.startElement(Elements.LICENSE.elementName)
112                     .attribute(Attributes.ID.attributeName(), license.getId())
113                     .attribute(Attributes.NAME.attributeName(), license.getName())
114                     .attribute(Attributes.APPROVAL.attributeName(), Boolean.toString(approved))
115                     .attribute(Attributes.FAMILY.attributeName(), license.getLicenseFamily().getFamilyCategory());
116 
117             if (StringUtils.isNotBlank(license.getNote())) {
118                 writer.startElement(Elements.NOTES.elementName).cdata(license.getNote()).closeElement();
119             }
120             writer.closeElement();
121         } catch (IOException e) {
122             throw new RatException(e);
123         }
124     }
125 
126     /**
127      * Creates a document element with attributes. Does <strong>NOT</strong> close the document element.
128      *
129      * @param document the document to write.
130      * @throws RatException on error.
131      */
132     public static void document(final XmlWriter writer, final Document document) throws RatException {
133         final MetaData metaData = document.getMetaData();
134         try {
135             writer.startElement(Elements.RESOURCE.elementName)
136                     .attribute(Attributes.NAME.attributeName(), document.getName().localized("/"))
137                     .attribute(Attributes.TYPE.attributeName(), metaData.getDocumentType().toString())
138                     .attribute(Attributes.MEDIA_TYPE.attributeName(), metaData.getMediaType().toString());
139             if (Document.Type.STANDARD == metaData.getDocumentType() || metaData.hasCharset()) {
140                 writer.attribute(Attributes.ENCODING.attributeName(), metaData.getCharset().displayName());
141             }
142             if (document.isIgnored()) {
143                 writer.attribute(Attributes.IS_DIRECTORY.attributeName(), Boolean.toString(document.isDirectory()));
144             }
145         } catch (IOException e) {
146             throw new RatException(e);
147         }
148     }
149 
150     /**
151      * Creates a statistics element.
152      *
153      * @throws RatException on error.
154      */
155     public static void statistics(final XmlWriter writer, final ClaimStatistic statistic, final ClaimValidator validator) throws RatException {
156         try {
157             writer.startElement(Elements.STATISTICS.elementName);
158             for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) {
159                 int count = statistic.getCounter(counter);
160                 XmlElements.statistic(writer, counter.displayName(), count, counter.getDescription(), validator.isValid(counter, count));
161             }
162             for (String category : statistic.getLicenseFamilyCategories()) {
163                 XmlElements.licenseCategory(writer, category, statistic.getLicenseCategoryCount(category));
164             }
165             for (String category : statistic.getLicenseNames()) {
166                 XmlElements.licenseName(writer, category, statistic.getLicenseNameCount(category));
167             }
168             for (Document.Type type : statistic.getDocumentTypes()) {
169                 XmlElements.documentType(writer, type.name(), statistic.getCounter(type));
170             }
171 
172             writer.closeElement();
173         } catch (IOException e) {
174             throw new RatException(e);
175         }
176     }
177 
178     /**
179      * Creates a statistic element. Closes the element before returning.
180      *
181      * @param name        the name of the statistics element.
182      * @param count       the count for the element.
183      * @param description description of this statistic.
184      * @param isOk        if {@code true} the count is within limits.
185      * @throws RatException on error.
186      */
187     public static void statistic(final XmlWriter writer, final String name, final int count, final String description, final boolean isOk) throws RatException {
188         try {
189             writer.startElement(Elements.STATISTIC.elementName)
190                     .attribute(Attributes.NAME.attributeName(), name)
191                     .attribute(Attributes.COUNT.attributeName(), Integer.toString(count))
192                     .attribute(Attributes.APPROVAL.attributeName(), Boolean.toString(isOk))
193                     .attribute(Attributes.DESCRIPTION.attributeName(), description)
194                     .closeElement();
195         } catch (IOException e) {
196             throw new RatException(e);
197         }
198     }
199 
200     /**
201      * Creates a statistic element. Closes the element before returning.
202      *
203      * @param name  the name of the statistics element.
204      * @param count the count for the element.
205      * @throws RatException on error.
206      */
207     public static void licenseCategory(final XmlWriter writer, final String name, final int count) throws RatException {
208         try {
209             writer.startElement(Elements.LICENSE_CATEGORY.elementName)
210                     .attribute(Attributes.NAME.attributeName(), name)
211                     .attribute(Attributes.COUNT.attributeName(), Integer.toString(count))
212                     .closeElement();
213         } catch (IOException e) {
214             throw new RatException(e);
215         }
216     }
217 
218     /**
219      * Creates a statistic element. Closes the element before returning.
220      *
221      * @param name  the name of the statistics element.
222      * @param count the count for the element.
223      * @throws RatException on error.
224      */
225     public static void licenseName(final XmlWriter writer, final String name, final int count) throws RatException {
226         try {
227             writer.startElement(Elements.LICENSE_NAME.elementName)
228                     .attribute(Attributes.NAME.attributeName(), name)
229                     .attribute(Attributes.COUNT.attributeName(), Integer.toString(count))
230                     .closeElement();
231         } catch (IOException e) {
232             throw new RatException(e);
233         }
234     }
235 
236     /**
237      * Creates a document type element. Closes the element before returning.
238      *
239      * @param name  the name of the document type element.
240      * @param count the count for the element.
241      * @throws RatException on error.
242      */
243     public static void documentType(final XmlWriter writer, final String name, final int count) throws RatException {
244         try {
245             writer.startElement(Elements.DOCUMENT_TYPE.elementName)
246                     .attribute(Attributes.NAME.attributeName(), name)
247                     .attribute(Attributes.COUNT.attributeName(), Integer.toString(count))
248                     .closeElement();
249         } catch (IOException e) {
250             throw new RatException(e);
251         }
252     }
253 
254     /**
255      * The elements in the report.
256      */
257     public enum Elements {
258         /**
259          * The start of the RAT report.
260          */
261         RAT_REPORT("rat-report"),
262         /**
263          * The version of RAT being run.
264          */
265         VERSION(),
266         /**
267          * A resource element.
268          */
269         RESOURCE(),
270         /**
271          * A license element.
272          */
273         LICENSE(),
274         /**
275          * A notes element.
276          */
277         NOTES(),
278         /**
279          * A statistics element.
280          */
281         STATISTICS(),
282         /**
283          * A statistic entry.
284          */
285         STATISTIC(),
286         /**
287          * A license name entry.
288          */
289         LICENSE_NAME(),
290         /**
291          * A license category entry.
292          */
293         LICENSE_CATEGORY(),
294         /**
295          * A document type entry.
296          */
297         DOCUMENT_TYPE();
298 
299         /**
300          * The XML name for the element
301          */
302         private final String elementName;
303 
304         /**
305          * Constructor.
306          *
307          * @param elementName the XML name for the element.
308          */
309         Elements(final String elementName) {
310             this.elementName = elementName;
311         }
312 
313         Elements() {
314             this.elementName = normalizeName(name());
315         }
316     }
317 
318     /**
319      * The attributes of elements in the report.
320      */
321     public enum Attributes {
322         /**
323          * A timestamp.
324          */
325         TIMESTAMP,
326         /**
327          * A version string.
328          */
329         VERSION,
330         /**
331          * The product identifier.
332          */
333         PRODUCT,
334         /**
335          * The vendor identifier.
336          */
337         VENDOR,
338         /**
339          * The approval flag.
340          */
341         APPROVAL,
342         /**
343          * The family category.
344          */
345         FAMILY,
346         /**
347          * The document type.
348          */
349         TYPE,
350         /**
351          * The id.
352          */
353         ID,
354         /**
355          * The name.
356          */
357         NAME,
358         /**
359          * A counter.
360          */
361         COUNT,
362         /**
363          * A description.
364          */
365         DESCRIPTION,
366         /**
367          * The media type for a document.
368          */
369         MEDIA_TYPE,
370         /**
371          * The encoding for a text document.
372          */
373         ENCODING,
374         /**
375          * Denotes a skipped directory.
376          */
377         IS_DIRECTORY;
378 
379         String attributeName() {
380             return normalizeName(this.name());
381         }
382     }
383 }