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.configuration;
20  
21  import java.io.IOException;
22  import java.io.Writer;
23  import java.lang.reflect.InvocationTargetException;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.HashSet;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.SortedSet;
30  import java.util.UUID;
31  import java.util.function.Predicate;
32  
33  import org.apache.commons.lang3.StringUtils;
34  import org.apache.rat.ImplementationException;
35  import org.apache.rat.ReportConfiguration;
36  import org.apache.rat.analysis.IHeaderMatcher;
37  import org.apache.rat.api.RatException;
38  import org.apache.rat.config.parameters.ComponentType;
39  import org.apache.rat.config.parameters.Description;
40  import org.apache.rat.configuration.builders.MatcherRefBuilder;
41  import org.apache.rat.license.ILicense;
42  import org.apache.rat.license.ILicenseFamily;
43  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
44  import org.apache.rat.report.xml.writer.IXmlWriter;
45  import org.apache.rat.report.xml.writer.XmlWriter;
46  
47  /**
48   * Writes the XML configuration file format.
49   */
50  public class XMLConfigurationWriter {
51      /** The configuration that is being written */
52      private final ReportConfiguration configuration;
53      /** The set of defined matcher IDs */
54      private final Set<String> matchers;
55      /** The set of defined license IDs */
56      private final Set<String> licenseChildren;
57  
58      /**
59       * Constructor
60       * @param configuration the configuration information to write.
61       */
62      public XMLConfigurationWriter(final ReportConfiguration configuration) {
63          this.configuration = configuration;
64          this.matchers = new HashSet<>();
65          licenseChildren = new HashSet<>(Arrays.asList(XMLConfig.LICENSE_CHILDREN));
66      }
67  
68      private Predicate<Description> attributeFilter(final Description parent) {
69          return d -> {
70              if (d.getType() == ComponentType.PARAMETER) {
71                  switch (parent.getType()) {
72                  case MATCHER:
73                      return !XMLConfig.isInlineNode(parent.getCommonName(), d.getCommonName());
74                  case LICENSE:
75                      return !licenseChildren.contains(d.getCommonName());
76                  default:
77                      return true;
78                  }
79              }
80              return false;
81          };
82      }
83  
84      /**
85       * Writes the configuration to the specified writer.
86       * @param plainWriter a writer to write the XML to.
87       * @throws RatException on error.
88       */
89      public void write(final Writer plainWriter) throws RatException {
90          write(new XmlWriter(plainWriter));
91      }
92  
93      /**
94       * Writes the configuration to an IXmlWriter instance.
95       * @param writer the IXmlWriter to write to.
96       * @throws RatException on error.
97       */
98      public void write(final IXmlWriter writer) throws RatException {
99          if (configuration.listFamilies() != LicenseFilter.NONE || configuration.listLicenses() != LicenseFilter.NONE) {
100             try {
101                 writer.openElement(XMLConfig.ROOT);
102 
103                 // write Families section
104                 SortedSet<ILicenseFamily> families = configuration.getLicenseFamilies(configuration.listFamilies());
105                 if (!families.isEmpty()) {
106                     writer.openElement(XMLConfig.FAMILIES);
107                     for (ILicenseFamily family : families) {
108                         writeFamily(writer, family);
109                     }
110                     writer.closeElement(); // FAMILIES
111                 }
112 
113                 // write licenses section
114                 SortedSet<ILicense> licenses = configuration.getLicenses(configuration.listLicenses());
115                 if (!licenses.isEmpty()) {
116                     writer.openElement(XMLConfig.LICENSES);
117                     for (ILicense license : licenses) {
118                         writeDescription(writer, license.getDescription(), license);
119                     }
120                     writer.closeElement(); // LICENSES
121                 }
122 
123                 // write approved section
124                 writer.openElement(XMLConfig.APPROVED);
125                 for (String family : configuration.getLicenseCategories(LicenseFilter.APPROVED)) {
126                     writer.openElement(XMLConfig.APPROVED).attribute(XMLConfig.ATT_LICENSE_REF, family.trim())
127                             .closeElement();
128                 }
129                 writer.closeElement(); // APPROVED
130 
131                 // write matchers section
132                 MatcherBuilderTracker tracker = MatcherBuilderTracker.instance();
133                 writer.openElement(XMLConfig.MATCHERS);
134                 for (Class<?> clazz : tracker.getClasses()) {
135                     writer.openElement(XMLConfig.MATCHER).attribute(XMLConfig.ATT_CLASS_NAME, clazz.getCanonicalName())
136                             .closeElement();
137                 }
138                 writer.closeElement(); // MATCHERS
139 
140                 writer.closeElement(); // ROOT
141             } catch (IOException e) {
142                 throw new RatException(e);
143             }
144         }
145     }
146 
147     private void writeFamily(final IXmlWriter writer, final ILicenseFamily family) throws RatException {
148         try {
149             writer.openElement(XMLConfig.FAMILY).attribute(XMLConfig.ATT_ID, family.getFamilyCategory().trim())
150                     .attribute(XMLConfig.ATT_NAME, family.getFamilyName());
151             writer.closeElement();
152         } catch (IOException e) {
153             throw new RatException(e);
154         }
155     }
156 
157     private void writeDescriptions(final IXmlWriter writer, final Collection<Description> descriptions, final IHeaderMatcher component)
158             throws RatException {
159         for (Description description : descriptions) {
160             writeDescription(writer, description, component);
161         }
162     }
163 
164     private void writeChildren(final IXmlWriter writer, final Description description, final IHeaderMatcher component)
165             throws RatException {
166         writeAttributes(writer, description.filterChildren(attributeFilter(component.getDescription())), component);
167         writeDescriptions(writer, description.filterChildren(attributeFilter(component.getDescription()).negate()),
168                 component);
169     }
170 
171     private void writeAttributes(final IXmlWriter writer, final Collection<Description> descriptions, final IHeaderMatcher component)
172             throws RatException {
173         for (Description d : descriptions) {
174             try {
175                 writeAttribute(writer, d, component);
176             } catch (IOException e) {
177                 throw new RatException(e);
178             }
179         }
180     }
181 
182     private void writeComment(final IXmlWriter writer, final Description description) throws IOException {
183         if (StringUtils.isNotBlank(description.getDescription())) {
184             writer.comment(description.getDescription());
185         }
186     }
187 
188     private void writeAttribute(final IXmlWriter writer, final Description description, final IHeaderMatcher component)
189             throws IOException {
190         String paramValue = description.getParamValue(component);
191         if (paramValue != null) {
192             writer.attribute(description.getCommonName(), paramValue);
193         }
194     }
195 
196     /* package private for testing */
197     @SuppressWarnings("unchecked")
198     void writeDescription(final IXmlWriter writer, final Description desc, final IHeaderMatcher comp) throws RatException {
199         Description description = desc;
200         IHeaderMatcher component = comp;
201         try {
202             switch (description.getType()) {
203             case MATCHER:
204                 // see if id was registered
205                 Optional<Description> id = description.childrenOfType(ComponentType.PARAMETER).stream()
206                         .filter(i -> XMLConfig.ATT_ID.equals(i.getCommonName())).findFirst();
207 
208                 // id will not be present in matcherRef
209                 if (id.isPresent()) {
210                     String matcherId = id.get().getParamValue(component);
211                     // if we have seen the ID before just put a reference to the other one.
212                     if (matchers.contains(matcherId)) {
213                         component = new MatcherRefBuilder.IHeaderMatcherProxy(matcherId, null);
214                         description = component.getDescription();
215                     } else {
216                         matchers.add(matcherId);
217                     }
218                     // remove the matcher id if it is a UUID
219                     try {
220                         UUID.fromString(matcherId);
221                         description.getChildren().remove(XMLConfig.ATT_ID);
222                     } catch (IllegalArgumentException expected) {
223                         if (description.getCommonName().equals("spdx")) {
224                             description.getChildren().remove(XMLConfig.ATT_ID);
225                         }
226                     }
227                 }
228 
229                 // if resource only list the resource not the contents of the matcher
230                 Optional<Description> resource = description.childrenOfType(ComponentType.PARAMETER).stream()
231                         .filter(i -> XMLConfig.ATT_RESOURCE.equals(i.getCommonName())).findFirst();
232                 if (resource.isPresent()) {
233                     String resourceStr = resource.get().getParamValue(component);
234                     if (StringUtils.isNotBlank(resourceStr)) {
235                         description.getChildren().remove("enclosed");
236                     }
237                 }
238                 writeComment(writer, description);
239                 writer.openElement(description.getCommonName());
240                 writeChildren(writer, description, component);
241                 writer.closeElement();
242                 break;
243             case LICENSE:
244                 writer.openElement(XMLConfig.LICENSE);
245                 writeChildren(writer, description, component);
246                 writer.closeElement();
247                 break;
248             case PARAMETER:
249                 if ("id".equals(description.getCommonName())) {
250                     try {
251                         String paramId = description.getParamValue(component);
252                         // if a UUID skip it.
253                         if (paramId != null) {
254                             UUID.fromString(paramId);
255                             return;
256                         }
257                     } catch (IllegalArgumentException expected) {
258                         // do nothing.
259                     }
260                 }
261                 if (description.getChildType() == String.class) {
262 
263                     boolean inline = XMLConfig.isInlineNode(component.getDescription().getCommonName(),
264                             description.getCommonName());
265                     String s = description.getParamValue(component);
266                     if (StringUtils.isNotBlank(s)) {
267                         if (!inline) {
268                             writer.openElement(description.getCommonName());
269                         }
270                         writer.content(description.getParamValue(component));
271                         if (!inline) {
272                             writer.closeElement();
273                         }
274                     }
275                 } else {
276                     try {
277                         if (description.isCollection()) {
278                             for (IHeaderMatcher matcher : (Collection<IHeaderMatcher>) description
279                                     .getter(component.getClass()).invoke(component)) {
280                                 writeDescription(writer, matcher.getDescription(), matcher);
281                             }
282                         } else {
283                             IHeaderMatcher matcher = (IHeaderMatcher) description.getter(component.getClass())
284                                     .invoke(component);
285                             writeDescription(writer, matcher.getDescription(), matcher);
286                         }
287                     } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
288                             | NoSuchMethodException | SecurityException | RatException e) {
289                         throw new ImplementationException(e);
290                     }
291                 }
292                 break;
293             case BUILD_PARAMETER:
294                 // ignore;
295                 break;
296             }
297         } catch (IOException e) {
298             throw new RatException(e);
299         }
300     }
301 }