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