1 /*
  2  * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 package jdk.jfr.event.metadata;
 25 
 26 import java.io.IOException;
 27 import java.io.StringReader;
 28 import java.util.ArrayList;
 29 import java.util.Collection;
 30 import java.util.HashMap;
 31 import java.util.HashSet;
 32 import java.util.List;
 33 import java.util.Map;
 34 import java.util.Set;
 35 
 36 import javax.xml.parsers.DocumentBuilder;
 37 import javax.xml.parsers.DocumentBuilderFactory;
 38 import javax.xml.parsers.ParserConfigurationException;
 39 
 40 import jdk.jfr.Configuration;
 41 import jdk.jfr.EventType;
 42 import jdk.jfr.FlightRecorder;
 43 import jdk.jfr.SettingDescriptor;
 44 import jdk.test.lib.jfr.EventNames;
 45 
 46 import org.w3c.dom.Attr;
 47 import org.w3c.dom.Document;
 48 import org.w3c.dom.Element;
 49 import org.w3c.dom.Node;
 50 import org.w3c.dom.NodeList;
 51 import org.xml.sax.InputSource;
 52 import org.xml.sax.SAXException;
 53 
 54 /**
 55  * @test
 56  * @key jfr
 57  * @requires vm.hasJFR
 58  *
 59  * @library /test/lib
 60  * @modules java.xml
 61  *          jdk.jfr
 62  *
 63  * @run main/othervm jdk.jfr.event.metadata.TestDefaultConfigurations
 64  */
 65 public class TestDefaultConfigurations {
 66 
 67     private static final String LINE_SEPARATOR = System.getProperty("line.separator");
 68 
 69     public static void main(String[] args) throws Exception {
 70         List<String> errors = new ArrayList<>();
 71 
 72         errors.addAll(testConfiguration(Configuration.getConfiguration("default")));
 73         errors.addAll(testConfiguration(Configuration.getConfiguration("profile")));
 74 
 75         if (!errors.isEmpty()) {
 76             throwExceptionWithErrors(errors);
 77         }
 78     }
 79 
 80     private static List<String> testConfiguration(Configuration config) throws ParserConfigurationException, SAXException, IOException {
 81         List<String> errors = new ArrayList<>();
 82 
 83         Map<String, EventType> eventTypeLookup = new HashMap<>();
 84         for (EventType t : FlightRecorder.getFlightRecorder().getEventTypes()) {
 85             eventTypeLookup.put(t.getName(), t);
 86         }
 87         String content = config.getContents();
 88         Document doc = createDocument(content);
 89         Element configuration = doc.getDocumentElement();
 90         errors.addAll(checkConfiguration(configuration));
 91         for (Element event : getChildElements(configuration, "event")) {
 92             String name = event.getAttribute("name");
 93 
 94             EventType cd = eventTypeLookup.get(name);
 95             if (cd != null) {
 96                 errors.addAll(checkSettings(config, cd, event));
 97             } else {
 98                 errors.add("Preset '" + config.getName() + "' reference unknown event '" + name + "'");
 99             }
100             eventTypeLookup.remove(name);
101         }
102         for (String name : eventTypeLookup.keySet()) {
103             errors.add("Preset '" + config.getName() + "' doesn't configure event '" + name + "'");
104         }
105 
106         return errors;
107     }
108 
109     private static void throwExceptionWithErrors(List<String> errors) throws Exception {
110         StringBuilder sb = new StringBuilder();
111         for (String error : errors) {
112             sb.append(error);
113             sb.append(LINE_SEPARATOR);
114         }
115         throw new Exception(sb.toString());
116     }
117 
118     private static List<String> checkConfiguration(Element configuration) {
119         List<String> errors = new ArrayList<>();
120         if (configuration.getAttribute("description").length() < 2) {
121             errors.add("Configuration should have a valid description!");
122         }
123         if (configuration.getAttribute("label").length() < 2) {
124             errors.add("Configuration should have a label!");
125         }
126         if (!configuration.getAttribute("provider").equals("Oracle")) {
127             errors.add("Configuration should set provider to 'Oracle'!");
128         }
129         return errors;
130     }
131 
132     private static List<String> checkSettings(Configuration config, EventType eventType, Element event) {
133         List<String> errors = new ArrayList<>();
134 
135         Set<String> requiredSettings = createRequiredSettingNameSet(eventType);
136         for (Element setting : getChildElements(event, "setting")) {
137             String settingName = setting.getAttribute("name");
138             if (requiredSettings.contains(settingName)) {
139                 requiredSettings.remove(settingName);
140             } else {
141                 errors.add("Setting '" + settingName + "' for event '" + eventType.getName() + "' should not be part of confirguaration '" + config.getName()
142                         + "' since it won't have an impact on the event.");
143             }
144         }
145         for (String required : requiredSettings) {
146             errors.add("Setting '" + required + "' in event '" + eventType.getName() + "' was not configured in the configuration '" + config.getName() + "'");
147         }
148 
149         return errors;
150     }
151 
152     private static Set<String> createRequiredSettingNameSet(EventType cd) {
153         Set<String> requiredSettings = new HashSet<>();
154         for (SettingDescriptor s : cd.getSettingDescriptors()) {
155             requiredSettings.add(s.getName());
156         }
157         return requiredSettings;
158     }
159 
160     private static Document createDocument(String content) throws ParserConfigurationException, SAXException, IOException {
161         DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
162         DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
163         Document doc = dBuilder.parse(new InputSource(new StringReader(content)));
164         doc.getDocumentElement().normalize();
165         // Don't want to add these settings to the jfc-files we ship since they
166         // are not useful to configure. They are however needed to make the test
167         // pass.
168         insertSetting(doc, EventNames.ActiveSetting, "stackTrace", "false");
169         insertSetting(doc, EventNames.ActiveSetting, "threshold", "0 ns");
170         insertSetting(doc, EventNames.ActiveRecording, "stackTrace", "false");
171         insertSetting(doc, EventNames.ActiveRecording, "threshold", "0 ns");
172         insertSetting(doc, EventNames.JavaExceptionThrow, "threshold", "0 ns");
173         insertSetting(doc, EventNames.JavaErrorThrow, "threshold", "0 ns");
174         insertSetting(doc, EventNames.SecurityProperty, "threshold", "0 ns");
175         insertSetting(doc, EventNames.TLSHandshake, "threshold", "0 ns");
176         insertSetting(doc, EventNames.X509Certificate, "threshold", "0 ns");
177         insertSetting(doc, EventNames.X509Validation, "threshold", "0 ns");
178         insertSetting(doc, EventNames.ProcessStart, "threshold", "0 ns");
179         insertSetting(doc, EventNames.Deserialization, "threshold", "0 ns");
180 
181         return doc;
182     }
183 
184     private static void insertSetting(Document doc, String eventName, String settingName, String settingValue) {
185         for (Element event : getChildElements(doc.getDocumentElement(), "event")) {
186             Attr attribute = event.getAttributeNode("name");
187             if (attribute != null) {
188                 if (eventName.equals(attribute.getValue())) {
189                     Element setting = doc.createElement("setting");
190                     setting.setAttribute("name", settingName);
191                     setting.setTextContent(settingValue);
192                     event.appendChild(setting);
193                 }
194             }
195         }
196     }
197 
198     private static Collection<Element> getChildElements(Element parent, String name) {
199         NodeList elementsByTagName = parent.getElementsByTagName(name);
200         List<Element> elements = new ArrayList<>();
201         for (int i = 0; i < elementsByTagName.getLength(); i++) {
202             Node node = elementsByTagName.item(i);
203             if (node.getNodeType() == Node.ELEMENT_NODE) {
204                 elements.add((Element) node);
205             }
206         }
207         return elements;
208     }
209 }