Stop swallowing exceptions in XmlElement optional methods.
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / xml / XmlElement.java
index 17ea3740fca77cd6124daad71050de6383970dc5..4529f81e575e9245013319ab75fd43a9974c5a16 100644 (file)
@@ -10,9 +10,16 @@ package org.opendaylight.controller.netconf.util.xml;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
+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.controller.netconf.api.NetconfDocumentedException;
 import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException;
 import org.opendaylight.controller.netconf.util.exception.UnexpectedElementException;
@@ -28,18 +35,12 @@ import org.w3c.dom.NodeList;
 import org.w3c.dom.Text;
 import org.xml.sax.SAXException;
 
-import javax.annotation.Nullable;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 public final class XmlElement {
 
+    public static final String DEFAULT_NAMESPACE_PREFIX = "";
+
     private final Element element;
-    private static final Logger logger = LoggerFactory.getLogger(XmlElement.class);
+    private static final Logger LOG = LoggerFactory.getLogger(XmlElement.class);
 
     private XmlElement(Element element) {
         this.element = element;
@@ -73,16 +74,16 @@ public final class XmlElement {
         return xmlElement;
     }
 
-    private static Map<String, String> extractNamespaces(Element typeElement) throws NetconfDocumentedException {
+    private Map<String, String> extractNamespaces() throws NetconfDocumentedException {
         Map<String, String> namespaces = new HashMap<>();
-        NamedNodeMap attributes = typeElement.getAttributes();
+        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 = "";
+                    prefix = DEFAULT_NAMESPACE_PREFIX;
                 } else {
                     if (!attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")){
                         throw new NetconfDocumentedException("Attribute doesn't start with :",
@@ -95,6 +96,15 @@ public final class XmlElement {
                 namespaces.put(prefix, attribute.getNodeValue());
             }
         }
+
+        // namespace does not have to be defined on this element but inherited
+        if(!namespaces.containsKey(DEFAULT_NAMESPACE_PREFIX)) {
+            Optional<String> namespaceOptionally = getNamespaceOptionally();
+            if(namespaceOptionally.isPresent()) {
+                namespaces.put(DEFAULT_NAMESPACE_PREFIX, namespaceOptionally.get());
+            }
+        }
+
         return namespaces;
     }
 
@@ -111,7 +121,7 @@ public final class XmlElement {
     public void checkNamespaceAttribute(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
         if (!getNamespaceAttribute().equals(expectedNamespace))
         {
-            throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s for element %s, should be %s",
+            throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
                     getNamespaceAttribute(),
                     expectedNamespace),
                     NetconfDocumentedException.ErrorType.application,
@@ -122,8 +132,8 @@ public final class XmlElement {
 
     public void checkNamespace(String expectedNamespace) throws UnexpectedNamespaceException, MissingNameSpaceException {
         if (!getNamespace().equals(expectedNamespace))
-       {
-            throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s for element %s, should be %s",
+        {
+            throw new UnexpectedNamespaceException(String.format("Unexpected namespace %s should be %s",
                     getNamespace(),
                     expectedNamespace),
                     NetconfDocumentedException.ErrorType.application,
@@ -133,7 +143,7 @@ public final class XmlElement {
     }
 
     public String getName() {
-        if (element.getLocalName()!=null && !element.getLocalName().equals("")){
+        if (element.getLocalName()!=null && !element.getLocalName().equals(DEFAULT_NAMESPACE_PREFIX)){
             return element.getLocalName();
         }
         return element.getTagName();
@@ -180,7 +190,7 @@ public final class XmlElement {
         final List<XmlElement> result = new ArrayList<>();
         for (int i = 0; i < childNodes.getLength(); i++) {
             Node item = childNodes.item(i);
-            if (item instanceof Element == false) {
+            if (!(item instanceof Element)) {
                 continue;
             }
             if (strat.accept((Element) item)) {
@@ -204,7 +214,7 @@ public final class XmlElement {
         return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace),
                 new Predicate<XmlElement>() {
                     @Override
-                    public boolean apply(@Nullable XmlElement xmlElement) {
+                    public boolean apply(XmlElement xmlElement) {
                         return xmlElement.getName().equals(childName);
                     }
                 }));
@@ -224,11 +234,17 @@ public final class XmlElement {
         });
     }
 
+    /**
+     *
+     * @param tagName tag name without prefix
+     * @return List of child elements
+     */
     public List<XmlElement> getChildElements(final String tagName) {
         return getChildElementsInternal(new ElementFilteringStrategy() {
             @Override
             public boolean accept(Element e) {
-                return e.getTagName().equals(tagName);
+                // localName returns pure localName without prefix
+                return e.getLocalName().equals(tagName);
             }
         });
     }
@@ -245,31 +261,47 @@ public final class XmlElement {
     }
 
     public Optional<XmlElement> getOnlyChildElementOptionally(String childName) {
-        try {
-            return Optional.of(getOnlyChildElement(childName));
-        } catch (Exception e) {
+        List<XmlElement> nameElements = getChildElements(childName);
+        if (nameElements.size() != 1) {
             return Optional.absent();
         }
+        return Optional.of(nameElements.get(0));
     }
 
-    public Optional<XmlElement> getOnlyChildElementOptionally(String childName, String namespace) {
-        try {
-            return Optional.of(getOnlyChildElement(childName, namespace));
-        } catch (Exception e) {
+    public Optional<XmlElement> getOnlyChildElementOptionally(final String childName, final String namespace) {
+        List<XmlElement> children = getChildElementsWithinNamespace(namespace);
+        children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
+            @Override
+            public boolean apply(XmlElement xmlElement) {
+                return xmlElement.getName().equals(childName);
+            }
+        }));
+        if (children.size() != 1){
             return Optional.absent();
         }
+        return Optional.of(children.get(0));
     }
 
     public XmlElement getOnlyChildElementWithSameNamespace(String childName) throws  NetconfDocumentedException {
         return getOnlyChildElement(childName, getNamespace());
     }
 
-    public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(String childName) {
-        try {
-            return Optional.of(getOnlyChildElement(childName, getNamespace()));
-        } catch (Exception e) {
-            return Optional.absent();
+    public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(final String childName) {
+        Optional<String> namespace = getNamespaceOptionally();
+        if (namespace.isPresent()) {
+            List<XmlElement> children = getChildElementsWithinNamespace(namespace.get());
+            children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
+                @Override
+                public boolean apply(XmlElement xmlElement) {
+                    return xmlElement.getName().equals(childName);
+                }
+            }));
+            if (children.size() != 1){
+                return Optional.absent();
+            }
+            return Optional.of(children.get(0));
         }
+        return Optional.absent();
     }
 
     public XmlElement getOnlyChildElementWithSameNamespace() throws NetconfDocumentedException {
@@ -279,20 +311,21 @@ public final class XmlElement {
     }
 
     public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally() {
-        try {
-            XmlElement childElement = getOnlyChildElement();
-            childElement.checkNamespace(getNamespace());
-            return Optional.of(childElement);
-        } catch (Exception e) {
-            return Optional.absent();
+        Optional<XmlElement> 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, String namespace) throws NetconfDocumentedException {
         List<XmlElement> children = getChildElementsWithinNamespace(namespace);
         children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
             @Override
-            public boolean apply(@Nullable XmlElement xmlElement) {
+            public boolean apply(XmlElement xmlElement) {
                 return xmlElement.getName().equals(childName);
             }
         }));
@@ -319,29 +352,47 @@ public final class XmlElement {
         return children.get(0);
     }
 
+    public Optional<XmlElement> getOnlyChildElementOptionally() {
+        List<XmlElement> children = getChildElements();
+        if (children.size() != 1) {
+            return Optional.absent();
+        }
+        return Optional.of(children.get(0));
+    }
+
     public String getTextContent() throws NetconfDocumentedException {
-        Node textChild = element.getFirstChild();
-        if (null == textChild){
-            throw new NetconfDocumentedException(String.format( "Child node expected, got null for " + getName() + " : " + element),
-                    NetconfDocumentedException.ErrorType.application,
-                    NetconfDocumentedException.ErrorTag.invalid_value,
-                    NetconfDocumentedException.ErrorSeverity.error);
+        NodeList childNodes = element.getChildNodes();
+        if (childNodes.getLength() == 0) {
+            return DEFAULT_NAMESPACE_PREFIX;
         }
-        if (!(textChild instanceof Text)){
-            throw new NetconfDocumentedException(String.format(getName() + " should contain text." +
-                    Text.class.getName() + " expected, got " + textChild),
-                    NetconfDocumentedException.ErrorType.application,
-                    NetconfDocumentedException.ErrorTag.invalid_value,
-                    NetconfDocumentedException.ErrorSeverity.error);
+        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 NetconfDocumentedException(getName() + " should contain text.",
+                NetconfDocumentedException.ErrorType.application,
+                NetconfDocumentedException.ErrorTag.invalid_value,
+                NetconfDocumentedException.ErrorSeverity.error
+        );
+    }
+
+    public Optional<String> 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());
+            }
         }
-        String content = textChild.getTextContent();
-        // Trim needed
-        return content.trim();
+        return Optional.absent();
     }
 
     public String getNamespaceAttribute() throws MissingNameSpaceException {
         String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
-        if (attribute == null || attribute.equals("")){
+        if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){
             throw new MissingNameSpaceException(String.format("Element %s must specify namespace",
                     toString()),
                     NetconfDocumentedException.ErrorType.application,
@@ -351,15 +402,32 @@ public final class XmlElement {
         return attribute;
     }
 
-    public String getNamespace() throws MissingNameSpaceException {
+    public Optional<String> getNamespaceAttributeOptionally(){
+        String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
+        if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){
+            return Optional.absent();
+        }
+        return Optional.of(attribute);
+    }
+
+    public Optional<String> getNamespaceOptionally() {
         String namespaceURI = element.getNamespaceURI();
-        if (namespaceURI  == null || namespaceURI.equals("")){
+        if (Strings.isNullOrEmpty(namespaceURI)) {
+            return Optional.absent();
+        } else {
+            return Optional.of(namespaceURI);
+        }
+    }
+
+    public String getNamespace() throws MissingNameSpaceException {
+        Optional<String> namespaceURI = getNamespaceOptionally();
+        if (!namespaceURI.isPresent()){
             throw new MissingNameSpaceException(String.format("No namespace defined for %s", this),
                     NetconfDocumentedException.ErrorType.application,
                     NetconfDocumentedException.ErrorTag.operation_failed,
                     NetconfDocumentedException.ErrorSeverity.error);
         }
-        return namespaceURI;
+        return namespaceURI.get();
     }
 
     @Override
@@ -370,7 +438,7 @@ public final class XmlElement {
             try {
                 sb.append(", namespace='").append(getNamespace()).append('\'');
             } catch (MissingNameSpaceException e) {
-                logger.trace("Missing namespace for element.");
+                LOG.trace("Missing namespace for element.");
             }
         }
         sb.append('}');
@@ -391,16 +459,16 @@ public final class XmlElement {
      * is found value will be null.
      */
     public Map.Entry<String/* prefix */, String/* namespace */> findNamespaceOfTextContent() throws NetconfDocumentedException {
-        Map<String, String> namespaces = extractNamespaces(element);
+        Map<String, String> namespaces = extractNamespaces();
         String textContent = getTextContent();
         int indexOfColon = textContent.indexOf(':');
         String prefix;
         if (indexOfColon > -1) {
             prefix = textContent.substring(0, indexOfColon);
         } else {
-            prefix = "";
+            prefix = DEFAULT_NAMESPACE_PREFIX;
         }
-        if (namespaces.containsKey(prefix) == false) {
+        if (!namespaces.containsKey(prefix)) {
             throw new IllegalArgumentException("Cannot find namespace for " + XmlUtil.toString(element) + ". Prefix from content is "
                     + prefix + ". Found namespaces " + namespaces);
         }
@@ -411,7 +479,7 @@ public final class XmlElement {
         List<XmlElement> children = getChildElementsWithinNamespace(getNamespace());
         return Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
             @Override
-            public boolean apply(@Nullable XmlElement xmlElement) {
+            public boolean apply(XmlElement xmlElement) {
                 return xmlElement.getName().equals(childName);
             }
         }));
@@ -447,11 +515,8 @@ public final class XmlElement {
 
         XmlElement that = (XmlElement) o;
 
-        if (!element.isEqualNode(that.element)) {
-            return false;
-        }
+        return element.isEqualNode(that.element);
 
-        return true;
     }
 
     @Override
@@ -460,15 +525,10 @@ public final class XmlElement {
     }
 
     public boolean hasNamespace() {
-        try {
-            getNamespaceAttribute();
-        } catch (MissingNameSpaceException e) {
-            try {
-                getNamespace();
-            } catch (MissingNameSpaceException e1) {
+        if (!getNamespaceAttributeOptionally().isPresent()) {
+            if (!getNamespaceOptionally().isPresent()) {
                 return false;
             }
-            return true;
         }
         return true;
     }