Add RFC7952 data model extensions 43/80643/29
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 28 Feb 2019 12:48:56 +0000 (13:48 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 5 Mar 2019 11:49:18 +0000 (12:49 +0100)
RFC7952 has implications on NormalizedNodeStreamWriter, namely
the ability to emit metadata attached to NormalizedNodes.

This patch introduces the concept of NormalizedMetadata, which
is the metadata counterpart to NormalizedNode. It also defines
NormalizedMetadataStreamWriter as an extension of
NormalizedNodeStreamWriter and implements NormalizedMetadataWriter,
which piggy-backs a NormalizedMetadata structure on top of a
NormalizedNode structure, so that the two are emitted as a single
event stream.

JIRA: YANGTOOLS-961
Change-Id: I8f1a69361d18cf5f9c14185778cca7c2d6ebc4f7
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
artifacts/pom.xml
features/odl-yangtools-data-api/pom.xml
yang/pom.xml
yang/rfc7952-data-api/pom.xml [new file with mode: 0644]
yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadata.java [new file with mode: 0644]
yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadataContainer.java [new file with mode: 0644]
yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadataStreamWriter.java [new file with mode: 0644]
yang/rfc7952-data-util/pom.xml [new file with mode: 0644]
yang/rfc7952-data-util/src/main/java/org/opendaylight/yangtools/rfc7952/data/util/NormalizedMetadataWriter.java [new file with mode: 0644]
yang/rfc7952-data-util/src/main/java/org/opendaylight/yangtools/rfc7952/data/util/NormalizedNodeStreamWriterMetadataDecorator.java [new file with mode: 0644]

index 8f166773b4303ed9c9b9ff943719526b40dd0c4b..d7a73660cb6770ea71317ea9fd6e40f54dd048c6 100644 (file)
                 <version>3.0.0-SNAPSHOT</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.opendaylight.yangtools</groupId>
+                <artifactId>rfc7952-data-api</artifactId>
+                <version>3.0.0-SNAPSHOT</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.yangtools</groupId>
+                <artifactId>rfc7952-data-util</artifactId>
+                <version>3.0.0-SNAPSHOT</version>
+            </dependency>
             <dependency>
                 <groupId>org.opendaylight.yangtools</groupId>
                 <artifactId>rfc7952-model-api</artifactId>
index 0c769efcb68027157be05cc15af29dda99dd32a7..cf64d8fb06a5523e33005b5a48061be97248ba13 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-data-util</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc7952-data-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc7952-data-util</artifactId>
+        </dependency>
     </dependencies>
 </project>
index 73c9914334a1884cf3cfc2452fe7aa64eca7afdb..aab8e175b29adbe4f2fe1cdc9b77e35b8364bab3 100644 (file)
@@ -74,6 +74,8 @@
         <module>rfc6536-parser-support</module>
 
         <!-- Metadata (annotation) metamodel support -->
+        <module>rfc7952-data-api</module>
+        <module>rfc7952-data-util</module>
         <module>rfc7952-model-api</module>
         <module>rfc7952-parser-support</module>
 
diff --git a/yang/rfc7952-data-api/pom.xml b/yang/rfc7952-data-api/pom.xml
new file mode 100644 (file)
index 0000000..812c2a8
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2019 PANTHEON.tech, s.r.o. 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>bundle-parent</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../../bundle-parent</relativePath>
+    </parent>
+
+    <artifactId>rfc7952-data-api</artifactId>
+    <packaging>bundle</packaging>
+    <name>${project.artifactId}</name>
+    <description>RFC7952 data model extensions</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>concepts</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc7952-model-api</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.github.spotbugs</groupId>
+                <artifactId>spotbugs-maven-plugin</artifactId>
+                <configuration>
+                    <failOnError>true</failOnError>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadata.java b/yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadata.java
new file mode 100644 (file)
index 0000000..813d7ff
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.rfc7952.data.api;
+
+import com.google.common.annotations.Beta;
+import java.util.Map;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+
+/**
+ * RFC7952 metadata counterpart to a {@link NormalizedNode}. This interface is meant to be used as a companion to
+ * a NormalizedNode instance, hence it does not support iterating over its structure like it is possible with
+ * {@link NormalizedNode#getValue()}.
+ *
+ * <p>
+ * This model of metadata <em>does not</em> have the RFC7952 restriction on metadata attachment to {@code list}s and
+ * {@code leaf-list}s because NormalizedNode data model has {@link LeafSetNode}, {@link MapNode} and
+ * {@link UnkeyedListNode} to which metadata can be attached.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public interface NormalizedMetadata extends Identifiable<PathArgument>, Immutable {
+    /**
+     * Return the set of annotations defined in this metadata node. Values are expected to be effectively-immutable
+     * scalar types, like {@link String}s, {@link Number}s and similar.
+     *
+     * @return The set of annotations attached to the corresponding data node.
+     */
+    @NonNull Map<QName, Object> getAnnotations();
+}
diff --git a/yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadataContainer.java b/yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadataContainer.java
new file mode 100644 (file)
index 0000000..99b939e
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.rfc7952.data.api;
+
+import com.google.common.annotations.Beta;
+import java.util.Optional;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+/**
+ * RFC7952 metadata counterpart to a {@link NormalizedNodeContainer}.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public interface NormalizedMetadataContainer extends NormalizedMetadata {
+    /**
+     * Returns child node identified by provided key.
+     *
+     * @param child Path argument identifying child node
+     * @return Optional with child node if child exists, {@link Optional#empty()} if it does not.
+     */
+    Optional<? extends NormalizedMetadata> getChild(PathArgument child);
+}
diff --git a/yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadataStreamWriter.java b/yang/rfc7952-data-api/src/main/java/org/opendaylight/yangtools/rfc7952/data/api/NormalizedMetadataStreamWriter.java
new file mode 100644 (file)
index 0000000..f4b9e34
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.rfc7952.data.api;
+
+import com.google.common.annotations.Beta;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.rfc7952.model.api.AnnotationSchemaNode;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriterExtension;
+
+/**
+ * Extension to the NormalizedNodeStreamWriter with metadata support. Semantically this extends the event model of
+ * {@link NormalizedNodeStreamWriter} with two new events:
+ * <ul>
+ * <li>{@link #startMetadata(int)} is within the scope of any open node and starts a block of metadata entries. It
+ * is recommended to emit this block before any other events. Only
+ * {@link #startMetadataEntry(QName, AnnotationSchemaNode)} and {@link NormalizedNodeStreamWriter#endNode()} are valid
+ * in this node. This event may be emitted at most once for any open node.
+ * </li>
+ * <li>{@link #startMetadataEntry(QName, AnnotationSchemaNode)} to start a metadata entry. Its value is must be emitted
+ * via {@link NormalizedNodeStreamWriter#nodeValue(Object)} before the entry is closed via
+ * {@link NormalizedNodeStreamWriter#endNode()}.
+ * </ul>
+ *
+ * <p>
+ * Note that some implementations of this interface, notably those targeting streaming XML, may require metadata to
+ * be emitted before any other events. Such requirement is communicated through {@link #requireMetadataFirst()} and
+ * users must honor it. If such requirement is not set, metadata may be emitted at any time.
+ *
+ * <p>
+ * Furthermore implementations targeting RFC7952 encoding towards external systems are required to handle metadata
+ * attached to {@code leaf-list} and {@code list} nodes by correctly extending them to each entry.
+ */
+@Beta
+public interface NormalizedMetadataStreamWriter extends NormalizedNodeStreamWriterExtension {
+    /**
+     * Start the metadata block for the currently-open node. Allowed events are
+     * {@link #startMetadataEntry(QName, AnnotationSchemaNode)} and {@link NormalizedNodeStreamWriter#endNode()}.
+     *
+     * @param childSizeHint Non-negative count of expected direct child nodes or
+     *                      {@link NormalizedNodeStreamWriter#UNKNOWN_SIZE} if count is unknown. This is only hint and
+     *                      should not fail writing of child events, if there are more events than count.
+     * @throws IllegalStateException if current node already has a metadata block or cannot receive metadata -- for
+     *                               example because {@link #requireMetadataFirst()} was not honored.
+     * @throws IOException if an underlying IO error occurs
+     */
+    void startMetadata(int childSizeHint) throws IOException;
+
+    /**
+     * Start a new metadata entry. The value of the metadata entry should be emitted through
+     * {@link NormalizedNodeStreamWriter#nodeValue(Object)}.
+     *
+     * @param name Metadata name, as defined through {@code md:annotation}
+     * @param schema Effective {@code md:annotation} schema, or null if unknown to the caller
+     * @throws NullPointerException if {@code name} is null
+     * @throws IllegalStateException when this method is invoked outside of a metadata block
+     * @throws IOException if an underlying IO error occurs
+     */
+    void startMetadataEntry(@NonNull QName name, @Nullable AnnotationSchemaNode schema) throws IOException;
+
+    /**
+     * Indicate whether metadata is required to be emitted just after an entry is open. The default implementation
+     * returns false.
+     *
+     * @return True if metadata must occur just after the start of an entry.
+     */
+    default boolean requireMetadataFirst() {
+        return false;
+    }
+}
diff --git a/yang/rfc7952-data-util/pom.xml b/yang/rfc7952-data-util/pom.xml
new file mode 100644 (file)
index 0000000..0449171
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2019 PANTHEON.tech, s.r.o. 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>bundle-parent</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../../bundle-parent</relativePath>
+    </parent>
+
+    <artifactId>rfc7952-data-util</artifactId>
+    <packaging>bundle</packaging>
+    <name>${project.artifactId}</name>
+    <description>RFC7952 data model utilities</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>concepts</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-data-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc7952-data-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc7952-model-api</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.github.spotbugs</groupId>
+                <artifactId>spotbugs-maven-plugin</artifactId>
+                <configuration>
+                    <failOnError>true</failOnError>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/yang/rfc7952-data-util/src/main/java/org/opendaylight/yangtools/rfc7952/data/util/NormalizedMetadataWriter.java b/yang/rfc7952-data-util/src/main/java/org/opendaylight/yangtools/rfc7952/data/util/NormalizedMetadataWriter.java
new file mode 100644 (file)
index 0000000..aac60f8
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.rfc7952.data.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+
+/**
+ * A utility class to attach {@link NormalizedMetadata} into a NormalizedNode stream, such as the one produced by
+ * {@link NormalizedNodeWriter}, so that a target {@link NormalizedNodeStreamWriter} sees both data and metadata in
+ * the stream. A typical use would like this:
+ *
+ * <p>
+ * <code>
+ *   // Data for output
+ *   NormalizedNode&lt;?, ?&gt; data;
+ *   // Metadata for output
+ *   NormalizedMetadata metadata;
+ *
+ *   // Target output writer
+ *   NormalizedNodeStreamWriter output = ...;
+ *   // Metadata writer
+ *   NormalizedMetadataStreamWriter metaWriter = NormalizedMetadataWriter.forStreamWriter(output);
+ *
+ *   // Write a normalized node and its metadata
+ *   dataWriter.write(data, metadata);
+ * </code>
+ *
+ * @author Robert Varga
+ */
+@Beta
+@NotThreadSafe
+public final class NormalizedMetadataWriter implements Closeable, Flushable {
+    private final NormalizedNodeStreamWriter writer;
+    private final boolean orderKeyLeaves;
+
+    private NormalizedMetadataWriter(final NormalizedNodeStreamWriter writer, final boolean orderKeyLeaves) {
+        this.writer = requireNonNull(writer);
+        this.orderKeyLeaves = orderKeyLeaves;
+    }
+
+    /**
+     * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple
+     * {@link #forStreamWriter(NormalizedNodeStreamWriter)} method, this allows the caller to switch off RFC6020 XML
+     * compliance, providing better throughput. The reason is that the XML mapping rules in RFC6020 require
+     * the encoding to emit leaf nodes which participate in a list's key first and in the order in which they are
+     * defined in the key. For JSON, this requirement is completely relaxed and leaves can be ordered in any way we
+     * see fit. The former requires a bit of work: first a lookup for each key and then for each emitted node we need
+     * to check whether it was already emitted.
+     *
+     * @param writer Back-end writer
+     * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
+     * @return A new instance.
+     */
+    public static @NonNull NormalizedMetadataWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+            final boolean orderKeyLeaves) {
+        return new NormalizedMetadataWriter(writer, orderKeyLeaves);
+    }
+
+    /**
+     * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. This is a convenience method for
+     * {@code forStreamWriter(writer, true)}.
+     *
+     * @param writer Back-end writer
+     * @return A new instance.
+     */
+    public static @NonNull NormalizedMetadataWriter forStreamWriter(final NormalizedNodeStreamWriter writer) {
+        return forStreamWriter(writer, true);
+    }
+
+    /**
+     * Iterate over the provided {@link NormalizedNode} and {@link NormalizedMetadata} and emit write events to the
+     * encapsulated {@link NormalizedNodeStreamWriter}.
+     *
+     * @param data NormalizedNode data
+     * @param metadata {@link NormalizedMetadata} metadata
+     * @return NormalizedNodeWriter this
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if metadata does not match data
+     * @throws IOException when thrown from the backing writer.
+     */
+    public @NonNull NormalizedMetadataWriter write(final NormalizedNode<?, ?> data, final NormalizedMetadata metadata)
+            throws IOException {
+        final PathArgument dataId = data.getIdentifier();
+        final PathArgument metaId = metadata.getIdentifier();
+        checkArgument(dataId.equals(metaId), "Mismatched data %s and metadata %s", dataId, metaId);
+
+        final NormalizedMetadataStreamWriter metaWriter = writer.getExtensions()
+                .getInstance(NormalizedMetadataStreamWriter.class);
+        final NormalizedNodeStreamWriter delegate = metaWriter == null ? writer
+                : new NormalizedNodeStreamWriterMetadataDecorator(writer, metaWriter, metadata);
+
+        final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(delegate, orderKeyLeaves);
+        nnWriter.write(data);
+        nnWriter.flush();
+        return this;
+    }
+
+    @Override
+    public void close() throws IOException {
+        writer.flush();
+        writer.close();
+    }
+
+    @Override
+    public void flush() throws IOException {
+        writer.flush();
+    }
+}
diff --git a/yang/rfc7952-data-util/src/main/java/org/opendaylight/yangtools/rfc7952/data/util/NormalizedNodeStreamWriterMetadataDecorator.java b/yang/rfc7952-data-util/src/main/java/org/opendaylight/yangtools/rfc7952/data/util/NormalizedNodeStreamWriterMetadataDecorator.java
new file mode 100644 (file)
index 0000000..9f8bfbe
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.rfc7952.data.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataContainer;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataStreamWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+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.api.schema.stream.ForwardingNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * A simple decorator on top of a NormalizedNodeStreamWriter, which attaches NormalizedMetadata to the event stream,
+ * so that the metadata is emitted along with data.
+ */
+final class NormalizedNodeStreamWriterMetadataDecorator extends ForwardingNormalizedNodeStreamWriter {
+    private final Deque<NormalizedMetadata> stack = new ArrayDeque<>();
+    private final NormalizedMetadataStreamWriter metaWriter;
+    private final NormalizedNodeStreamWriter writer;
+    private final NormalizedMetadata metadata;
+
+    NormalizedNodeStreamWriterMetadataDecorator(final NormalizedNodeStreamWriter writer,
+            final NormalizedMetadataStreamWriter metaWriter, final NormalizedMetadata metadata) {
+        this.writer = requireNonNull(writer);
+        this.metaWriter = requireNonNull(metaWriter);
+        this.metadata = requireNonNull(metadata);
+    }
+
+    @Override
+    protected NormalizedNodeStreamWriter delegate() {
+        return writer;
+    }
+
+    @Override
+    public void startLeafNode(final NodeIdentifier name) throws IOException {
+        super.startLeafNode(name);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startLeafSet(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startOrderedLeafSet(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
+        super.startLeafSetEntryNode(name);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startContainerNode(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startUnkeyedList(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startUnkeyedListItem(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startMapNode(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
+            throws IOException {
+        super.startMapEntryNode(identifier, childSizeHint);
+        enterMetadataNode(identifier);
+    }
+
+    @Override
+    public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startOrderedMapNode(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startChoiceNode(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
+        super.startAugmentationNode(identifier);
+        enterMetadataNode(identifier);
+    }
+
+    @Override
+    public void startAnyxmlNode(final NodeIdentifier name) throws IOException {
+        super.startAnyxmlNode(name);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+        super.startYangModeledAnyXmlNode(name, childSizeHint);
+        enterMetadataNode(name);
+    }
+
+    @Override
+    public void endNode() throws IOException {
+        super.endNode();
+        stack.pop();
+    }
+
+    private void enterMetadataNode(final PathArgument name) throws IOException {
+        final NormalizedMetadata child = findMetadata(name);
+        if (child != null) {
+            emitAnnotations(child.getAnnotations());
+        }
+        stack.push(child);
+    }
+
+    private @Nullable NormalizedMetadata findMetadata(final PathArgument name) {
+        final NormalizedMetadata current = stack.peek();
+        if (current instanceof NormalizedMetadataContainer) {
+            return ((NormalizedMetadataContainer) current).getChild(name).orElse(null);
+        }
+
+        // This may either be the first entry or unattached metadata nesting
+        return stack.isEmpty() ? metadata : null;
+    }
+
+    private void emitAnnotations(final Map<QName, Object> annotations) throws IOException {
+        if (!annotations.isEmpty()) {
+            metaWriter.startMetadata(annotations.size());
+            for (Entry<QName, Object> entry : annotations.entrySet()) {
+                metaWriter.startMetadataEntry(entry.getKey(), null);
+                writer.nodeValue(entry.getValue());
+                writer.endNode();
+            }
+            writer.endNode();
+        }
+    }
+}