X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;ds=sidebyside;f=yang%2Fyang-data-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fimpl%2Fcodec%2Fxml%2FXmlDocumentUtils.java;h=7345d3dded5ae16c61256750213fa988ebafb76e;hb=4a5d26c9de475a4cff151ee4255e027211c2eeb4;hp=63968e056f7d5bf6bd693ca2a8ad6de1f020fdc1;hpb=3c08a3b59e0f20bdcd962ec49ba647f1528ba024;p=yangtools.git diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XmlDocumentUtils.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XmlDocumentUtils.java index 63968e056f..7345d3dded 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XmlDocumentUtils.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XmlDocumentUtils.java @@ -10,8 +10,12 @@ package org.opendaylight.yangtools.yang.data.impl.codec.xml; import static com.google.common.base.Preconditions.checkState; import java.net.URI; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Random; import java.util.Set; import javax.activation.UnsupportedDataTypeException; @@ -22,16 +26,33 @@ import javax.xml.parsers.ParserConfigurationException; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.AttributesContainer; import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.ModifyAction; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.SimpleNode; import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; -import org.opendaylight.yangtools.yang.model.api.*; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; 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.NodeList; @@ -55,27 +76,72 @@ public class XmlDocumentUtils { private static final Logger logger = LoggerFactory.getLogger(XmlDocumentUtils.class); + /** + * Converts Data DOM structure to XML Document for specified XML Codec Provider and corresponding + * Data Node Container schema. The CompositeNode data parameter enters as root of Data DOM tree and will + * be transformed to root in XML Document. Each element of Data DOM tree is compared against specified Data + * Node Container Schema and transformed accordingly. + * + * @param data Data DOM root element + * @param schema Data Node Container Schema + * @param codecProvider XML Codec Provider + * @return new instance of XML Document + * @throws UnsupportedDataTypeException + */ public static Document toDocument(CompositeNode data, DataNodeContainer schema, XmlCodecProvider codecProvider) throws UnsupportedDataTypeException { Preconditions.checkNotNull(data); Preconditions.checkNotNull(schema); + Document doc = getDocument(); + + if (schema instanceof ContainerSchemaNode || schema instanceof ListSchemaNode) { + doc.appendChild(createXmlRootElement(doc, data, (SchemaNode) schema, codecProvider)); + return doc; + } else { + throw new UnsupportedDataTypeException( + "Schema can be ContainerSchemaNode or ListSchemaNode. Other types are not supported yet."); + } + } + + public static Document getDocument() { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document doc = null; try { DocumentBuilder bob = dbf.newDocumentBuilder(); doc = bob.newDocument(); } catch (ParserConfigurationException e) { - return null; + throw new RuntimeException(e); } + return doc; + } - if (schema instanceof ContainerSchemaNode || schema instanceof ListSchemaNode) { - doc.appendChild(createXmlRootElement(doc, data, (SchemaNode) schema, codecProvider)); - return doc; - } else { - throw new UnsupportedDataTypeException( - "Schema can be ContainerSchemaNode or ListSchemaNode. Other types are not supported yet."); + /** + * Converts Data DOM structure to XML Document for specified XML Codec Provider. The CompositeNode + * data parameter enters as root of Data DOM tree and will be transformed to root in XML Document. The child + * nodes of Data Tree are transformed accordingly. + * + * @param data Data DOM root element + * @param codecProvider XML Codec Provider + * @return new instance of XML Document + * @throws UnsupportedDataTypeException + */ + public static Document toDocument(CompositeNode data, XmlCodecProvider codecProvider) + throws UnsupportedDataTypeException { + Preconditions.checkNotNull(data); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + Document doc = null; + try { + DocumentBuilder bob = dbf.newDocumentBuilder(); + doc = bob.newDocument(); + } catch (ParserConfigurationException e) { + return null; } + + doc.appendChild(createXmlRootElement(doc, data, null, codecProvider)); + return doc; } private static Element createXmlRootElement(Document doc, Node data, SchemaNode schema, @@ -113,7 +179,7 @@ public class XmlDocumentUtils { return itemEl; } - private static Element createElementFor(Document doc, Node data) { + public static Element createElementFor(Document doc, Node data) { QName dataType = data.getNodeType(); Element ret; if (dataType.getNamespace() != null) { @@ -134,11 +200,16 @@ public class XmlDocumentUtils { public static void writeValueByType(Element element, SimpleNode node, TypeDefinition type, DataSchemaNode schema, XmlCodecProvider codecProvider) { - TypeDefinition baseType = resolveBaseTypeFrom(type); + Object nodeValue = node.getValue(); + writeValueByType(element, type, codecProvider, nodeValue); + } + + public static void writeValueByType(Element element, TypeDefinition type, XmlCodecProvider codecProvider, Object nodeValue) { + TypeDefinition baseType = resolveBaseTypeFrom(type); if (baseType instanceof IdentityrefTypeDefinition) { - if (node.getValue() instanceof QName) { - QName value = (QName) node.getValue(); + if (nodeValue instanceof QName) { + QName value = (QName) nodeValue; String prefix = "x"; if (value.getPrefix() != null && !value.getPrefix().isEmpty()) { prefix = value.getPrefix(); @@ -146,24 +217,104 @@ public class XmlDocumentUtils { element.setAttribute("xmlns:" + prefix, value.getNamespace().toString()); element.setTextContent(prefix + ":" + value.getLocalName()); } else { - logger.debug("Value of {}:{} is not instance of QName but is {}", baseType.getQName().getNamespace(), // - baseType.getQName().getLocalName(), // - node.getValue().getClass()); - element.setTextContent(String.valueOf(node.getValue())); + Object value = nodeValue; + logger.debug("Value of {}:{} is not instance of QName but is {}", baseType.getQName().getNamespace(), + baseType.getQName().getLocalName(), value != null ? value.getClass() : "null"); + if (value != null) { + element.setTextContent(String.valueOf(value)); + } + } + } else if (baseType instanceof InstanceIdentifierTypeDefinition) { + if (nodeValue instanceof InstanceIdentifier) { + // Map< key = namespace, value = prefix> + Map prefixes = new HashMap<>(); + InstanceIdentifier instanceIdentifier = (InstanceIdentifier) nodeValue; + StringBuilder textContent = new StringBuilder(); + for (PathArgument pathArgument : instanceIdentifier.getPath()) { + textContent.append("/"); + writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType(), prefixes); + if (pathArgument instanceof NodeIdentifierWithPredicates) { + Map predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues(); + + for (QName keyValue : predicates.keySet()) { + String predicateValue = String.valueOf(predicates.get(keyValue)); + textContent.append("["); + writeIdentifierWithNamespacePrefix(element, textContent, keyValue, prefixes); + textContent.append("='"); + textContent.append(predicateValue); + textContent.append("'"); + textContent.append("]"); + } + } else if (pathArgument instanceof NodeWithValue) { + textContent.append("[.='"); + textContent.append(((NodeWithValue)pathArgument).getValue()); + textContent.append("'"); + textContent.append("]"); + } + } + element.setTextContent(textContent.toString()); + + } else { + Object value = nodeValue; + logger.debug("Value of {}:{} is not instance of InstanceIdentifier but is {}", baseType.getQName() + .getNamespace(), // + baseType.getQName().getLocalName(), value != null ? value.getClass() : "null"); + if (value != null) { + element.setTextContent(String.valueOf(value)); + } } } else { - if (node.getValue() != null) { - try { - String value = codecProvider.codecFor(baseType).serialize(node.getValue()); - element.setTextContent(value); - } catch (ClassCastException e) { - element.setTextContent(String.valueOf(node.getValue())); - logger.error("Provided node did not have type required by mapping. Using stream instead. {}", e); + if (nodeValue != null) { + final TypeDefinitionAwareCodec codec = codecProvider.codecFor(baseType); + if (codec != null) { + try { + final String text = codec.serialize(nodeValue); + element.setTextContent(text); + } catch (ClassCastException e) { + logger.error("Provided node value {} did not have type {} required by mapping. Using stream instead.", nodeValue, baseType, e); + element.setTextContent(String.valueOf(nodeValue)); + } + } else { + logger.error("Failed to find codec for {}, falling back to using stream", baseType); + element.setTextContent(String.valueOf(nodeValue)); } } } } + private static void writeIdentifierWithNamespacePrefix(Element element, StringBuilder textContent, QName qName, + Map prefixes) { + String namespace = qName.getNamespace().toString(); + String prefix = prefixes.get(namespace); + if (prefix == null) { + prefix = qName.getPrefix(); + if (prefix == null || prefix.isEmpty() || prefixes.containsValue(prefix)) { + prefix = generateNewPrefix(prefixes.values()); + } + } + + element.setAttribute("xmlns:" + prefix, namespace.toString()); + textContent.append(prefix); + prefixes.put(namespace, prefix); + + textContent.append(":"); + textContent.append(qName.getLocalName()); + } + + private static String generateNewPrefix(Collection prefixes) { + StringBuilder result = null; + Random random = new Random(); + do { + result = new StringBuilder(); + for (int i = 0; i < 4; i++) { + int randomNumber = 0x61 + (Math.abs(random.nextInt()) % 26); + result.append(Character.toChars(randomNumber)); + } + } while (prefixes.contains(result.toString())); + + return result.toString(); + } + public final static TypeDefinition resolveBaseTypeFrom(TypeDefinition type) { TypeDefinition superType = type; while (superType.getBaseType() != null) { @@ -205,7 +356,7 @@ public class XmlDocumentUtils { return node.toInstance(); } - private static QName qNameFromElement(Element xmlElement) { + public static QName qNameFromElement(Element xmlElement) { String namespace = xmlElement.getNamespaceURI(); String localName = xmlElement.getLocalName(); return QName.create(namespace != null ? URI.create(namespace) : null, null, localName); @@ -235,7 +386,9 @@ public class XmlDocumentUtils { } else { value = xmlElement.getTextContent(); } - return new SimpleNodeTOImpl(schema.getQName(), null, value); + + Optional modifyAction = getModifyOperationFromAttributes(xmlElement); + return new SimpleNodeTOImpl<>(schema.getQName(), null, value, modifyAction.orNull()); } private static Node toSimpleNodeWithType(Element xmlElement, LeafListSchemaNode schema, @@ -249,13 +402,29 @@ public class XmlDocumentUtils { } else { value = xmlElement.getTextContent(); } - return new SimpleNodeTOImpl(schema.getQName(), null, value); + + Optional modifyAction = getModifyOperationFromAttributes(xmlElement); + return new SimpleNodeTOImpl<>(schema.getQName(), null, value, modifyAction.orNull()); } private static Node toCompositeNodeWithSchema(Element xmlElement, QName qName, DataNodeContainer schema, XmlCodecProvider codecProvider) { List> values = toDomNodes(xmlElement, Optional.fromNullable(schema.getChildNodes())); - return ImmutableCompositeNode.create(qName, values); + Optional modifyAction = getModifyOperationFromAttributes(xmlElement); + return ImmutableCompositeNode.create(qName, values, modifyAction.orNull()); + } + + public static final QName OPERATION_ATTRIBUTE_QNAME = QName.create(URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"), null, "operation"); + + public static Optional getModifyOperationFromAttributes(Element xmlElement) { + Attr attributeNodeNS = xmlElement.getAttributeNodeNS(OPERATION_ATTRIBUTE_QNAME.getNamespace().toString(), OPERATION_ATTRIBUTE_QNAME.getLocalName()); + if(attributeNodeNS == null) + return Optional.absent(); + + ModifyAction action = ModifyAction.fromXmlValue(attributeNodeNS.getValue()); + Preconditions.checkArgument(action.isOnElementPermitted(), "Unexpected operation %s on %s", action, xmlElement); + + return Optional.of(action); } private static void checkQName(Element xmlElement, QName qName) { @@ -263,7 +432,7 @@ public class XmlDocumentUtils { checkState(qName.getLocalName().equals(xmlElement.getLocalName())); } - private static final Optional findFirstSchema(QName qname, Set dataSchemaNode) { + public static final Optional findFirstSchema(QName qname, Set dataSchemaNode) { if (dataSchemaNode != null && !dataSchemaNode.isEmpty() && qname != null) { for (DataSchemaNode dsn : dataSchemaNode) { if (qname.isEqualWithoutRevision(dsn.getQName())) { @@ -271,7 +440,7 @@ public class XmlDocumentUtils { } else if (dsn instanceof ChoiceNode) { for (ChoiceCaseNode choiceCase : ((ChoiceNode) dsn).getCases()) { Optional foundDsn = findFirstSchema(qname, choiceCase.getChildNodes()); - if (foundDsn != null) { + if (foundDsn != null && foundDsn.isPresent()) { return foundDsn; } } @@ -324,13 +493,70 @@ public class XmlDocumentUtils { toNodeWithSchema(input, schemaNode.get(), DEFAULT_XML_VALUE_CODEC_PROVIDER)); } } - return Optional.> fromNullable(toDomNode(element)); + return Optional.> fromNullable(toDomNode(input)); } }); } + /** + * Converts XML Document containing notification data from Netconf device to + * Data DOM Nodes.
+ * By specification defined in RFC 6020 + * there are xml elements containing notifications metadata, like eventTime + * or root notification element which specifies namespace for which is + * notification defined in yang model. Those elements MUST be stripped off + * notifications body. This method returns pure notification body which + * begins in element which is equal to notifications name defined in + * corresponding yang model. Rest of notification metadata are obfuscated, + * thus Data DOM contains only pure notification body. + * + * @param document + * XML Document containing notification body + * @param notifications + * Notifications Definition Schema + * @return Data DOM Nodes containing xml notification body definition or + * null if there is no NotificationDefinition with + * Element with equal notification QName defined in XML Document. + */ + public static CompositeNode notificationToDomNodes(final Document document, + final Optional> notifications) { + if (notifications.isPresent() && (document != null) && (document.getDocumentElement() != null)) { + final NodeList originChildNodes = document.getDocumentElement().getChildNodes(); + + for (int i = 0; i < originChildNodes.getLength(); i++) { + org.w3c.dom.Node child = originChildNodes.item(i); + if (child instanceof Element) { + final Element childElement = (Element) child; + final QName partialQName = qNameFromElement(childElement); + final Optional notificationDef = findNotification(partialQName, + notifications.get()); + if (notificationDef.isPresent()) { + final Set dataNodes = notificationDef.get().getChildNodes(); + final List> domNodes = toDomNodes(childElement, + Optional.> fromNullable(dataNodes)); + return ImmutableCompositeNode.create(notificationDef.get().getQName(), domNodes); + } + } + } + } + return null; + } + + private static Optional findNotification(final QName notifName, + final Set notifications) { + if ((notifName != null) && (notifications != null)) { + for (final NotificationDefinition notification : notifications) { + if ((notification != null) && notifName.isEqualWithoutRevision(notification.getQName())) { + return Optional.fromNullable(notification); + } + } + } + return Optional.absent(); + } + private static final List forEachChild(NodeList nodes, Function> forBody) { ImmutableList.Builder ret = ImmutableList. builder(); for (int i = 0; i < nodes.getLength(); i++) {