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