Refactor AbstractNormalizedNodeBodyWriter 57/107857/5
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 13 Sep 2023 17:27:02 +0000 (19:27 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 14 Sep 2023 08:59:28 +0000 (10:59 +0200)
AbstractNormalizedNodeBodyWriter so that the three common dispatches are
handled in common code. This also modernizes them and pulls common bits
together.

This allows us to also switch the dispatch logic, so it operates on
SchemaInferenceStack instead of on SchemaNode.

All of this ends up showing that there is a bug in operation output
code, which causes us to emit an unnecessary declaration.

JIRA: NETCONF-1157
Change-Id: Icc781e884cecf7a5d64de63dd65b97e963fd4abe
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonNormalizedNodeBodyWriter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java

index 4788501b608b538a093353a4564ffc5b66d27a76..a69a1296a85951f61902accb8d2b456870dcbcf5 100644 (file)
@@ -17,10 +17,13 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.MessageBodyWriter;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 abstract class AbstractNormalizedNodeBodyWriter implements MessageBodyWriter<NormalizedNodePayload> {
     @Override
@@ -43,10 +46,27 @@ abstract class AbstractNormalizedNodeBodyWriter implements MessageBodyWriter<Nor
             }
         }
 
-        writeTo(context.getInstanceIdentifierContext(), context.getWriterParameters(), data,
-            requireNonNull(entityStream));
+        final var output = requireNonNull(entityStream);
+        final var iidContext = context.getInstanceIdentifierContext();
+        final var stack = iidContext.inference().toSchemaInferenceStack();
+        // FIXME: this dispatch is here to handle codec transition to 'output', but that should be completely okay with
+        //        the instantiation path we are using (based in Inference).
+        if (!stack.isEmpty()) {
+            final var stmt = stack.currentStatement();
+            if (stmt instanceof RpcEffectiveStatement rpc) {
+                stack.enterSchemaTree(rpc.output().argument());
+                writeOperationOutput(stack, context.getWriterParameters(), (ContainerNode) data, output);
+            } else if (stmt instanceof ActionEffectiveStatement action) {
+                stack.enterSchemaTree(action.output().argument());
+                writeOperationOutput(stack, context.getWriterParameters(), (ContainerNode) data, output);
+            }
+        }
+        writeData(stack, context.getWriterParameters(), data, output);
     }
 
-    abstract void writeTo(@NonNull InstanceIdentifierContext context, @NonNull QueryParameters writerParameters,
+    abstract void writeOperationOutput(@NonNull SchemaInferenceStack stack, @NonNull QueryParameters writerParameters,
+        @NonNull ContainerNode output, @NonNull OutputStream entityStream) throws IOException;
+
+    abstract void writeData(@NonNull SchemaInferenceStack stack, @NonNull QueryParameters writerParameters,
         @NonNull NormalizedNode data, @NonNull OutputStream entityStream) throws IOException;
 }
index 1c5d5e9ea928ce7a8fe003d5698a5959178e5381..6f2ae62170df69853be08f52dd83f26af294fda2 100644 (file)
@@ -12,18 +12,14 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Set;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.ext.Provider;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.query.DepthParam;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
@@ -32,8 +28,6 @@ import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
@@ -43,67 +37,42 @@ public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBo
     private static final int DEFAULT_INDENT_SPACES_NUM = 2;
 
     @Override
