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 private final Element element;
41 private static final Logger logger = LoggerFactory.getLogger(XmlElement.class);
43 private XmlElement(Element element) {
44 this.element = element;
47 public static XmlElement fromDomElement(Element e) {
48 return new XmlElement(e);
51 public static XmlElement fromDomDocument(Document xml) {
52 return new XmlElement(xml.getDocumentElement());
55 public static XmlElement fromString(String s) throws NetconfDocumentedException {
57 return new XmlElement(XmlUtil.readXmlToElement(s));
58 } catch (IOException | SAXException e) {
59 throw NetconfDocumentedException.wrap(e);
63 public static XmlElement fromDomElementWithExpected(Element element, String expectedName) throws NetconfDocumentedException {
64 XmlElement xmlElement = XmlElement.fromDomElement(element);
65 xmlElement.checkName(expectedName);
69 public static XmlElement fromDomElementWithExpected(Element element, String expectedName, String expectedNamespace) throws NetconfDocumentedException {
70 XmlElement xmlElement = XmlElement.fromDomElementWithExpected(element, expectedName);
71 xmlElement.checkNamespace(expectedNamespace);
75 private static Map<String, String> extractNamespaces(Element typeElement) throws NetconfDocumentedException {
76 Map<String, String> namespaces = new HashMap<>();
77 NamedNodeMap attributes = typeElement.getAttributes();
78 for (int i = 0; i < attributes.getLength(); i++) {
79 Node attribute = attributes.item(i);
80 String attribKey = attribute.getNodeName();
81 if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
83 if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) {
86 if (!attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")){
87 throw new NetconfDocumentedException("Attribute doesn't start with :",
88 NetconfDocumentedException.ErrorType.application,
89 NetconfDocumentedException.ErrorTag.invalid_value,
90 NetconfDocumentedException.ErrorSeverity.error);
92 prefix = attribKey.substring(XmlUtil.XMLNS_ATTRIBUTE_KEY.length() + 1);
94 namespaces.put(prefix, attribute.getNodeValue());
100 public void checkName(String expectedName) throws UnexpectedElementException {
101 if (!getName().equals(expectedName)){
102 throw new UnexpectedElementException(String.format("Expected %s xml element but was %s", expectedName,
104 NetconfDocumentedException.ErrorType.application,
105 NetconfDocumentedException.ErrorTag.operation_failed,
106 NetconfDocumentedException.ErrorSeverity.error);
110 public void checkNamespaceAttribute(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
111 if (!getNamespaceAttribute().equals(expectedNamespace))
113 throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
114 getNamespaceAttribute(),
116 NetconfDocumentedException.ErrorType.application,
117 NetconfDocumentedException.ErrorTag.operation_failed,
118 NetconfDocumentedException.ErrorSeverity.error);
122 public void checkNamespace(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
123 if (!getNamespace().equals(expectedNamespace))
125 throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
128 NetconfDocumentedException.ErrorType.application,
129 NetconfDocumentedException.ErrorTag.operation_failed,
130 NetconfDocumentedException.ErrorSeverity.error);
134 public String getName() {
135 if (element.getLocalName()!=null && !element.getLocalName().equals("")){
136 return element.getLocalName();
138 return element.getTagName();
141 public String getAttribute(String attributeName) {
142 return element.getAttribute(attributeName);
145 public String getAttribute(String attributeName, String namespace) {
146 return element.getAttributeNS(namespace, attributeName);
149 public NodeList getElementsByTagName(String name) {
150 return element.getElementsByTagName(name);
153 public void appendChild(Element element) {
154 this.element.appendChild(element);
157 public Element getDomElement() {
161 public Map<String, Attr> getAttributes() {
163 Map<String, Attr> mappedAttributes = Maps.newHashMap();
165 NamedNodeMap attributes = element.getAttributes();
166 for (int i = 0; i < attributes.getLength(); i++) {
167 Attr attr = (Attr) attributes.item(i);
168 mappedAttributes.put(attr.getNodeName(), attr);
171 return mappedAttributes;
177 private List<XmlElement> getChildElementsInternal(ElementFilteringStrategy strat) {
178 NodeList childNodes = element.getChildNodes();
179 final List<XmlElement> result = new ArrayList<>();
180 for (int i = 0; i < childNodes.getLength(); i++) {
181 Node item = childNodes.item(i);
182 if (!(item instanceof Element)) {
185 if (strat.accept((Element) item)) {
186 result.add(new XmlElement((Element) item));
193 public List<XmlElement> getChildElements() {
194 return getChildElementsInternal(new ElementFilteringStrategy() {
196 public boolean accept(Element e) {
202 public List<XmlElement> getChildElementsWithinNamespace(final String childName, String namespace) {
203 return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace),
204 new Predicate<XmlElement>() {
206 public boolean apply(XmlElement xmlElement) {
207 return xmlElement.getName().equals(childName);
212 public List<XmlElement> getChildElementsWithinNamespace(final String namespace) {
213 return getChildElementsInternal(new ElementFilteringStrategy() {
215 public boolean accept(Element e) {
217 return XmlElement.fromDomElement(e).getNamespace().equals(namespace);
218 } catch (MissingNameSpaceException e1) {
228 * @param tagName tag name without prefix
231 public List<XmlElement> getChildElements(final String tagName) {
232 return getChildElementsInternal(new ElementFilteringStrategy() {
234 public boolean accept(Element e) {
235 // localName returns pure localName without prefix
236 return e.getLocalName().equals(tagName);
241 public XmlElement getOnlyChildElement(String childName) throws NetconfDocumentedException {
242 List<XmlElement> nameElements = getChildElements(childName);
243 if (nameElements.size() != 1){
244 throw new NetconfDocumentedException("One element " + childName + " expected in " + toString(),
245 NetconfDocumentedException.ErrorType.application,
246 NetconfDocumentedException.ErrorTag.invalid_value,
247 NetconfDocumentedException.ErrorSeverity.error);
249 return nameElements.get(0);
252 public Optional<XmlElement> getOnlyChildElementOptionally(String childName) {
254 return Optional.of(getOnlyChildElement(childName));
255 } catch (Exception e) {
256 return Optional.absent();
260 public Optional<XmlElement> getOnlyChildElementOptionally(String childName, String namespace) {
262 return Optional.of(getOnlyChildElement(childName, namespace));
263 } catch (Exception e) {
264 return Optional.absent();
268 public XmlElement getOnlyChildElementWithSameNamespace(String childName) throws NetconfDocumentedException {
269 return getOnlyChildElement(childName, getNamespace());
272 public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(String childName) {
274 return Optional.of(getOnlyChildElement(childName, getNamespace()));
275 } catch (Exception e) {
276 return Optional.absent();
280 public XmlElement getOnlyChildElementWithSameNamespace() throws NetconfDocumentedException {
281 XmlElement childElement = getOnlyChildElement();
282 childElement.checkNamespace(getNamespace());
286 public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally() {
288 XmlElement childElement = getOnlyChildElement();
289 childElement.checkNamespace(getNamespace());
290 return Optional.of(childElement);
291 } catch (Exception e) {
292 return Optional.absent();
296 public XmlElement getOnlyChildElement(final String childName, String namespace) throws NetconfDocumentedException {
297 List<XmlElement> children = getChildElementsWithinNamespace(namespace);
298 children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
300 public boolean apply(XmlElement xmlElement) {
301 return xmlElement.getName().equals(childName);
304 if (children.size() != 1){
305 throw new NetconfDocumentedException(String.format("One element %s:%s expected in %s but was %s", namespace,
306 childName, toString(), children.size()),
307 NetconfDocumentedException.ErrorType.application,
308 NetconfDocumentedException.ErrorTag.invalid_value,
309 NetconfDocumentedException.ErrorSeverity.error);
312 return children.get(0);
315 public XmlElement getOnlyChildElement() throws NetconfDocumentedException {
316 List<XmlElement> children = getChildElements();
317 if (children.size() != 1){
318 throw new NetconfDocumentedException(String.format( "One element expected in %s but was %s", toString(),
320 NetconfDocumentedException.ErrorType.application,
321 NetconfDocumentedException.ErrorTag.invalid_value,
322 NetconfDocumentedException.ErrorSeverity.error);
324 return children.get(0);
327 public String getTextContent() throws NetconfDocumentedException {
328 NodeList childNodes = element.getChildNodes();
329 if (childNodes.getLength() == 0) {
332 for(int i = 0; i < childNodes.getLength(); i++) {
333 Node textChild = childNodes.item(i);
334 if (textChild instanceof Text) {
335 String content = textChild.getTextContent();
336 return content.trim();
339 throw new NetconfDocumentedException(getName() + " should contain text.",
340 NetconfDocumentedException.ErrorType.application,
341 NetconfDocumentedException.ErrorTag.invalid_value,
342 NetconfDocumentedException.ErrorSeverity.error
346 public Optional<String> getOnlyTextContentOptionally() {
347 // only return text content if this node has exactly one Text child node
348 if (element.getChildNodes().getLength() == 1) {
349 Node item = element.getChildNodes().item(0);
350 if (item instanceof Text) {
351 return Optional.of(((Text) item).getWholeText());
354 return Optional.absent();
357 public String getNamespaceAttribute() throws MissingNameSpaceException {
358 String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
359 if (attribute == null || attribute.equals("")){
360 throw new MissingNameSpaceException(String.format("Element %s must specify namespace",
362 NetconfDocumentedException.ErrorType.application,
363 NetconfDocumentedException.ErrorTag.operation_failed,
364 NetconfDocumentedException.ErrorSeverity.error);
369 public Optional<String> getNamespaceOptionally() {
370 String namespaceURI = element.getNamespaceURI();
371 if (Strings.isNullOrEmpty(namespaceURI)) {
372 return Optional.absent();
374 return Optional.of(namespaceURI);
378 public String getNamespace() throws MissingNameSpaceException {
379 Optional<String> namespaceURI = getNamespaceOptionally();
380 if (namespaceURI.isPresent() == false){
381 throw new MissingNameSpaceException(String.format("No namespace defined for %s", this),
382 NetconfDocumentedException.ErrorType.application,
383 NetconfDocumentedException.ErrorTag.operation_failed,
384 NetconfDocumentedException.ErrorSeverity.error);
386 return namespaceURI.get();
390 public String toString() {
391 final StringBuilder sb = new StringBuilder("XmlElement{");
392 sb.append("name='").append(getName()).append('\'');
393 if (element.getNamespaceURI() != null) {
395 sb.append(", namespace='").append(getNamespace()).append('\'');
396 } catch (MissingNameSpaceException e) {
397 logger.trace("Missing namespace for element.");
401 return sb.toString();
405 * Search for element's attributes defining namespaces. Look for the one
406 * namespace that matches prefix of element's text content. E.g.
410 * xmlns:th-java="urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl">th-java:threadfactory-naming</type>
413 * returns {"th-java","urn:.."}. If no prefix is matched, then default
414 * namespace is returned with empty string as key. If no default namespace
415 * is found value will be null.
417 public Map.Entry<String/* prefix */, String/* namespace */> findNamespaceOfTextContent() throws NetconfDocumentedException {
418 Map<String, String> namespaces = extractNamespaces(element);
419 String textContent = getTextContent();
420 int indexOfColon = textContent.indexOf(':');
422 if (indexOfColon > -1) {
423 prefix = textContent.substring(0, indexOfColon);
427 if (!namespaces.containsKey(prefix)) {
428 throw new IllegalArgumentException("Cannot find namespace for " + XmlUtil.toString(element) + ". Prefix from content is "
429 + prefix + ". Found namespaces " + namespaces);
431 return Maps.immutableEntry(prefix, namespaces.get(prefix));
434 public List<XmlElement> getChildElementsWithSameNamespace(final String childName) throws MissingNameSpaceException {
435 List<XmlElement> children = getChildElementsWithinNamespace(getNamespace());
436 return Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
438 public boolean apply(XmlElement xmlElement) {
439 return xmlElement.getName().equals(childName);
444 public void checkUnrecognisedElements(List<XmlElement> recognisedElements,
445 XmlElement... additionalRecognisedElements) throws NetconfDocumentedException {
446 List<XmlElement> childElements = getChildElements();
447 childElements.removeAll(recognisedElements);
448 for (XmlElement additionalRecognisedElement : additionalRecognisedElements) {
449 childElements.remove(additionalRecognisedElement);
451 if (!childElements.isEmpty()){
452 throw new NetconfDocumentedException(String.format("Unrecognised elements %s in %s", childElements, this),
453 NetconfDocumentedException.ErrorType.application,
454 NetconfDocumentedException.ErrorTag.invalid_value,
455 NetconfDocumentedException.ErrorSeverity.error);
459 public void checkUnrecognisedElements(XmlElement... additionalRecognisedElements) throws NetconfDocumentedException {
460 checkUnrecognisedElements(Collections.<XmlElement>emptyList(), additionalRecognisedElements);
464 public boolean equals(Object o) {
468 if (o == null || getClass() != o.getClass()) {
472 XmlElement that = (XmlElement) o;
474 if (!element.isEqualNode(that.element)) {
482 public int hashCode() {
483 return element.hashCode();
486 public boolean hasNamespace() {
488 getNamespaceAttribute();
489 } catch (MissingNameSpaceException e) {
492 } catch (MissingNameSpaceException e1) {
500 private interface ElementFilteringStrategy {
501 boolean accept(Element e);