Eliminate NormalizedNodePayload
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / RestconfStrategy.java
index 0120dac88e079ba5fab90b7283153058034e67b0..074f466284b637f31de40bb5f6c2a74cd9616411 100644 (file)
@@ -58,12 +58,8 @@ import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
 import org.opendaylight.restconf.common.patch.PatchContext;
-import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.common.patch.PatchStatusEntity;
 import org.opendaylight.restconf.nb.rfc8040.Insert;
 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
 import org.opendaylight.restconf.server.api.ChildBody;
 import org.opendaylight.restconf.server.api.ConfigurationMetadata;
 import org.opendaylight.restconf.server.api.CreateResourceResult;
@@ -81,14 +77,19 @@ import org.opendaylight.restconf.server.api.DatabindPath.Data;
 import org.opendaylight.restconf.server.api.DatabindPath.InstanceReference;
 import org.opendaylight.restconf.server.api.DatabindPath.OperationPath;
 import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
-import org.opendaylight.restconf.server.api.InvokeParams;
 import org.opendaylight.restconf.server.api.InvokeResult;
 import org.opendaylight.restconf.server.api.OperationInputBody;
 import org.opendaylight.restconf.server.api.PatchBody;
+import org.opendaylight.restconf.server.api.PatchStatusContext;
+import org.opendaylight.restconf.server.api.PatchStatusEntity;
 import org.opendaylight.restconf.server.api.ResourceBody;
+import org.opendaylight.restconf.server.api.ServerRequest;
 import org.opendaylight.restconf.server.spi.ApiPathCanonizer;
 import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
 import org.opendaylight.restconf.server.spi.DefaultResourceContext;
+import org.opendaylight.restconf.server.spi.HttpGetResource;
+import org.opendaylight.restconf.server.spi.NormalizedFormattableBody;
+import org.opendaylight.restconf.server.spi.NormalizedNodeWriterFactory;
 import org.opendaylight.restconf.server.spi.OperationInput;
 import org.opendaylight.restconf.server.spi.OperationOutputBody;
 import org.opendaylight.restconf.server.spi.OperationsResource;
@@ -136,7 +137,6 @@ import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
 import org.opendaylight.yangtools.yang.model.api.source.YinTextSource;
 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -184,11 +184,12 @@ public abstract class RestconfStrategy {
     private final DOMMountPointService mountPointService;
     private final DOMActionService actionService;
     private final DOMRpcService rpcService;
-    private final OperationsResource operations;
+    private final HttpGetResource operations;
 
     RestconfStrategy(final DatabindContext databind, final ImmutableMap<QName, RpcImplementation> localRpcs,
             final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
-            final YangTextSourceExtension sourceProvider, final @Nullable DOMMountPointService mountPointService) {
+            final @Nullable YangTextSourceExtension sourceProvider,
+            final @Nullable DOMMountPointService mountPointService) {
         this.databind = requireNonNull(databind);
         this.localRpcs = requireNonNull(localRpcs);
         this.rpcService = rpcService;
@@ -255,8 +256,8 @@ public abstract class RestconfStrategy {
         }
         final var dataBroker = mountPoint.getService(DOMDataBroker.class);
         if (dataBroker.isPresent()) {
-            return new MdsalRestconfStrategy(mountDatabind, dataBroker.orElseThrow(), rpcService, actionService,
-                sourceProvider, mountPointService);
+            return new MdsalRestconfStrategy(mountDatabind, dataBroker.orElseThrow(), ImmutableMap.of(), rpcService,
+                actionService, sourceProvider, mountPointService);
         }
         LOG.warn("Mount point {} does not expose a suitable access interface", mountPath);
         throw new RestconfDocumentedException("Could not find a supported access interface in mount point",
@@ -325,8 +326,8 @@ public abstract class RestconfStrategy {
         }, MoreExecutors.directExecutor());
     }
 
-    public @NonNull RestconfFuture<DataPutResult> dataPUT(final ApiPath apiPath, final ResourceBody body,
-            final Map<String, String> queryParameters) {
+    public @NonNull RestconfFuture<DataPutResult> dataPUT(final ServerRequest request, final ApiPath apiPath,
+            final ResourceBody body) {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
@@ -336,7 +337,7 @@ public abstract class RestconfStrategy {
 
         final Insert insert;
         try {
-            insert = Insert.ofQueryParameters(databind, queryParameters);
+            insert = Insert.of(databind, request.queryParameters());
         } catch (IllegalArgumentException e) {
             return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
                 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
@@ -615,6 +616,7 @@ public abstract class RestconfStrategy {
      * @param patch Patch context to be processed
      * @return {@link PatchStatusContext}
      */
+    @VisibleForTesting
     public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
         final var editCollection = new ArrayList<PatchStatusEntity>();
         final var tx = prepareWriteExecution();
@@ -689,7 +691,7 @@ public abstract class RestconfStrategy {
         if (!noError) {
             tx.cancel();
             ret.set(new DataYangPatchResult(
-                new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false, null)));
+                new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false, null)));
             return ret;
         }
 
@@ -697,14 +699,14 @@ public abstract class RestconfStrategy {
             @Override
             public void onSuccess(final CommitInfo result) {
                 ret.set(new DataYangPatchResult(
-                    new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), true, null)));
+                    new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), true, null)));
             }
 
             @Override
             public void onFailure(final Throwable cause) {
                 // if errors occurred during transaction commit then patch failed and global errors are reported
                 ret.set(new DataYangPatchResult(
-                    new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false,
+                    new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false,
                         TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors())));
             }
         }, MoreExecutors.directExecutor());
