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                  return switch (parent.getType()) {
72                      case MATCHER -> !XMLConfig.isInlineNode(parent.getCommonName(), d.getCommonName());
73                      case LICENSE -> !licenseChildren.contains(d.getCommonName());
74                      default -> 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(final 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(final IXmlWriter writer) throws RatException {
96          if (configuration.listFamilies() != LicenseFilter.NONE || configuration.listLicenses() != LicenseFilter.NONE) {
97              try {
98                  writer.openElement(XMLConfig.ROOT);
99  
100                 // 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                 // 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                 // approved section
121                 writer.openElement(XMLConfig.APPROVED);
122                 for (String family : configuration.getLicenseCategories(LicenseFilter.APPROVED)) {
123                     writer.openElement(XMLConfig.APPROVED).attribute(XMLConfig.ATT_LICENSE_REF, family.trim())
124                             .closeElement();
125                 }
126                 writer.closeElement(); // APPROVED
127 
128                 // 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(final IXmlWriter writer, final 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(final IXmlWriter writer, final Collection<Description> descriptions, final IHeaderMatcher component)
155             throws RatException {
156         for (Description description : descriptions) {
157             writeDescription(writer, description, component);
158         }
159     }
160 
161     private void writeChildren(final IXmlWriter writer, final Description description, final 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(final IXmlWriter writer, final Collection<Description> descriptions, final 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(final IXmlWriter writer, final Description description) throws IOException {
180         if (StringUtils.isNotBlank(description.getDescription())) {
181             writer.comment(description.getDescription().replace("-->", "-&ndash;>"));
182         }
183     }
184 
185     private void writeAttribute(final IXmlWriter writer, final Description description, final IHeaderMatcher component)
186             throws IOException {
187         String paramValue = description.getParamValue(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(final IXmlWriter writer, final Description desc, final IHeaderMatcher comp) throws RatException {
196         Description description = desc;
197         IHeaderMatcher component = comp;
198         try {
199             switch (description.getType()) {
200             case MATCHER:
201                 // see if id was registered
202                 Optional<Description> id = description.childrenOfType(ComponentType.PARAMETER).stream()
203                         .filter(i -> XMLConfig.ATT_ID.equals(i.getCommonName())).findFirst();
204 
205                 // id will not be present in matcherRef
206                 if (id.isPresent()) {
207                     String matcherId = id.get().getParamValue(component);
208                     // if we have seen the ID before, put a reference to the other one.
209                     if (matchers.contains(matcherId)) {
210                         component = new MatcherRefBuilder.IHeaderMatcherProxy(matcherId, null);
211                         description = component.getDescription();
212                     } else {
213                         matchers.add(matcherId);
214                     }
215                     // remove the matcher id if it is a UUID
216                     try {
217                         UUID.fromString(matcherId);
218                         description.getChildren().remove(XMLConfig.ATT_ID);
219                     } catch (IllegalArgumentException expected) {
220                         if (description.getCommonName().equals("spdx")) {
221                             description.getChildren().remove(XMLConfig.ATT_ID);
222                         }
223                     }
224                 }
225 
226                 // if only a resource, list the resource not the contents of the matcher
227                 Optional<Description> resource = description.childrenOfType(ComponentType.PARAMETER).stream()
228                         .filter(i -> XMLConfig.ATT_RESOURCE.equals(i.getCommonName())).findFirst();
229                 if (resource.isPresent()) {
230                     String resourceStr = resource.get().getParamValue(component);
231                     if (StringUtils.isNotBlank(resourceStr)) {
232                         description.getChildren().remove("enclosed");
233                     }
234                 }
235                 writeComment(writer, description);
236                 writer.openElement(description.getCommonName());
237                 writeChildren(writer, description, component);
238                 writer.closeElement();
239                 break;
240             case LICENSE:
241                 writer.openElement(XMLConfig.LICENSE);
242                 writeChildren(writer, description, component);
243                 writer.closeElement();
244                 break;
245             case PARAMETER:
246                 if ("id".equals(description.getCommonName())) {
247                     try {
248                         String paramId = description.getParamValue(component);
249                         // if a UUID skip it.
250                         if (paramId != null) {
251                             UUID.fromString(paramId);
252                             return;
253                         }
254                     } catch (IllegalArgumentException expected) {
255                         // do nothing.
256                     }
257                 }
258                 if (description.getChildType() == String.class) {
259 
260                     boolean inline = XMLConfig.isInlineNode(component.getDescription().getCommonName(),
261                             description.getCommonName());
262                     String s = description.getParamValue(component);
263                     if (StringUtils.isNotBlank(s)) {
264                         if (!inline) {
265                             writer.openElement(description.getCommonName());
266                         }
267                         writer.content(description.getParamValue(component));
268                         if (!inline) {
269                             writer.closeElement();
270                         }
271                     }
272                 } else {
273                     try {
274                         if (description.isCollection()) {
275                             for (IHeaderMatcher matcher : (Collection<IHeaderMatcher>) description
276                                     .getter(component.getClass()).invoke(component)) {
277                                 writeDescription(writer, matcher.getDescription(), matcher);
278                             }
279                         } else {
280                             IHeaderMatcher matcher = (IHeaderMatcher) description.getter(component.getClass())
281                                     .invoke(component);
282                             writeDescription(writer, matcher.getDescription(), matcher);
283                         }
284                     } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
285                             | NoSuchMethodException | SecurityException | RatException e) {
286                         throw new ImplementationException(e);
287                     }
288                 }
289                 break;
290             case BUILD_PARAMETER:
291                 // ignore;
292                 break;
293             }
294         } catch (IOException e) {
295             throw new RatException(e);
296         }
297     }
298 }