1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.test.rules.jdk; 34 35 import java.io.File; 36 import java.io.FileNotFoundException; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.io.OutputStream; 40 import java.util.ArrayDeque; 41 import java.util.ArrayList; 42 import java.util.Collection; 43 import java.util.Deque; 44 import java.util.Iterator; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.SortedMap; 48 import java.util.TimeZone; 49 import java.util.TreeMap; 50 import java.util.concurrent.ExecutionException; 51 import java.util.concurrent.RunnableFuture; 52 53 import javax.xml.parsers.DocumentBuilder; 54 import javax.xml.parsers.DocumentBuilderFactory; 55 import javax.xml.parsers.ParserConfigurationException; 56 import javax.xml.transform.OutputKeys; 57 import javax.xml.transform.Transformer; 58 import javax.xml.transform.TransformerException; 59 import javax.xml.transform.TransformerFactory; 60 import javax.xml.transform.dom.DOMSource; 61 import javax.xml.transform.stream.StreamResult; 62 import javax.xml.xpath.XPath; 63 import javax.xml.xpath.XPathConstants; 64 import javax.xml.xpath.XPathExpression; 65 import javax.xml.xpath.XPathExpressionException; 66 import javax.xml.xpath.XPathFactory; 67 68 import org.junit.After; 69 import org.junit.Assert; 70 import org.junit.Before; 71 import org.junit.Test; 72 import org.junit.experimental.categories.Category; 73 import org.openjdk.jmc.common.item.IAttribute; 74 import org.openjdk.jmc.common.item.IItem; 75 import org.openjdk.jmc.common.item.IItemCollection; 76 import org.openjdk.jmc.common.item.IItemIterable; 77 import org.openjdk.jmc.common.item.IItemQuery; 78 import org.openjdk.jmc.common.item.IMemberAccessor; 79 import org.openjdk.jmc.common.item.IType; 80 import org.openjdk.jmc.common.test.SlowTests; 81 import org.openjdk.jmc.common.test.TestToolkit; 82 import org.openjdk.jmc.common.test.io.IOResource; 83 import org.openjdk.jmc.common.test.io.IOResourceSet; 84 import org.openjdk.jmc.common.util.IPreferenceValueProvider; 85 import org.openjdk.jmc.flightrecorder.CouldNotLoadRecordingException; 86 import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit; 87 import org.openjdk.jmc.flightrecorder.rules.IRule; 88 import org.openjdk.jmc.flightrecorder.rules.Result; 89 import org.openjdk.jmc.flightrecorder.rules.RuleRegistry; 90 import org.openjdk.jmc.flightrecorder.rules.Severity; 91 import org.w3c.dom.Document; 92 import org.w3c.dom.Element; 93 import org.w3c.dom.Node; 94 import org.w3c.dom.NodeList; 95 import org.xml.sax.SAXException; 96 97 /** 98 * Class for testing jfr rule report consistency 99 */ 100 @SuppressWarnings("nls") 101 public class TestRulesWithJfr { 102 private static final String JFR_RULE_BASELINE_JFR = "JfrRuleBaseline.xml"; 103 private static final String BASELINE_DIR = "baseline"; 104 private static final String RECORDINGS_DIR = "jfr"; 105 private static final String RECORDINGS_INDEXFILE = "index.txt"; 106 107 private TimeZone defaultTimeZone; 108 109 @Before 110 public void before() { 111 // empty the log before each test 112 DetailsTracker.clear(); 113 // force UTC time zone during test 114 defaultTimeZone = TimeZone.getDefault(); 115 TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 116 } 117 118 @After 119 public void after() { 120 // restore previous default time zone 121 TimeZone.setDefault(defaultTimeZone); 122 } 123 124 @Test 125 public void verifyOneResult() throws IOException { 126 verifyRuleResults(true); 127 } 128 129 @Category(value = {SlowTests.class}) 130 @Test 131 public void verifyAllResults() throws IOException { 132 verifyRuleResults(false); 133 } 134 135 private void verifyRuleResults(boolean onlyOneRecording) throws IOException { 136 IOResourceSet jfrs = TestToolkit.getResourcesInDirectory(TestRulesWithJfr.class, RECORDINGS_DIR, RECORDINGS_INDEXFILE); 137 String reportName = null; 138 if (onlyOneRecording) { 139 IOResource firstJfr = jfrs.iterator().next(); 140 jfrs = new IOResourceSet(firstJfr); 141 reportName = firstJfr.getName(); 142 } 143 // Run all the .jfr files in the directory through the rule engine 144 ReportCollection rulesReport = generateRulesReport(jfrs); 145 146 // Parse the baseline XML file 147 ReportCollection baselineReport = parseRulesReportXml(BASELINE_DIR, JFR_RULE_BASELINE_JFR, reportName); 148 149 // Compare the baseline with the current rule results 150 boolean resultsEqual = rulesReport.compareAndLog(baselineReport); 151 152 // Save file for later inspection and/or updating the baseline with 153 if (!resultsEqual) { 154 // Save the generated file to XML 155 saveToFile(rulesReport.toXml(), BASELINE_DIR, JFR_RULE_BASELINE_JFR, onlyOneRecording); 156 } 157 158 // Assert that the comparison returned true 159 Assert.assertTrue(DetailsTracker.getEntries(), resultsEqual); 160 } 161 162 private static void saveToFile(Document doc, String directory, String fileName, boolean onlyOneRecording) { 163 String filePath = getResultDir().getAbsolutePath() + File.separator 164 + ((directory != null) ? (directory + File.separator) : "") 165 + (onlyOneRecording ? "Generated_One_" : "Generated_") + fileName; 166 File resultFile = new File(filePath); 167 prepareFile(resultFile); 168 try { 169 writeDomToStream(doc, new FileOutputStream(resultFile)); 170 } catch (FileNotFoundException e) { 171 e.printStackTrace(); 172 } 173 } 174 175 private static void prepareFile(File file) { 176 if (file.exists()) { 177 file.delete(); 178 } 179 File parent = file.getParentFile(); 180 if (parent != null) { 181 parent.mkdirs(); 182 } 183 try { 184 file.createNewFile(); 185 } catch (IOException e) { 186 e.printStackTrace(); 187 Assert.fail("Error creating file \"" + file.getAbsolutePath() + "\". Error:\n" + e.getMessage()); 188 } 189 } 190 191 private static void writeDomToStream(Document doc, OutputStream os) { 192 try { 193 TransformerFactory transformerFactory = TransformerFactory.newInstance(); 194 Transformer transformer = transformerFactory.newTransformer(); 195 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 196 DOMSource source = new DOMSource(doc); 197 StreamResult console = new StreamResult(os); 198 transformer.transform(source, console); 199 } catch (TransformerException e) { 200 e.printStackTrace(); 201 } 202 } 203 204 private static ReportCollection parseRulesReportXml(String directory, String fileName, String reportName) { 205 ReportCollection collection = new ReportCollection(); 206 try { 207 // FIXME: No need to go via temp file. Just get the input stream directly from the resource. 208 File dir = TestToolkit.materialize(TestRulesWithJfr.class, directory, fileName); 209 File file = new File(dir, fileName); 210 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 211 DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); 212 Document baselineDoc = docBuilder.parse(file); 213 collection = ReportCollection.fromXml(baselineDoc, reportName); 214 } catch (ParserConfigurationException | SAXException | IOException e) { 215 e.printStackTrace(); 216 } 217 return collection; 218 } 219 220 private static ReportCollection generateRulesReport(IOResourceSet jfrs) { 221 ReportCollection collection = new ReportCollection(); 222 for (IOResource jfr : jfrs) { 223 Report report = generateReport(jfr, false, null); 224 collection.put(report.getName(), report); 225 } 226 return collection; 227 } 228 229 private static File getResultDir() { 230 if (System.getProperty("results.dir") != null) { 231 return new File(System.getProperty("results.dir")); 232 } else { 233 return new File(System.getProperty("user.dir")); 234 } 235 } 236 237 private static Report generateReport(IOResource jfr, boolean verbose, Severity minSeverity) { 238 Report report = new Report(jfr.getName()); 239 try { 240 IItemCollection events = JfrLoaderToolkit.loadEvents(jfr.open()); 241 242 for (IRule rule : RuleRegistry.getRules()) { 243 try { 244 RunnableFuture<Result> future = rule.evaluate(events, 245 IPreferenceValueProvider.DEFAULT_VALUES); 246 future.run(); 247 Result result = future.get(); 248 // for (Result result : results) { 249 if (minSeverity == null || Severity.get(result.getScore()).compareTo(minSeverity) >= 0) { 250 ItemSet itemSet = null; 251 IItemQuery itemQuery = result.getItemQuery(); 252 if (verbose && itemQuery != null && !itemQuery.getAttributes().isEmpty()) { 253 itemSet = new ItemSet(); 254 IItemCollection resultEvents = events.apply(itemQuery.getFilter()); 255 Collection<? extends IAttribute<?>> attributes = itemQuery.getAttributes(); 256 for (IAttribute<?> attribute : attributes) { 257 itemSet.addField(attribute.getName()); 258 } 259 Iterator<? extends IItemIterable> iterables = resultEvents.iterator(); 260 while (iterables.hasNext()) { 261 IItemIterable ii = iterables.next(); 262 IType<IItem> type = ii.getType(); 263 List<IMemberAccessor<?, IItem>> accessors = new ArrayList<>(attributes.size()); 264 for (IAttribute<?> a : attributes) { 265 accessors.add(a.getAccessor(type)); 266 } 267 Iterator<? extends IItem> items = ii.iterator(); 268 while (items.hasNext()) { 269 ItemList itemList = new ItemList(); 270 IItem item = items.next(); 271 for (IMemberAccessor<?, IItem> a : accessors) { 272 itemList.add(String.valueOf(a.getMember(item))); 273 } 274 itemSet.addItem(itemList); 275 } 276 } 277 } 278 RuleResult ruleResult = new RuleResult(String.valueOf(result.getRule().getId()), 279 Severity.get(result.getScore()).getLocalizedName(), String.valueOf(result.getScore()), 280 result.getShortDescription(), result.getLongDescription(), itemSet); 281 report.put(String.valueOf(result.getRule().getId()), ruleResult); 282 // } 283 } 284 } catch (RuntimeException | InterruptedException | ExecutionException e) { 285 System.out.println("Problem while evaluating rules for \"" + jfr.getName() + "\". Message: " 286 + e.getLocalizedMessage()); 287 } 288 } 289 } catch (IOException | CouldNotLoadRecordingException e) { 290 e.printStackTrace(); 291 } 292 return report; 293 } 294 295 private static Element createValueNode(Document doc, String name, String value) { 296 Element node = doc.createElement(name); 297 node.appendChild(doc.createTextNode(value != null ? value : "")); 298 return node; 299 } 300 301 private static List<String> getNodeValues(String xpathExpr, Node node) { 302 List<String> values = new ArrayList<>(); 303 try { 304 XPath xpath = XPathFactory.newInstance().newXPath(); 305 XPathExpression expression = xpath.compile(xpathExpr); 306 NodeList nodes = ((NodeList) expression.evaluate(node, XPathConstants.NODESET)); 307 for (int i = 0; i < nodes.getLength(); i++) { 308 Node thisNodeOnly = nodes.item(i); 309 thisNodeOnly.getParentNode().removeChild(thisNodeOnly); 310 Node child = thisNodeOnly.getFirstChild(); 311 if (child != null) { 312 values.add(child.getNodeValue()); 313 } else { 314 values.add(""); 315 } 316 } 317 } catch (XPathExpressionException e) { 318 e.printStackTrace(); 319 } 320 return values; 321 } 322 323 private static NodeList getNodeSet(String expr, Node node) { 324 NodeList result = null; 325 try { 326 XPath xpath = XPathFactory.newInstance().newXPath(); 327 XPathExpression xPath = xpath.compile(expr); 328 result = (NodeList) xPath.evaluate(node, XPathConstants.NODESET); 329 } catch (XPathExpressionException e) { 330 e.printStackTrace(); 331 } 332 return result; 333 } 334 335 private static class ReportCollection { 336 private SortedMap<String, Report> reports; 337 338 public ReportCollection() { 339 reports = new TreeMap<>(); 340 } 341 342 public void put(String filename, Report report) { 343 reports.put(filename, report); 344 } 345 346 public Report get(String filename) { 347 return reports.get(filename); 348 } 349 350 public boolean compareAndLog(Object other) { 351 ReportCollection otherReportCollection = (ReportCollection) other; 352 boolean equals = reports.size() == otherReportCollection.reports.size(); 353 if (!equals) { 354 if (reports.size() > otherReportCollection.reports.size()) { 355 for (String reportname : reports.keySet()) { 356 if (otherReportCollection.get(reportname) == null) { 357 DetailsTracker.log("Report for " + reportname 358 + " could not be found in the other report collection. "); 359 } 360 } 361 } else { 362 for (String reportname : otherReportCollection.reports.keySet()) { 363 if (reports.get(reportname) == null) { 364 DetailsTracker.log( 365 "Report for " + reportname + " could not be found in this report collection. "); 366 } 367 } 368 } 369 DetailsTracker.log("\n"); 370 } 371 for (String reportname : reports.keySet()) { 372 Report otherReport = otherReportCollection.get(reportname); 373 if (otherReport != null) { 374 equals = reports.get(reportname).compareAndLog(otherReport) && equals; 375 } else { 376 DetailsTracker 377 .log("\nReport for " + reportname + " could not be found in the other report collection. "); 378 equals = false; 379 } 380 } 381 return equals; 382 } 383 384 public Document toXml() { 385 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 386 DocumentBuilder docBuilder; 387 Document doc = null; 388 try { 389 docBuilder = docFactory.newDocumentBuilder(); 390 doc = docBuilder.newDocument(); 391 Element rootElement = doc.createElement("reportcollection"); 392 doc.appendChild(rootElement); 393 for (Report report : reports.values()) { 394 report.toXml(rootElement); 395 } 396 } catch (ParserConfigurationException e) { 397 e.printStackTrace(); 398 } 399 return doc; 400 } 401 402 public static ReportCollection fromXml(Document doc, String reportName) { 403 ReportCollection collection = new ReportCollection(); 404 NodeList reports = getNodeSet("//report", doc); 405 for (int i = 0; i < reports.getLength(); i++) { 406 Node thisReportOnly = reports.item(i); 407 thisReportOnly.getParentNode().removeChild(thisReportOnly); 408 Report report = Report.fromXml(thisReportOnly); 409 if (reportName == null || report.getName().equals(reportName)) { 410 collection.put(report.getName(), report); 411 } 412 } 413 return collection; 414 } 415 } 416 417 private static class Report { 418 private String filename; 419 private SortedMap<String, RuleResult> rules; 420 421 public Report(String filename) { 422 this.filename = filename; 423 rules = new TreeMap<>(); 424 } 425 426 public void put(String id, RuleResult rule) { 427 rules.put(id, rule); 428 } 429 430 public RuleResult get(String id) { 431 return rules.get(id); 432 } 433 434 public String getName() { 435 return filename; 436 } 437 438 public boolean compareAndLog(Object other) { 439 Report otherReport = (Report) other; 440 boolean equals = rules.size() == otherReport.rules.size(); 441 boolean fileNamePrinted = false; 442 if (equals) { 443 for (String rulename : rules.keySet()) { 444 RuleResult otherRule = otherReport.get(rulename); 445 if (otherRule != null) { 446 equals = rules.get(rulename).compareAndLog(otherRule) && equals; 447 if (!equals && !fileNamePrinted) { 448 DetailsTracker.log("\n\nReport: \"" + filename + "\", "); 449 fileNamePrinted = true; 450 } 451 } else { 452 DetailsTracker.log("\n\nReport: \"" + filename + "\". Rule result for " + rulename 453 + " could not be found in the other report. "); 454 equals = false; 455 } 456 } 457 } else { 458 if (rules.size() > otherReport.rules.size()) { 459 for (String ruleId : rules.keySet()) { 460 RuleResult otherRule = otherReport.get(ruleId); 461 if (otherRule != null) { 462 equals = rules.get(ruleId).compareAndLog(otherRule) && equals; 463 } else { 464 DetailsTracker.log("\nReport for file \"" + filename + "\", rule result for \"" + ruleId 465 + "\" could not be found in the other report. "); 466 } 467 } 468 } else { 469 for (String ruleId : otherReport.rules.keySet()) { 470 RuleResult rule = rules.get(ruleId); 471 if (rule != null) { 472 equals = rule.compareAndLog(otherReport.rules.get(ruleId)) && equals; 473 } else { 474 DetailsTracker.log("\nReport for file \"" + filename + "\", rule result for \"" + ruleId 475 + "\" could not be found in this report. "); 476 } 477 } 478 } 479 DetailsTracker.log("\n"); 480 } 481 return equals; 482 } 483 484 public void toXml(Element parent) { 485 Element reportNode = parent.getOwnerDocument().createElement("report"); 486 parent.appendChild(reportNode); 487 reportNode.appendChild(createValueNode(parent.getOwnerDocument(), "file", filename)); 488 for (RuleResult rule : rules.values()) { 489 rule.toXml(reportNode); 490 } 491 } 492 493 public static Report fromXml(Node node) { 494 Report report = new Report(getNodeValues("./file", node).get(0)); 495 NodeList rules = getNodeSet("./rule", node); 496 for (int i = 0; i < rules.getLength(); i++) { 497 Node thisRuleOnly = rules.item(i); 498 thisRuleOnly.getParentNode().removeChild(thisRuleOnly); 499 RuleResult rule = RuleResult.fromXml(thisRuleOnly); 500 report.put(rule.getId(), rule); 501 } 502 return report; 503 } 504 } 505 506 private static class RuleResult { 507 private String id; 508 private String severity; 509 private String score; 510 private String shortDescription; 511 private String longDescription; 512 private ItemSet itemset; 513 514 public RuleResult(String id, String severity, String score, String shortDescription, String longDescription, 515 ItemSet itemset) { 516 this.id = id; 517 this.severity = severity; 518 this.score = score; 519 this.shortDescription = shortDescription; 520 this.longDescription = longDescription; 521 this.itemset = itemset; 522 } 523 524 public String getId() { 525 return id; 526 } 527 528 public boolean compareAndLog(Object other) { 529 RuleResult otherRule = (RuleResult) other; 530 boolean scoreEquals = Objects.equals(score, otherRule.score); 531 if (!scoreEquals) { 532 // determine if this is just a rounding error 533 scoreEquals = (Math.abs(Float.valueOf(score) - Float.valueOf(otherRule.score)) < 0.0000000000001f) ? true 534 : false; 535 if (scoreEquals) { 536 // apparently a rounding issue. Print it out for informational purposes 537 System.out 538 .println("Rule \"" + id + "\": Encountered rounding issue for score when comparing values " 539 + score + " and " + otherRule.score); 540 } 541 } 542 boolean itemSetEquality = compareAndLogItemSets(other); 543 boolean ruleEquality = Objects.equals(severity, otherRule.severity) && scoreEquals 544 && Objects.equals(shortDescription, otherRule.shortDescription) 545 && Objects.equals(longDescription, otherRule.longDescription); 546 if (!ruleEquality) { 547 if (!Objects.equals(severity, otherRule.severity)) { 548 DetailsTracker.log("\n Severity mismatch: \"" + severity + "\" was not equal to \"" 549 + otherRule.severity + "\". "); 550 } 551 if (!scoreEquals) { 552 DetailsTracker.log( 553 "\n Score mismatch: \"" + score + "\" was not equal to \"" + otherRule.score + "\". "); 554 } 555 if (!Objects.equals(shortDescription, otherRule.shortDescription)) { 556 DetailsTracker.log("\n Message mismatch: \"" + shortDescription + "\" was not equal to \"" 557 + otherRule.shortDescription + "\". "); 558 } 559 if (!Objects.equals(longDescription, otherRule.longDescription)) { 560 DetailsTracker.log("\n Description mismatch: \"" + longDescription + "\" was not equal to \"" 561 + otherRule.longDescription + "\". "); 562 } 563 } 564 if (!(itemSetEquality && ruleEquality)) { 565 DetailsTracker.log("\n Rule: \"" + id + "\". "); 566 } 567 return itemSetEquality && ruleEquality; 568 } 569 570 private boolean compareAndLogItemSets(Object other) { 571 RuleResult otherRule = (RuleResult) other; 572 if (itemset != null && otherRule.itemset != null) { 573 // both rules have items, compare these 574 return itemset.compareAndLog(otherRule.itemset); 575 } else if (itemset == null && otherRule.itemset == null) { 576 // no items in any of the rules (both null) 577 return true; 578 } else { 579 if (itemset == null) { 580 DetailsTracker.log("\n This item set was null while the other wasn't. The other: " 581 + otherRule.itemset + ". "); 582 } else { 583 DetailsTracker.log("\n The other item set was null while this wasn't. This: " + itemset + ". "); 584 } 585 return false; 586 } 587 } 588 589 public void toXml(Element parent) { 590 Element ruleNode = parent.getOwnerDocument().createElement("rule"); 591 parent.appendChild(ruleNode); 592 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "id", id)); 593 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "severity", severity)); 594 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "score", score)); 595 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "shortDescription", shortDescription)); 596 if (longDescription != null) { 597 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "longDescription", longDescription)); 598 } 599 if (itemset != null) { 600 itemset.toXml(ruleNode); 601 } 602 } 603 604 public static RuleResult fromXml(Node node) { 605 RuleResult rule = null; 606 List<String> longDescriptions = getNodeValues("./longDescription", node); 607 String longDescription = null; 608 if (longDescriptions != null && longDescriptions.size() == 1) { 609 longDescription = longDescriptions.get(0); 610 } 611 NodeList items = getNodeSet("./itemset", node); 612 ItemSet itemset = null; 613 if (items != null && items.getLength() == 1) { 614 itemset = ItemSet.fromXml(items.item(0)); 615 } 616 rule = new RuleResult(getNodeValues("./id", node).get(0), getNodeValues("./severity", node).get(0), 617 getNodeValues("./score", node).get(0), getNodeValues("./shortDescription", node).get(0), 618 longDescription, itemset); 619 return rule; 620 } 621 } 622 623 private static class ItemSet { 624 private List<String> fields; 625 private List<ItemList> items; 626 627 public ItemSet() { 628 fields = new ArrayList<>(); 629 items = new ArrayList<>(); 630 } 631 632 private ItemSet(List<String> fields, List<ItemList> items) { 633 this.fields = fields; 634 this.items = items; 635 } 636 637 public void addField(String field) { 638 fields.add(field); 639 } 640 641 public void addItem(ItemList itemList) { 642 items.add(itemList); 643 } 644 645 @Override 646 public String toString() { 647 return "Fields: " + fields + "\n Items: " + items; 648 } 649 650 public boolean compareAndLog(Object other) { 651 ItemSet otherItemSet = (ItemSet) other; 652 boolean fieldEquality = fields.equals(otherItemSet.fields); 653 if (!fieldEquality) { 654 DetailsTracker.log("Item fields differ: " + fields + " was not equal to " + otherItemSet.fields + ". "); 655 } 656 boolean itemEquality = items.equals(otherItemSet.items); 657 return itemEquality && fieldEquality; 658 } 659 660 public void toXml(Element parent) { 661 Element itemSetNode = parent.getOwnerDocument().createElement("itemset"); 662 parent.appendChild(itemSetNode); 663 Element fieldsNode = parent.getOwnerDocument().createElement("fields"); 664 itemSetNode.appendChild(fieldsNode); 665 for (String field : fields) { 666 Element fieldNode = parent.getOwnerDocument().createElement("field"); 667 fieldsNode.appendChild(fieldNode); 668 fieldNode.appendChild(createValueNode(parent.getOwnerDocument(), "name", field)); 669 } 670 Element itemsNode = parent.getOwnerDocument().createElement("items"); 671 itemSetNode.appendChild(itemsNode); 672 for (ItemList list : items) { 673 list.toXml(itemsNode); 674 } 675 } 676 677 public static ItemSet fromXml(Node node) { 678 ItemSet set = null; 679 List<ItemList> itemList = new ArrayList<>(); 680 NodeList items = getNodeSet("./items/item", node); 681 for (int i = 0; i < items.getLength(); i++) { 682 Node thisItemOnly = items.item(i); 683 thisItemOnly.getParentNode().removeChild(thisItemOnly); 684 itemList.add(ItemList.fromXml(thisItemOnly)); 685 } 686 List<String> fields = getNodeValues("./fields/field/name", node); 687 set = new ItemSet(fields, itemList); 688 return set; 689 } 690 691 } 692 693 private static class ItemList { 694 private List<String> items; 695 696 public ItemList() { 697 items = new ArrayList<>(); 698 } 699 700 private ItemList(List<String> list) { 701 items = list; 702 } 703 704 public void add(String item) { 705 items.add(item); 706 } 707 708 @Override 709 public String toString() { 710 return items.toString(); 711 } 712 713 @Override 714 public boolean equals(Object other) { 715 ItemList otherItemList = (ItemList) other; 716 boolean equals = items.equals(otherItemList.items); 717 if (!equals) { 718 DetailsTracker.log("Item lists differ: " + items + " was not equal to " + otherItemList.items + ". "); 719 } 720 return equals; 721 } 722 723 public void toXml(Element parent) { 724 Element itemNode = parent.getOwnerDocument().createElement("item"); 725 parent.appendChild(itemNode); 726 for (String item : items) { 727 itemNode.appendChild(createValueNode(parent.getOwnerDocument(), "value", item)); 728 } 729 } 730 731 public static ItemList fromXml(Node node) { 732 return new ItemList(getNodeValues("./value", node)); 733 } 734 } 735 736 // FIXME: This class is not thread safe. Make non-static! 737 private static class DetailsTracker { 738 private static Deque<String> entries = new ArrayDeque<>(); 739 740 private DetailsTracker() { 741 } 742 743 public static void log(String entry) { 744 entries.addFirst(entry); 745 } 746 747 public static String getEntries() { 748 StringBuilder sb = new StringBuilder(); 749 for (String entry : entries) { 750 sb.append(entry); 751 } 752 return sb.toString(); 753 } 754 755 public static void clear() { 756 entries.clear(); 757 } 758 } 759 760 }