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 org.opendaylight.controller.netconf.api.NetconfDocumentedException;
24 import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException;
25 import org.opendaylight.controller.netconf.util.exception.UnexpectedElementException;
26 import org.opendaylight.controller.netconf.util.exception.UnexpectedNamespaceException;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.w3c.dom.Attr;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.NamedNodeMap;
33 import org.w3c.dom.Node;
34 import org.w3c.dom.NodeList;
35 import org.w3c.dom.Text;
36 import org.xml.sax.SAXException;
38 public final class XmlElement {
40 public static final String DEFAULT_NAMESPACE_PREFIX = "";
42 private final Element element;
43 private static final Logger LOG = LoggerFactory.getLogger(XmlElement.class);
45 private XmlElement(Element element) {
46 this.element = element;
49 public static XmlElement fromDomElement(Element e) {
50 return new XmlElement(e);
53 public static XmlElement fromDomDocument(Document xml) {
54 return new XmlElement(xml.getDocumentElement());
57 public static XmlElement fromString(String s) throws NetconfDocumentedException {
59 return new XmlElement(XmlUtil.readXmlToElement(s));
60 } catch (IOException | SAXException e) {
61 throw NetconfDocumentedException.wrap(e);
65 public static XmlElement fromDomElementWithExpected(Element element, String expectedName) throws NetconfDocumentedException {
66 XmlElement xmlElement = XmlElement.fromDomElement(element);
67 xmlElement.checkName(expectedName);
71 public static XmlElement fromDomElementWithExpected(Element element, String expectedName, String expectedNamespace) throws NetconfDocumentedException {
72 XmlElement xmlElement = XmlElement.fromDomElementWithExpected(element, expectedName);
73 xmlElement.checkNamespace(expectedNamespace);
77 private Map<String, String> extractNamespaces() throws NetconfDocumentedException {
78 Map<String, String> namespaces = new HashMap<>();
79 NamedNodeMap attributes = element.getAttributes();
80 for (int i = 0; i < attributes.getLength(); i++) {
81 Node attribute = attributes.item(i);
82 String attribKey = attribute.getNodeName();
83 if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
85 if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
86 prefix = DEFAULT_NAMESPACE_PREFIX;
88 if (!attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")){
89 throw new NetconfDocumentedException("Attribute doesn't start with :",
90 NetconfDocumentedException.ErrorType.application,
91 NetconfDocumentedException.ErrorTag.invalid_value,
92 NetconfDocumentedException.ErrorSeverity.error);
94 prefix = attribKey.substring(XmlUtil.XMLNS_ATTRIBUTE_KEY.length() + 1);
96 namespaces.put(prefix, attribute.getNodeValue());
100 // namespace does not have to be defined on this element but inherited
101 if(!namespaces.containsKey(DEFAULT_NAMESPACE_PREFIX)) {
102 Optional<String> namespaceOptionally = getNamespaceOptionally();
103 if(namespaceOptionally.isPresent()) {
104 namespaces.put(DEFAULT_NAMESPACE_PREFIX, namespaceOptionally.get());
111 public void checkName(String expectedName) throws UnexpectedElementException {
112 if (!getName().equals(expectedName)){
113 throw new UnexpectedElementException(String.format("Expected %s xml element but was %s", expectedName,
115 NetconfDocumentedException.ErrorType.application,
116 NetconfDocumentedException.ErrorTag.operation_failed,
117 NetconfDocumentedException.ErrorSeverity.error);
121 public void checkNamespaceAttribute(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
122 if (!getNamespaceAttribute().equals(expectedNamespace))
124 throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
125 getNamespaceAttribute(),
127 NetconfDocumentedException.ErrorType.application,
128 NetconfDocumentedException.ErrorTag.operation_failed,
129 NetconfDocumentedException.ErrorSeverity.error);
133 public void checkNamespace(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
134 if (!getNamespace().equals(expectedNamespace))
136 throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
139 NetconfDocumentedException.ErrorType.application,
140 NetconfDocumentedException.ErrorTag.operation_failed,
141 NetconfDocumentedException.ErrorSeverity.error);
145 public String getName() {
146 if (element.getLocalName()!=null && !element.getLocalName().equals(DEFAULT_NAMESPACE_PREFIX)){
147 return element.getLocalName();
149 return element.getTagName();
152 public String getAttribute(String attributeName) {
153 return element.getAttribute(attributeName);
156 public String getAttribute(String attributeName, String namespace) {
157 return element.getAttributeNS(namespace, attributeName);
160 public NodeList getElementsByTagName(String name) {
161 return element.getElementsByTagName(name);
164 public void appendChild(Element element) {
165 this.element.appendChild(element);
168 public Element getDomElement() {
172 public Map<String, Attr> getAttributes() {
174 Map<String, Attr> mappedAttributes = Maps.newHashMap();
176 NamedNodeMap attributes = element.getAttributes();
177 for (int i = 0; i < attributes.getLength(); i++) {
178 Attr attr = (Attr) attributes.item(i);
179 mappedAttributes.put(attr.getNodeName(), attr);
182 return mappedAttributes;
188 private List<XmlElement> getChildElementsInternal(ElementFilteringStrategy strat) {
189 NodeList childNodes = element.getChildNodes();
190 final List<XmlElement> result = new ArrayList<>();
191 for (int i = 0; i < childNodes.getLength(); i++) {
192 Node item = childNodes.item(i);
193 if (!(item instanceof Element)) {
196 if (strat.accept((Element) item)) {
197 result.add(new XmlElement((Element) item));
204 public List<XmlElement> getChildElements() {
205 return getChildElementsInternal(new ElementFilteringStrategy() {
207 public boolean accept(Element e) {
213 public List<XmlElement> getChildElementsWithinNamespace(final String childName, String namespace) {
214 return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace),
215 new Predicate<XmlElement>() {
217 public boolean apply(XmlElement xmlElement) {
218 return xmlElement.getName().equals(childName);
223 public List<XmlElement> getChildElementsWithinNamespace(final String namespace) {
224 return getChildElementsInternal(new ElementFilteringStrategy() {
226 public boolean accept(Element e) {
228 return XmlElement.fromDomElement(e).getNamespace().equals(namespace);
229 } catch (MissingNameSpaceException e1) {
239 * @param tagName tag name without prefix
240 * @return List of child elements
242 public List<XmlElement> getChildElements(final String tagName) {
243 return getChildElementsInternal(new ElementFilteringStrategy() {
245 public boolean accept(Element e) {
246 // localName returns pure localName without prefix
247 return e.getLocalName().equals(tagName);
252 public XmlElement getOnlyChildElement(String childName) throws NetconfDocumentedException {
253 List<XmlElement> nameElements = getChildElements(childName);
254 if (nameElements.size() != 1){
255 throw new NetconfDocumentedException("One element " + childName + " expected in " + toString(),
256 NetconfDocumentedException.ErrorType.application,
257 NetconfDocumentedException.ErrorTag.invalid_value,
258 NetconfDocumentedException.ErrorSeverity.error);
260 return nameElements.get(0);
263 public Optional<XmlElement> getOnlyChildElementOptionally(String childName) {
264 List<XmlElement> nameElements = getChildElements(childName);
265 if (nameElements.size() != 1) {
266 return Optional.absent();
268 return Optional.of(nameElements.get(0));
271 public Optional<XmlElement> getOnlyChildElementOptionally(final String childName, final String namespace) {
272 List<XmlElement> children = getChildElementsWithinNamespace(namespace);
273 children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
275 public boolean apply(XmlElement xmlElement) {
276 return xmlElement.getName().equals(childName);
279 if (children.size() != 1){
280 return Optional.absent();
282 return Optional.of(children.get(0));
285 public XmlElement getOnlyChildElementWithSameNamespace(String childName) throws NetconfDocumentedException {
286 return getOnlyChildElement(childName, getNamespace());
289 public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(final String childName) {
290 Optional<String> namespace = getNamespaceOptionally();
291 if (namespace.isPresent()) {
292 List<XmlElement> children = getChildElementsWithinNamespace(namespace.get());
293 children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
295 public boolean apply(XmlElement xmlElement) {
296 return xmlElement.getName().equals(childName);
299 if (children.size() != 1){
300 return Optional.absent();
302 return Optional.of(children.get(0));
304 return Optional.absent();
307 public XmlElement getOnlyChildElementWithSameNamespace() throws NetconfDocumentedException {
308 XmlElement childElement = getOnlyChildElement();
309 childElement.checkNamespace(getNamespace());
313 public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally() {
314 Optional<XmlElement> child = getOnlyChildElementOptionally();
315 if (child.isPresent()
316 && child.get().getNamespaceOptionally().isPresent()
317 && getNamespaceOptionally().isPresent()
318 && getNamespaceOptionally().get().equals(child.get().getNamespaceOptionally().get())) {
321 return Optional.absent();
324 public XmlElement getOnlyChildElement(final String childName, String namespace) throws NetconfDocumentedException {
325 List<XmlElement> children = getChildElementsWithinNamespace(namespace);
326 children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
328 public boolean apply(XmlElement xmlElement) {
329 return xmlElement.getName().equals(childName);
332 if (children.size() != 1){
333 throw new NetconfDocumentedException(String.format("One element %s:%s expected in %s but was %s", namespace,
334 childName, toString(), children.size()),
335 NetconfDocumentedException.ErrorType.application,
336 NetconfDocumentedException.ErrorTag.invalid_value,
337 NetconfDocumentedException.ErrorSeverity.error);
340 return children.get(0);
343 public XmlElement getOnlyChildElement() throws NetconfDocumentedException {
344 List<XmlElement> children = getChildElements();
345 if (children.size() != 1){
346 throw new NetconfDocumentedException(String.format( "One element expected in %s but was %s", toString(),
348 NetconfDocumentedException.ErrorType.application,
349 NetconfDocumentedException.ErrorTag.invalid_value,
350 NetconfDocumentedException.ErrorSeverity.error);
352 return children.get(0);
355 public Optional<XmlElement> getOnlyChildElementOptionally() {
356 List<XmlElement> children = getChildElements();
357 if (children.size() != 1) {
358 return Optional.absent();
360 return Optional.of(children.get(0));
363 public String getTextContent() throws NetconfDocumentedException {
364 NodeList childNodes = element.getChildNodes();
365 if (childNodes.getLength() == 0) {
366 return DEFAULT_NAMESPACE_PREFIX;
368 for(int i = 0; i < childNodes.getLength(); i++) {
369 Node textChild = childNodes.item(i);
370 if (textChild instanceof Text) {
371 String content = textChild.getTextContent();
372 return content.trim();
375 throw new NetconfDocumentedException(getName() + " should contain text.",
376 NetconfDocumentedException.ErrorType.application,
377 NetconfDocumentedException.ErrorTag.invalid_value,
378 NetconfDocumentedException.ErrorSeverity.error
382 public Optional<String> getOnlyTextContentOptionally() {
383 // only return text content if this node has exactly one Text child node
384 if (element.getChildNodes().getLength() == 1) {
385 Node item = element.getChildNodes().item(0);
386 if (item instanceof Text) {
387 return Optional.of(((Text) item).getWholeText());
390 return Optional.absent();
393 public String getNamespaceAttribute() throws MissingNameSpaceException {
394 String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
395 if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){
396 throw new MissingNameSpaceException(String.format("Element %s must specify namespace",
398 NetconfDocumentedException.ErrorType.application,
399 NetconfDocumentedException.ErrorTag.operation_failed,
400 NetconfDocumentedException.ErrorSeverity.error);
405 public Optional<String> getNamespaceAttributeOptionally(){
406 String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
407 if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){
408 return Optional.absent();
410 return Optional.of(attribute);
413 public Optional<String> getNamespaceOptionally() {
414 String namespaceURI = element.getNamespaceURI();
415 if (Strings.isNullOrEmpty(namespaceURI)) {
416 return Optional.absent();
418 return Optional.of(namespaceURI);
422 public String getNamespace() throws MissingNameSpaceException {
423 Optional<String> namespaceURI = getNamespaceOptionally();
424 if (!namespaceURI.isPresent()){
425 throw new MissingNameSpaceException(String.format("No namespace defined for %s", this),
426 NetconfDocumentedException.ErrorType.application,
427 NetconfDocumentedException.ErrorTag.operation_failed,
428 NetconfDocumentedException.ErrorSeverity.error);
430 return namespaceURI.get();
434 public String toString() {
435 final StringBuilder sb = new StringBuilder("XmlElement{");
436 sb.append("name='").append(getName()).append('\'');
437 if (element.getNamespaceURI() != null) {
439 sb.append(", namespace='").append(getNamespace()).append('\'');
440 } catch (MissingNameSpaceException e) {
441 LOG.trace("Missing namespace for element.");
445 return sb.toString();
449 * Search for element's attributes defining namespaces. Look for the one
450 * namespace that matches prefix of element's text content. E.g.
454 * xmlns:th-java="urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl">th-java:threadfactory-naming</type>
457 * returns {"th-java","urn:.."}. If no prefix is matched, then default
458 * namespace is returned with empty string as key. If no default namespace
459 * is found value will be null.
461 public Map.Entry<String/* prefix */, String/* namespace */> findNamespaceOfTextContent() throws NetconfDocumentedException {
462 Map<String, String> namespaces = extractNamespaces();
463 String textContent = getTextContent();
464 int indexOfColon = textContent.indexOf(':');
466 if (indexOfColon > -1) {
467 prefix = textContent.substring(0, indexOfColon);
469 prefix = DEFAULT_NAMESPACE_PREFIX;
471 if (!namespaces.containsKey(prefix)) {
472 throw new IllegalArgumentException("Cannot find namespace for " + XmlUtil.toString(element) + ". Prefix from content is "
473 + prefix + ". Found namespaces " + namespaces);
475 return Maps.immutableEntry(prefix, namespaces.get(prefix));
478 public List<XmlElement> getChildElementsWithSameNamespace(final String childName) throws MissingNameSpaceException {
479 List<XmlElement> children = getChildElementsWithinNamespace(getNamespace());
480 return Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
482 public boolean apply(XmlElement xmlElement) {
483 return xmlElement.getName().equals(childName);
488 public void checkUnrecognisedElements(List<XmlElement> recognisedElements,
489 XmlElement... additionalRecognisedElements) throws NetconfDocumentedException {
490 List<XmlElement> childElements = getChildElements();
491 childElements.removeAll(recognisedElements);
492 for (XmlElement additionalRecognisedElement : additionalRecognisedElements) {
493 childElements.remove(additionalRecognisedElement);
495 if (!childElements.isEmpty()){
496 throw new NetconfDocumentedException(String.format("Unrecognised elements %s in %s", childElements, this),
497 NetconfDocumentedException.ErrorType.application,
498 NetconfDocumentedException.ErrorTag.invalid_value,
499 NetconfDocumentedException.ErrorSeverity.error);
503 public void checkUnrecognisedElements(XmlElement... additionalRecognisedElements) throws NetconfDocumentedException {
504 checkUnrecognisedElements(Collections.<XmlElement>emptyList(), additionalRecognisedElements);
508 public boolean equals(Object o) {
512 if (o == null || getClass() != o.getClass()) {
516 XmlElement that = (XmlElement) o;
518 return element.isEqualNode(that.element);
523 public int hashCode() {
524 return element.hashCode();
527 public boolean hasNamespace() {
528 if (!getNamespaceAttributeOptionally().isPresent()) {
529 if (!getNamespaceOptionally().isPresent()) {
536 private interface ElementFilteringStrategy {
537 boolean accept(Element e);