1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.configuration;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.Reader;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.SortedSet;
34 import java.util.TreeSet;
35 import java.util.function.BiPredicate;
36 import java.util.function.Consumer;
37 import java.util.stream.Collectors;
38
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42
43 import org.apache.commons.lang3.StringUtils;
44 import org.apache.commons.lang3.tuple.ImmutablePair;
45 import org.apache.commons.lang3.tuple.Pair;
46 import org.apache.rat.BuilderParams;
47 import org.apache.rat.ConfigurationException;
48 import org.apache.rat.ImplementationException;
49 import org.apache.rat.analysis.IHeaderMatcher;
50 import org.apache.rat.config.parameters.ComponentType;
51 import org.apache.rat.config.parameters.Description;
52 import org.apache.rat.config.parameters.DescriptionBuilder;
53 import org.apache.rat.configuration.builders.AbstractBuilder;
54 import org.apache.rat.license.ILicense;
55 import org.apache.rat.license.ILicenseFamily;
56 import org.apache.rat.license.LicenseFamilySetFactory;
57 import org.apache.rat.license.LicenseSetFactory;
58 import org.apache.rat.utils.DefaultLog;
59 import org.apache.rat.utils.Log;
60 import org.w3c.dom.DOMException;
61 import org.w3c.dom.Document;
62 import org.w3c.dom.Element;
63 import org.w3c.dom.NamedNodeMap;
64 import org.w3c.dom.Node;
65 import org.w3c.dom.NodeList;
66 import org.xml.sax.InputSource;
67 import org.xml.sax.SAXException;
68
69
70
71
72 public final class XMLConfigurationReader implements LicenseReader, MatcherReader {
73
74 private Log log;
75 private Document document;
76 private final Element rootElement;
77 private final Element familiesElement;
78 private final Element licensesElement;
79 private final Element approvedElement;
80 private final Element matchersElement;
81
82 private final SortedSet<ILicense> licenses;
83
84 private final Map<String, IHeaderMatcher> matchers;
85 private final BuilderParams builderParams;
86 private final SortedSet<ILicenseFamily> licenseFamilies;
87 private final SortedSet<String> approvedFamilies;
88
89
90
91
92 public XMLConfigurationReader() {
93 try {
94 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
95 } catch (ParserConfigurationException e) {
96 throw new IllegalStateException("No XML parser defined", e);
97 }
98 rootElement = document.createElement(XMLConfig.ROOT);
99 document.appendChild(rootElement);
100 familiesElement = document.createElement(XMLConfig.FAMILIES);
101 rootElement.appendChild(familiesElement);
102 licensesElement = document.createElement(XMLConfig.LICENSES);
103 rootElement.appendChild(licensesElement);
104 approvedElement = document.createElement(XMLConfig.APPROVED);
105 rootElement.appendChild(approvedElement);
106 matchersElement = document.createElement(XMLConfig.MATCHERS);
107 rootElement.appendChild(matchersElement);
108 licenses = LicenseSetFactory.emptyLicenseSet();
109 licenseFamilies = LicenseFamilySetFactory.emptyLicenseFamilySet();
110 approvedFamilies = new TreeSet<>();
111 matchers = new HashMap<>();
112 builderParams = new BuilderParams() {
113 @Override
114 public Map<String, IHeaderMatcher> matcherMap() {
115 return matchers;
116 }
117
118 @Override
119 public SortedSet<ILicenseFamily> licenseFamilies() {
120 return licenseFamilies;
121 }
122 };
123 }
124
125 @Override
126 public void setLog(Log log) {
127 this.log = log;
128 }
129
130
131
132
133
134 public Log getLog() {
135 return log == null ? DefaultLog.getInstance() : log;
136 }
137
138 @Override
139 public void addLicenses(URL url) {
140 read(url);
141 }
142
143
144
145
146
147
148 public void read(Reader reader) {
149 DocumentBuilder builder;
150 try {
151 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
152 } catch (ParserConfigurationException e) {
153 throw new ConfigurationException("Unable to create DOM builder", e);
154 }
155
156 try {
157 add(builder.parse(new InputSource(reader)));
158 } catch (SAXException | IOException e) {
159 throw new ConfigurationException("Unable to read inputSource", e);
160 }
161
162 }
163
164
165
166
167
168
169 public void read(URL... urls) {
170 DocumentBuilder builder;
171 try {
172 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
173 } catch (ParserConfigurationException e) {
174 throw new ConfigurationException("Unable to create DOM builder", e);
175 }
176 for (URL url : urls) {
177 try (InputStream inputStream = url.openStream()){
178 add(builder.parse(inputStream));
179 } catch (SAXException | IOException e) {
180 throw new ConfigurationException("Unable to read url: " + url, e);
181 }
182 }
183 }
184
185
186
187
188
189
190
191 private void nodeListConsumer(NodeList list, Consumer<Node> consumer) {
192 for (int i = 0; i < list.getLength(); i++) {
193 consumer.accept(list.item(i));
194 }
195 }
196
197
198
199
200
201
202 public void add(Document newDoc) {
203 nodeListConsumer(newDoc.getElementsByTagName(XMLConfig.FAMILIES), nl -> nodeListConsumer(nl.getChildNodes(),
204 n -> familiesElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true)))));
205 nodeListConsumer(newDoc.getElementsByTagName(XMLConfig.LICENSE),
206 n -> licensesElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true))));
207 nodeListConsumer(newDoc.getElementsByTagName(XMLConfig.APPROVED), nl -> nodeListConsumer(nl.getChildNodes(),
208 n -> approvedElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true)))));
209 nodeListConsumer(newDoc.getElementsByTagName(XMLConfig.MATCHERS),
210 n -> matchersElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true))));
211 }
212
213
214
215
216
217
218
219 private Map<String, String> attributes(Node node) {
220 NamedNodeMap nnm = node.getAttributes();
221 Map<String, String> result = new HashMap<>();
222 for (int i = 0; i < nnm.getLength(); i++) {
223 Node n = nnm.item(i);
224 result.put(n.getNodeName(), n.getNodeValue());
225 }
226 return result;
227 }
228
229 private void callSetter(Description desc, IHeaderMatcher.Builder builder, Object value) {
230 try {
231 desc.setter(builder.getClass()).invoke(builder, value);
232 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
233 | SecurityException e) {
234 throw new ConfigurationException(e.getMessage(), e);
235 }
236 }
237
238 private void processBuilderParams(Description description, IHeaderMatcher.Builder builder) {
239 for (Description desc : description.childrenOfType(ComponentType.BUILD_PARAMETER)) {
240 Method m = builderParams.get(desc.getCommonName());
241 try {
242 callSetter(desc, builder, m.invoke(builderParams));
243 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
244 throw ImplementationException.makeInstance(e);
245 }
246 }
247 }
248
249
250
251
252
253
254
255
256
257
258
259
260 private void processChildren(Description description, List<Node> children,
261 BiPredicate<Node, Description> childProcessor) {
262 Iterator<Node> iter = children.iterator();
263 while (iter.hasNext()) {
264 Node child = iter.next();
265 Description childDescription = description.getChildren().get(child.getNodeName());
266 if (childDescription != null) {
267 if (childProcessor.test(child, childDescription)) {
268 iter.remove();
269 }
270 }
271 }
272 }
273
274 private BiPredicate<Node, Description> matcherChildNodeProcessor(AbstractBuilder builder, Description description) {
275 return (child, childDescription) -> {
276 switch (childDescription.getType()) {
277 case LICENSE:
278 case BUILD_PARAMETER:
279 throw new ConfigurationException(String.format(
280 "%s may not be used as an enclosed matcher. %s '%s' found in '%s'", childDescription.getType(),
281 childDescription.getType(), childDescription.getCommonName(), description.getCommonName()));
282 case MATCHER:
283 AbstractBuilder b = parseMatcher(child);
284 callSetter(b.getDescription(), builder, b);
285 return true;
286 case PARAMETER:
287 if (!XMLConfig.isInlineNode(description.getCommonName(), childDescription.getCommonName())
288 || childDescription.getChildType() == String.class) {
289 callSetter(childDescription, builder, child.getTextContent());
290 } else {
291 callSetter(childDescription, builder, parseMatcher(child));
292 }
293 return true;
294 }
295 return false;
296 };
297 }
298
299 private void setValue(Description description, Description childDescription, IHeaderMatcher.Builder builder,
300 Node child) {
301 if (childDescription.getChildType() == String.class) {
302 callSetter(description, builder, child.getTextContent());
303 } else {
304 callSetter(description, builder, parseMatcher(child));
305 }
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319
320 private Pair<Boolean, List<Node>> processChildNodes(Description description, Node parent,
321 BiPredicate<Node, Description> childProcessor) {
322 boolean foundChildren = false;
323 List<Node> children = new ArrayList<>();
324
325 if (parent.hasChildNodes()) {
326
327 nodeListConsumer(parent.getChildNodes(), (n) -> {
328 if (n.getNodeType() == Node.ELEMENT_NODE) {
329 children.add(n);
330 }
331 });
332 foundChildren = !children.isEmpty();
333 if (foundChildren) {
334 processChildren(description, children, childProcessor);
335 }
336 }
337 return new ImmutablePair<>(foundChildren, children);
338 }
339
340 private AbstractBuilder parseMatcher(Node matcherNode) {
341 final AbstractBuilder builder = MatcherBuilderTracker.getMatcherBuilder(matcherNode.getNodeName());
342
343 try {
344 final Description description = DescriptionBuilder.buildMap(builder.builtClass());
345 if (description == null) {
346 throw new ConfigurationException(String.format("Unable to build description for %s", builder.builtClass()));
347 }
348 processBuilderParams(description, builder);
349
350
351 description.setChildren(getLog(), builder, attributes(matcherNode));
352
353
354 Pair<Boolean, List<Node>> pair = processChildNodes(description, matcherNode,
355 matcherChildNodeProcessor(builder, description));
356 boolean foundChildren = pair.getLeft();
357 List<Node> children = pair.getRight();
358
359
360 List<Description> childDescriptions = description.getChildren().values().stream()
361 .filter(d -> XMLConfig.isInlineNode(description.getCommonName(), d.getCommonName()))
362 .collect(Collectors.toList());
363
364 for (Description childDescription : childDescriptions) {
365 if (XMLConfig.isInlineNode(description.getCommonName(), childDescription.getCommonName())) {
366
367 if (childDescription.getChildType() == String.class) {
368 if (!foundChildren) {
369 callSetter(childDescription, builder, matcherNode.getTextContent());
370 }
371 } else {
372 Iterator<Node> iter = children.iterator();
373 while (iter.hasNext()) {
374 Node child = iter.next();
375 callSetter(childDescription, builder, parseMatcher(child));
376 iter.remove();
377 }
378 }
379
380 } else {
381 processChildren(description, children, (child, childD) -> {
382 if (childD.getChildType().equals(description.getChildType())) {
383 setValue(childDescription, childD, builder, child);
384 return true;
385 }
386 return false;
387 });
388 }
389
390 }
391
392 if (!children.isEmpty()) {
393 children.forEach(n -> getLog().warn(String.format("unrecognised child node '%s' in node '%s'%n",
394 n.getNodeName(), matcherNode.getNodeName())));
395 }
396
397 } catch (DOMException e) {
398 throw new ConfigurationException(e);
399 }
400 return builder.hasId() ? new IDRecordingBuilder(matchers, builder) : builder;
401 }
402
403 private BiPredicate<Node, Description> licenseChildNodeProcessor(ILicense.Builder builder,
404 Description description) {
405 return (child, childDescription) -> {
406 switch (childDescription.getType()) {
407 case LICENSE:
408 throw new ConfigurationException(String.format(
409 "%s may not be enclosed in another license. %s '%s' found in '%s'", childDescription.getType(),
410 childDescription.getType(), childDescription.getCommonName(), description.getCommonName()));
411 case BUILD_PARAMETER:
412 break;
413 case MATCHER:
414 AbstractBuilder b = parseMatcher(child);
415 callSetter(b.getDescription(), builder, b);
416 return true;
417 case PARAMETER:
418 if ((!XMLConfig.isLicenseChild(childDescription.getCommonName()))
419 || childDescription.getChildType() == String.class) {
420 callSetter(childDescription, builder, child.getTextContent());
421 } else {
422 callSetter(childDescription, builder, parseMatcher(child));
423 }
424 return true;
425 }
426 return false;
427 };
428 }
429
430 private ILicense parseLicense(Node licenseNode) {
431 ILicense.Builder builder = ILicense.builder();
432
433 Description description = builder.getDescription();
434
435 processBuilderParams(description, builder);
436
437 description.setChildren(getLog(), builder, attributes(licenseNode));
438
439 Pair<Boolean, List<Node>> pair = processChildNodes(description, licenseNode,
440 licenseChildNodeProcessor(builder, description));
441 List<Node> children = pair.getRight();
442
443
444 List<Description> childDescriptions = description.getChildren().values().stream()
445 .filter(d -> XMLConfig.isLicenseInline(d.getCommonName())).collect(Collectors.toList());
446 for (Description childDescription : childDescriptions) {
447 Iterator<Node> iter = children.iterator();
448 while (iter.hasNext()) {
449 callSetter(childDescription, builder, parseMatcher(iter.next()));
450 iter.remove();
451 }
452 }
453
454 if (!children.isEmpty()) {
455 children.forEach(n -> getLog().warn(String.format("unrecognised child node '%s' in node '%s'%n",
456 n.getNodeName(), licenseNode.getNodeName())));
457 }
458 return builder.build();
459 }
460
461 @Override
462 public SortedSet<ILicense> readLicenses() {
463 readFamilies();
464 readMatcherBuilders();
465 if (licenses.isEmpty()) {
466 nodeListConsumer(document.getElementsByTagName(XMLConfig.LICENSE), x -> licenses.add(parseLicense(x)));
467 document = null;
468 }
469 return Collections.unmodifiableSortedSet(licenses);
470 }
471
472 @Override
473 public SortedSet<ILicenseFamily> readFamilies() {
474 if (licenseFamilies.isEmpty()) {
475 nodeListConsumer(document.getElementsByTagName(XMLConfig.FAMILIES),
476 x -> nodeListConsumer(x.getChildNodes(), this::parseFamily));
477 nodeListConsumer(document.getElementsByTagName(XMLConfig.APPROVED),
478 x -> nodeListConsumer(x.getChildNodes(), this::parseApproved));
479 }
480 return Collections.unmodifiableSortedSet(licenseFamilies);
481 }
482
483 private ILicenseFamily parseFamily(Map<String, String> attributes) {
484 if (attributes.containsKey(XMLConfig.ATT_ID)) {
485 ILicenseFamily.Builder builder = ILicenseFamily.builder();
486 builder.setLicenseFamilyCategory(attributes.get(XMLConfig.ATT_ID));
487 builder.setLicenseFamilyName(
488 StringUtils.defaultIfBlank(attributes.get(XMLConfig.ATT_NAME), attributes.get(XMLConfig.ATT_ID)));
489 return builder.build();
490 }
491 return null;
492 }
493
494 private void parseFamily(Node familyNode) {
495 if (XMLConfig.FAMILY.equals(familyNode.getNodeName())) {
496 ILicenseFamily result = parseFamily(attributes(familyNode));
497 if (result == null) {
498 throw new ConfigurationException(
499 String.format("families/family tag requires %s attribute", XMLConfig.ATT_ID));
500 }
501 licenseFamilies.add(result);
502 }
503 }
504
505 private void parseApproved(Node approvedNode) {
506 if (XMLConfig.FAMILY.equals(approvedNode.getNodeName())) {
507 Map<String, String> attributes = attributes(approvedNode);
508 if (attributes.containsKey(XMLConfig.ATT_LICENSE_REF)) {
509 approvedFamilies.add(attributes.get(XMLConfig.ATT_LICENSE_REF));
510 } else if (attributes.containsKey(XMLConfig.ATT_ID)) {
511 ILicenseFamily target = parseFamily(attributes);
512 licenseFamilies.add(target);
513 approvedFamilies.add(target.getFamilyCategory());
514 } else {
515 throw new ConfigurationException(String.format("family tag requires %s or %s attribute",
516 XMLConfig.ATT_LICENSE_REF, XMLConfig.ATT_ID));
517 }
518 }
519 }
520
521
522 @Override
523 public SortedSet<String> approvedLicenseId() {
524 if (licenses.isEmpty()) {
525 this.readLicenses();
526 }
527 if (approvedFamilies.isEmpty()) {
528 SortedSet<String> result = new TreeSet<>();
529 licenses.stream().map(x -> x.getLicenseFamily().getFamilyCategory()).forEach(result::add);
530 return result;
531 }
532 return Collections.unmodifiableSortedSet(approvedFamilies);
533 }
534
535 private void parseMatcherBuilder(Node classNode) {
536 Map<String, String> attributes = attributes(classNode);
537 if (attributes.get(XMLConfig.ATT_CLASS_NAME) == null) {
538 throw new ConfigurationException("matcher must have a " + XMLConfig.ATT_CLASS_NAME + " attribute");
539 }
540 MatcherBuilderTracker.addBuilder(attributes.get(XMLConfig.ATT_CLASS_NAME), attributes.get(XMLConfig.ATT_NAME));
541 }
542
543 @Override
544 public void readMatcherBuilders() {
545 nodeListConsumer(document.getElementsByTagName(XMLConfig.MATCHER), this::parseMatcherBuilder);
546 }
547
548 @Override
549 public void addMatchers(URL url) {
550 read(url);
551 }
552
553
554
555
556
557 static class IDRecordingBuilder extends AbstractBuilder {
558 private final AbstractBuilder delegate;
559 private final Map<String, IHeaderMatcher> matchers;
560
561 IDRecordingBuilder(final Map<String, IHeaderMatcher> matchers, final AbstractBuilder delegate) {
562 this.delegate = delegate;
563 this.matchers = matchers;
564 setId(delegate.getId());
565 }
566
567 @Override
568 public IHeaderMatcher build() {
569 IHeaderMatcher result = delegate.build();
570 matchers.put(result.getId(), result);
571 return result;
572 }
573
574 @Override
575 public Class<?> builtClass() throws SecurityException {
576 return delegate.builtClass();
577 }
578
579 @Override
580 public Description getDescription() {
581 return delegate.getDescription();
582 }
583 }
584 }