/* * Copyright 2016-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.drivers.utilities; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.configuration.tree.ConfigurationNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static org.onlab.util.Tools.nullIsNotFound; /** * Util CLass for Yang models. * Clean abstraction to read, obtain and populate * XML from Yang models translated into XML skeletons. */ public class YangXmlUtils { public final Logger log = LoggerFactory .getLogger(getClass()); private static YangXmlUtils instance = null; //no instantiation, single instance. protected YangXmlUtils() { } /** * Retrieves a valid XML configuration for a specific XML path for a single * instance of the Map specified key-value pairs. * * @param file path of the file to be used. * @param values map of key and values to set under the generic path. * @return Hierarchical configuration containing XML with values. */ public XMLConfiguration getXmlConfiguration(String file, Map<String, String> values) { InputStream stream = getCfgInputStream(file); XMLConfiguration cfg = loadXml(stream); XMLConfiguration complete = new XMLConfiguration(); List<String> paths = new ArrayList<>(); Map<String, String> valuesWithKey = new HashMap<>(); values.keySet().forEach(path -> { List<String> allPaths = findPaths(cfg, path); String key = nullIsNotFound(allPaths.isEmpty() ? null : allPaths.get(0), "Yang model does not contain desired path"); paths.add(key); valuesWithKey.put(key, values.get(path)); }); Collections.sort(paths, new StringLengthComparator()); paths.forEach(key -> complete.setProperty(key, valuesWithKey.get(key))); addProperties(cfg, complete); return complete; } /** * Retrieves a valid XML configuration for a specific XML path for multiple * instance of YangElements objects. * * @param file path of the file to be used. * @param elements List of YangElements that are to be set. * @return Hierachical configuration containing XML with values. */ public XMLConfiguration getXmlConfiguration(String file, List<YangElement> elements) { InputStream stream = getCfgInputStream(file); HierarchicalConfiguration cfg = loadXml(stream); XMLConfiguration complete = new XMLConfiguration(); Multimap<String, YangElement> commonElements = ArrayListMultimap.create(); //saves the elements in a Multimap based on the computed key. elements.forEach(element -> { String completeKey = nullIsNotFound(findPath(cfg, element.getBaseKey()), "Yang model does not contain desired path"); commonElements.put(completeKey, element); }); //iterates over the elements and constructs the configuration commonElements.keySet().forEach(key -> { // if there is more than one element for a given path if (commonElements.get(key).size() > 1) { //creates a list of nodes that have to be added for that specific path ArrayList<ConfigurationNode> nodes = new ArrayList<>(); //creates the nodes commonElements.get(key).forEach(element -> nodes.add(getInnerNode(element).getRootNode())); //computes the parent path String parentPath = key.substring(0, key.lastIndexOf(".")); //adds the nodes to the complete configuration complete.addNodes(parentPath, nodes); } else { //since there is only a single element we can assume it's the first one. Map<String, String> keysAndValues = commonElements.get(key).stream(). findFirst().get().getKeysAndValues(); keysAndValues.forEach((k, v) -> complete.setProperty(key + "." + k, v)); } }); addProperties(cfg, complete); return complete; } //Adds all the properties of the original configuration to the new one. private void addProperties(HierarchicalConfiguration cfg, HierarchicalConfiguration complete) { cfg.getKeys().forEachRemaining(key -> { String property = (String) cfg.getProperty(key); if (!"".equals(property)) { complete.setProperty(key, property); } }); } protected InputStream getCfgInputStream(String file) { return getClass().getResourceAsStream(file); } /** * Reads a valid XML configuration and returns a Map containing XML field name. * and value contained for every subpath. * * @param cfg the Configuration to read. * @param path path of the information to be read. * @return list of elements containing baskey and map of key value pairs. */ public List<YangElement> readXmlConfiguration(HierarchicalConfiguration cfg, String path) { List<YangElement> elements = new ArrayList<>(); String key = nullIsNotFound(findPath(cfg, path), "Configuration does not contain desired path"); getElements(cfg.configurationsAt(key), elements, key, cfg, path, key); return ImmutableList.copyOf(elements); } private void getElements(List<HierarchicalConfiguration> configurations, List<YangElement> elements, String basekey, HierarchicalConfiguration originalCfg, String path, String originalKey) { //consider each sub configuration configurations.forEach(config -> { YangElement element = new YangElement(path, new HashMap<>()); //for each of the keys of the sub configuration config.getKeys().forEachRemaining(key -> { //considers only one step ahead //if one step ahead has other steps calls self to analize them //else adds to yang element. if (key.split("\\.").length > 1) { getElements(originalCfg.configurationsAt(basekey + "." + key.split("\\.")[0]), elements, basekey + "." + key.split("\\.")[0], originalCfg, path, originalKey); } else { String replaced = basekey.replace(originalKey, ""); String partialKey = replaced.isEmpty() ? key : replaced.substring(1) + "." + key; partialKey = partialKey.isEmpty() ? originalKey : partialKey; //Adds values to the element with a subkey starting from the requeste path onwards element.getKeysAndValues().put(partialKey, config.getProperty(key).toString()); } }); //if the element doesnt already exist if (!elements.contains(element) && !element.getKeysAndValues().isEmpty()) { elements.add(element); } }); } /** * Single Instance of Yang utilities retriever. * * @return instance of YangXmlUtils */ public static YangXmlUtils getInstance() { if (instance == null) { instance = new YangXmlUtils(); } return instance; } /** * Return the string representation of the XMLConfig without header * and configuration element. * * @param cfg the XML to convert * @return the cfg string. */ public String getString(XMLConfiguration cfg) { StringWriter stringWriter = new StringWriter(); try { cfg.save(stringWriter); } catch (ConfigurationException e) { log.error("Cannot convert configuration", e.getMessage()); } String xml = stringWriter.toString(); xml = xml.substring(xml.indexOf("\n")); xml = xml.substring(xml.indexOf(">") + 1); return xml; } /** * Method to read an input stream into a XMLConfiguration. * @param xmlStream inputstream containing XML description * @return the XMLConfiguration object */ public XMLConfiguration loadXml(InputStream xmlStream) { XMLConfiguration cfg = new XMLConfiguration(); try { cfg.load(xmlStream); return cfg; } catch (ConfigurationException e) { throw new IllegalArgumentException("Cannot load xml from Stream", e); } } //Finds all paths for a corresponding element private List<String> findPaths(HierarchicalConfiguration cfg, String path) { List<String> paths = new ArrayList<>(); cfg.getKeys().forEachRemaining(key -> { if (key.equals(path)) { paths.add(key); } if (key.contains("." + path)) { paths.add(key); } }); return paths; } //Finds the first parent path corresponding to an element. private String findPath(HierarchicalConfiguration cfg, String element) { Iterator<String> it = cfg.getKeys(); while (it.hasNext()) { String key = it.next(); String[] arr = key.split("\\."); for (int i = 0; i < arr.length; i++) { if (element.equals(arr[i])) { String completeKey = ""; for (int j = 0; j <= i; j++) { completeKey = completeKey + "." + arr[j]; } return completeKey.substring(1); } } } return null; } //creates a node based on a single Yang element. private HierarchicalConfiguration getInnerNode(YangElement element) { HierarchicalConfiguration node = new HierarchicalConfiguration(); node.setRoot(new HierarchicalConfiguration.Node(element.getBaseKey())); element.getKeysAndValues().forEach(node::setProperty); return node; } //String lenght comparator private class StringLengthComparator implements Comparator<String> { public int compare(String o1, String o2) { if (o2 == null && o1 == null) { return 0; } if (o1 == null) { return o2.length(); } if (o2 == null) { return o1.length(); } if (o1.length() != o2.length()) { return o1.length() - o2.length(); //overflow impossible since lengths are non-negative } return o1.compareTo(o2); } } }