X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=netconf%2Fnetconf-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fapi%2Fxml%2FXmlElement.java;fp=netconf%2Fnetconf-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fapi%2Fxml%2FXmlElement.java;h=ad90fb3cb2d9ff20a21bbd614de82949b1863da5;hb=1aaec97aa24f5dfe62fd3e6523e651db883ab51f;hp=0000000000000000000000000000000000000000;hpb=34187b5b4efeb1232d80e285857449dbc1c9ada2;p=netconf.git diff --git a/netconf/netconf-api/src/main/java/org/opendaylight/netconf/api/xml/XmlElement.java b/netconf/netconf-api/src/main/java/org/opendaylight/netconf/api/xml/XmlElement.java new file mode 100644 index 0000000000..ad90fb3cb2 --- /dev/null +++ b/netconf/netconf-api/src/main/java/org/opendaylight/netconf/api/xml/XmlElement.java @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.netconf.api.xml; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.opendaylight.netconf.api.DocumentedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.SAXException; + +public final class XmlElement { + + public static final String DEFAULT_NAMESPACE_PREFIX = ""; + + private final Element element; + private static final Logger LOG = LoggerFactory.getLogger(XmlElement.class); + + private XmlElement(final Element element) { + this.element = element; + } + + public static XmlElement fromDomElement(final Element element) { + return new XmlElement(element); + } + + public static XmlElement fromDomDocument(final Document xml) { + return new XmlElement(xml.getDocumentElement()); + } + + public static XmlElement fromString(final String str) throws DocumentedException { + try { + return new XmlElement(XmlUtil.readXmlToElement(str)); + } catch (IOException | SAXException e) { + throw DocumentedException.wrap(e); + } + } + + public static XmlElement fromDomElementWithExpected(final Element element, final String expectedName) + throws DocumentedException { + XmlElement xmlElement = XmlElement.fromDomElement(element); + xmlElement.checkName(expectedName); + return xmlElement; + } + + public static XmlElement fromDomElementWithExpected(final Element element, final String expectedName, + final String expectedNamespace) throws DocumentedException { + XmlElement xmlElement = XmlElement.fromDomElementWithExpected(element, expectedName); + xmlElement.checkNamespace(expectedNamespace); + return xmlElement; + } + + private Map extractNamespaces() throws DocumentedException { + Map namespaces = new HashMap<>(); + NamedNodeMap attributes = element.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Node attribute = attributes.item(i); + String attribKey = attribute.getNodeName(); + if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) { + String prefix; + if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) { + prefix = DEFAULT_NAMESPACE_PREFIX; + } else { + if (!attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")) { + throw new DocumentedException("Attribute doesn't start with :", + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.INVALID_VALUE, + DocumentedException.ErrorSeverity.ERROR); + } + prefix = attribKey.substring(XmlUtil.XMLNS_ATTRIBUTE_KEY.length() + 1); + } + namespaces.put(prefix, attribute.getNodeValue()); + } + } + + // namespace does not have to be defined on this element but inherited + if (!namespaces.containsKey(DEFAULT_NAMESPACE_PREFIX)) { + Optional namespaceOptionally = getNamespaceOptionally(); + if (namespaceOptionally.isPresent()) { + namespaces.put(DEFAULT_NAMESPACE_PREFIX, namespaceOptionally.get()); + } + } + + return namespaces; + } + + public void checkName(final String expectedName) throws UnexpectedElementException { + if (!getName().equals(expectedName)) { + throw new UnexpectedElementException(String.format("Expected %s xml element but was %s", expectedName, + getName()), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.OPERATION_FAILED, + DocumentedException.ErrorSeverity.ERROR); + } + } + + public void checkNamespaceAttribute(final String expectedNamespace) + throws UnexpectedNamespaceException, MissingNameSpaceException { + if (!getNamespaceAttribute().equals(expectedNamespace)) { + throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s", + getNamespaceAttribute(), + expectedNamespace), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.OPERATION_FAILED, + DocumentedException.ErrorSeverity.ERROR); + } + } + + public void checkNamespace(final String expectedNamespace) + throws UnexpectedNamespaceException, MissingNameSpaceException { + if (!getNamespace().equals(expectedNamespace)) { + throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s", + getNamespace(), + expectedNamespace), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.OPERATION_FAILED, + DocumentedException.ErrorSeverity.ERROR); + } + } + + public String getName() { + final String localName = element.getLocalName(); + if (!Strings.isNullOrEmpty(localName)) { + return localName; + } + return element.getTagName(); + } + + public String getAttribute(final String attributeName) { + return element.getAttribute(attributeName); + } + + public String getAttribute(final String attributeName, final String namespace) { + return element.getAttributeNS(namespace, attributeName); + } + + public NodeList getElementsByTagName(final String name) { + return element.getElementsByTagName(name); + } + + public void appendChild(final Element toAppend) { + this.element.appendChild(toAppend); + } + + public Element getDomElement() { + return element; + } + + public Map getAttributes() { + + Map mappedAttributes = Maps.newHashMap(); + + NamedNodeMap attributes = element.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Attr attr = (Attr) attributes.item(i); + mappedAttributes.put(attr.getNodeName(), attr); + } + + return mappedAttributes; + } + + /** + * Non recursive. + */ + private List getChildElementsInternal(final ElementFilteringStrategy strat) { + NodeList childNodes = element.getChildNodes(); + final List result = new ArrayList<>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + if (!(item instanceof Element)) { + continue; + } + if (strat.accept((Element) item)) { + result.add(new XmlElement((Element) item)); + } + } + + return result; + } + + public List getChildElements() { + return getChildElementsInternal(e -> true); + } + + /** + * Returns the child elements for the given tag. + * + * @param tagName tag name without prefix + * @return List of child elements + */ + public List getChildElements(final String tagName) { + return getChildElementsInternal(e -> { + // localName returns pure localName without prefix + return e.getLocalName().equals(tagName); + }); + } + + public List getChildElementsWithinNamespace(final String childName, final String namespace) { + return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace), + xmlElement -> xmlElement.getName().equals(childName))); + } + + public List getChildElementsWithinNamespace(final String namespace) { + return getChildElementsInternal(e -> { + try { + return XmlElement.fromDomElement(e).getNamespace().equals(namespace); + } catch (final MissingNameSpaceException e1) { + return false; + } + }); + } + + public Optional getOnlyChildElementOptionally(final String childName) { + List nameElements = getChildElements(childName); + if (nameElements.size() != 1) { + return Optional.absent(); + } + return Optional.of(nameElements.get(0)); + } + + public Optional getOnlyChildElementOptionally(final String childName, final String namespace) { + List children = getChildElementsWithinNamespace(namespace); + children = Lists.newArrayList(Collections2.filter(children, + xmlElement -> xmlElement.getName().equals(childName))); + if (children.size() != 1) { + return Optional.absent(); + } + return Optional.of(children.get(0)); + } + + public Optional getOnlyChildElementOptionally() { + List children = getChildElements(); + if (children.size() != 1) { + return Optional.absent(); + } + return Optional.of(children.get(0)); + } + + public XmlElement getOnlyChildElementWithSameNamespace(final String childName) throws DocumentedException { + return getOnlyChildElement(childName, getNamespace()); + } + + public XmlElement getOnlyChildElementWithSameNamespace() throws DocumentedException { + XmlElement childElement = getOnlyChildElement(); + childElement.checkNamespace(getNamespace()); + return childElement; + } + + public Optional getOnlyChildElementWithSameNamespaceOptionally(final String childName) { + Optional namespace = getNamespaceOptionally(); + if (namespace.isPresent()) { + List children = getChildElementsWithinNamespace(namespace.get()); + children = Lists.newArrayList(Collections2.filter(children, + xmlElement -> xmlElement.getName().equals(childName))); + if (children.size() != 1) { + return Optional.absent(); + } + return Optional.of(children.get(0)); + } + return Optional.absent(); + } + + public Optional getOnlyChildElementWithSameNamespaceOptionally() { + Optional child = getOnlyChildElementOptionally(); + if (child.isPresent() + && child.get().getNamespaceOptionally().isPresent() + && getNamespaceOptionally().isPresent() + && getNamespaceOptionally().get().equals(child.get().getNamespaceOptionally().get())) { + return child; + } + return Optional.absent(); + } + + public XmlElement getOnlyChildElement(final String childName, final String namespace) throws DocumentedException { + List children = getChildElementsWithinNamespace(namespace); + children = Lists.newArrayList(Collections2.filter(children, + xmlElement -> xmlElement.getName().equals(childName))); + if (children.size() != 1) { + throw new DocumentedException(String.format("One element %s:%s expected in %s but was %s", namespace, + childName, toString(), children.size()), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.INVALID_VALUE, + DocumentedException.ErrorSeverity.ERROR); + } + + return children.get(0); + } + + public XmlElement getOnlyChildElement(final String childName) throws DocumentedException { + List nameElements = getChildElements(childName); + if (nameElements.size() != 1) { + throw new DocumentedException("One element " + childName + " expected in " + toString(), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.INVALID_VALUE, + DocumentedException.ErrorSeverity.ERROR); + } + return nameElements.get(0); + } + + public XmlElement getOnlyChildElement() throws DocumentedException { + List children = getChildElements(); + if (children.size() != 1) { + throw new DocumentedException(String.format("One element expected in %s but was %s", toString(), + children.size()), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.INVALID_VALUE, + DocumentedException.ErrorSeverity.ERROR); + } + return children.get(0); + } + + public String getTextContent() throws DocumentedException { + NodeList childNodes = element.getChildNodes(); + if (childNodes.getLength() == 0) { + return DEFAULT_NAMESPACE_PREFIX; + } + for (int i = 0; i < childNodes.getLength(); i++) { + Node textChild = childNodes.item(i); + if (textChild instanceof Text) { + String content = textChild.getTextContent(); + return content.trim(); + } + } + throw new DocumentedException(getName() + " should contain text.", + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.INVALID_VALUE, + DocumentedException.ErrorSeverity.ERROR + ); + } + + public Optional getOnlyTextContentOptionally() { + // only return text content if this node has exactly one Text child node + if (element.getChildNodes().getLength() == 1) { + Node item = element.getChildNodes().item(0); + if (item instanceof Text) { + return Optional.of(((Text) item).getWholeText()); + } + } + return Optional.absent(); + } + + public String getNamespaceAttribute() throws MissingNameSpaceException { + String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY); + if (attribute.isEmpty() || attribute.equals(DEFAULT_NAMESPACE_PREFIX)) { + throw new MissingNameSpaceException(String.format("Element %s must specify namespace", + toString()), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.OPERATION_FAILED, + DocumentedException.ErrorSeverity.ERROR); + } + return attribute; + } + + public Optional getNamespaceAttributeOptionally() { + String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY); + if (attribute.isEmpty() || attribute.equals(DEFAULT_NAMESPACE_PREFIX)) { + return Optional.absent(); + } + return Optional.of(attribute); + } + + public Optional getNamespaceOptionally() { + String namespaceURI = element.getNamespaceURI(); + if (Strings.isNullOrEmpty(namespaceURI)) { + return Optional.absent(); + } else { + return Optional.of(namespaceURI); + } + } + + public String getNamespace() throws MissingNameSpaceException { + Optional namespaceURI = getNamespaceOptionally(); + if (!namespaceURI.isPresent()) { + throw new MissingNameSpaceException(String.format("No namespace defined for %s", this), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.OPERATION_FAILED, + DocumentedException.ErrorSeverity.ERROR); + } + return namespaceURI.get(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("XmlElement{"); + sb.append("name='").append(getName()).append('\''); + if (element.getNamespaceURI() != null) { + try { + sb.append(", namespace='").append(getNamespace()).append('\''); + } catch (final MissingNameSpaceException e) { + LOG.trace("Missing namespace for element."); + } + } + sb.append('}'); + return sb.toString(); + } + + /** + * Search for element's attributes defining namespaces. Look for the one + * namespace that matches prefix of element's text content. E.g. + * + *
+     * <type
+     * xmlns:th-java="urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl">
+     *     th-java:threadfactory-naming</type>
+     * 
+ * + *

+ * returns {"th-java","urn:.."}. If no prefix is matched, then default + * namespace is returned with empty string as key. If no default namespace + * is found value will be null. + */ + public Map.Entry findNamespaceOfTextContent() + throws DocumentedException { + Map namespaces = extractNamespaces(); + String textContent = getTextContent(); + int indexOfColon = textContent.indexOf(':'); + String prefix; + if (indexOfColon > -1) { + prefix = textContent.substring(0, indexOfColon); + } else { + prefix = DEFAULT_NAMESPACE_PREFIX; + } + if (!namespaces.containsKey(prefix)) { + throw new IllegalArgumentException("Cannot find namespace for " + XmlUtil.toString(element) + + ". Prefix from content is " + prefix + ". Found namespaces " + namespaces); + } + return Maps.immutableEntry(prefix, namespaces.get(prefix)); + } + + public List getChildElementsWithSameNamespace(final String childName) throws MissingNameSpaceException { + List children = getChildElementsWithinNamespace(getNamespace()); + return Lists.newArrayList(Collections2.filter(children, xmlElement -> xmlElement.getName().equals(childName))); + } + + public void checkUnrecognisedElements(final List recognisedElements, + final XmlElement... additionalRecognisedElements) throws DocumentedException { + List childElements = getChildElements(); + childElements.removeAll(recognisedElements); + for (XmlElement additionalRecognisedElement : additionalRecognisedElements) { + childElements.remove(additionalRecognisedElement); + } + if (!childElements.isEmpty()) { + throw new DocumentedException(String.format("Unrecognised elements %s in %s", childElements, this), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.INVALID_VALUE, + DocumentedException.ErrorSeverity.ERROR); + } + } + + public void checkUnrecognisedElements(final XmlElement... additionalRecognisedElements) throws DocumentedException { + checkUnrecognisedElements(Collections.emptyList(), additionalRecognisedElements); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + XmlElement that = (XmlElement) obj; + + return element.isEqualNode(that.element); + + } + + @Override + public int hashCode() { + return element.hashCode(); + } + + public boolean hasNamespace() { + return getNamespaceAttributeOptionally().isPresent() || getNamespaceOptionally().isPresent(); + } + + private interface ElementFilteringStrategy { + boolean accept(Element element); + } +}