2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.netconf.util.xml;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Predicate;
13 import com.google.common.base.Strings;
14 import com.google.common.collect.Collections2;
15 import com.google.common.collect.Lists;
16 import com.google.common.collect.Maps;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.List;
23 import javax.annotation.Nullable;
24 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
25 import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException;
26 import org.opendaylight.controller.netconf.util.exception.UnexpectedElementException;
27 import org.opendaylight.controller.netconf.util.exception.UnexpectedNamespaceException;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.w3c.dom.Attr;
31 import org.w3c.dom.Document;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.NamedNodeMap;
34 import org.w3c.dom.Node;
35 import org.w3c.dom.NodeList;
36 import org.w3c.dom.Text;
37 import org.xml.sax.SAXException;
39 public final class XmlElement {
41 private final Element element;
42 private static final Logger logger = LoggerFactory.getLogger(XmlElement.class);
44 private XmlElement(Element element) {
45 this.element = element;
48 public static XmlElement fromDomElement(Element e) {
49 return new XmlElement(e);
52 public static XmlElement fromDomDocument(Document xml) {
53 return new XmlElement(xml.getDocumentElement());
56 public static XmlElement fromString(String s) throws NetconfDocumentedException {
58 return new XmlElement(XmlUtil.readXmlToElement(s));
59 } catch (IOException | SAXException e) {
60 throw NetconfDocumentedException.wrap(e);
64 public static XmlElement fromDomElementWithExpected(Element element, String expectedName) throws NetconfDocumentedException {
65 XmlElement xmlElement = XmlElement.fromDomElement(element);
66 xmlElement.checkName(expectedName);
70 public static XmlElement fromDomElementWithExpected(Element element, String expectedName, String expectedNamespace) throws NetconfDocumentedException {
71 XmlElement xmlElement = XmlElement.fromDomElementWithExpected(element, expectedName);
72 xmlElement.checkNamespace(expectedNamespace);
76 private static Map<String, String> extractNamespaces(Element typeElement) throws NetconfDocumentedException {
77 Map<String, String> namespaces = new HashMap<>();
78 NamedNodeMap attributes = typeElement.getAttributes();
79 for (int i = 0; i < attributes.getLength(); i++) {
80 Node attribute = attributes.item(i);
81 String attribKey = attribute.getNodeName();
82 if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
84 if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
87 if (!attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")){
88 throw new NetconfDocumentedException("Attribute doesn't start with :",
89 NetconfDocumentedException.ErrorType.application,
90 NetconfDocumentedException.ErrorTag.invalid_value,
91 NetconfDocumentedException.ErrorSeverity.error);
93 prefix = attribKey.substring(XmlUtil.XMLNS_ATTRIBUTE_KEY.length() + 1);
95 namespaces.put(prefix, attribute.getNodeValue());
101 public void checkName(String expectedName) throws UnexpectedElementException {
102 if (!getName().equals(expectedName)){
103 throw new UnexpectedElementException(String.format("Expected %s xml element but was %s", expectedName,
105 NetconfDocumentedException.ErrorType.application,
106 NetconfDocumentedException.ErrorTag.operation_failed,
107 NetconfDocumentedException.ErrorSeverity.error);
111 public void checkNamespaceAttribute(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
112 if (!getNamespaceAttribute().equals(expectedNamespace))
114 throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
115 getNamespaceAttribute(),
117 NetconfDocumentedException.ErrorType.application,
118 NetconfDocumentedException.ErrorTag.operation_failed,
119 NetconfDocumentedException.ErrorSeverity.error);
123 public void checkNamespace(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
124 if (!getNamespace().equals(expectedNamespace))
126 throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
129 NetconfDocumentedException.ErrorType.application,
130 NetconfDocumentedException.ErrorTag.operation_failed,
131 NetconfDocumentedException.ErrorSeverity.error);
135 public String getName() {
136 if (element.getLocalName()!=null && !element.getLocalName().equals("")){
137 return element.getLocalName();
139 return element.getTagName();
142 public String getAttribute(String attributeName) {
143 return element.getAttribute(attributeName);
146 public String getAttribute(String attributeName, String namespace) {
147 return element.getAttributeNS(namespace, attributeName);
150 public NodeList getElementsByTagName(String name) {
151 return element.getElementsByTagName(name);
154 public void appendChild(Element element) {
155 this.element.appendChild(element);
158 public Element getDomElement() {
162 public Map<String, Attr> getAttributes() {
164 Map<String, Attr> mappedAttributes = Maps.newHashMap();
166 NamedNodeMap attributes = element.getAttributes();
167 for (int i = 0; i < attributes.getLength(); i++) {
168 Attr attr = (Attr) attributes.item(i);
169 mappedAttributes.put(attr.getNodeName(), attr);
172 return mappedAttributes;
178 private List<XmlElement> getChildElementsInternal(ElementFilteringStrategy strat) {
179 NodeList childNodes = element.getChildNodes();
180 final List<XmlElement> result = new ArrayList<>();
181 for (int i = 0; i < childNodes.getLength(); i++) {
182 Node item = childNodes.item(i);
183 if (!(item instanceof Element)) {
186 if (strat.accept((Element) item)) {
187 result.add(new XmlElement((Element) item));
194 public List<XmlElement> getChildElements() {
195 return getChildElementsInternal(new ElementFilteringStrategy() {
197 public boolean accept(Element e) {
203 public List<XmlElement> getChildElementsWithinNamespace(final String childName, String namespace) {
204 return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace),
205 new Predicate<XmlElement>() {
207 public boolean apply(@Nullable XmlElement xmlElement) {
208 return xmlElement.getName().equals(childName);
213 public List<XmlElement> getChildElementsWithinNamespace(final String namespace) {
214 return getChildElementsInternal(new ElementFilteringStrategy() {
216 public boolean accept(Element e) {
218 return XmlElement.fromDomElement(e).getNamespace().equals(namespace);
219 } catch (MissingNameSpaceException e1) {
229 * @param tagName tag name without prefix
232 public List<XmlElement> getChildElements(final String tagName) {
233 return getChildElementsInternal(new ElementFilteringStrategy() {
235 public boolean accept(Element e) {
236 // localName returns pure localName without prefix
237 return e.getLocalName().equals(tagName);
242 public XmlElement getOnlyChildElement(String childName) throws NetconfDocumentedException {
243 List<XmlElement> nameElements = getChildElements(childName);
244 if (nameElements.size() != 1){
245 throw new NetconfDocumentedException("One element " + childName + " expected in " + toString(),
246 NetconfDocumentedException.ErrorType.application,
247 NetconfDocumentedException.ErrorTag.invalid_value,
248 NetconfDocumentedException.ErrorSeverity.error);
250 return nameElements.get(0);
253 public Optional<XmlElement> getOnlyChildElementOptionally(String childName) {
255 return Optional.of(getOnlyChildElement(childName));
256 } catch (Exception e) {
257 return Optional.absent();
261 public Optional<XmlElement> getOnlyChildElementOptionally(String childName, String namespace) {
263 return Optional.of(getOnlyChildElement(childName, namespace));
264 } catch (Exception e) {
265 return Optional.absent();
269 public XmlElement getOnlyChildElementWithSameNamespace(String childName) throws NetconfDocumentedException {
270 return getOnlyChildElement(childName, getNamespace());
273 public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(String childName) {
275 return Optional.of(getOnlyChildElement(childName, getNamespace()));
276 } catch (Exception e) {
277 return Optional.absent();
281 public XmlElement getOnlyChildElementWithSameNamespace() throws NetconfDocumentedException {
282 XmlElement childElement = getOnlyChildElement();
283 childElement.checkNamespace(getNamespace());
287 public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally() {
289 XmlElement childElement = getOnlyChildElement();
290 childElement.checkNamespace(getNamespace());
291 return Optional.of(childElement);
292 } catch (Exception e) {
293 return Optional.absent();
297 public XmlElement getOnlyChildElement(final String childName, String namespace) throws NetconfDocumentedException {
298 List<XmlElement> children = getChildElementsWithinNamespace(namespace);
299 children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
301 public boolean apply(@Nullable XmlElement xmlElement) {
302 return xmlElement.getName().equals(childName);
305 if (children.size() != 1){
306 throw new NetconfDocumentedException(String.format("One element %s:%s expected in %s but was %s", namespace,
307 childName, toString(), children.size()),
308 NetconfDocumentedException.ErrorType.application,
309 NetconfDocumentedException.ErrorTag.invalid_value,
310 NetconfDocumentedException.ErrorSeverity.error);
313 return children.get(0);
316 public XmlElement getOnlyChildElement() throws NetconfDocumentedException {
317 List<XmlElement> children = getChildElements();
318 if (children.size() != 1){
319 throw new NetconfDocumentedException(String.format( "One element expected in %s but was %s", toString(),
321 NetconfDocumentedException.ErrorType.application,
322 NetconfDocumentedException.ErrorTag.invalid_value,
323 NetconfDocumentedException.ErrorSeverity.error);
325 return children.get(0);
328 public String getTextContent() throws NetconfDocumentedException {
329 NodeList childNodes = element.getChildNodes();
330 if (childNodes.getLength() == 0) {
333 for(int i = 0; i < childNodes.getLength(); i++) {
334 Node textChild = childNodes.item(i);
335 if (textChild instanceof Text) {
336 String content = textChild.getTextContent();
337 return content.trim();
340 throw new NetconfDocumentedException(getName() + " should contain text.",
341 NetconfDocumentedException.ErrorType.application,
342 NetconfDocumentedException.ErrorTag.invalid_value,
343 NetconfDocumentedException.ErrorSeverity.error
347 public Optional<String> getOnlyTextContentOptionally() {
348 // only return text content if this node has exactly one Text child node
349 if (element.getChildNodes().getLength() == 1) {
350 Node item = element.getChildNodes().item(0);
351 if (item instanceof Text) {
352 return Optional.of(((Text) item).getWholeText());
355 return Optional.absent();
358 public String getNamespaceAttribute() throws MissingNameSpaceException {
359 String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
360 if (attribute == null || attribute.equals("")){
361 throw new MissingNameSpaceException(String.format("Element %s must specify namespace",
363 NetconfDocumentedException.ErrorType.application,
364 NetconfDocumentedException.ErrorTag.operation_failed,
365 NetconfDocumentedException.ErrorSeverity.error);
370 public Optional<String> getNamespaceOptionally() {
371 String namespaceURI = element.getNamespaceURI();
372 if (Strings.isNullOrEmpty(namespaceURI)) {
373 return Optional.absent();
375 return Optional.of(namespaceURI);
379 public String getNamespace() throws MissingNameSpaceException {
380 Optional<String> namespaceURI = getNamespaceOptionally();
381 if (namespaceURI.isPresent() == false){
382 throw new MissingNameSpaceException(String.format("No namespace defined for %s", this),
383 NetconfDocumentedException.ErrorType.application,
384 NetconfDocumentedException.ErrorTag.operation_failed,
385 NetconfDocumentedException.ErrorSeverity.error);
387 return namespaceURI.get();
391 public String toString() {
392 final StringBuilder sb = new StringBuilder("XmlElement{");
393 sb.append("name='").append(getName()).append('\'');
394 if (element.getNamespaceURI() != null) {
396 sb.append(", namespace='").append(getNamespace()).append('\'');
397 } catch (MissingNameSpaceException e) {
398 logger.trace("Missing namespace for element.");
402 return sb.toString();
406 * Search for element's attributes defining namespaces. Look for the one
407 * namespace that matches prefix of element's text content. E.g.
411 * xmlns:th-java="urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl">th-java:threadfactory-naming</type>
414 * returns {"th-java","urn:.."}. If no prefix is matched, then default
415 * namespace is returned with empty string as key. If no default namespace
416 * is found value will be null.
418 public Map.Entry<String/* prefix */, String/* namespace */> findNamespaceOfTextContent() throws NetconfDocumentedException {
419 Map<String, String> namespaces = extractNamespaces(element);
420 String textContent = getTextContent();
421 int indexOfColon = textContent.indexOf(':');
423 if (indexOfColon > -1) {
424 prefix = textContent.substring(0, indexOfColon);
428 if (!namespaces.containsKey(prefix)) {
429 throw new IllegalArgumentException("Cannot find namespace for " + XmlUtil.toString(element) + ". Prefix from content is "
430 + prefix + ". Found namespaces " + namespaces);
432 return Maps.immutableEntry(prefix, namespaces.get(prefix));
435 public List<XmlElement> getChildElementsWithSameNamespace(final String childName) throws MissingNameSpaceException {
436 List<XmlElement> children = getChildElementsWithinNamespace(getNamespace());
437 return Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
439 public boolean apply(@Nullable XmlElement xmlElement) {
440 return xmlElement.getName().equals(childName);
445 public void checkUnrecognisedElements(List<XmlElement> recognisedElements,
446 XmlElement... additionalRecognisedElements) throws NetconfDocumentedException {
447 List<XmlElement> childElements = getChildElements();
448 childElements.removeAll(recognisedElements);
449 for (XmlElement additionalRecognisedElement : additionalRecognisedElements) {
450 childElements.remove(additionalRecognisedElement);
452 if (!childElements.isEmpty()){
453 throw new NetconfDocumentedException(String.format("Unrecognised elements %s in %s", childElements, this),
454 NetconfDocumentedException.ErrorType.application,
455 NetconfDocumentedException.ErrorTag.invalid_value,
456 NetconfDocumentedException.ErrorSeverity.error);
460 public void checkUnrecognisedElements(XmlElement... additionalRecognisedElements) throws NetconfDocumentedException {
461 checkUnrecognisedElements(Collections.<XmlElement>emptyList(), additionalRecognisedElements);
465 public boolean equals(Object o) {
469 if (o == null || getClass() != o.getClass()) {
473 XmlElement that = (XmlElement) o;
475 if (!element.isEqualNode(that.element)) {
483 public int hashCode() {
484 return element.hashCode();
487 public boolean hasNamespace() {
489 getNamespaceAttribute();
490 } catch (MissingNameSpaceException e) {
493 } catch (MissingNameSpaceException e1) {
501 private interface ElementFilteringStrategy {
502 boolean accept(Element e);