X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fimpl%2Fcodec%2Fxml%2FXmlDocumentUtils.java;h=7345d3dded5ae16c61256750213fa988ebafb76e;hb=4a5d26c9de475a4cff151ee4255e027211c2eeb4;hp=ad639b74c86811144f7228af29fd93664c1e8fd5;hpb=e98a1027fca4e4b27c0e7e00a3723993935c34ad;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 ad639b74c8..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 @@ -1,5 +1,21 @@ +/* + * Copyright (c) 2013 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.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; @@ -8,18 +24,45 @@ import javax.xml.parsers.DocumentBuilderFactory; 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.model.api.*; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +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; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; public class XmlDocumentUtils { @@ -33,33 +76,77 @@ 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, XmlCodecProvider codecProvider) throws UnsupportedDataTypeException { - QName dataType = data.getNodeType(); - Element itemEl = doc.createElementNS(dataType.getNamespace().toString(), dataType.getLocalName()); + Element itemEl = createElementFor(doc, data); if (data instanceof SimpleNode) { if (schema instanceof LeafListSchemaNode) { writeValueByType(itemEl, (SimpleNode) data, ((LeafListSchemaNode) schema).getType(), @@ -92,14 +179,37 @@ public class XmlDocumentUtils { return itemEl; } + public static Element createElementFor(Document doc, Node data) { + QName dataType = data.getNodeType(); + Element ret; + if (dataType.getNamespace() != null) { + ret = doc.createElementNS(dataType.getNamespace().toString(), dataType.getLocalName()); + } else { + ret = doc.createElementNS(null, dataType.getLocalName()); + } + if (data instanceof AttributesContainer && ((AttributesContainer) data).getAttributes() != null) { + for (Entry attribute : ((AttributesContainer) data).getAttributes().entrySet()) { + ret.setAttributeNS(attribute.getKey().getNamespace().toString(), attribute.getKey().getLocalName(), + attribute.getValue()); + } + + } + return ret; + } + 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(); @@ -107,25 +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) { @@ -152,6 +341,236 @@ public class XmlDocumentUtils { return null; } + public static Node toDomNode(Element xmlElement, Optional schema, + Optional codecProvider) { + if (schema.isPresent()) { + return toNodeWithSchema(xmlElement, schema.get(), codecProvider.or(DEFAULT_XML_VALUE_CODEC_PROVIDER)); + } + return toDomNode(xmlElement); + } + + public static CompositeNode fromElement(Element xmlElement) { + CompositeNodeBuilder node = ImmutableCompositeNode.builder(); + node.setQName(qNameFromElement(xmlElement)); + + return node.toInstance(); + } + + 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); + } + + private static Node toNodeWithSchema(Element xmlElement, DataSchemaNode schema, XmlCodecProvider codecProvider) { + checkQName(xmlElement, schema.getQName()); + if (schema instanceof DataNodeContainer) { + return toCompositeNodeWithSchema(xmlElement, schema.getQName(), (DataNodeContainer) schema, codecProvider); + } else if (schema instanceof LeafSchemaNode) { + return toSimpleNodeWithType(xmlElement, (LeafSchemaNode) schema, codecProvider); + } else if (schema instanceof LeafListSchemaNode) { + return toSimpleNodeWithType(xmlElement, (LeafListSchemaNode) schema, codecProvider); + + } + return null; + } + + private static Node toSimpleNodeWithType(Element xmlElement, LeafSchemaNode schema, + XmlCodecProvider codecProvider) { + TypeDefinitionAwareCodec> codec = codecProvider.codecFor(schema.getType()); + String text = xmlElement.getTextContent(); + Object value; + if (codec != null) { + value = codec.deserialize(text); + + } else { + value = xmlElement.getTextContent(); + } + + Optional modifyAction = getModifyOperationFromAttributes(xmlElement); + return new SimpleNodeTOImpl<>(schema.getQName(), null, value, modifyAction.orNull()); + } + + private static Node toSimpleNodeWithType(Element xmlElement, LeafListSchemaNode schema, + XmlCodecProvider codecProvider) { + TypeDefinitionAwareCodec> codec = codecProvider.codecFor(schema.getType()); + String text = xmlElement.getTextContent(); + Object value; + if (codec != null) { + value = codec.deserialize(text); + + } else { + value = xmlElement.getTextContent(); + } + + 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())); + 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) { + checkState(Objects.equal(xmlElement.getNamespaceURI(), qName.getNamespace().toString())); + checkState(qName.getLocalName().equals(xmlElement.getLocalName())); + } + + 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())) { + return Optional. of(dsn); + } else if (dsn instanceof ChoiceNode) { + for (ChoiceCaseNode choiceCase : ((ChoiceNode) dsn).getCases()) { + Optional foundDsn = findFirstSchema(qname, choiceCase.getChildNodes()); + if (foundDsn != null && foundDsn.isPresent()) { + return foundDsn; + } + } + } + } + } + return Optional.absent(); + } + + public static Node toDomNode(Document doc) { + return toDomNode(doc.getDocumentElement()); + } + + private static Node toDomNode(Element element) { + QName qname = qNameFromElement(element); + + ImmutableList.Builder> values = ImmutableList.> builder(); + NodeList nodes = element.getChildNodes(); + boolean isSimpleObject = true; + String value = null; + for (int i = 0; i < nodes.getLength(); i++) { + org.w3c.dom.Node child = nodes.item(i); + if (child instanceof Element) { + isSimpleObject = false; + values.add(toDomNode((Element) child)); + } + if (isSimpleObject && child instanceof org.w3c.dom.Text) { + value = element.getTextContent(); + if (!Strings.isNullOrEmpty(value)) { + isSimpleObject = true; + } + } + } + if (isSimpleObject) { + return new SimpleNodeTOImpl<>(qname, null, value); + } + return ImmutableCompositeNode.create(qname, values.build()); + } + + public static List> toDomNodes(final Element element, final Optional> context) { + return forEachChild(element.getChildNodes(), new Function>>() { + + @Override + public Optional> apply(Element input) { + if (context.isPresent()) { + QName partialQName = qNameFromElement(input); + Optional schemaNode = findFirstSchema(partialQName, context.get()); + if (schemaNode.isPresent()) { + return Optional.> fromNullable(// + toNodeWithSchema(input, schemaNode.get(), DEFAULT_XML_VALUE_CODEC_PROVIDER)); + } + } + 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++) { + org.w3c.dom.Node child = nodes.item(i); + if (child instanceof Element) { + Optional result = forBody.apply((Element) child); + if (result.isPresent()) { + ret.add(result.get()); + } + } + } + return ret.build(); + } + public static final XmlCodecProvider defaultValueCodecProvider() { return DEFAULT_XML_VALUE_CODEC_PROVIDER; }