Promote FormattableBody to restconf.api
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / jersey / providers / JsonNormalizedNodeBodyWriter.java
index ab1f0a06dc4b2af6931697dfb204d677982d20a6..a92a75fdf48aed227339c1c8f2ee5f9fc5d5b8d2 100644 (file)
@@ -10,172 +10,60 @@ package org.opendaylight.restconf.nb.rfc8040.jersey.providers;
 import com.google.gson.stream.JsonWriter;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.Provider;
-import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
-import org.opendaylight.restconf.nb.rfc8040.DepthParam;
-import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.api.MediaTypes;
 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.server.spi.FormattableBodySupport;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 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.codec.gson.JSONCodecFactory;
 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.Module;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 
 @Provider
 @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
-public class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
-    private static final int DEFAULT_INDENT_SPACES_NUM = 2;
-
+public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
     @Override
-    public void writeTo(final NormalizedNodePayload context,
-                        final Class<?> type,
-                        final Type genericType,
-                        final Annotation[] annotations,
-                        final MediaType mediaType,
-                        final MultivaluedMap<String, Object> httpHeaders,
-                        final OutputStream entityStream) throws IOException, WebApplicationException {
-        final NormalizedNode data = context.getData();
-        if (data == null) {
-            return;
-        }
-
-        final InstanceIdentifierContext identifierCtx = context.getInstanceIdentifierContext();
-        final var pretty = context.getWriterParameters().prettyPrint();
-
-        try (JsonWriter jsonWriter = createJsonWriter(entityStream, pretty == null ? false : pretty.value())) {
-            jsonWriter.beginObject();
-            writeNormalizedNode(jsonWriter, identifierCtx, data,
-                    context.getWriterParameters().depth(), context.getWriterParameters().fields());
-            jsonWriter.endObject();
-            jsonWriter.flush();
-        }
-
-        if (httpHeaders != null) {
-            for (final Map.Entry<String, Object> entry : context.getNewHeaders().entrySet()) {
-                httpHeaders.add(entry.getKey(), entry.getValue());
-            }
+    void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
+            final OutputStream entityStream) throws IOException {
+        if (!stack.isEmpty()) {
+            stack.exit();
         }
-    }
-
-    private static void writeNormalizedNode(final JsonWriter jsonWriter, final InstanceIdentifierContext context,
-            final NormalizedNode data, final DepthParam depth, final List<Set<QName>> fields) throws IOException {
-        final SchemaNode schemaNode = context.getSchemaNode();
-        final RestconfNormalizedNodeWriter nnWriter;
-        if (schemaNode instanceof RpcDefinition rpc) {
-            final var stack = SchemaInferenceStack.of(context.getSchemaContext());
-            stack.enterSchemaTree(rpc.getQName());
-            stack.enterSchemaTree(rpc.getOutput().getQName());
 
-            // RpcDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit initial output
-            // declaration
-            nnWriter = createNormalizedNodeWriter(
-                    context,
-                    stack.toInference(),
-                    jsonWriter,
-                    depth,
-                    fields);
-            final Module module = context.getSchemaContext().findModule(data.getIdentifier().getNodeType().getModule())
-                .orElseThrow();
-            jsonWriter.name(module.getName() + ":output");
-            jsonWriter.beginObject();
-            writeChildren(nnWriter, (ContainerNode) data);
-            jsonWriter.endObject();
-        } else if (schemaNode instanceof ActionDefinition action) {
-            // 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.newSystemMapBuilder()
+                .withNodeIdentifier(new NodeIdentifier(data.name().getNodeType()))
+                .withChild(mapEntry)
+                .build()
+                : data;
 
-            nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, depth, fields);
-            final Module module = context.getSchemaContext().findModule(data.getIdentifier().getNodeType().getModule())
-                .orElseThrow();
-            jsonWriter.name(module.getName() + ":output");
+        try (var jsonWriter = FormattableBodySupport.createJsonWriter(entityStream, writerParameters)) {
             jsonWriter.beginObject();
-            writeChildren(nnWriter, (ContainerNode) data);
-            jsonWriter.endObject();
-        } else {
-            final var stack = context.inference().toSchemaInferenceStack();
-            if (!stack.isEmpty()) {
-                stack.exit();
-            }
-            nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, 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.getIdentifier().getNodeType())
-                    .withChild(mapEntry)
-                    .build());
-            } else {
-                nnWriter.write(data);
-            }
-        }
 
-        nnWriter.flush();
-    }
-
-    private static void writeChildren(final RestconfNormalizedNodeWriter nnWriter, final ContainerNode data)
-            throws IOException {
-        for (var child : data.body()) {
-            nnWriter.write(child);
-        }
-    }
-
-    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(
-            final InstanceIdentifierContext context, final Inference inference, final JsonWriter jsonWriter,
-            final DepthParam depth, final List<Set<QName>> fields) {
-
-        final SchemaNode schema = context.getSchemaNode();
-        final JSONCodecFactory codecs = getCodecFactory(context);
-
-        final NormalizedNodeStreamWriter streamWriter = JSONNormalizedNodeStreamWriter.createNestedWriter(
-                codecs, inference, initialNamespaceFor(schema), jsonWriter);
-
-        return ParameterAwareNormalizedNodeWriter.forStreamWriter(streamWriter, depth, fields);
-    }
-
-    private static XMLNamespace initialNamespaceFor(final SchemaNode schema) {
-        if (schema instanceof RpcDefinition) {
-            return schema.getQName().getNamespace();
-        }
-        // For top-level elements we always want to use namespace prefix, hence use a null initial namespace
-        return null;
-    }
+            final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters, null);
+            nnWriter.write(toSerialize);
+            nnWriter.flush();
 
-    private static JsonWriter createJsonWriter(final OutputStream entityStream, final boolean prettyPrint) {
-        if (prettyPrint) {
-            return JsonWriterFactory.createJsonWriter(
-                    new OutputStreamWriter(entityStream, StandardCharsets.UTF_8), DEFAULT_INDENT_SPACES_NUM);
+            jsonWriter.endObject().flush();
         }
-        return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8));
     }
 
-    private static JSONCodecFactory getCodecFactory(final InstanceIdentifierContext context) {
+    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
-        return JSONCodecFactorySupplier.RFC7951.getShared(context.getSchemaContext());
+        final var codecs = JSONCodecFactorySupplier.RFC7951.getShared(inference.modelContext());
+        return ParameterAwareNormalizedNodeWriter.forStreamWriter(
+            JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, inference,
+                initialNamespace, jsonWriter), writerParameters.depth(), writerParameters.fields());
     }
 }