--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.xml.codec;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class InstanceIdentifierForXmlCodec {
+ private static final Pattern PREDICATE_PATTERN = Pattern.compile("\\[(.*?)\\]");
+ private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+ private static final Splitter COLON_SPLITTER = Splitter.on(':');
+ private static final Splitter AT_SPLITTER = Splitter.on('@');
+ private static final Logger logger = LoggerFactory.getLogger(InstanceIdentifierForXmlCodec.class);
+
+ private InstanceIdentifierForXmlCodec() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ public static YangInstanceIdentifier deserialize(final Element element, final SchemaContext schemaContext) {
+ Preconditions.checkNotNull(element, "Value of element for deserialization can't be null");
+ Preconditions.checkNotNull(schemaContext,
+ "Schema context for deserialization of instance identifier type can't be null");
+
+ final String valueTrimmed = element.getTextContent().trim();
+ logger.debug("Instance identifier derserialize: splitting the text {} with Slash to find path arguments", valueTrimmed);
+ final Iterator<String> xPathParts = SLASH_SPLITTER.split(valueTrimmed).iterator();
+
+ // must be at least "/pr:node"
+ if (!xPathParts.hasNext() || !xPathParts.next().isEmpty() || !xPathParts.hasNext()) {
+ logger.debug("Instance identifier derserialize: No path argument found for element.");
+ return null;
+ }
+
+ List<PathArgument> result = new ArrayList<>();
+ while (xPathParts.hasNext()) {
+ String xPathPartTrimmed = xPathParts.next().trim();
+
+ PathArgument pathArgument = toPathArgument(xPathPartTrimmed, element, schemaContext);
+ if (pathArgument != null) {
+ result.add(pathArgument);
+ }
+ }
+ return YangInstanceIdentifier.create(result);
+ }
+
+ public static Element serialize(final YangInstanceIdentifier id, final Element element) {
+ Preconditions.checkNotNull(id, "Variable should contain instance of instance identifier and can't be null");
+ Preconditions.checkNotNull(element, "DOM element can't be null");
+
+ final RandomPrefix prefixes = new RandomPrefix();
+ final String str = XmlUtils.encodeIdentifier(prefixes, id);
+
+ for (Entry<URI, String> e: prefixes.getPrefixes()) {
+ element.setAttribute("xmlns:" + e.getValue(), e.getKey().toString());
+ }
+ element.setTextContent(str);
+ return element;
+ }
+
+ private static String getIdAndPrefixAsStr(final String pathPart) {
+ int predicateStartIndex = pathPart.indexOf('[');
+ return predicateStartIndex == -1 ? pathPart : pathPart.substring(0, predicateStartIndex);
+ }
+
+ private static PathArgument toPathArgument(final String xPathArgument, final Element element, final SchemaContext schemaContext) {
+ final QName mainQName = toIdentity(xPathArgument, element, schemaContext);
+
+ // predicates
+ final Matcher matcher = PREDICATE_PATTERN.matcher(xPathArgument);
+ final Map<QName, Object> predicates = new HashMap<>();
+ QName currentQName = mainQName;
+
+ while (matcher.find()) {
+ final String predicateStr = matcher.group(1).trim();
+ final int indexOfEqualityMark = predicateStr.indexOf('=');
+ if (indexOfEqualityMark != -1) {
+ final Object predicateValue = toPredicateValue(predicateStr.substring(indexOfEqualityMark + 1));
+ if (predicateValue == null) {
+ return null;
+ }
+
+ if (predicateStr.charAt(0) != '.') {
+ // target is not a leaf-list
+ currentQName = toIdentity(predicateStr.substring(0, indexOfEqualityMark), element, schemaContext);
+ if (currentQName == null) {
+ return null;
+ }
+ }
+ logger.debug("Instance identifier derserialize: finding predicates of node {}", predicateValue);
+ predicates.put(currentQName, predicateValue);
+ }
+ }
+
+ if (predicates.isEmpty()) {
+ return new YangInstanceIdentifier.NodeIdentifier(mainQName);
+ } else {
+ return new YangInstanceIdentifier.NodeIdentifierWithPredicates(mainQName, predicates);
+ }
+
+ }
+
+ public static QName toIdentity(final String xPathArgument, final Element element, final SchemaContext schemaContext) {
+ final String xPathPartTrimmed = getIdAndPrefixAsStr(xPathArgument).trim();
+ final Iterator<String> it = COLON_SPLITTER.split(xPathPartTrimmed).iterator();
+
+ // Empty string
+ if (!it.hasNext()) {
+ return null;
+ }
+
+ final String prefix = it.next().trim();
+ if (prefix.isEmpty()) {
+ return null;
+ }
+
+ // it is not "prefix:value"
+ if (!it.hasNext()) {
+ return null;
+ }
+
+ final String identifier = it.next().trim();
+ if (identifier.isEmpty()) {
+ return null;
+ }
+
+ URI namespace = null;
+ String namespaceStr = null;
+ try {
+ namespaceStr = element.getAttribute("xmlns:"+prefix);
+ namespace = new URI(namespaceStr);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("It wasn't possible to convert " + namespaceStr + " to URI object.");
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException("I wasn't possible to get namespace for prefix " + prefix);
+ }
+
+ Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null);
+ return QName.create(module.getQNameModule(), identifier);
+ }
+
+ private static String trimIfEndIs(final String str, final char end) {
+ final int l = str.length() - 1;
+ if (str.charAt(l) != end) {
+ return null;
+ }
+
+ return str.substring(1, l);
+ }
+
+ private static Object toPredicateValue(final String predicatedValue) {
+ logger.debug("Instance identifier derserialize: converting the predicate vstring to object {}", predicatedValue);
+ final String predicatedValueTrimmed = predicatedValue.trim();
+ if (predicatedValue.isEmpty()) {
+ return null;
+ }
+ String updatedValue = null;
+ switch (predicatedValueTrimmed.charAt(0)) {
+ case '"':
+ updatedValue = trimIfEndIs(predicatedValueTrimmed, '"');
+ break;
+ case '\'':
+ updatedValue = trimIfEndIs(predicatedValueTrimmed, '\'');
+ break;
+ default:
+ updatedValue = predicatedValueTrimmed;
+ }
+ Iterator<String> it = AT_SPLITTER.split(updatedValue).iterator();
+ // Empty string
+ if (!it.hasNext()) {
+ return null;
+ }
+
+ final String value = it.next().trim();
+ if (value.isEmpty()) {
+ return null;
+ }
+
+ if (!it.hasNext()) {
+ return value;
+ }
+
+ final String type = it.next().trim();
+ if (type.isEmpty()) {
+ return value;
+ }
+ Object predicateObject = null;
+ try {
+ logger.debug("Instance identifier derserialize: converting the predicate value {{}}to correct object type {{}}", value, type);
+ predicateObject = Class.forName(type).getConstructor(String.class).newInstance(value);
+ } catch (Exception e) {
+ logger.error("Could not convert to valid type of value", e);
+ }
+ return predicateObject;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.xml.codec;
+
+import org.opendaylight.yangtools.yang.common.QName;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Picked from Yang tools because it was not public class
+ */
+
+final class RandomPrefix {
+ final Map<URI, String> prefixes = new HashMap<>();
+
+ Iterable<Entry<URI, String>> getPrefixes() {
+ return prefixes.entrySet();
+ }
+
+ String encodeQName(final QName qname) {
+ String prefix = prefixes.get(qname.getNamespace());
+ if (prefix == null) {
+ prefix = qname.getPrefix();
+ if (prefix == null || prefix.isEmpty() || prefixes.containsValue(prefix)) {
+ final ThreadLocalRandom random = ThreadLocalRandom.current();
+ do {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 4; i++) {
+ sb.append((char)('a' + random.nextInt(25)));
+ }
+
+ prefix = sb.toString();
+ } while (prefixes.containsValue(prefix));
+ }
+
+ prefixes.put(qname.getNamespace(), prefix);
+ }
+
+ return prefix + ':' + qname.getLocalName();
+ }
+}
--- /dev/null
+/*
+ * 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.controller.xml.codec;
+
+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;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.Node;
+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.codec.xml.XmlCodecProvider;
+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.SchemaContext;
+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.util.InstanceIdentifierType;
+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 javax.activation.UnsupportedDataTypeException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.dom.DOMResult;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public class XmlDocumentUtils {
+ private static class ElementWithSchemaContext {
+ Element element;
+ SchemaContext schemaContext;
+
+ ElementWithSchemaContext(final Element element,final SchemaContext schemaContext) {
+ this.schemaContext = schemaContext;
+ this.element = element;
+ }
+
+ Element getElement() {
+ return element;
+ }
+
+ SchemaContext getSchemaContext() {
+ return schemaContext;
+ }
+ }
+
+ public static final QName OPERATION_ATTRIBUTE_QNAME = QName.create(URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"), null, "operation");
+ private static final Logger logger = LoggerFactory.getLogger(XmlDocumentUtils.class);
+ private static final XMLOutputFactory FACTORY = XMLOutputFactory.newFactory();
+
+ /**
+ * 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 javax.activation.UnsupportedDataTypeException
+ */
+ public static Document toDocument(final CompositeNode data, final DataNodeContainer schema, final XmlCodecProvider codecProvider)
+ throws UnsupportedDataTypeException {
+ Preconditions.checkNotNull(data);
+ Preconditions.checkNotNull(schema);
+
+ if (!(schema instanceof ContainerSchemaNode || schema instanceof ListSchemaNode)) {
+ throw new UnsupportedDataTypeException("Schema can be ContainerSchemaNode or ListSchemaNode. Other types are not supported yet.");
+ }
+
+ final DOMResult result = new DOMResult(getDocument());
+ try {
+ final XMLStreamWriter writer = FACTORY.createXMLStreamWriter(result);
+ XmlStreamUtils.create(codecProvider).writeDocument(writer, data, (SchemaNode)schema);
+ writer.close();
+ return (Document)result.getNode();
+ } catch (XMLStreamException e) {
+ logger.error("Failed to serialize data {}", data, e);
+ return null;
+ }
+ }
+
+ public static Document getDocument() {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ Document doc = null;
+ try {
+ DocumentBuilder bob = dbf.newDocumentBuilder();
+ doc = bob.newDocument();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ return doc;
+ }
+
+
+ public static QName qNameFromElement(final 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(final Element xmlElement, final DataSchemaNode schema, final XmlCodecProvider codecProvider,final SchemaContext schemaCtx) {
+ checkQName(xmlElement, schema.getQName());
+ if (schema instanceof DataNodeContainer) {
+ return toCompositeNodeWithSchema(xmlElement, schema.getQName(), (DataNodeContainer) schema, schemaCtx);
+ } else if (schema instanceof LeafSchemaNode) {
+ return toSimpleNodeWithType(xmlElement, (LeafSchemaNode) schema, codecProvider,schemaCtx);
+ } else if (schema instanceof LeafListSchemaNode) {
+ return toSimpleNodeWithType(xmlElement, (LeafListSchemaNode) schema, codecProvider,schemaCtx);
+ }
+ return null;
+ }
+
+
+
+ private static Node<?> toSimpleNodeWithType(final Element xmlElement, final LeafSchemaNode schema,
+ final XmlCodecProvider codecProvider,final SchemaContext schemaCtx) {
+ TypeDefinitionAwareCodec<? extends Object, ? extends TypeDefinition<?>> codec = codecProvider.codecFor(schema.getType());
+ String text = xmlElement.getTextContent();
+ Object value = null;
+ if (codec != null) {
+ logger.debug("toSimpleNodeWithType: found codec, deserializing text {}", text);
+ value = codec.deserialize(text);
+ }
+
+ final TypeDefinition<?> baseType = XmlUtils.resolveBaseTypeFrom(schema.getType());
+ if (baseType instanceof InstanceIdentifierType) {
+ logger.debug("toSimpleNodeWithType: base type of node is instance identifier, deserializing element", xmlElement);
+ value = InstanceIdentifierForXmlCodec.deserialize(xmlElement,schemaCtx);
+
+ } else if(baseType instanceof IdentityrefTypeDefinition){
+ logger.debug("toSimpleNodeWithType: base type of node is IdentityrefTypeDefinition, deserializing element", xmlElement);
+ value = InstanceIdentifierForXmlCodec.toIdentity(xmlElement.getTextContent(), xmlElement, schemaCtx);
+
+ }
+
+ if (value == null) {
+ logger.debug("toSimpleNodeWithType: no type found for element, returning just the text string value of element {}", xmlElement);
+ value = xmlElement.getTextContent();
+ }
+
+ Optional<ModifyAction> modifyAction = getModifyOperationFromAttributes(xmlElement);
+ return new SimpleNodeTOImpl<>(schema.getQName(), null, value, modifyAction.orNull());
+ }
+
+ private static Node<?> toSimpleNodeWithType(final Element xmlElement, final LeafListSchemaNode schema,
+ final XmlCodecProvider codecProvider,final SchemaContext schemaCtx) {
+ TypeDefinitionAwareCodec<? extends Object, ? extends TypeDefinition<?>> codec = codecProvider.codecFor(schema.getType());
+ String text = xmlElement.getTextContent();
+ Object value = null;
+ if (codec != null) {
+ logger.debug("toSimpleNodeWithType: found codec, deserializing text {}", text);
+ value = codec.deserialize(text);
+ }
+
+ final TypeDefinition<?> baseType = XmlUtils.resolveBaseTypeFrom(schema.getType());
+ if (baseType instanceof InstanceIdentifierType) {
+ logger.debug("toSimpleNodeWithType: base type of node is instance identifier, deserializing element", xmlElement);
+ value = InstanceIdentifierForXmlCodec.deserialize(xmlElement,schemaCtx);
+ }
+
+ if (value == null) {
+ logger.debug("toSimpleNodeWithType: no type found for element, returning just the text string value of element {}", xmlElement);
+ value = xmlElement.getTextContent();
+ }
+
+ Optional<ModifyAction> modifyAction = getModifyOperationFromAttributes(xmlElement);
+ return new SimpleNodeTOImpl<>(schema.getQName(), null, value, modifyAction.orNull());
+ }
+
+ private static Node<?> toCompositeNodeWithSchema(final Element xmlElement, final QName qName, final DataNodeContainer schema,
+ final SchemaContext schemaCtx) {
+ List<Node<?>> values = toDomNodes(xmlElement, Optional.fromNullable(schema.getChildNodes()),schemaCtx);
+ Optional<ModifyAction> modifyAction = getModifyOperationFromAttributes(xmlElement);
+ return ImmutableCompositeNode.create(qName, values, modifyAction.orNull());
+ }
+
+ private static Optional<ModifyAction> getModifyOperationFromAttributes(final 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(final Element xmlElement, final QName qName) {
+ checkState(Objects.equal(xmlElement.getNamespaceURI(), qName.getNamespace().toString()));
+ checkState(qName.getLocalName().equals(xmlElement.getLocalName()));
+ }
+
+ public static final Optional<DataSchemaNode> findFirstSchema(final QName qname, final Collection<DataSchemaNode> dataSchemaNode) {
+ if (dataSchemaNode != null && !dataSchemaNode.isEmpty() && qname != null) {
+ for (DataSchemaNode dsn : dataSchemaNode) {
+ if (qname.isEqualWithoutRevision(dsn.getQName())) {
+ return Optional.<DataSchemaNode> of(dsn);
+ } else if (dsn instanceof ChoiceNode) {
+ for (ChoiceCaseNode choiceCase : ((ChoiceNode) dsn).getCases()) {
+ Optional<DataSchemaNode> foundDsn = findFirstSchema(qname, choiceCase.getChildNodes());
+ if (foundDsn != null && foundDsn.isPresent()) {
+ return foundDsn;
+ }
+ }
+ }
+ }
+ }
+ return Optional.absent();
+ }
+
+ private static Node<?> toDomNode(Element element) {
+ QName qname = qNameFromElement(element);
+
+ ImmutableList.Builder<Node<?>> values = ImmutableList.<Node<?>> 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<Node<?>> toDomNodes(final Element element, final Optional<Collection<DataSchemaNode>> context,SchemaContext schemaCtx) {
+ return forEachChild(element.getChildNodes(),schemaCtx, new Function<ElementWithSchemaContext, Optional<Node<?>>>() {
+
+ @Override
+ public Optional<Node<?>> apply(ElementWithSchemaContext input) {
+ if (context.isPresent()) {
+ QName partialQName = qNameFromElement(input.getElement());
+ Optional<DataSchemaNode> schemaNode = XmlDocumentUtils.findFirstSchema(partialQName, context.get());
+ if (schemaNode.isPresent()) {
+ return Optional.<Node<?>> fromNullable(//
+ toNodeWithSchema(input.getElement(), schemaNode.get(), XmlDocumentUtils.defaultValueCodecProvider(),input.getSchemaContext()));
+ }
+ }
+ return Optional.<Node<?>> fromNullable(toDomNode(input.getElement()));
+ }
+
+ });
+
+ }
+
+ private static final <T> List<T> forEachChild(final NodeList nodes, final SchemaContext schemaContext, final Function<ElementWithSchemaContext, Optional<T>> forBody) {
+ final int l = nodes.getLength();
+ if (l == 0) {
+ return ImmutableList.of();
+ }
+
+ final List<T> list = new ArrayList<>(l);
+ for (int i = 0; i < l; i++) {
+ org.w3c.dom.Node child = nodes.item(i);
+ if (child instanceof Element) {
+ Optional<T> result = forBody.apply(new ElementWithSchemaContext((Element) child,schemaContext));
+ if (result.isPresent()) {
+ list.add(result.get());
+ }
+ }
+ }
+ return ImmutableList.copyOf(list);
+ }
+
+ public static final XmlCodecProvider defaultValueCodecProvider() {
+ return XmlUtils.DEFAULT_XML_CODEC_PROVIDER;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.xml.codec;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+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.Node;
+import org.opendaylight.yangtools.yang.data.api.SimpleNode;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlCodecProvider;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
+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.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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.net.URI;
+import java.util.Map.Entry;
+
+/**
+ * Utility class for bridging JAXP Stream and YANG Data APIs. Note that the definition of this class
+ * by no means final and subject to change as more functionality is centralized here.
+ */
+@Beta
+public class XmlStreamUtils {
+ private static final Logger LOG = LoggerFactory.getLogger(XmlStreamUtils.class);
+ private final XmlCodecProvider codecProvider;
+
+ protected XmlStreamUtils(final XmlCodecProvider codecProvider) {
+ this.codecProvider = Preconditions.checkNotNull(codecProvider);
+ }
+
+ /**
+ * Create a new instance encapsulating a particular codec provider.
+ *
+ * @param codecProvider XML codec provider
+ * @return A new instance
+ */
+ public static XmlStreamUtils create(final XmlCodecProvider codecProvider) {
+ return new XmlStreamUtils(codecProvider);
+ }
+
+ /**
+ * Check if a particular data element can be emitted as an empty element, bypassing value encoding. This
+ * functionality is optional, as valid XML stream is produced even if start/end element is produced unconditionally.
+ *
+ * @param data Data node
+ * @return True if the data node will result in empty element body.
+ */
+ public static boolean isEmptyElement(final Node<?> data) {
+ if (data == null) {
+ return true;
+ }
+
+ if (data instanceof CompositeNode) {
+ return ((CompositeNode) data).getValue().isEmpty();
+ }
+ if (data instanceof SimpleNode) {
+ return data.getValue() == null;
+ }
+
+ // Safe default
+ return false;
+ }
+
+ /**
+ * Write an InstanceIdentifier into the output stream. Calling corresponding {@link javax.xml.stream.XMLStreamWriter#writeStartElement(String)}
+ * and {@link javax.xml.stream.XMLStreamWriter#writeEndElement()} is the responsibility of the caller.
+ *
+ * @param writer XML Stream writer
+ * @param id InstanceIdentifier
+ * @throws javax.xml.stream.XMLStreamException
+ */
+ public static void write(final @Nonnull XMLStreamWriter writer, final @Nonnull YangInstanceIdentifier id) throws XMLStreamException {
+ Preconditions.checkNotNull(writer, "Writer may not be null");
+ Preconditions.checkNotNull(id, "Variable should contain instance of instance identifier and can't be null");
+ LOG.debug("Writing Instance identifier with Random prefix");
+ final RandomPrefix prefixes = new RandomPrefix();
+ final String str = XmlUtils.encodeIdentifier(prefixes, id);
+
+ for (Entry<URI, String> e: prefixes.getPrefixes()) {
+ writer.writeNamespace(e.getValue(), e.getKey().toString());
+ }
+ LOG.debug("Instance identifier with Random prefix is now {}", str);
+ writer.writeCharacters(str);
+ }
+
+ /**
+ * Write a full XML document corresponding to a CompositeNode into an XML stream writer.
+ *
+ * @param writer XML Stream writer
+ * @param data data node
+ * @param schema corresponding schema node, may be null
+ * @throws javax.xml.stream.XMLStreamException if an encoding problem occurs
+ */
+ public void writeDocument(final @Nonnull XMLStreamWriter writer, final @Nonnull CompositeNode data, final @Nullable SchemaNode schema) throws XMLStreamException {
+ // final Boolean repairing = (Boolean) writer.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES);
+ // Preconditions.checkArgument(repairing == true, "XML Stream Writer has to be repairing namespaces");
+
+ writer.writeStartDocument();
+ writeElement(writer, data, schema);
+ writer.writeEndDocument();
+ writer.flush();
+ }
+
+
+ /**
+ * Write an element into a XML stream writer. This includes the element start/end tags and
+ * the value of the element.
+ *
+ * @param writer XML Stream writer
+ * @param data data node
+ * @param schema Schema node
+ * @throws javax.xml.stream.XMLStreamException if an encoding problem occurs
+ */
+ public void writeElement(final XMLStreamWriter writer, final @Nonnull Node<?> data, final SchemaNode schema) throws XMLStreamException {
+ final QName qname = data.getNodeType();
+ final String pfx = qname.getPrefix() != null ? qname.getPrefix() : "";
+ final String ns = qname.getNamespace() != null ? qname.getNamespace().toString() : "";
+
+ if (isEmptyElement(data)) {
+ writer.writeEmptyElement(pfx, qname.getLocalName(), ns);
+ return;
+ }
+
+ writer.writeStartElement(pfx, qname.getLocalName(), ns);
+ if (data instanceof AttributesContainer && ((AttributesContainer) data).getAttributes() != null) {
+ for (Entry<QName, String> attribute : ((AttributesContainer) data).getAttributes().entrySet()) {
+ writer.writeAttribute(attribute.getKey().getNamespace().toString(), attribute.getKey().getLocalName(), attribute.getValue());
+ }
+ }
+
+ if (data instanceof SimpleNode<?>) {
+ LOG.debug("writeElement : node is of type SimpleNode");
+ // Simple node
+ if (schema instanceof LeafListSchemaNode) {
+ writeValue(writer, ((LeafListSchemaNode) schema).getType(), data.getValue());
+ } else if (schema instanceof LeafSchemaNode) {
+ writeValue(writer, ((LeafSchemaNode) schema).getType(), data.getValue());
+ } else {
+ Object value = data.getValue();
+ if (value != null) {
+ writer.writeCharacters(String.valueOf(value));
+ }
+ }
+ } else {
+ LOG.debug("writeElement : node is of type CompositeNode");
+ // CompositeNode
+ for (Node<?> child : ((CompositeNode) data).getValue()) {
+ DataSchemaNode childSchema = null;
+ if (schema instanceof DataNodeContainer) {
+ childSchema = SchemaUtils.findFirstSchema(child.getNodeType(), ((DataNodeContainer) schema).getChildNodes()).orNull();
+ if (childSchema == null) {
+ LOG.debug("Probably the data node \"{}\" does not conform to schema", child == null ? "" : child.getNodeType().getLocalName());
+ }
+ }
+
+ writeElement(writer, child, childSchema);
+ }
+ }
+
+ writer.writeEndElement();
+ }
+
+ /**
+ * Write a value into a XML stream writer. This method assumes the start and end of element is
+ * emitted by the caller.
+ *
+ * @param writer XML Stream writer
+ * @param type type definitions
+ * @param value object value
+ * @throws javax.xml.stream.XMLStreamException if an encoding problem occurs
+ */
+ public void writeValue(final @Nonnull XMLStreamWriter writer, final @Nonnull TypeDefinition<?> type, final Object value) throws XMLStreamException {
+ if (value == null) {
+ LOG.debug("Value of {}:{} is null, not encoding it", type.getQName().getNamespace(), type.getQName().getLocalName());
+ return;
+ }
+
+ final TypeDefinition<?> baseType = XmlUtils.resolveBaseTypeFrom(type);
+ if (baseType instanceof IdentityrefTypeDefinition) {
+ write(writer, (IdentityrefTypeDefinition) baseType, value);
+ } else if (baseType instanceof InstanceIdentifierTypeDefinition) {
+ write(writer, (InstanceIdentifierTypeDefinition) baseType, value);
+ } else {
+ final TypeDefinitionAwareCodec<Object, ?> codec = codecProvider.codecFor(baseType);
+ String text;
+ if (codec != null) {
+ try {
+ text = codec.serialize(value);
+ } catch (ClassCastException e) {
+ LOG.error("Provided node value {} did not have type {} required by mapping. Using stream instead.", value, baseType, e);
+ text = String.valueOf(value);
+ }
+ } else {
+ LOG.error("Failed to find codec for {}, falling back to using stream", baseType);
+ text = String.valueOf(value);
+ }
+ writer.writeCharacters(text);
+ }
+ }
+
+ private static void write(final @Nonnull XMLStreamWriter writer, final @Nonnull IdentityrefTypeDefinition type, final @Nonnull Object value) throws XMLStreamException {
+ if (value instanceof QName) {
+ final QName qname = (QName) value;
+ final String prefix;
+ if (qname.getPrefix() != null && !qname.getPrefix().isEmpty()) {
+ prefix = qname.getPrefix();
+ } else {
+ prefix = "x";
+ }
+
+ writer.writeNamespace(prefix, qname.getNamespace().toString());
+ writer.writeCharacters(prefix + ':' + qname.getLocalName());
+ } else {
+ LOG.debug("Value of {}:{} is not a QName but {}", type.getQName().getNamespace(), type.getQName().getLocalName(), value.getClass());
+ writer.writeCharacters(String.valueOf(value));
+ }
+ }
+
+ private static void write(final @Nonnull XMLStreamWriter writer, final @Nonnull InstanceIdentifierTypeDefinition type, final @Nonnull Object value) throws XMLStreamException {
+ if (value instanceof YangInstanceIdentifier) {
+ LOG.debug("Writing InstanceIdentifier object {}", value);
+ write(writer, (YangInstanceIdentifier)value);
+ } else {
+ LOG.debug("Value of {}:{} is not an InstanceIdentifier but {}", type.getQName().getNamespace(), type.getQName().getLocalName(), value.getClass());
+ writer.writeCharacters(String.valueOf(value));
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.xml.codec;
+
+import com.google.common.base.Optional;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.Node;
+import org.opendaylight.yangtools.yang.data.api.SimpleNode;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.XmlTreeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlCodecProvider;
+import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.activation.UnsupportedDataTypeException;
+import javax.annotation.Nonnull;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Common XML-related utility methods, which are not specific to a particular
+ * JAXP API.
+ */
+public class XmlUtils {
+
+ public static final XmlCodecProvider DEFAULT_XML_CODEC_PROVIDER = new XmlCodecProvider() {
+ @Override
+ public TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> codecFor(final TypeDefinition<?> baseType) {
+ return TypeDefinitionAwareCodec.from(baseType);
+ }
+ };
+
+ private XmlUtils() {
+ }
+
+ private static final String BLANK = "";
+ private static final Logger LOG = LoggerFactory.getLogger(XmlUtils.class);
+
+ /**
+ * Converts the composite node to xml using rpc input schema node
+ * @param cNode
+ * @param schemaContext
+ * @return xml String
+ */
+ public static String inputCompositeNodeToXml(CompositeNode cNode, SchemaContext schemaContext){
+ LOG.debug("Converting input composite node to xml {}", cNode);
+ if (cNode == null) return BLANK;
+
+ if(schemaContext == null) return BLANK;
+
+ Document domTree = null;
+ try {
+ Set<RpcDefinition> rpcs = schemaContext.getOperations();
+ for(RpcDefinition rpc : rpcs) {
+ if(rpc.getQName().equals(cNode.getNodeType())){
+ LOG.debug("Found the rpc definition from schema context matching with input composite node {}", rpc.getQName());
+
+ CompositeNode inputContainer = cNode.getFirstCompositeByName(QName.create(cNode.getNodeType(), "input"));
+ domTree = XmlDocumentUtils.toDocument(inputContainer, rpc.getInput(), XmlDocumentUtils.defaultValueCodecProvider());
+
+ LOG.debug("input composite node to document conversion complete, document is {}", domTree);
+ break;
+ }
+ }
+
+ } catch (UnsupportedDataTypeException e) {
+ LOG.error("Error during translation of CompositeNode to Document", e);
+ }
+ return domTransformer(domTree);
+ }
+
+ /**
+ * Converts the composite node to xml String using rpc output schema node
+ * @param cNode
+ * @param schemaContext
+ * @return xml string
+ */
+ public static String outputCompositeNodeToXml(CompositeNode cNode, SchemaContext schemaContext){
+ LOG.debug("Converting output composite node to xml {}", cNode);
+ if (cNode == null) return BLANK;
+
+ if(schemaContext == null) return BLANK;
+
+ Document domTree = null;
+ try {
+ Set<RpcDefinition> rpcs = schemaContext.getOperations();
+ for(RpcDefinition rpc : rpcs) {
+ if(rpc.getQName().equals(cNode.getNodeType())){
+ LOG.debug("Found the rpc definition from schema context matching with output composite node {}", rpc.getQName());
+
+ CompositeNode outputContainer = cNode.getFirstCompositeByName(QName.create(cNode.getNodeType(), "output"));
+ domTree = XmlDocumentUtils.toDocument(outputContainer, rpc.getOutput(), XmlDocumentUtils.defaultValueCodecProvider());
+
+ LOG.debug("output composite node to document conversion complete, document is {}", domTree);
+ break;
+ }
+ }
+
+ } catch (UnsupportedDataTypeException e) {
+ LOG.error("Error during translation of CompositeNode to Document", e);
+ }
+ return domTransformer(domTree);
+ }
+
+ private static String domTransformer(Document domTree) {
+ StringWriter writer = new StringWriter();
+ try {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(new DOMSource(domTree), new StreamResult(writer));
+ } catch (TransformerException e) {
+
+ LOG.error("Error during translation of Document to OutputStream", e);
+ }
+ LOG.debug("Document to string conversion complete, xml string is {} ", writer.toString());
+
+ return writer.toString();
+ }
+
+ public static CompositeNode xmlToCompositeNode(String xml){
+ if (xml==null || xml.length()==0) return null;
+
+ Node<?> dataTree;
+ try {
+ dataTree = XmlTreeBuilder.buildDataTree(new ByteArrayInputStream(xml.getBytes()));
+ } catch (XMLStreamException e) {
+ LOG.error("Error during building data tree from XML", e);
+ return null;
+ }
+ if (dataTree == null) {
+ LOG.error("data tree is null");
+ return null;
+ }
+ if (dataTree instanceof SimpleNode) {
+ LOG.error("RPC XML was resolved as SimpleNode");
+ return null;
+ }
+ return (CompositeNode) dataTree;
+ }
+
+ /**
+ * Converts the xml to composite node using rpc input schema node
+ * @param rpc
+ * @param xml
+ * @param schemaContext
+ * @return CompositeNode object based on the input, if any of the input parameter is null, a null object is returned
+ */
+ public static CompositeNode inputXmlToCompositeNode(QName rpc, String xml, SchemaContext schemaContext){
+ LOG.debug("Converting input xml to composite node {}", xml);
+ if (xml==null || xml.length()==0) return null;
+
+ if(rpc == null) return null;
+
+ if(schemaContext == null) return null;
+
+ CompositeNode compositeNode = null;
+ try {
+
+ Document doc = XmlUtil.readXmlToDocument(xml);
+ Set<RpcDefinition> rpcs = schemaContext.getOperations();
+ for(RpcDefinition rpcDef : rpcs) {
+ if(rpcDef.getQName().equals(rpc)){
+ LOG.debug("found the rpc definition from schema context matching rpc {}", rpc);
+
+ if(rpcDef.getInput() == null) {
+ LOG.warn("found rpc definition's input is null");
+ return null;
+ }
+
+ QName input = rpcDef.getInput().getQName();
+ NodeList nodeList = doc.getElementsByTagNameNS(input.getNamespace().toString(), "input");
+ if(nodeList == null || nodeList.getLength() < 1) {
+ LOG.warn("xml does not have input entry. {}", xml);
+ return null;
+ }
+ Element xmlData = (Element)nodeList.item(0);
+
+ List<Node<?>> dataNodes = XmlDocumentUtils.toDomNodes(xmlData,
+ Optional.of(rpcDef.getInput().getChildNodes()), schemaContext);
+
+ LOG.debug("Converted xml input to list of nodes {}", dataNodes);
+
+ final CompositeNodeBuilder<ImmutableCompositeNode> it = ImmutableCompositeNode.builder();
+ it.setQName(input);
+ it.add(ImmutableCompositeNode.create(input, dataNodes));
+ compositeNode = it.toInstance();
+ break;
+ }
+ }
+ } catch (SAXException e) {
+ LOG.error("Error during building data tree from XML", e);
+ } catch (IOException e) {
+ LOG.error("Error during building data tree from XML", e);
+ }
+
+ LOG.debug("Xml to composite node conversion complete {} ", compositeNode);
+ return compositeNode;
+ }
+
+ public static TypeDefinition<?> resolveBaseTypeFrom(final @Nonnull TypeDefinition<?> type) {
+ TypeDefinition<?> superType = type;
+ while (superType.getBaseType() != null) {
+ superType = superType.getBaseType();
+ }
+ return superType;
+ }
+
+ /**
+ * This code is picked from yangtools and modified to add type of instance identifier
+ * output of instance identifier something like below for a flow ref composite node of type instance identifier,
+ * which has path arguments with predicates, whose value is of type java.lang.short
+ * <flow-ref xmlns:bgkj="urn:opendaylight:flow:inventory" xmlns:jdlk="urn:opendaylight:inventory">
+ * /jdlk:nodes/jdlk:node[jdlk:id='openflow:205558455098190@java.lang.String']
+ * /bgkj:table[bgkj:id='3@java.lang.Short']
+ * /bgkj:flow[bgkj:id='156@java.lang.String']
+ * </flow-ref>
+ *
+ */
+
+ public static String encodeIdentifier(final RandomPrefix prefixes, final YangInstanceIdentifier id) {
+ StringBuilder textContent = new StringBuilder();
+ for (PathArgument pathArgument : id.getPathArguments()) {
+ textContent.append('/');
+ textContent.append(prefixes.encodeQName(pathArgument.getNodeType()));
+ if (pathArgument instanceof NodeIdentifierWithPredicates) {
+ Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
+
+ for (QName keyValue : predicates.keySet()) {
+ Object value = predicates.get(keyValue);
+ String type = value.getClass().getName();
+ String predicateValue = String.valueOf(value);
+ textContent.append('[');
+ textContent.append(prefixes.encodeQName(keyValue));
+ textContent.append("='");
+ textContent.append(predicateValue);
+ textContent.append("@");
+ textContent.append(type);
+ textContent.append("']");
+ }
+ } else if (pathArgument instanceof NodeWithValue) {
+ textContent.append("[.='");
+ textContent.append(((NodeWithValue) pathArgument).getValue());
+ textContent.append("']");
+ }
+ }
+
+ return textContent.toString();
+ }
+}