Retrofit mount-point awareness into yang-data-util 77/82977/20
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 9 Jul 2019 10:57:04 +0000 (12:57 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 9 Jul 2019 23:10:49 +0000 (01:10 +0200)
{Container,ListEntry}NodeDataWithSchema can potentially contain
out-of-band mount point data, which needs intermediate processing
once all data has been acquired.

We introduce AbstractMountPointDataWithSchema as a common superclass,
which optionally holds MountPointData.

MountPointData contains enough information to resolve the data nodes
in the context of a schema context supplied either statically or
dynamically (based on the mount data).

JIRA: YANGTOOLS-1007
Change-Id: I08ba4dcf3aaf758f12b42e625d9d17a774d07825
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/rfc8528-data-api/src/main/java/org/opendaylight/yangtools/rfc8528/data/api/MountPointStreamWriter.java
yang/rfc8528-model-api/src/main/java/org/opendaylight/yangtools/rfc8528/model/api/StaticMountPointSchemaResolver.java
yang/rfc8528-model-api/src/main/java/org/opendaylight/yangtools/rfc8528/model/api/YangLibraryConstants.java
yang/yang-data-util/pom.xml
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractMountPointDataWithSchema.java [new file with mode: 0644]
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ContainerNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ListEntryNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/MountPointChild.java [new file with mode: 0644]
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/MountPointData.java [new file with mode: 0644]

index 5c020ad485359c37d8a4d750d75e8ac8c1fc2283..c2fbe802a477b0405025d71a5ea15fef05c85d17 100644 (file)
@@ -12,7 +12,9 @@ import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.rfc8528.model.api.MountPointSchemaResolver;
 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;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 /**
  * A {@link NormalizedNodeStreamWriterExtension} exposed by stream writers which can handle mount point data, notably
@@ -30,4 +32,13 @@ public interface MountPointStreamWriter extends NormalizedNodeStreamWriterExtens
      * @throws NullPointerException if label is null
      */
     Optional<MountPointSchemaResolver> findMountPoint(@NonNull QName label);
+
+    /**
+     * Start a new mount point with a specific root context.
+     *
+     * @param label Mount point label
+     * @param mountContext SchemaContext associated with the context
+     * @return A new NormalizedNodeStreamWriter, or empty if the mount point data should be ignored
+     */
+    Optional<NormalizedNodeStreamWriter> startMountPoint(@NonNull QName label, @NonNull SchemaContext mountContext);
 }
index 8831c498b3e0a5b627dc1dd52b01efe6ade8dc97..88715ab177833fd8cc271601315549c0cbade2af 100644 (file)
@@ -20,5 +20,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
 @Beta
 public interface StaticMountPointSchemaResolver extends MountPointSchemaResolver, SchemaContextProvider {
     @Override
+    // FIXME: remove this override when SchemaContextProvider's method has sane semantics.
     @NonNull SchemaContext getSchemaContext();
 }