-    void writeTo(final InstanceIdentifierContext context, final QueryParameters writerParameters,
-            final NormalizedNode data, final OutputStream entityStream) throws IOException {
-        final var pretty = writerParameters.prettyPrint();
-        try (var jsonWriter = createJsonWriter(entityStream, pretty == null ? false : pretty.value())) {
-            jsonWriter.beginObject();
-            writeNormalizedNode(jsonWriter, context, data, writerParameters.depth(), writerParameters.fields());
-            jsonWriter.endObject();
-            jsonWriter.flush();
+    void writeOperationOutput(final SchemaInferenceStack stack, final QueryParameters writerParameters,
+            final ContainerNode output, final OutputStream entityStream) throws IOException {
+        // RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
+        // initial output declaration
+        try (var jsonWriter = createJsonWriter(entityStream, writerParameters.prettyPrint())) {
+            final var module = stack.currentModule();
+            jsonWriter.beginObject().name(module.argument().getLocalName() + ":output").beginObject();
+
+            final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters,
+                module.namespace().argument());
+            writeChildren(nnWriter, output);
+            nnWriter.flush();
+
+            jsonWriter.endObject().endObject();
         }
     }
 
-    private static void writeNormalizedNode(final JsonWriter jsonWriter, final InstanceIdentifierContext context,
-            final NormalizedNode data, final DepthParam depth, final List<Set<QName>> fields) throws IOException {
-        final var schemaNode = context.getSchemaNode();
-        if (schemaNode instanceof RpcDefinition rpc) {
-            // RpcDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit initial output
-            // declaration
-            final var stack = SchemaInferenceStack.of(context.getSchemaContext());
-            stack.enterSchemaTree(rpc.getQName());
-            stack.enterSchemaTree(rpc.getOutput().getQName());
-
-            jsonWriter.name(stack.currentModule().argument().getLocalName() + ":output");
-            jsonWriter.beginObject();
-
-            final var nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, depth, fields,
-                rpc.getQName().getNamespace());
-            writeChildren(nnWriter, (ContainerNode) data);
-            nnWriter.flush();
-
-            jsonWriter.endObject();
-        } else if (schemaNode instanceof ActionDefinition action) {
-            // FIXME: why is this different from RPC?!
+    @Override
+    void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
+            final OutputStream entityStream) throws IOException {
+        if (!stack.isEmpty()) {
+            stack.exit();
+        }
 
-            // ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit initial output
-            // declaration
-            final var stack = context.inference().toSchemaInferenceStack();
-            stack.enterSchemaTree(action.getOutput().getQName());
+        // RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly
+        final var toSerialize = data instanceof MapEntryNode mapEntry
+            ? ImmutableNodes.mapNodeBuilder(data.name().getNodeType()).withChild(mapEntry).build() : data;
 
-            jsonWriter.name(stack.currentModule().argument().getLocalName() + ":output");
+        try (var jsonWriter = createJsonWriter(entityStream, writerParameters.prettyPrint())) {
             jsonWriter.beginObject();
 
-            final var nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, depth, fields,
-                null);
-            writeChildren(nnWriter, (ContainerNode) data);
-            nnWriter.flush();
-
-            jsonWriter.endObject();
-        } else {
-            final var stack = context.inference().toSchemaInferenceStack();
-            if (!stack.isEmpty()) {
-                stack.exit();
-            }
-
-            // RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly
-            final var toSerialize = data instanceof MapEntryNode mapEntry
-                ? ImmutableNodes.mapNodeBuilder(data.name().getNodeType()).withChild(mapEntry).build() : data;
-
-            final var nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, depth, fields,
-                null);
+            final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters, null);
             nnWriter.write(toSerialize);
             nnWriter.flush();
+
+            jsonWriter.endObject().flush();
         }
     }
 
@@ -114,19 +83,21 @@ public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBo
         }
     }
 
-    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final InstanceIdentifierContext context,
-            final Inference inference, final JsonWriter jsonWriter, final DepthParam depth,
-            final List<Set<QName>> fields, final @Nullable XMLNamespace initialNamespace) {
+    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final Inference inference,
+            final JsonWriter jsonWriter, final QueryParameters writerParameters,
+            final @Nullable XMLNamespace initialNamespace) {
         // TODO: Performance: Cache JSON Codec factory and schema context
-        final var codecs = JSONCodecFactorySupplier.RFC7951.getShared(context.getSchemaContext());
+        final var codecs = JSONCodecFactorySupplier.RFC7951.getShared(inference.getEffectiveModelContext());
         return ParameterAwareNormalizedNodeWriter.forStreamWriter(
             JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, inference,
-                initialNamespace, jsonWriter), depth, fields);
+                initialNamespace, jsonWriter), writerParameters.depth(), writerParameters.fields());
     }
 
-    private static JsonWriter createJsonWriter(final OutputStream entityStream, final boolean prettyPrint) {
+    private static JsonWriter createJsonWriter(final OutputStream entityStream,
+            final @Nullable PrettyPrintParam prettyPrint) {
         final var outputWriter = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8);
-        return prettyPrint ? JsonWriterFactory.createJsonWriter(outputWriter, DEFAULT_INDENT_SPACES_NUM)
-            : JsonWriterFactory.createJsonWriter(outputWriter);
+        return prettyPrint != null && prettyPrint.value()
+            ? JsonWriterFactory.createJsonWriter(outputWriter, DEFAULT_INDENT_SPACES_NUM)
+                : JsonWriterFactory.createJsonWriter(outputWriter);
     }
 }
index 9d7b6e63d369323be0cec07ece0e376dbec1e3cb..9ee1b4aced99a2fa990b9dec619e12633739c124 100644 (file)
@@ -10,8 +10,6 @@ package org.opendaylight.restconf.nb.rfc8040.jersey.providers;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Set;
 import javanet.staxutils.IndentingXMLStreamWriter;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
@@ -21,10 +19,10 @@ import javax.xml.stream.FactoryConfigurationError;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
-import org.opendaylight.restconf.api.query.DepthParam;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@@ -32,8 +30,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
@@ -48,79 +44,62 @@ public final class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBod
     }
 
     @Override
