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.lang.reflect.InvocationTargetException;
23 import java.net.URL;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29 import java.util.function.Consumer;
30
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
34
35 import org.apache.commons.beanutils.MethodUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.rat.ConfigurationException;
38 import org.apache.rat.analysis.IHeaderMatcher;
39 import org.apache.rat.analysis.matchers.FullTextMatcher;
40 import org.apache.rat.analysis.matchers.SimpleTextMatcher;
41 import org.apache.rat.configuration.builders.AbstractBuilder;
42 import org.apache.rat.configuration.builders.ChildContainerBuilder;
43 import org.apache.rat.configuration.builders.MatcherRefBuilder;
44 import org.apache.rat.configuration.builders.TextCaptureBuilder;
45 import org.apache.rat.license.ILicense;
46 import org.apache.rat.license.ILicenseFamily;
47 import org.apache.rat.license.LicenseFamilySetFactory;
48 import org.apache.rat.license.LicenseSetFactory;
49 import org.w3c.dom.DOMException;
50 import org.w3c.dom.Document;
51 import org.w3c.dom.Element;
52 import org.w3c.dom.NamedNodeMap;
53 import org.w3c.dom.Node;
54 import org.w3c.dom.NodeList;
55 import org.xml.sax.SAXException;
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public class XMLConfigurationReader implements LicenseReader, MatcherReader {
85
86 private final static String ATT_ID = "id";
87 private final static String ATT_NAME = "name";
88 private final static String ATT_DERIVED_FROM = "derived_from";
89 private final static String ATT_LICENSE_REF = "license_ref";
90 private final static String ATT_CLASS_NAME = "class";
91
92 private final static String ROOT = "rat-config";
93 private final static String FAMILIES = "families";
94 private final static String LICENSES = "licenses";
95 private final static String LICENSE = "license";
96 private final static String APPROVED = "approved";
97 private final static String FAMILY = "family";
98 private final static String NOTE = "note";
99 private final static String MATCHERS = "matchers";
100 private final static String MATCHER = "matcher";
101
102 private Document document;
103 private final Element rootElement;
104 private final Element familiesElement;
105 private final Element licensesElement;
106 private final Element approvedElement;
107 private final Element matchersElement;
108
109 private final SortedSet<ILicense> licenses;
110 private final Map<String, IHeaderMatcher> matchers;
111 private final SortedSet<ILicenseFamily> licenseFamilies;
112 private final SortedSet<String> approvedFamilies;
113
114
115
116
117 public XMLConfigurationReader() {
118 try {
119 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
120 } catch (ParserConfigurationException e) {
121 throw new IllegalStateException("No XML parser defined", e);
122 }
123 rootElement = document.createElement(ROOT);
124 document.appendChild(rootElement);
125 familiesElement = document.createElement(FAMILIES);
126 rootElement.appendChild(familiesElement);
127 licensesElement = document.createElement(LICENSES);
128 rootElement.appendChild(licensesElement);
129 approvedElement = document.createElement(APPROVED);
130 rootElement.appendChild(approvedElement);
131 matchersElement = document.createElement(MATCHERS);
132 rootElement.appendChild(matchersElement);
133 licenses = LicenseSetFactory.emptyLicenseSet();
134 licenseFamilies = LicenseFamilySetFactory.emptyLicenseFamilySet();
135 approvedFamilies = new TreeSet<>();
136 matchers = new HashMap<>();
137 }
138
139 @Override
140 public void addLicenses(URL url) {
141 read(url);
142 }
143
144
145
146
147
148
149 public void read(URL... urls) {
150 DocumentBuilder builder;
151 try {
152 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
153 } catch (ParserConfigurationException e) {
154 throw new ConfigurationException("Unable to create DOM builder", e);
155 }
156 for (URL url : urls) {
157 try {
158 add(builder.parse(url.openStream()));
159 } catch (SAXException | IOException e) {
160 throw new ConfigurationException("Unable to read url: " + url, e);
161 }
162 }
163 }
164
165
166
167
168
169
170
171 private void nodeListConsumer(NodeList list, Consumer<Node> consumer) {
172 for (int i = 0; i < list.getLength(); i++) {
173 consumer.accept(list.item(i));
174 }
175 }
176
177
178
179
180
181
182 public void add(Document newDoc) {
183 nodeListConsumer(newDoc.getElementsByTagName(FAMILIES),
184 nl -> nodeListConsumer( nl.getChildNodes(),
185 n -> familiesElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true)))));
186 nodeListConsumer(newDoc.getElementsByTagName(LICENSE),
187 n -> licensesElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true))));
188 nodeListConsumer(newDoc.getElementsByTagName(APPROVED),
189 nl -> nodeListConsumer( nl.getChildNodes(),
190 n -> approvedElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true)))));
191 nodeListConsumer(newDoc.getElementsByTagName(MATCHERS),
192 n -> matchersElement.appendChild(rootElement.getOwnerDocument().adoptNode(n.cloneNode(true))));
193 }
194
195
196
197
198
199
200
201 private Map<String, String> attributes(Node node) {
202 NamedNodeMap nnm = node.getAttributes();
203 Map<String, String> result = new HashMap<>();
204 for (int i = 0; i < nnm.getLength(); i++) {
205 Node n = nnm.item(i);
206 result.put(n.getNodeName(), n.getNodeValue());
207 }
208 return result;
209 }
210
211
212
213
214
215
216
217
218
219 public static IHeaderMatcher createTextMatcher(String id, String txt) {
220 boolean complex = txt.contains(" ") | txt.contains("\\t") | txt.contains("\\n") | txt.contains("\\r")
221 | txt.contains("\\f") | txt.contains("\\v");
222 return complex ? new FullTextMatcher(id, txt) : new SimpleTextMatcher(id, txt);
223 }
224
225 private AbstractBuilder parseMatcher(Node matcherNode) {
226 AbstractBuilder builder = MatcherBuilderTracker.getMatcherBuilder(matcherNode.getNodeName());
227
228 NamedNodeMap nnm = matcherNode.getAttributes();
229 for (int i = 0; i < nnm.getLength(); i++) {
230 Node n = nnm.item(i);
231 String methodName = "set" + StringUtils.capitalize(n.getNodeName());
232 try {
233 MethodUtils.invokeExactMethod(builder, methodName, n.getNodeValue());
234 } catch (NoSuchMethodException e) {
235 throw new ConfigurationException(
236 String.format("'%s' does not have a setter '%s' that takes a String argument",
237 matcherNode.getNodeName(), methodName));
238 } catch (IllegalAccessException | InvocationTargetException | DOMException e) {
239 throw new ConfigurationException(e);
240 }
241 }
242 if (builder instanceof ChildContainerBuilder) {
243 ChildContainerBuilder ccb = (ChildContainerBuilder) builder;
244 nodeListConsumer(matcherNode.getChildNodes(), x -> {
245 if (x.getNodeType() == Node.ELEMENT_NODE) {
246 ccb.add(parseMatcher(x));
247 }
248 });
249 }
250 if (builder instanceof TextCaptureBuilder) {
251 ((TextCaptureBuilder) builder).setText(matcherNode.getTextContent().trim());
252 }
253
254 if (builder instanceof MatcherRefBuilder) {
255 ((MatcherRefBuilder) builder).setMatchers(matchers);
256 }
257
258 if (builder.hasId()) {
259 builder = new DelegatingBuilder(builder) {
260 @Override
261 public IHeaderMatcher build() {
262 IHeaderMatcher result = delegate.build();
263 matchers.put(result.getId(), result);
264 return result;
265 }
266 };
267 }
268 return builder;
269 }
270
271 private ILicense parseLicense(Node licenseNode) {
272 Map<String, String> attributes = attributes(licenseNode);
273 ILicense.Builder builder = ILicense.builder();
274
275 builder.setLicenseFamilyCategory(attributes.get(FAMILY));
276 builder.setName(attributes.get(ATT_NAME));
277 builder.setId(attributes.get(ATT_ID));
278
279 StringBuilder notesBuilder = new StringBuilder();
280 nodeListConsumer(licenseNode.getChildNodes(), x -> {
281 if (x.getNodeType() == Node.ELEMENT_NODE) {
282 if (x.getNodeName().equals(NOTE)) {
283 notesBuilder.append(x.getTextContent()).append("\n");
284 } else {
285 builder.setMatcher(parseMatcher(x));
286 }
287 }
288 });
289 builder.setDerivedFrom(StringUtils.defaultIfBlank(attributes.get(ATT_DERIVED_FROM), null));
290 builder.setNotes(StringUtils.defaultIfBlank(notesBuilder.toString().trim(), null));
291 return builder.build(licenseFamilies);
292 }
293
294 @Override
295 public SortedSet<ILicense> readLicenses() {
296 readFamilies();
297 readMatcherBuilders();
298 if (licenses.isEmpty()) {
299 nodeListConsumer(document.getElementsByTagName(LICENSE), x -> licenses.add(parseLicense(x)));
300 document = null;
301 }
302 return Collections.unmodifiableSortedSet(licenses);
303 }
304
305
306 @Override
307 public SortedSet<ILicenseFamily> readFamilies() {
308 if (licenseFamilies.isEmpty()) {
309 nodeListConsumer(document.getElementsByTagName(FAMILIES),
310 x -> nodeListConsumer(x.getChildNodes(), this::parseFamily));
311 nodeListConsumer(document.getElementsByTagName(APPROVED),
312 x -> nodeListConsumer(x.getChildNodes(), this::parseApproved));
313 }
314 return Collections.unmodifiableSortedSet(licenseFamilies);
315 }
316
317 private ILicenseFamily parseFamily(Map<String, String> attributes) {
318 if (attributes.containsKey(ATT_ID)) {
319 ILicenseFamily.Builder builder = ILicenseFamily.builder();
320 builder.setLicenseFamilyCategory(attributes.get(ATT_ID));
321 builder.setLicenseFamilyName(StringUtils.defaultIfBlank(attributes.get(ATT_NAME), attributes.get(ATT_ID)));
322 return builder.build();
323 }
324 return null;
325 }
326
327 private void parseFamily(Node familyNode) {
328 if (FAMILY.equals(familyNode.getNodeName())) {
329 ILicenseFamily result = parseFamily(attributes(familyNode));
330 if (result == null) {
331 throw new ConfigurationException(String.format("families/family tag requires %s attribute", ATT_ID));
332 }
333 licenseFamilies.add(result);
334 }
335 }
336
337 private void parseApproved(Node approvedNode) {
338 if (FAMILY.equals(approvedNode.getNodeName())) {
339 Map<String, String> attributes = attributes(approvedNode);
340 if (attributes.containsKey(ATT_LICENSE_REF)) {
341 approvedFamilies.add(attributes.get(ATT_LICENSE_REF));
342 } else if (attributes.containsKey(ATT_ID)) {
343 ILicenseFamily target = parseFamily(attributes);
344 licenseFamilies.add(target);
345 approvedFamilies.add(target.getFamilyCategory());
346 } else {
347 throw new ConfigurationException(
348 String.format("family tag requires %s or %s attribute", ATT_LICENSE_REF, ATT_ID));
349 }
350 }
351 }
352
353 @Override
354 public SortedSet<String> approvedLicenseId() {
355 if (licenses.isEmpty()) {
356 this.readLicenses();
357 }
358 if (approvedFamilies.isEmpty()) {
359 SortedSet<String> result = new TreeSet<>();
360 licenses.stream().map(x -> x.getLicenseFamily().getFamilyCategory()).forEach(result::add);
361 return result;
362 }
363 return Collections.unmodifiableSortedSet(approvedFamilies);
364 }
365
366 private void parseMatcherBuilder(Node classNode) {
367 Map<String, String> attributes = attributes(classNode);
368 if (attributes.get(ATT_CLASS_NAME) == null) {
369 throw new ConfigurationException("matcher must have a " + ATT_CLASS_NAME + " attribute");
370 }
371 MatcherBuilderTracker.addBuilder(attributes.get(ATT_CLASS_NAME), attributes.get(ATT_NAME));
372 }
373
374 @Override
375 public void readMatcherBuilders() {
376 nodeListConsumer(document.getElementsByTagName(MATCHER), this::parseMatcherBuilder);
377 }
378
379 @Override
380 public void addMatchers(URL url) {
381 read(url);
382 }
383
384
385
386
387 abstract static class DelegatingBuilder extends AbstractBuilder {
388 protected final AbstractBuilder delegate;
389
390 DelegatingBuilder(AbstractBuilder delegate) {
391 this.delegate = delegate;
392 }
393 }
394 }