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 if (this.document != null) {
507 readFamilies();
508 readMatcherBuilders();
509 if (licenses.isEmpty()) {
510 nodeListConsumer(document.getElementsByTagName(XMLConfig.LICENSE), x -> licenses.add(parseLicense(x)));
511 document = null;
512 }
513 }
514 return Collections.unmodifiableSortedSet(licenses);
515 }
516
517 @Override
518 public SortedSet<ILicenseFamily> readFamilies() {
519 if (licenseFamilies.isEmpty()) {
520 nodeListConsumer(document.getElementsByTagName(XMLConfig.FAMILIES),
521 x -> nodeListConsumer(x.getChildNodes(), this::parseFamily));
522 nodeListConsumer(document.getElementsByTagName(XMLConfig.APPROVED),
523 x -> nodeListConsumer(x.getChildNodes(), this::parseApproved));
524 }
525 return Collections.unmodifiableSortedSet(licenseFamilies);
526 }
527
528
529
530
531
532
533 private ILicenseFamily parseFamily(final Map<String, String> attributes) {
534 if (attributes.containsKey(XMLConfig.ATT_ID)) {
535 ILicenseFamily.Builder builder = ILicenseFamily.builder();
536 builder.setLicenseFamilyCategory(attributes.get(XMLConfig.ATT_ID));
537 builder.setLicenseFamilyName(
538 StringUtils.defaultIfBlank(attributes.get(XMLConfig.ATT_NAME), attributes.get(XMLConfig.ATT_ID)));
539 return builder.build();
540 }
541 return null;
542 }
543
544
545
546
547
548 private void parseFamily(final Node familyNode) {
549 if (XMLConfig.FAMILY.equals(familyNode.getNodeName())) {
550 try {
551 ILicenseFamily result = parseFamily(attributes(familyNode));
552 if (result == null) {
553 throw new ConfigurationException(
554 String.format("families/family tag requires %s attribute", XMLConfig.ATT_ID));
555 }
556 licenseFamilies.add(result);
557 } catch (RuntimeException exception) {
558 DefaultLog.getInstance().error(String.format("Family error in: '%s'", nodeText(familyNode)));
559 throw exception;
560 }
561 }
562 }
563
564
565
566
567
568
569 private void parseApproved(final Node approvedNode) {
570 if (XMLConfig.FAMILY.equals(approvedNode.getNodeName())) {
571 try {
572 Map<String, String> attributes = attributes(approvedNode);
573 if (attributes.containsKey(XMLConfig.ATT_LICENSE_REF)) {
574 approvedFamilies.add(attributes.get(XMLConfig.ATT_LICENSE_REF));
575 } else if (attributes.containsKey(XMLConfig.ATT_ID)) {
576 ILicenseFamily target = parseFamily(attributes);
577 if (target != null) {
578 licenseFamilies.add(target);
579 String familyCategory = target.getFamilyCategory();
580 if (StringUtils.isNotBlank(familyCategory)) {
581 approvedFamilies.add(familyCategory);
582 }
583 }
584 } else {
585 throw new ConfigurationException(String.format("family tag requires %s or %s attribute",
586 XMLConfig.ATT_LICENSE_REF, XMLConfig.ATT_ID));
587 }
588 } catch (RuntimeException exception) {
589 DefaultLog.getInstance().error(String.format("Approved error in: '%s'", nodeText(approvedNode)));
590 throw exception;
591 }
592 }
593 }
594
595
596 @Override
597 public SortedSet<String> approvedLicenseId() {
598 if (licenses.isEmpty()) {
599 this.readLicenses();
600 }
601 if (approvedFamilies.isEmpty()) {
602 SortedSet<String> result = new TreeSet<>();
603 licenses.stream().map(x -> x.getLicenseFamily().getFamilyCategory()).forEach(result::add);
604 return result;
605 }
606 return Collections.unmodifiableSortedSet(approvedFamilies);
607 }
608
609 private void parseMatcherBuilder(final Node classNode) {
610 try {
611 Map<String, String> attributes = attributes(classNode);
612 if (attributes.get(XMLConfig.ATT_CLASS_NAME) == null) {
613 throw new ConfigurationException("matcher must have a " + XMLConfig.ATT_CLASS_NAME + " attribute");
614 }
615 MatcherBuilderTracker.addBuilder(attributes.get(XMLConfig.ATT_CLASS_NAME), attributes.get(XMLConfig.ATT_NAME));
616 } catch (RuntimeException exception) {
617 DefaultLog.getInstance().error(String.format("Matcher error in: '%s'", nodeText(classNode)));
618 throw exception;
619 }
620 }
621
622 @Override
623 public void readMatcherBuilders() {
624 nodeListConsumer(document.getElementsByTagName(XMLConfig.MATCHER), this::parseMatcherBuilder);
625 }
626
627 @Override
628 public void addMatchers(final URI uri) {
629 read(uri);
630 }
631
632
633
634
635 static class IDRecordingBuilder extends AbstractBuilder {
636
637 private final AbstractBuilder delegate;
638
639
640
641
642 private final Map<String, IHeaderMatcher> matchers;
643
644 IDRecordingBuilder(final Map<String, IHeaderMatcher> matchers, final AbstractBuilder delegate) {
645 this.delegate = delegate;
646 this.matchers = matchers;
647 setId(delegate.getId());
648 }
649
650 @Override
651 public IHeaderMatcher build() {
652 IHeaderMatcher result = delegate.build();
653 matchers.put(result.getId(), result);
654 return result;
655 }
656
657 @Override
658 public Description getDescription() {
659 return delegate.getDescription();
660 }
661 }
662 }