Introduce DataPutPath 88/109088/1
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 28 Nov 2023 03:45:38 +0000 (04:45 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 28 Nov 2023 03:48:13 +0000 (04:48 +0100)
We need a way to identify resources, the PUT operation on top of /data
provides the semantics. This again allows us to share codecs.

JIRA: NETCONF-1157
Change-Id: I7099ce5e48d080b734705005c6331bd9a11a173c
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonResourceBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/ResourceBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlResourceBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataPutPath.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractResourceBodyTest.java

index 2d7ffd4dc3dd9109ff198874a9b47d4eb1682379..7146ea582751c5bcef7a5b096c79857b21c5d23b 100644 (file)
@@ -12,11 +12,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
 /**
  * A JSON-encoded {@link ResourceBody}.
@@ -27,22 +26,23 @@ public final class JsonResourceBody extends ResourceBody {
     }
 
     @Override
-    void streamTo(final InputStream inputStream, final Inference inference, final PathArgument name,
+    void streamTo(final DataPutPath path, final PathArgument name, final InputStream inputStream,
             final NormalizedNodeStreamWriter writer) throws IOException {
-        try (var jsonParser = newParser(inference, writer)) {
+        try (var jsonParser = newParser(path, writer)) {
             try (var reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                 jsonParser.parse(reader);
             }
         }
     }
 
-    private static JsonParserStream newParser(final Inference inference, final NormalizedNodeStreamWriter writer) {
-        final var codecs = JSONCodecFactorySupplier.RFC7951.getShared(inference.getEffectiveModelContext());
-        final var stack = inference.toSchemaInferenceStack();
-        if (stack.isEmpty()) {
+    private static JsonParserStream newParser(final DataPutPath path, final NormalizedNodeStreamWriter writer) {
+        final var codecs = path.databind().jsonCodecs();
+        final var inference = path.inference();
+        if (inference.isEmpty()) {
             return JsonParserStream.create(writer, codecs);
         }
 
+        final var stack = inference.toSchemaInferenceStack();
         stack.exit();
         return JsonParserStream.create(writer, codecs, stack.toInference());
     }
index 8f2d3d2ec6914caa4f1272adb0cb87d0f210c3b1..3db17365dfcfc8835d91ef4aae7c5f994dd0ed8f 100644 (file)
@@ -15,6 +15,7 @@ import java.util.List;
 import java.util.Map;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.restconf.restconf.Data;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -31,7 +32,6 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeS
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,17 +51,17 @@ public abstract sealed class ResourceBody extends AbstractBody permits JsonResou
      * Acquire the {@link NormalizedNode} representation of this body.
      *
      * @param path A {@link YangInstanceIdentifier} corresponding to the body
-     * @param inference An {@link Inference} the statement corresponding to the body
      * @throws RestconfDocumentedException if the body cannot be decoded or it does not match {@code path}
      */
     // TODO: pass down DatabindContext corresponding to inference
     @SuppressWarnings("checkstyle:illegalCatch")
-    public @NonNull NormalizedNode toNormalizedNode(final @NonNull YangInstanceIdentifier path,
-            final @NonNull Inference inference, final @NonNull SchemaNode schemaNode) {
-        final var expected = path.isEmpty() ? DATA_NID : path.getLastPathArgument();
+    public @NonNull NormalizedNode toNormalizedNode(final @NonNull DataPutPath path,
+            final @NonNull SchemaNode schemaNode) {
+        final var instance = path.instance();
+        final var expected = instance.isEmpty() ? DATA_NID : instance.getLastPathArgument();
         final var holder = new NormalizationResultHolder();
         try (var streamWriter = ImmutableNormalizedNodeStreamWriter.from(holder)) {
-            streamTo(acquireStream(), inference, expected, streamWriter);
+            streamTo(path, expected, acquireStream(), streamWriter);
         } catch (IOException e) {
             LOG.debug("Error reading input", e);
             throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
@@ -84,12 +84,11 @@ public abstract sealed class ResourceBody extends AbstractBody permits JsonResou
         }
 
         validTopLevelNodeName(expected, data);
-        validateListKeysEqualityInPayloadAndUri(schemaNode, path, data);
-
+        validateListKeysEqualityInPayloadAndUri(schemaNode, instance, data);
         return data;
     }
 
-    abstract void streamTo(@NonNull InputStream inputStream, @NonNull Inference inference, @NonNull PathArgument name,
+    abstract void streamTo(@NonNull DataPutPath path, @NonNull PathArgument name, @NonNull InputStream inputStream,
         @NonNull NormalizedNodeStreamWriter writer) throws IOException;
 
     /**
index 8e4762f3c0e58f50c9b9d574dcffe28069321e9b..b5764dd45113512d1471556f9556dffc5c213e0a 100644 (file)
@@ -12,13 +12,13 @@ import java.io.InputStream;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.transform.dom.DOMSource;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
@@ -34,9 +34,9 @@ public final class XmlResourceBody extends ResourceBody {
     }
 
     @Override
-    void streamTo(final InputStream inputStream, final Inference inference, final PathArgument name,
+    void streamTo(final DataPutPath path, final PathArgument name, final InputStream inputStream,
             final NormalizedNodeStreamWriter writer) throws IOException {
-        try (var xmlParser = XmlParserStream.create(writer, inference)) {
+        try (var xmlParser = XmlParserStream.create(writer, path.databind().xmlCodecs(), path.inference())) {
             final var doc = UntrustedXML.newDocumentBuilder().parse(inputStream);
             final var docRoot = doc.getDocumentElement();
             final var docRootName = docRoot.getLocalName();
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataPutPath.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataPutPath.java
new file mode 100644 (file)
index 0000000..ace15e9
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023 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.restconf.server.api;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+
+/**
+ * An {@link ApiPath} subpath of {@code /data} {@code PUT} HTTP operation, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>.
+ *
+ * @param databind Associated {@link DatabindContext}
+ * @param inference Associated {@link Inference} pointing to the {@link EffectiveStatement} of the last ApiPath element.
+ *                  This can be one of:
+ *                  <ul>
+ *                    <li>a datatore, inference being {@link Inference#isEmpty() empty}</li>
+ *                    <li>a data resource, inference pointing to the the {@code data schema node} identified by
+ *                        {@code instance}</li>
+ *                  </ul>
+ * @param instance Associated {@link YangInstanceIdentifier}
+ * @see DataPostBody
+ */
+@NonNullByDefault
+public record DataPutPath(DatabindContext databind, Inference inference, YangInstanceIdentifier instance)
+        implements DatabindAware {
+    public DataPutPath {
+        requireNonNull(databind);
+        requireNonNull(inference);
+        requireNonNull(instance);
+    }
+}
index c8548f5e53353baa7fc4719bffa9fe581d1fc8f7..819acc16626e4ec6d1e1b413b1a7bd473afe86ad 100644 (file)
@@ -73,6 +73,7 @@ import org.opendaylight.restconf.server.api.DataPostPath;
 import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
+import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.restconf.server.api.DataPutResult;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.ModulesGetResult;
@@ -656,10 +657,9 @@ public final class MdsalRestconfServer
 
     private @NonNull ResourceRequest bindResourceRequest(final InstanceIdentifierContext reqPath,
             final ResourceBody body) {
-        final var path = reqPath.getInstanceIdentifier();
-        final var data = body.toNormalizedNode(path, reqPath.inference(), reqPath.getSchemaNode());
-
-        return new ResourceRequest(getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint()), path, data);
+        final var putPath = new DataPutPath(reqPath.databind(), reqPath.inference(), reqPath.getInstanceIdentifier());
+        return new ResourceRequest(getRestconfStrategy(putPath.databind(), reqPath.getMountPoint()), putPath.instance(),
+            body.toNormalizedNode(putPath, reqPath.getSchemaNode()));
     }
 
     @VisibleForTesting
index 3803f53ddbd5506b5f379597fa355d2d45a3ad4a..4345bc491948eb2693ea91147760c0977fa3a52b 100644 (file)
@@ -29,6 +29,7 @@ import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
+import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -97,7 +98,9 @@ abstract class AbstractResourceBodyTest extends AbstractBodyTest {
 
         try (var body = bodyConstructor.apply(patchBody)) {
             final var context = InstanceIdentifierContext.ofApiPath(apiPath, DATABIND, mountPointService);
-            return body.toNormalizedNode(context.getInstanceIdentifier(), context.inference(), context.getSchemaNode());
+            return body.toNormalizedNode(
+                new DataPutPath(context.databind(), context.inference(), context.getInstanceIdentifier()),
+                context.getSchemaNode());
         }
     }