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 // FIXME : JMC-6072 160 // Assert.assertTrue(DetailsTracker.getEntries(), resultsEqual); 161 } 162 163 private static void saveToFile(Document doc, String directory, String fileName, boolean onlyOneRecording) { 164 String filePath = getResultDir().getAbsolutePath() + File.separator 165 + ((directory != null) ? (directory + File.separator) : "") 166 + (onlyOneRecording ? "Generated_One_" : "Generated_") + fileName; 167 File resultFile = new File(filePath); 168 prepareFile(resultFile); 169 try { 170 writeDomToStream(doc, new FileOutputStream(resultFile)); 171 } catch (FileNotFoundException e) { 172 e.printStackTrace(); 173 } 174 } 175 176 private static void prepareFile(File file) { 177 if (file.exists()) { 178 file.delete(); 179 } 180 File parent = file.getParentFile(); 181 if (parent != null) { 182 parent.mkdirs(); 183 } 184 try { 185 file.createNewFile(); 186 } catch (IOException e) { 187 e.printStackTrace(); 188 Assert.fail("Error creating file \"" + file.getAbsolutePath() + "\". Error:\n" + e.getMessage()); 189 } 190 } 191 192 private static void writeDomToStream(Document doc, OutputStream os) { 193 try { 194 TransformerFactory transformerFactory = TransformerFactory.newInstance(); 195 Transformer transformer = transformerFactory.newTransformer(); 196 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 197 DOMSource source = new DOMSource(doc); 198 StreamResult console = new StreamResult(os); 199 transformer.transform(source, console); 200 } catch (TransformerException e) { 201 e.printStackTrace(); 202 } 203 } 204 205 private static ReportCollection parseRulesReportXml(String directory, String fileName, String reportName) { 206 ReportCollection collection = new ReportCollection(); 207 try { 208 // FIXME: No need to go via temp file. Just get the input stream directly from the resource. 209 File dir = TestToolkit.materialize(TestRulesWithJfr.class, directory, fileName); 210 File file = new File(dir, fileName); 211 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 212 DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); 213 Document baselineDoc = docBuilder.parse(file); 214 collection = ReportCollection.fromXml(baselineDoc, reportName); 215 } catch (ParserConfigurationException | SAXException | IOException e) { 216 e.printStackTrace(); 217 } 218 return collection; 219 } 220 221 private static ReportCollection generateRulesReport(IOResourceSet jfrs) { 222 ReportCollection collection = new ReportCollection(); 223 for (IOResource jfr : jfrs) { 224 Report report = generateReport(jfr, false, null); 225 collection.put(report.getName(), report); 226 } 227 return collection; 228 } 229 230 private static File getResultDir() { 231 if (System.getProperty("results.dir") != null) { 232 return new File(System.getProperty("results.dir")); 233 } else { 234 return new File(System.getProperty("user.dir")); 235 } 236 } 237 238 private static Report generateReport(IOResource jfr, boolean verbose, Severity minSeverity) { 239 Report report = new Report(jfr.getName()); 240 try { 241 IItemCollection events = JfrLoaderToolkit.loadEvents(jfr.open()); 242 243 for (IRule rule : RuleRegistry.getRules()) { 244 try { 245 RunnableFuture<Result> future = rule.evaluate(events, 246 IPreferenceValueProvider.DEFAULT_VALUES); 247 future.run(); 248 Result result = future.get(); 249 // for (Result result : results) { 250 if (minSeverity == null || Severity.get(result.getScore()).compareTo(minSeverity) >= 0) { 251 ItemSet itemSet = null; 252 IItemQuery itemQuery = result.getItemQuery(); 253 if (verbose && itemQuery != null && !itemQuery.getAttributes().isEmpty()) { 254 itemSet = new ItemSet(); 255 IItemCollection resultEvents = events.apply(itemQuery.getFilter()); 256 Collection<? extends IAttribute<?>> attributes = itemQuery.getAttributes(); 257 for (IAttribute<?> attribute : attributes) { 258 itemSet.addField(attribute.getName()); 259 } 260 Iterator<? extends IItemIterable> iterables = resultEvents.iterator(); 261 while (iterables.hasNext()) { 262 IItemIterable ii = iterables.next(); 263 IType<IItem> type = ii.getType(); 264 List<IMemberAccessor<?, IItem>> accessors = new ArrayList<>(attributes.size()); 265 for (IAttribute<?> a : attributes) { 266 accessors.add(a.getAccessor(type)); 267 } 268 Iterator<? extends IItem> items = ii.iterator(); 269 while (items.hasNext()) { 270 ItemList itemList = new ItemList(); 271 IItem item = items.next(); 272 for (IMemberAccessor<?, IItem> a : accessors) { 273 itemList.add(String.valueOf(a.getMember(item))); 274 } 275 itemSet.addItem(itemList); 276 } 277 } 278 } 279 RuleResult ruleResult = new RuleResult(String.valueOf(result.getRule().getId()), 280 Severity.get(result.getScore()).getLocalizedName(), String.valueOf(result.getScore()), 281 result.getShortDescription(), result.getLongDescription(), itemSet); 282 report.put(String.valueOf(result.getRule().getId()), ruleResult); 283 // } 284 } 285 } catch (RuntimeException | InterruptedException | ExecutionException e) { 286 System.out.println("Problem while evaluating rules for \"" + jfr.getName() + "\". Message: " 287 + e.getLocalizedMessage()); 288 } 289 } 290 } catch (IOException | CouldNotLoadRecordingException e) { 291 e.printStackTrace(); 292 } 293 return report; 294 } 295 296 private static Element createValueNode(Document doc, String name, String value) { 297 Element node = doc.createElement(name); 298 node.appendChild(doc.createTextNode(value != null ? value : "")); 299 return node; 300 } 301 302 private static List<String> getNodeValues(String xpathExpr, Node node) { 303 List<String> values = new ArrayList<>(); 304 try { 305 XPath xpath = XPathFactory.newInstance().newXPath(); 306 XPathExpression expression = xpath.compile(xpathExpr); 307 NodeList nodes = ((NodeList) expression.evaluate(node, XPathConstants.NODESET)); 308 for (int i = 0; i < nodes.getLength(); i++) { 309 Node thisNodeOnly = nodes.item(i); 310 thisNodeOnly.getParentNode().removeChild(thisNodeOnly); 311 Node child = thisNodeOnly.getFirstChild(); 312 if (child != null) { 313 values.add(child.getNodeValue()); 314 } else { 315 values.add(""); 316 } 317 } 318 } catch (XPathExpressionException e) { 319 e.printStackTrace(); 320 } 321 return values; 322 } 323 324 private static NodeList getNodeSet(String expr, Node node) { 325 NodeList result = null; 326 try { 327 XPath xpath = XPathFactory.newInstance().newXPath(); 328 XPathExpression xPath = xpath.compile(expr); 329 result = (NodeList) xPath.evaluate(node, XPathConstants.NODESET); 330 } catch (XPathExpressionException e) { 331 e.printStackTrace(); 332 } 333 return result; 334 } 335 336 private static class ReportCollection { 337 private SortedMap<String, Report> reports; 338 339 public ReportCollection() { 340 reports = new TreeMap<>(); 341 } 342 343 public void put(String filename, Report report) { 344 reports.put(filename, report); 345 } 346 347 public Report get(String filename) { 348 return reports.get(filename); 349 } 350 351 public boolean compareAndLog(Object other) { 352 ReportCollection otherReportCollection = (ReportCollection) other; 353 boolean equals = reports.size() == otherReportCollection.reports.size(); 354 if (!equals) { 355 if (reports.size() > otherReportCollection.reports.size()) { 356 for (String reportname : reports.keySet()) { 357 if (otherReportCollection.get(reportname) == null) { 358 DetailsTracker.log("Report for " + reportname 359 + " could not be found in the other report collection. "); 360 } 361 } 362 } else { 363 for (String reportname : otherReportCollection.reports.keySet()) { 364 if (reports.get(reportname) == null) { 365 DetailsTracker.log( 366 "Report for " + reportname + " could not be found in this report collection. "); 367 } 368 } 369 } 370 DetailsTracker.log("\n"); 371 } 372 for (String reportname : reports.keySet()) { 373 Report otherReport = otherReportCollection.get(reportname); 374 if (otherReport != null) { 375 equals = reports.get(reportname).compareAndLog(otherReport) && equals; 376 } else { 377 DetailsTracker 378 .log("\nReport for " + reportname + " could not be found in the other report collection. "); 379 equals = false; 380 } 381 } 382 return equals; 383 } 384 385 public Document toXml() { 386 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 387 DocumentBuilder docBuilder; 388 Document doc = null; 389 try { 390 docBuilder = docFactory.newDocumentBuilder(); 391 doc = docBuilder.newDocument(); 392 Element rootElement = doc.createElement("reportcollection"); 393 doc.appendChild(rootElement); 394 for (Report report : reports.values()) { 395 report.toXml(rootElement); 396 } 397 } catch (ParserConfigurationException e) { 398 e.printStackTrace(); 399 } 400 return doc; 401 } 402 403 public static ReportCollection fromXml(Document doc, String reportName) { 404 ReportCollection collection = new ReportCollection(); 405 NodeList reports = getNodeSet("//report", doc); 406 for (int i = 0; i < reports.getLength(); i++) { 407 Node thisReportOnly = reports.item(i); 408 thisReportOnly.getParentNode().removeChild(thisReportOnly); 409 Report report = Report.fromXml(thisReportOnly); 410 if (reportName == null || report.getName().equals(reportName)) { 411 collection.put(report.getName(), report); 412 } 413 } 414 return collection; 415 } 416 } 417 418 private static class Report { 419 private String filename; 420 private SortedMap<String, RuleResult> rules; 421 422 public Report(String filename) { 423 this.filename = filename; 424 rules = new TreeMap<>(); 425 } 426 427 public void put(String id, RuleResult rule) { 428 rules.put(id, rule); 429 } 430 431 public RuleResult get(String id) { 432 return rules.get(id); 433 } 434 435 public String getName() { 436 return filename; 437 } 438 439 public boolean compareAndLog(Object other) { 440 Report otherReport = (Report) other; 441 boolean equals = rules.size() == otherReport.rules.size(); 442 boolean fileNamePrinted = false; 443 if (equals) { 444 for (String rulename : rules.keySet()) { 445 RuleResult otherRule = otherReport.get(rulename); 446 if (otherRule != null) { 447 equals = rules.get(rulename).compareAndLog(otherRule) && equals; 448 if (!equals && !fileNamePrinted) { 449 DetailsTracker.log("\n\nReport: \"" + filename + "\", "); 450 fileNamePrinted = true; 451 } 452 } else { 453 DetailsTracker.log("\n\nReport: \"" + filename + "\". Rule result for " + rulename 454 + " could not be found in the other report. "); 455 equals = false; 456 } 457 } 458 } else { 459 if (rules.size() > otherReport.rules.size()) { 460 for (String ruleId : rules.keySet()) { 461 RuleResult otherRule = otherReport.get(ruleId); 462 if (otherRule != null) { 463 equals = rules.get(ruleId).compareAndLog(otherRule) && equals; 464 } else { 465 DetailsTracker.log("\nReport for file \"" + filename + "\", rule result for \"" + ruleId 466 + "\" could not be found in the other report. "); 467 } 468 } 469 } else { 470 for (String ruleId : otherReport.rules.keySet()) { 471 RuleResult rule = rules.get(ruleId); 472 if (rule != null) { 473 equals = rule.compareAndLog(otherReport.rules.get(ruleId)) && equals; 474 } else { 475 DetailsTracker.log("\nReport for file \"" + filename + "\", rule result for \"" + ruleId 476 + "\" could not be found in this report. "); 477 } 478 } 479 } 480 DetailsTracker.log("\n"); 481 } 482 return equals; 483 } 484 485 public void toXml(Element parent) { 486 Element reportNode = parent.getOwnerDocument().createElement("report"); 487 parent.appendChild(reportNode); 488 reportNode.appendChild(createValueNode(parent.getOwnerDocument(), "file", filename)); 489 for (RuleResult rule : rules.values()) { 490 rule.toXml(reportNode); 491 } 492 } 493 494 public static Report fromXml(Node node) { 495 Report report = new Report(getNodeValues("./file", node).get(0)); 496 NodeList rules = getNodeSet("./rule", node); 497 for (int i = 0; i < rules.getLength(); i++) { 498 Node thisRuleOnly = rules.item(i); 499 thisRuleOnly.getParentNode().removeChild(thisRuleOnly); 500 RuleResult rule = RuleResult.fromXml(thisRuleOnly); 501 report.put(rule.getId(), rule); 502 } 503 return report; 504 } 505 } 506 507 private static class RuleResult { 508 private String id; 509 private String severity; 510 private String score; 511 private String shortDescription; 512 private String longDescription; 513 private ItemSet itemset; 514 515 public RuleResult(String id, String severity, String score, String shortDescription, String longDescription, 516 ItemSet itemset) { 517 this.id = id; 518 this.severity = severity; 519 this.score = score; 520 this.shortDescription = shortDescription; 521 this.longDescription = longDescription; 522 this.itemset = itemset; 523 } 524 525 public String getId() { 526 return id; 527 } 528 529 public boolean compareAndLog(Object other) { 530 RuleResult otherRule = (RuleResult) other; 531 boolean scoreEquals = Objects.equals(score, otherRule.score); 532 if (!scoreEquals) { 533 // determine if this is just a rounding error 534 scoreEquals = (Math.abs(Float.valueOf(score) - Float.valueOf(otherRule.score)) < 0.0000000000001f) ? true 535 : false; 536 if (scoreEquals) { 537 // apparently a rounding issue. Print it out for informational purposes 538 System.out 539 .println("Rule \"" + id + "\": Encountered rounding issue for score when comparing values " 540 + score + " and " + otherRule.score); 541 } 542 } 543 boolean itemSetEquality = compareAndLogItemSets(other); 544 boolean ruleEquality = Objects.equals(severity, otherRule.severity) && scoreEquals 545 && Objects.equals(shortDescription, otherRule.shortDescription) 546 && Objects.equals(longDescription, otherRule.longDescription); 547 if (!ruleEquality) { 548 if (!Objects.equals(severity, otherRule.severity)) { 549 DetailsTracker.log("\n Severity mismatch: \"" + severity + "\" was not equal to \"" 550 + otherRule.severity + "\". "); 551 } 552 if (!scoreEquals) { 553 DetailsTracker.log( 554 "\n Score mismatch: \"" + score + "\" was not equal to \"" + otherRule.score + "\". "); 555 } 556 if (!Objects.equals(shortDescription, otherRule.shortDescription)) { 557 DetailsTracker.log("\n Message mismatch: \"" + shortDescription + "\" was not equal to \"" 558 + otherRule.shortDescription + "\". "); 559 } 560 if (!Objects.equals(longDescription, otherRule.longDescription)) { 561 DetailsTracker.log("\n Description mismatch: \"" + longDescription + "\" was not equal to \"" 562 + otherRule.longDescription + "\". "); 563 } 564 } 565 if (!(itemSetEquality && ruleEquality)) { 566 DetailsTracker.log("\n Rule: \"" + id + "\". "); 567 } 568 return itemSetEquality && ruleEquality; 569 } 570 571 private boolean compareAndLogItemSets(Object other) { 572 RuleResult otherRule = (RuleResult) other; 573 if (itemset != null && otherRule.itemset != null) { 574 // both rules have items, compare these 575 return itemset.compareAndLog(otherRule.itemset); 576 } else if (itemset == null && otherRule.itemset == null) { 577 // no items in any of the rules (both null) 578 return true; 579 } else { 580 if (itemset == null) { 581 DetailsTracker.log("\n This item set was null while the other wasn't. The other: " 582 + otherRule.itemset + ". "); 583 } else { 584 DetailsTracker.log("\n The other item set was null while this wasn't. This: " + itemset + ". "); 585 } 586 return false; 587 } 588 } 589 590 public void toXml(Element parent) { 591 Element ruleNode = parent.getOwnerDocument().createElement("rule"); 592 parent.appendChild(ruleNode); 593 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "id", id)); 594 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "severity", severity)); 595 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "score", score)); 596 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "shortDescription", shortDescription)); 597 if (longDescription != null) { 598 ruleNode.appendChild(createValueNode(parent.getOwnerDocument(), "longDescription", longDescription)); 599 } 600 if (itemset != null) { 601 itemset.toXml(ruleNode); 602 } 603 } 604 605 public static RuleResult fromXml(Node node) { 606 RuleResult rule = null; 607 List<String> longDescriptions = getNodeValues("./longDescription", node); 608 String longDescription = null; 609 if (longDescriptions != null && longDescriptions.size() == 1) { 610 longDescription = longDescriptions.get(0); 611 } 612 NodeList items = getNodeSet("./itemset", node); 613 ItemSet itemset = null; 614 if (items != null && items.getLength() == 1) { 615 itemset = ItemSet.fromXml(items.item(0)); 616 } 617 rule = new RuleResult(getNodeValues("./id", node).get(0), getNodeValues("./severity", node).get(0), 618 getNodeValues("./score", node).get(0), getNodeValues("./shortDescription", node).get(0), 619 longDescription, itemset); 620 return rule; 621 } 622 } 623 624 private static class ItemSet { 625 private List<String> fields; 626 private List<ItemList> items; 627 628 public ItemSet() { 629 fields = new ArrayList<>(); 630 items = new ArrayList<>(); 631 } 632 633 private ItemSet(List<String> fields, List<ItemList> items) { 634 this.fields = fields; 635 this.items = items; 636 } 637 638 public void addField(String field) { 639 fields.add(field); 640 } 641 642 public void addItem(ItemList itemList) { 643 items.add(itemList); 644 } 645 646 @Override 647 public String toString() { 648 return "Fields: " + fields + "\n Items: " + items; 649 } 650 651 public boolean compareAndLog(Object other) { 652 ItemSet otherItemSet = (ItemSet) other; 653 boolean fieldEquality = fields.equals(otherItemSet.fields); 654 if (!fieldEquality) { 655 DetailsTracker.log("Item fields differ: " + fields + " was not equal to " + otherItemSet.fields + ". "); 656 } 657 boolean itemEquality = items.equals(otherItemSet.items); 658 return itemEquality && fieldEquality; 659 } 660 661 public void toXml(Element parent) { 662 Element itemSetNode = parent.getOwnerDocument().createElement("itemset"); 663 parent.appendChild(itemSetNode); 664 Element fieldsNode = parent.getOwnerDocument().createElement("fields"); 665 itemSetNode.appendChild(fieldsNode); 666 for (String field : fields) { 667 Element fieldNode = parent.getOwnerDocument().createElement("field"); 668 fieldsNode.appendChild(fieldNode); 669 fieldNode.appendChild(createValueNode(parent.getOwnerDocument(), "name", field)); 670 } 671 Element itemsNode = parent.getOwnerDocument().createElement("items"); 672 itemSetNode.appendChild(itemsNode); 673 for (ItemList list : items) { 674 list.toXml(itemsNode); 675 } 676 } 677 678 public static ItemSet fromXml(Node node) { 679 ItemSet set = null; 680 List<ItemList> itemList = new ArrayList<>(); 681 NodeList items = getNodeSet("./items/item", node); 682 for (int i = 0; i < items.getLength(); i++) { 683 Node thisItemOnly = items.item(i); 684 thisItemOnly.getParentNode().removeChild(thisItemOnly); 685 itemList.add(ItemList.fromXml(thisItemOnly)); 686 } 687 List<String> fields = getNodeValues("./fields/field/name", node); 688 set = new ItemSet(fields, itemList); 689 return set; 690 } 691 692 } 693 694 private static class ItemList { 695 private List<String> items; 696 697 public ItemList() { 698 items = new ArrayList<>(); 699 } 700 701 private ItemList(List<String> list) { 702 items = list; 703 } 704 705 public void add(String item) { 706 items.add(item); 707 } 708 709 @Override 710 public String toString() { 711 return items.toString(); 712 } 713 714 @Override 715 public boolean equals(Object other) { 716 ItemList otherItemList = (ItemList) other; 717 boolean equals = items.equals(otherItemList.items); 718 if (!equals) { 719 DetailsTracker.log("Item lists differ: " + items + " was not equal to " + otherItemList.items + ". "); 720 } 721 return equals; 722 } 723 724 public void toXml(Element parent) { 725 Element itemNode = parent.getOwnerDocument().createElement("item"); 726 parent.appendChild(itemNode); 727 for (String item : items) { 728 itemNode.appendChild(createValueNode(parent.getOwnerDocument(), "value", item)); 729 } 730 } 731 732 public static ItemList fromXml(Node node) { 733 return new ItemList(getNodeValues("./value", node)); 734 } 735 } 736 737 // FIXME: This class is not thread safe. Make non-static! 738 private static class DetailsTracker { 739 private static Deque<String> entries = new ArrayDeque<>(); 740 741 private DetailsTracker() { 742 } 743 744 public static void log(String entry) { 745 entries.addFirst(entry); 746 } 747 748 public static String getEntries() { 749 StringBuilder sb = new StringBuilder(); 750 for (String entry : entries) { 751 sb.append(entry); 752 } 753 return sb.toString(); 754 } 755 756 public static void clear() { 757 entries.clear(); 758 } 759 } 760 761 }