index 25392c7d48f9b944e908dfe8175a729ba6060ef3..98af1e6c903e00ddd1c2a1ec0bdc9432ccdd2258 100644 (file)
@@ -10,7 +10,11 @@ package org.opendaylight.yangtools.rfc8528.model.api;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 import java.net.URI;
+import java.util.Arrays;
+import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 
 /**
@@ -42,17 +46,22 @@ public final class YangLibraryConstants {
     public static final String MODULE_NAME = "ietf-yang-library";
 
     /**
-     * Top-level containers which hold YANG Library information.
+     * Top-level containers which hold YANG Library information, ordered by descending preference, with more modern
+     * and/or preferred entries first.
      */
     public enum ContainerName {
+        // Note: order this enum from most-preferred to least-preferred name
         /**
-         * Container in RFC7895 (pre-NMDA) YANG Library.
+         * Container in RFC8525 (NMDA) YANG Library.
          */
-        RFC7895("modules-state"),
+        RFC8525("yang-library"),
         /**
-         * Container in RFC8525 (NMDA) YANG Library.
+         * Container in RFC7895 (pre-NMDA) YANG Library.
          */
-        RFC8525("yang-library");
+        RFC7895("modules-state");
+
+        private static final ImmutableMap<String, ContainerName> NAME_TO_ENUM = Maps.uniqueIndex(
+            Arrays.asList(values()), ContainerName::getLocalName);
 
         private final String localName;
 
@@ -63,6 +72,10 @@ public final class YangLibraryConstants {
         public String getLocalName() {
             return localName;
         }
+
+        public static Optional<ContainerName> forLocalName(final String localName) {
+            return Optional.ofNullable(NAME_TO_ENUM.get(requireNonNull(localName)));
+        }
     }
 
     private YangLibraryConstants() {
index 93da76e8743d41564b7839e0d20950ed5c6274a7..08a67b249eaa7d0a762101238e78935a873fdf3d 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>rfc7952-data-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc8528-data-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-model-api</artifactId>
diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractMountPointDataWithSchema.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractMountPointDataWithSchema.java
new file mode 100644 (file)
index 0000000..e8eb9a4
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.yang.data.util;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.Beta;
+import java.io.IOException;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataStreamWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+/**
+ * A {@link CompositeNodeDataWithSchema} which can hold mount-point data. This data is manipulated through
+ * {@link #getMountPointData(QName)}.
+ */
+@Beta
+public abstract class AbstractMountPointDataWithSchema<T extends DataSchemaNode>
+        extends CompositeNodeDataWithSchema<T> {
+    private MountPointData mountedData;
+
+    AbstractMountPointDataWithSchema(final T schema) {
+        super(schema);
+    }
+
+    @Override
+    public void write(final NormalizedNodeStreamWriter writer, final NormalizedMetadataStreamWriter metaWriter)
+            throws IOException {
+        super.write(writer, metaWriter);
+        if (mountedData != null) {
+            mountedData.write(writer);
+        }
+    }
+
+    public final MountPointData getMountPointData(final QName label) {
+        if (mountedData != null) {
+            final QName existing = mountedData.getIdentifier();
+            checkState(label.equals(existing), "Mismatched mount label {}, already have {}", label, existing);
+        } else {
+            mountedData = new MountPointData(label);
+        }
+        return mountedData;
+    }
+}
index feb41b446dc097f3ab3d7707c2821c1033817c4a..ca0bb2a6685897985c74031e8c8ac231af6ea2d3 100644 (file)
@@ -19,7 +19,7 @@ import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
  * <p>
  * Represents a YANG container node.
  */
-public class ContainerNodeDataWithSchema extends CompositeNodeDataWithSchema<ContainerSchemaNode> {
+public class ContainerNodeDataWithSchema extends AbstractMountPointDataWithSchema<ContainerSchemaNode> {
 
     public ContainerNodeDataWithSchema(final ContainerSchemaNode schema) {
         super(schema);
index 930d74eb062c489761634fd64806ccbcc10b010c..483315f286c37f5c1ec19c77d8c5af3865c6e11f 100644 (file)
@@ -29,7 +29,7 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
  * <p>
  * Represents a YANG list entry node.
  */
-public abstract class ListEntryNodeDataWithSchema extends CompositeNodeDataWithSchema<ListSchemaNode> {
+public abstract class ListEntryNodeDataWithSchema extends AbstractMountPointDataWithSchema<ListSchemaNode> {
     private static final class Keyed extends ListEntryNodeDataWithSchema {
         private final Map<QName, SimpleNodeDataWithSchema<?>> keyValues = new HashMap<>();
         // This template results in Maps in schema definition order
diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/MountPointChild.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/MountPointChild.java
new file mode 100644 (file)
index 0000000..6bee66f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.yang.data.util;
+
+import com.google.common.annotations.Beta;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizableAnydata;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * A raw child of {@link MountPointData}. This is similar in functionality to {@link NormalizableAnydata}, but
+ * rather than normalizing, the data is fed into a combination of a SchemaContext and NormalizedNodeStreamWriter.
+ */
+@Beta
+@NonNullByDefault
+public interface MountPointChild {
+    /**
+     * Stream this child into a writer, with the help of a SchemaContext.
+     *
+     * @param writer Writer to emit the child into
+     * @param schemaContext SchemaContext for normalization purposes
+     * @throws IOException if an underlying error occurs
+     * @throws NullPointerException if any of the arguments is null
+     */
+    void writeTo(NormalizedNodeStreamWriter writer, SchemaContext schemaContext) throws IOException;
+
+    /**
+     * Normalized this child to a particular SchemaContext.
+     *
+     * @param schemaContext SchemaContext for normalization purposes
+     * @return A NormalizedNode representation of this child
+     * @throws IOException if an underlying error occurs
+     * @throws NullPointerException if any of the arguments is null
+     */
+    NormalizedNode<?, ?> normalizeTo(SchemaContext schemaContext) throws IOException;
+}
diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/MountPointData.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/MountPointData.java
new file mode 100644 (file)
index 0000000..52892ad
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.yang.data.util;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.AbstractIdentifiable;
+import org.opendaylight.yangtools.rfc8528.data.api.DynamicMountPointSchemaResolver;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointStreamWriter;
+import org.opendaylight.yangtools.rfc8528.model.api.MountPointSchemaResolver;
+import org.opendaylight.yangtools.rfc8528.model.api.StaticMountPointSchemaResolver;
+import org.opendaylight.yangtools.rfc8528.model.api.YangLibraryConstants.ContainerName;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * YANG Schema Mount-supported data attached to either a {@code list} item or a {@code container}.
+ */
+@Beta
+public final class MountPointData extends AbstractIdentifiable<QName> {
+    private static final Logger LOG = LoggerFactory.getLogger(MountPointData.class);
+
+    private final Map<ContainerName, MountPointChild> yangLib = new EnumMap<>(ContainerName.class);
+    private final List<MountPointChild> children = new ArrayList<>();
+
+    MountPointData(final QName label) {
+        super(label);
+    }
+
+    public void setContainer(final @NonNull ContainerName containerName, final @NonNull MountPointChild data) {
+        final MountPointChild prev = yangLib.putIfAbsent(containerName, requireNonNull(data));
+        checkState(prev == null, "Attempted to duplicate container %s data %s with %s", containerName, prev, data);
+        addChild(data);
+    }
+
+    public void addChild(final @NonNull MountPointChild data) {
+        children.add(requireNonNull(data));
+    }
+
+    void write(final @NonNull NormalizedNodeStreamWriter writer) throws IOException {
+        final MountPointStreamWriter mountWriter = writer.getExtensions().getInstance(MountPointStreamWriter.class);
+        if (mountWriter == null) {
+            LOG.debug("Writer {} does not support mount points, ignoring data in {}", writer, getIdentifier());
+            return;
+        }
+
+        final Optional<MountPointSchemaResolver> optResolver = mountWriter.findMountPoint(getIdentifier());
+        if (!optResolver.isPresent()) {
+            LOG.debug("Mount point for {} is not present, ignoring it", getIdentifier());
+            return;
+        }
+
+        final MountPointSchemaResolver resolver = optResolver.get();
+        if (resolver instanceof StaticMountPointSchemaResolver) {
+            writeStatic(mountWriter, ((StaticMountPointSchemaResolver) resolver).getSchemaContext());
+        } else if (resolver instanceof DynamicMountPointSchemaResolver) {
+            writeDynamic(mountWriter, (DynamicMountPointSchemaResolver) resolver);
+        } else {
+            throw new IOException("Unhandled resolver " + resolver);
+        }
+    }
+
+    private void writeDynamic(final @NonNull MountPointStreamWriter mountWriter,
+            final DynamicMountPointSchemaResolver resolver) throws IOException {
+        for (Entry<ContainerName, MountPointChild> entry : yangLib.entrySet()) {
+            final Optional<SchemaContext> optContext = resolver.findContainerContext(entry.getKey());
+            if (!optContext.isPresent()) {
+                LOG.debug("YANG Library context for mount point {} container {} not found", getIdentifier(),
+                    entry.getKey());
+                continue;
+            }
+
+            final NormalizedNode<?, ?> data = entry.getValue().normalizeTo(optContext.get());
+            if (!(data instanceof ContainerNode)) {
+                throw new IOException("Invalid non-container " + data);
+            }
+
+            final SchemaContext context;
+            try {
+                context = resolver.assembleSchemaContext((ContainerNode) data);
+            } catch (YangParserException e) {
+                throw new IOException("Failed to assemble context for " + data, e);
+            }
+
+            writeStatic(mountWriter, context);
+            return;
+        }
+
+        LOG.warn("Failed to create a dynamic context for mount point {}, ignoring its data", getIdentifier());
+    }
+
+    private void writeStatic(final @NonNull MountPointStreamWriter mountWriter,
+            final @NonNull SchemaContext schemaContext) throws IOException {
+        final Optional<NormalizedNodeStreamWriter> optWriter = mountWriter.startMountPoint(getIdentifier(),
+            schemaContext);
+        if (!optWriter.isPresent()) {
+            LOG.debug("Ignoring mount point {} data due to writer decision", getIdentifier());
+            return;
+        }
+
+        try (NormalizedNodeStreamWriter writer = optWriter.get()) {
+            for (MountPointChild child : children) {
+                child.writeTo(writer, schemaContext);
+            }
+        }
+    }
+}