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