5a7fde4e3ddd3e0f29720ebb914cf1a447817d8b
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / xml / XmlElement.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.netconf.util.xml;
10
11 import java.io.IOException;
12 import java.util.*;
13
14 import javax.annotation.Nullable;
15
16 import org.w3c.dom.*;
17 import org.xml.sax.SAXException;
18
19 import com.google.common.base.Optional;
20 import com.google.common.base.Preconditions;
21 import com.google.common.base.Predicate;
22 import com.google.common.collect.Collections2;
23 import com.google.common.collect.Lists;
24 import com.google.common.collect.Maps;
25
26 public class XmlElement {
27
28     public final Element element;
29
30     private XmlElement(Element element) {
31         this.element = element;
32     }
33
34     public static XmlElement fromDomElement(Element e) {
35         return new XmlElement(e);
36     }
37
38     public static XmlElement fromDomDocument(Document xml) {
39         return new XmlElement(xml.getDocumentElement());
40     }
41
42     public static XmlElement fromString(String s) {
43         try {
44             return new XmlElement(XmlUtil.readXmlToElement(s));
45         } catch (IOException | SAXException e) {
46             throw new IllegalArgumentException("Unable to create from " + s, e);
47         }
48     }
49
50     public static XmlElement fromDomElementWithExpected(Element element, String expectedName) {
51         XmlElement xmlElement = XmlElement.fromDomElement(element);
52         xmlElement.checkName(expectedName);
53         return xmlElement;
54     }
55
56     public static XmlElement fromDomElementWithExpected(Element element, String expectedName, String expectedNamespace) {
57         XmlElement xmlElement = XmlElement.fromDomElementWithExpected(element, expectedName);
58         xmlElement.checkNamespace(expectedNamespace);
59         return xmlElement;
60     }
61
62     private static Map<String, String> extractNamespaces(Element typeElement) {
63         Map<String, String> namespaces = new HashMap<>();
64         NamedNodeMap attributes = typeElement.getAttributes();
65         for (int i = 0; i < attributes.getLength(); i++) {
66             Node attribute = attributes.item(i);
67             String attribKey = attribute.getNodeName();
68             if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
69                 String prefix;
70                 if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
71                     prefix = "";
72                 } else {
73                     Preconditions.checkState(attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":"));
74                     prefix = attribKey.substring(XmlUtil.XMLNS_ATTRIBUTE_KEY.length() + 1);
75                 }
76                 namespaces.put(prefix, attribute.getNodeValue());
77             }
78         }
79         return namespaces;
80     }
81
82     public void checkName(String expectedName) {
83         Preconditions.checkArgument(getName().equals(expectedName), "Expected %s xml element but was %s", expectedName,
84                 getName());
85     }
86
87     public void checkNamespaceAttribute(String expectedNamespace) {
88         Preconditions.checkArgument(getNamespaceAttribute().equals(expectedNamespace),
89                 "Unexpected namespace %s for element %s, should be %s", getNamespaceAttribute(), expectedNamespace);
90     }
91
92     public void checkNamespace(String expectedNamespace) {
93         Preconditions.checkArgument(getNamespace().equals(expectedNamespace),
94                 "Unexpected namespace %s for element %s, should be %s", getNamespace(), expectedNamespace);
95     }
96
97     public String getName() {
98         return element.getTagName();
99     }
100
101     public String getAttribute(String attributeName) {
102         return element.getAttribute(attributeName);
103     }
104
105     public String getAttribute(String attributeName, String namespace) {
106         return element.getAttributeNS(namespace, attributeName);
107     }
108
109     public void appendChild(Element element) {
110         this.element.appendChild(element);
111         // Element newElement = (Element) element.cloneNode(true);
112         // newElement.appendChild(configElement);
113         // return XmlElement.fromDomElement(newElement);
114     }
115
116     public Element getDomElement() {
117         return element;
118     }
119
120     public Map<String, Attr> getAttributes() {
121
122         Map<String, Attr> mappedAttributes = Maps.newHashMap();
123
124         NamedNodeMap attributes = element.getAttributes();
125         for (int i = 0; i < attributes.getLength(); i++) {
126             Attr attr = (Attr) attributes.item(i);
127             mappedAttributes.put(attr.getNodeName(), attr);
128         }
129
130         return mappedAttributes;
131     }
132
133     /**
134      * Non recursive
135      */
136     private List<XmlElement> getChildElementsInternal(ElementFilteringStrategy strat) {
137         NodeList childNodes = element.getChildNodes();
138         final List<XmlElement> result = new ArrayList<>();
139         for (int i = 0; i < childNodes.getLength(); i++) {
140             Node item = childNodes.item(i);
141             if (item instanceof Element == false)
142                 continue;
143             if (strat.accept((Element) item))
144                 result.add(new XmlElement((Element) item));
145         }
146
147         return result;
148     }
149
150     public List<XmlElement> getChildElements() {
151         return getChildElementsInternal(new ElementFilteringStrategy() {
152             @Override
153             public boolean accept(Element e) {
154                 return true;
155             }
156         });
157     }
158
159     public List<XmlElement> getChildElementsWithinNamespace(final String childName, String namespace) {
160         return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace),
161                 new Predicate<XmlElement>() {
162                     @Override
163                     public boolean apply(@Nullable XmlElement xmlElement) {
164                         return xmlElement.getName().equals(childName);
165                     }
166                 }));
167     }
168
169     public List<XmlElement> getChildElementsWithinNamespace(final String namespace) {
170         return getChildElementsInternal(new ElementFilteringStrategy() {
171             @Override
172             public boolean accept(Element e) {
173                 return XmlElement.fromDomElement(e).getNamespace().equals(namespace);
174             }
175
176         });
177     }
178
179     public List<XmlElement> getChildElements(final String tagName) {
180         return getChildElementsInternal(new ElementFilteringStrategy() {
181             @Override
182             public boolean accept(Element e) {
183                 return e.getTagName().equals(tagName);
184             }
185         });
186     }
187
188     public XmlElement getOnlyChildElement(String childName) {
189         List<XmlElement> nameElements = getChildElements(childName);
190         Preconditions.checkState(nameElements.size() == 1, "One element " + childName + " expected in " + toString());
191         return nameElements.get(0);
192     }
193
194     public Optional<XmlElement> getOnlyChildElementOptionally(String childName) {
195         try {
196             return Optional.of(getOnlyChildElement(childName));
197         } catch (Exception e) {
198             return Optional.absent();
199         }
200     }
201
202     public Optional<XmlElement> getOnlyChildElementOptionally(String childName, String namespace) {
203         try {
204             return Optional.of(getOnlyChildElement(childName, namespace));
205         } catch (Exception e) {
206             return Optional.absent();
207         }
208     }
209
210     public XmlElement getOnlyChildElementWithSameNamespace(String childName) {
211         return getOnlyChildElement(childName, getNamespace());
212     }
213
214     public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(String childName) {
215         try {
216             return Optional.of(getOnlyChildElement(childName, getNamespace()));
217         } catch (Exception e) {
218             return Optional.absent();
219         }
220     }
221
222     public XmlElement getOnlyChildElementWithSameNamespace() {
223         XmlElement childElement = getOnlyChildElement();
224         childElement.checkNamespace(getNamespace());
225         return childElement;
226     }
227
228     public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally() {
229         try {
230             XmlElement childElement = getOnlyChildElement();
231             childElement.checkNamespace(getNamespace());
232             return Optional.of(childElement);
233         } catch (Exception e) {
234             return Optional.absent();
235         }
236     }
237
238     public XmlElement getOnlyChildElement(final String childName, String namespace) {
239         List<XmlElement> children = getChildElementsWithinNamespace(namespace);
240         children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
241             @Override
242             public boolean apply(@Nullable XmlElement xmlElement) {
243                 return xmlElement.getName().equals(childName);
244             }
245         }));
246         Preconditions.checkState(children.size() == 1, "One element %s:%s expected in %s but was %s", namespace,
247                 childName, toString(), children.size());
248         return children.get(0);
249     }
250
251     public XmlElement getOnlyChildElement() {
252         List<XmlElement> children = getChildElements();
253         Preconditions.checkState(children.size() == 1, "One element expected in %s but was %s", toString(),
254                 children.size());
255         return children.get(0);
256     }
257
258     public String getTextContent() {
259         Node textChild = element.getFirstChild();
260         Preconditions.checkState(textChild instanceof Text, getName() + " should contain text");
261         String content = textChild.getTextContent();
262         // Trim needed
263         return content.trim();
264     }
265
266     public String getNamespaceAttribute() {
267         String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
268         Preconditions.checkState(attribute != null && !attribute.equals(""), "Element %s must specify a %s attribute",
269                 toString(), XmlUtil.XMLNS_ATTRIBUTE_KEY);
270         return attribute;
271     }
272
273     public String getNamespace() {
274         String namespaceURI = element.getNamespaceURI();
275         Preconditions.checkState(namespaceURI != null, "No namespace defined for %s", this);
276         return namespaceURI.toString();
277     }
278
279     @Override
280     public String toString() {
281         final StringBuffer sb = new StringBuffer("XmlElement{");
282         sb.append("name='").append(getName()).append('\'');
283         if (element.getNamespaceURI() != null) {
284             sb.append(", namespace='").append(getNamespace()).append('\'');
285         }
286         sb.append('}');
287         return sb.toString();
288     }
289
290     /**
291      * Search for element's attributes defining namespaces. Look for the one
292      * namespace that matches prefix of element's text content. E.g.
293      *
294      * <pre>
295      * &lt;type
296      * xmlns:th-java="urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl"&gt;th-java:threadfactory-naming&lt;/type&gt;
297      * </pre>
298      *
299      * returns {"th-java","urn:.."}. If no prefix is matched, then default
300      * namespace is returned with empty string as key. If no default namespace
301      * is found value will be null.
302      */
303     public Map.Entry<String/* prefix */, String/* namespace */> findNamespaceOfTextContent() {
304         Map<String, String> namespaces = extractNamespaces(element);
305         String textContent = getTextContent();
306         int indexOfColon = textContent.indexOf(":");
307         String prefix;
308         if (indexOfColon > -1) {
309             prefix = textContent.substring(0, indexOfColon);
310         } else {
311             prefix = "";
312         }
313         if (namespaces.containsKey(prefix) == false) {
314             throw new IllegalArgumentException("Cannot find namespace for " + element + ". Prefix from content is "
315                     + prefix + ". Found namespaces " + namespaces);
316         }
317         return Maps.immutableEntry(prefix, namespaces.get(prefix));
318     }
319
320     public List<XmlElement> getChildElementsWithSameNamespace(final String childName) {
321         List<XmlElement> children = getChildElementsWithinNamespace(getNamespace());
322         return Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
323             @Override
324             public boolean apply(@Nullable XmlElement xmlElement) {
325                 return xmlElement.getName().equals(childName);
326             }
327         }));
328     }
329
330     public void checkUnrecognisedElements(List<XmlElement> recognisedElements,
331             XmlElement... additionalRecognisedElements) {
332         List<XmlElement> childElements = getChildElements();
333         childElements.removeAll(recognisedElements);
334         for (XmlElement additionalRecognisedElement : additionalRecognisedElements) {
335             childElements.remove(additionalRecognisedElement);
336         }
337         Preconditions.checkState(childElements.isEmpty(), "Unrecognised elements %s in %s", childElements, this);
338     }
339
340     public void checkUnrecognisedElements(XmlElement... additionalRecognisedElements) {
341         checkUnrecognisedElements(Collections.<XmlElement> emptyList(), additionalRecognisedElements);
342     }
343
344     @Override
345     public boolean equals(Object o) {
346         if (this == o)
347             return true;
348         if (o == null || getClass() != o.getClass())
349             return false;
350
351         XmlElement that = (XmlElement) o;
352
353         if (!element.isEqualNode(that.element))
354             return false;
355
356         return true;
357     }
358
359     @Override
360     public int hashCode() {
361         return element.hashCode();
362     }
363
364     private static interface ElementFilteringStrategy {
365         boolean accept(Element e);
366     }
367 }