@@ -712,7 +714,7 @@ public abstract class RestconfStrategy {
         return ret;
     }
 
-    private void insertWithPointPost(final RestconfTransaction tx, final YangInstanceIdentifier path,
+    private static void insertWithPointPost(final RestconfTransaction tx, final YangInstanceIdentifier path,
             final NormalizedNode data, final PathArgument pointArg, final NormalizedNodeContainer<?> readList,
             final boolean before) {
         tx.remove(path);
@@ -799,8 +801,9 @@ public abstract class RestconfStrategy {
      * @return A {@link RestconfFuture}
      * @throws NullPointerException if {@code apiPath} is {@code null}
      */
+    @NonNullByDefault
     @SuppressWarnings("checkstyle:abbreviationAsWordInName")
-    public final @NonNull RestconfFuture<Empty> dataDELETE(final ApiPath apiPath) {
+    public final RestconfFuture<Empty> dataDELETE(final ServerRequest request, final ApiPath apiPath) {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
@@ -810,37 +813,47 @@ public abstract class RestconfStrategy {
 
         // FIXME: reject empty YangInstanceIdentifier, as datastores may not be deleted
         final var ret = new SettableRestconfFuture<Empty>();
-        delete(ret, path.instance());
+        delete(ret, request, path.instance());
         return ret;
     }
 
-    abstract void delete(@NonNull SettableRestconfFuture<Empty> future, @NonNull YangInstanceIdentifier path);
+    @NonNullByDefault
+    abstract void delete(SettableRestconfFuture<Empty> future, ServerRequest request, YangInstanceIdentifier path);
 
-    public final @NonNull RestconfFuture<DataGetResult> dataGET(final ApiPath apiPath,
-            final DataGetParams params) {
+    public final @NonNull RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final ApiPath apiPath) {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
         } catch (RestconfDocumentedException e) {
             return RestconfFuture.failed(e);
         }
-        return dataGET(path, params);
+
+        final DataGetParams getParams;
+        try {
+            getParams = DataGetParams.of(request.queryParameters());
+        } catch (IllegalArgumentException e) {
+            return RestconfFuture.failed(new RestconfDocumentedException(e,
+                new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, "Invalid GET /data parameters", null,
+                    e.getMessage())));
+        }
+        return dataGET(request, path, getParams);
     }
 
-    abstract @NonNull RestconfFuture<DataGetResult> dataGET(Data path, DataGetParams params);
+    abstract @NonNull RestconfFuture<DataGetResult> dataGET(ServerRequest request, Data path, DataGetParams params);
 
-    static final @NonNull RestconfFuture<DataGetResult> completeDataGET(final Inference inference,
-            final QueryParameters queryParams, final @Nullable NormalizedNode node,
-            final @Nullable ConfigurationMetadata metadata) {
+    @NonNullByDefault
+    static final RestconfFuture<DataGetResult> completeDataGET(final @Nullable NormalizedNode node, final Data path,
+            final NormalizedNodeWriterFactory writerFactory, final @Nullable ConfigurationMetadata metadata) {
+        // Non-existing data
         if (node == null) {
             return RestconfFuture.failed(new RestconfDocumentedException(
                 "Request could not be completed because the relevant data model content does not exist",
                 ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
         }
 
-        final var payload = new NormalizedNodePayload(inference, node, queryParams);
-        return RestconfFuture.of(metadata == null ? new DataGetResult(payload)
-            : new DataGetResult(payload, metadata.entityTag(), metadata.lastModified()));
+        final var body = NormalizedFormattableBody.of(path, node, writerFactory);
+        return RestconfFuture.of(metadata == null ? new DataGetResult(body)
+            : new DataGetResult(body, metadata.entityTag(), metadata.lastModified()));
     }
 
     /**
@@ -1233,16 +1246,18 @@ public abstract class RestconfStrategy {
             y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
     }
 
-    public @NonNull RestconfFuture<FormattableBody> operationsGET() {
-        return operations.httpGET();
+    @NonNullByDefault
+    public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request) {
+        return operations.httpGET(request);
     }
 
-    public @NonNull RestconfFuture<FormattableBody> operationsGET(final ApiPath apiPath) {
-        return operations.httpGET(apiPath);
+    @NonNullByDefault
+    public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request, final ApiPath apiPath) {
+        return operations.httpGET(request, apiPath);
     }
 
-    public @NonNull RestconfFuture<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
-            final Map<String, String> queryParameters, final OperationInputBody body) {
+    public @NonNull RestconfFuture<InvokeResult> operationsPOST(final ServerRequest request, final URI restconfURI,
+            final ApiPath apiPath, final OperationInputBody body) {
         final Rpc path;
         try {
             path = pathNormalizer.normalizeRpcPath(apiPath);
@@ -1250,14 +1265,6 @@ public abstract class RestconfStrategy {
             return RestconfFuture.failed(e);
         }
 
-        final InvokeParams params;
-        try {
-            params = InvokeParams.ofQueryParameters(queryParameters);
-        } catch (IllegalArgumentException e) {
-            return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
-        }
-
         final ContainerNode data;
         try {
             data = body.toContainerNode(path);
@@ -1271,7 +1278,7 @@ public abstract class RestconfStrategy {
         final var local = localRpcs.get(type);
         if (local != null) {
             return local.invoke(restconfURI, new OperationInput(path, data))
-                .transform(output -> outputToInvokeResult(path, params, output));
+                .transform(output -> outputToInvokeResult(path, output));
         }
         if (rpcService == null) {
             LOG.debug("RPC invocation is not available");
@@ -1285,7 +1292,7 @@ public abstract class RestconfStrategy {
             public void onSuccess(final DOMRpcResult response) {
                 final var errors = response.errors();
                 if (errors.isEmpty()) {
-                    ret.set(outputToInvokeResult(path, params, response.value()));
+                    ret.set(outputToInvokeResult(path, response.value()));
                 } else {
                     LOG.debug("RPC invocation reported {}", response.errors());
                     ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
@@ -1309,9 +1316,9 @@ public abstract class RestconfStrategy {
     }
 
     private static @NonNull InvokeResult outputToInvokeResult(final @NonNull OperationPath path,
-            final @NonNull InvokeParams params, final @Nullable ContainerNode value) {
+            final @Nullable ContainerNode value) {
         return value == null || value.isEmpty() ? InvokeResult.EMPTY
-            : new InvokeResult(new OperationOutputBody(params, path, value));
+            : new InvokeResult(new OperationOutputBody(path, value));
     }
 
     public @NonNull RestconfFuture<CharSource> resolveSource(final SourceIdentifier source,
@@ -1372,10 +1379,10 @@ public abstract class RestconfStrategy {
             ErrorType.APPLICATION, ErrorTag.DATA_MISSING));
     }
 
-    public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath apiPath,
-            final DataPostBody body, final Map<String, String> queryParameters) {
-        if (apiPath.steps().isEmpty()) {
-            return dataCreatePOST(body.toResource(), queryParameters);
+    public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ServerRequest request,
+            final ApiPath apiPath, final DataPostBody body) {
+        if (apiPath.isEmpty()) {
+            return dataCreatePOST(request, body.toResource());
         }
         final InstanceReference path;
         try {
@@ -1385,12 +1392,12 @@ public abstract class RestconfStrategy {
         }
         if (path instanceof Data dataPath) {
             try (var resourceBody = body.toResource()) {
-                return dataCreatePOST(dataPath, resourceBody, queryParameters);
+                return dataCreatePOST(request, dataPath, resourceBody);
             }
         }
         if (path instanceof Action actionPath) {
             try (var inputBody = body.toOperationInput()) {
-                return dataInvokePOST(actionPath, inputBody, queryParameters);
+                return dataInvokePOST(actionPath, inputBody);
             }
         }
         // Note: this should never happen
@@ -1398,16 +1405,16 @@ public abstract class RestconfStrategy {
         return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path));
     }
 
-    public @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final ChildBody body,
-            final Map<String, String> queryParameters) {
-        return dataCreatePOST(new DatabindPath.Data(databind), body, queryParameters);
+    public @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final ServerRequest request,
+            final ChildBody body) {
+        return dataCreatePOST(request, new DatabindPath.Data(databind), body);
     }
 
-    private @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final DatabindPath.Data path,
-            final ChildBody body, final Map<String, String> queryParameters) {
+    private @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final ServerRequest request,
+            final DatabindPath.Data path, final ChildBody body) {
         final Insert insert;
         try {
-            insert = Insert.ofQueryParameters(path.databind(), queryParameters);
+            insert = Insert.of(path.databind(), request.queryParameters());
         } catch (IllegalArgumentException e) {
             return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
                 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
@@ -1426,15 +1433,7 @@ public abstract class RestconfStrategy {
     }
 
     private @NonNull RestconfFuture<InvokeResult> dataInvokePOST(final @NonNull Action path,
-            final @NonNull OperationInputBody body, final Map<String, String> queryParameters) {
-        final InvokeParams params;
-        try {
-            params = InvokeParams.ofQueryParameters(queryParameters);
-        } catch (IllegalArgumentException e) {
-            return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
-        }
-
+            final @NonNull OperationInputBody body) {
         final ContainerNode input;
         try {
             input = body.toContainerNode(path);
@@ -1449,7 +1448,7 @@ public abstract class RestconfStrategy {
         }
 
         return dataInvokePOST(actionService, path, input)
-            .transform(result -> outputToInvokeResult(path, params, result.getOutput().orElse(null)));
+            .transform(result -> outputToInvokeResult(path, result.getOutput().orElse(null)));
     }
 
     /**