-    void writeTo(final InstanceIdentifierContext context, final QueryParameters writerParameters,
-            final NormalizedNode data, final OutputStream entityStream) throws IOException {
-        XMLStreamWriter xmlWriter;
-        try {
-            xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
-
-            final var prettyPrint = writerParameters.prettyPrint();
-            if (prettyPrint != null && prettyPrint.value()) {
-                xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
-            }
-        } catch (XMLStreamException | FactoryConfigurationError e) {
-            throw new IllegalStateException(e);
-        }
-
-        writeNormalizedNode(xmlWriter, context, data, writerParameters.depth(), writerParameters.fields());
+    void writeOperationOutput(final SchemaInferenceStack stack, final QueryParameters writerParameters,
+            final ContainerNode output, final OutputStream entityStream) throws IOException {
+        // RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
+        // initial output declaration.
+        final var xmlWriter = createXmlWriter(entityStream, writerParameters.prettyPrint());
+        final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters);
+        writeElements(xmlWriter, nnWriter, output);
+        nnWriter.flush();
     }
 
-    private static void writeNormalizedNode(final XMLStreamWriter xmlWriter,
-            final InstanceIdentifierContext pathContext, final NormalizedNode data, final DepthParam depth,
-            final List<Set<QName>> fields) throws IOException {
-        final var schemaNode = pathContext.getSchemaNode();
-        if (schemaNode instanceof RpcDefinition rpc) {
-            // RpcDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit initial output
-            // declaration.
-            final var stack = SchemaInferenceStack.of(pathContext.getSchemaContext());
-            stack.enterSchemaTree(rpc.getQName());
-            stack.enterSchemaTree(rpc.getOutput().getQName());
-
-            final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth, fields);
-            writeElements(xmlWriter, nnWriter, (ContainerNode) data);
-            nnWriter.flush();
-        } else if (schemaNode instanceof ActionDefinition action) {
-            // ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit initial output
-            // declaration.
-            final var stack = pathContext.inference().toSchemaInferenceStack();
-            stack.enterSchemaTree(action.getOutput().getQName());
-
-            final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth, fields);
-            writeElements(xmlWriter, nnWriter, (ContainerNode) data);
-            nnWriter.flush();
+    @Override
+    void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
+            final OutputStream entityStream) throws IOException {
+        final boolean isRoot;
+        if (!stack.isEmpty()) {
+            stack.exit();
+            isRoot = false;
         } else {
-            final var stack = pathContext.inference().toSchemaInferenceStack();
-            final boolean isRoot;
-            if (!stack.isEmpty()) {
-                stack.exit();
-                isRoot = false;
-            } else {
-                isRoot = true;
-            }
+            isRoot = true;
+        }
 
-            final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth, fields);
-            if (data instanceof MapEntryNode mapEntry) {
-                // Restconf allows returning one list item. We need to wrap it
-                // in map node in order to serialize it properly
-                nnWriter.write(ImmutableNodes.mapNodeBuilder(data.name().getNodeType()).addChild(mapEntry).build());
-            } else if (isRoot) {
-                if (data instanceof ContainerNode container && container.isEmpty()) {
-                    writeEmptyDataNode(xmlWriter, container);
-                } else {
-                    writeAndWrapInDataNode(xmlWriter, nnWriter, data);
-                }
+        final var xmlWriter = createXmlWriter(entityStream, writerParameters.prettyPrint());
+        final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters);
+        if (data instanceof MapEntryNode mapEntry) {
+            // Restconf allows returning one list item. We need to wrap it
+            // in map node in order to serialize it properly
+            nnWriter.write(ImmutableNodes.mapNodeBuilder(data.name().getNodeType()).addChild(mapEntry).build());
+        } else if (isRoot) {
+            if (data instanceof ContainerNode container && container.isEmpty()) {
+                writeEmptyDataNode(xmlWriter, container);
             } else {
-                nnWriter.write(data);
+                writeAndWrapInDataNode(xmlWriter, nnWriter, data);
             }
-            nnWriter.flush();
+        } else {
+            nnWriter.write(data);
         }
+        nnWriter.flush();
+    }
+
+    private static XMLStreamWriter createXmlWriter(final OutputStream entityStream,
+            final @Nullable PrettyPrintParam prettyPrint) {
+        final XMLStreamWriter xmlWriter;
+        try {
+            xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
+        } catch (XMLStreamException | FactoryConfigurationError e) {
+            throw new IllegalStateException(e);
+        }
+
+        return prettyPrint != null && prettyPrint.value() ? new IndentingXMLStreamWriter(xmlWriter) : xmlWriter;
     }
 
     private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
-            final Inference inference, final DepthParam depth,
-            final List<Set<QName>> fields) {
+            final Inference inference, final QueryParameters writerParameters) {
         return ParameterAwareNormalizedNodeWriter.forStreamWriter(
-            XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, inference), depth, fields);
+            XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, inference),
+            writerParameters.depth(), writerParameters.fields());
     }
 
     private static void writeAndWrapInDataNode(final XMLStreamWriter xmlWriter,