X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Frestconf-nb%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Frestconf%2Fnb%2Frfc8040%2Frests%2Ftransactions%2FRestconfStrategy.java;h=a091c955bf0debfed2cc55ad65743de47efb2b44;hb=36b7055c4730b406ded47936641855466f574f9f;hp=c00b903a258b1e231a4de1e194e73d82056d35ab;hpb=b14e3fb6a934e373c2f88d9d58c6585acddb81f5;p=netconf.git diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java index c00b903a25..a091c955bf 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java @@ -13,8 +13,6 @@ import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.fr import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; import com.google.common.io.CharSource; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -24,11 +22,8 @@ import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.CancellationException; @@ -55,46 +50,50 @@ import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.netconf.dom.api.NetconfDataTreeService; import org.opendaylight.restconf.api.ApiPath; +import org.opendaylight.restconf.api.FormattableBody; import org.opendaylight.restconf.api.query.ContentParam; +import org.opendaylight.restconf.api.query.PrettyPrintParam; import org.opendaylight.restconf.api.query.WithDefaultsParam; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; 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.nb.rfc8040.legacy.WriterParameters; import org.opendaylight.restconf.server.api.ChildBody; import org.opendaylight.restconf.server.api.ConfigurationMetadata; +import org.opendaylight.restconf.server.api.CreateResourceResult; import org.opendaylight.restconf.server.api.DataGetParams; import org.opendaylight.restconf.server.api.DataGetResult; -import org.opendaylight.restconf.server.api.DataPatchPath; import org.opendaylight.restconf.server.api.DataPatchResult; import org.opendaylight.restconf.server.api.DataPostBody; -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.DataYangPatchResult; import org.opendaylight.restconf.server.api.DatabindContext; +import org.opendaylight.restconf.server.api.DatabindPath; +import org.opendaylight.restconf.server.api.DatabindPath.Action; +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.InvokeResult; import org.opendaylight.restconf.server.api.OperationInputBody; -import org.opendaylight.restconf.server.api.OperationsGetResult; -import org.opendaylight.restconf.server.api.OperationsPostPath; -import org.opendaylight.restconf.server.api.OperationsPostResult; 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.ApiPathNormalizer.InstanceReference; -import org.opendaylight.restconf.server.spi.ApiPathNormalizer.Path.Action; -import org.opendaylight.restconf.server.spi.ApiPathNormalizer.Path.Data; -import org.opendaylight.restconf.server.spi.ApiPathNormalizer.Path.Rpc; +import org.opendaylight.restconf.server.spi.DefaultResourceContext; +import org.opendaylight.restconf.server.spi.HttpGetResource; import org.opendaylight.restconf.server.spi.OperationInput; +import org.opendaylight.restconf.server.spi.OperationOutputBody; +import org.opendaylight.restconf.server.spi.OperationsResource; import org.opendaylight.restconf.server.spi.RpcImplementation; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.with.defaults.rev110601.WithDefaultsMode; import org.opendaylight.yangtools.yang.common.Empty; @@ -102,9 +101,7 @@ import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; -import org.opendaylight.yangtools.yang.common.Revision; import org.opendaylight.yangtools.yang.common.RpcResultBuilder; -import org.opendaylight.yangtools.yang.common.XMLNamespace; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; @@ -140,9 +137,7 @@ import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation; 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.RpcEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement; -import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -191,10 +186,12 @@ public abstract class RestconfStrategy { private final DOMMountPointService mountPointService; private final DOMActionService actionService; private final DOMRpcService rpcService; + private final HttpGetResource operations; RestconfStrategy(final DatabindContext databind, final ImmutableMap 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; @@ -202,6 +199,7 @@ public abstract class RestconfStrategy { this.sourceProvider = sourceProvider; this.mountPointService = mountPointService; pathNormalizer = new ApiPathNormalizer(databind); + operations = new OperationsResource(pathNormalizer); } public final @NonNull StrategyAndPath resolveStrategyPath(final ApiPath path) { @@ -260,8 +258,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", @@ -330,8 +328,8 @@ public abstract class RestconfStrategy { }, MoreExecutors.directExecutor()); } - public @NonNull RestconfFuture dataPUT(final ApiPath apiPath, final ResourceBody body, - final Map queryParameters) { + public @NonNull RestconfFuture dataPUT(final ServerRequest request, final ApiPath apiPath, + final ResourceBody body) { final Data path; try { path = pathNormalizer.normalizeDataPath(apiPath); @@ -341,14 +339,14 @@ 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)); } final NormalizedNode data; try { - data = body.toNormalizedNode(new DataPutPath(databind, path.inference(), path.instance())); + data = body.toNormalizedNode(path); } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } @@ -499,7 +497,7 @@ public abstract class RestconfStrategy { * @param insert {@link Insert} * @return A {@link RestconfFuture} */ - public final @NonNull RestconfFuture postData(final YangInstanceIdentifier path, + public final @NonNull RestconfFuture postData(final YangInstanceIdentifier path, final NormalizedNode data, final @Nullable Insert insert) { final ListenableFuture future; if (insert != null) { @@ -509,13 +507,13 @@ public abstract class RestconfStrategy { future = createAndCommit(prepareWriteExecution(), path, data); } - final var ret = new SettableRestconfFuture(); + final var ret = new SettableRestconfFuture(); Futures.addCallback(future, new FutureCallback() { @Override public void onSuccess(final CommitInfo result) { - ret.set(new CreateResource(new ApiPathNormalizer(databind).canonicalize( + ret.set(new CreateResourceResult(new ApiPathCanonizer(databind).dataToApiPath( data instanceof MapNode mapData && !mapData.isEmpty() - ? path.node(mapData.body().iterator().next().name()) : path).toString())); + ? path.node(mapData.body().iterator().next().name()) : path))); } @Override @@ -587,7 +585,7 @@ public abstract class RestconfStrategy { final NormalizedNode data; try { - data = body.toNormalizedNode(new DataPutPath(databind, path.inference(), path.instance())); + data = body.toNormalizedNode(path); } catch (RestconfDocumentedException e) { return RestconfFuture.failed(e); } @@ -605,7 +603,7 @@ public abstract class RestconfStrategy { final PatchContext patch; try { - patch = body.toPatchContext(new DataPatchPath(databind, path.instance())); + patch = body.toPatchContext(new DefaultResourceContext(path)); } catch (IOException e) { LOG.debug("Error parsing YANG Patch input", e); return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(), @@ -620,6 +618,7 @@ public abstract class RestconfStrategy { * @param patch Patch context to be processed * @return {@link PatchStatusContext} */ + @VisibleForTesting public final @NonNull RestconfFuture patchData(final PatchContext patch) { final var editCollection = new ArrayList(); final var tx = prepareWriteExecution(); @@ -694,7 +693,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; } @@ -702,14 +701,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()); @@ -717,7 +716,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); @@ -804,8 +803,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 dataDELETE(final ApiPath apiPath) { + public final RestconfFuture dataDELETE(final ServerRequest request, final ApiPath apiPath) { final Data path; try { path = pathNormalizer.normalizeDataPath(apiPath); @@ -815,27 +815,36 @@ public abstract class RestconfStrategy { // FIXME: reject empty YangInstanceIdentifier, as datastores may not be deleted final var ret = new SettableRestconfFuture(); - delete(ret, path.instance()); + delete(ret, request, path.instance()); return ret; } - abstract void delete(@NonNull SettableRestconfFuture future, @NonNull YangInstanceIdentifier path); + @NonNullByDefault + abstract void delete(SettableRestconfFuture future, ServerRequest request, YangInstanceIdentifier path); - public final @NonNull RestconfFuture dataGET(final ApiPath apiPath, - final DataGetParams params) { + public final @NonNull RestconfFuture 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 dataGET(Data path, DataGetParams params); + abstract @NonNull RestconfFuture dataGET(ServerRequest request, Data path, DataGetParams params); - static final @NonNull RestconfFuture completeDataGET(final Inference inference, - final QueryParameters queryParams, final @Nullable NormalizedNode node, + static final @NonNull RestconfFuture completeDataGET(final PrettyPrintParam prettyPrint, + final Inference inference, final WriterParameters writerParams, final @Nullable NormalizedNode node, final @Nullable ConfigurationMetadata metadata) { if (node == null) { return RestconfFuture.failed(new RestconfDocumentedException( @@ -843,7 +852,7 @@ public abstract class RestconfStrategy { ErrorType.PROTOCOL, ErrorTag.DATA_MISSING)); } - final var payload = new NormalizedNodePayload(inference, node, queryParams); + final var payload = new NormalizedNodePayload(inference, node, writerParams, prettyPrint); return RestconfFuture.of(metadata == null ? new DataGetResult(payload) : new DataGetResult(payload, metadata.entityTag(), metadata.lastModified())); } @@ -1238,56 +1247,18 @@ public abstract class RestconfStrategy { y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey())))); } - public @NonNull RestconfFuture operationsGET() { - final var modelContext = modelContext(); - final var modules = modelContext.getModuleStatements(); - if (modules.isEmpty()) { - // No modules, or defensive return empty content - return RestconfFuture.of(new OperationsGetResult.Container(modelContext, ImmutableSetMultimap.of())); - } - - // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us. - final var table = new HashMap>>(); - for (var entry : modules.entrySet()) { - final var module = entry.getValue(); - final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class) - .map(RpcEffectiveStatement::argument) - .collect(ImmutableSet.toImmutableSet()); - if (!rpcNames.isEmpty()) { - final var namespace = entry.getKey(); - table.computeIfAbsent(namespace.namespace(), ignored -> new HashMap<>()) - .put(namespace.revision(), rpcNames); - } - } - - // Now pick the latest revision for each namespace - final var rpcs = ImmutableSetMultimap.builder(); - for (var entry : table.entrySet()) { - entry.getValue().entrySet().stream() - .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first))) - .findFirst() - .ifPresent(row -> rpcs.putAll(QNameModule.of(entry.getKey(), row.getKey()), row.getValue())); - } - return RestconfFuture.of(new OperationsGetResult.Container(modelContext, rpcs.build())); + @NonNullByDefault + public RestconfFuture operationsGET(final ServerRequest request) { + return operations.httpGET(request); } - public @NonNull RestconfFuture operationsGET(final ApiPath apiPath) { - if (apiPath.steps().isEmpty()) { - return operationsGET(); - } - - final Rpc rpc; - try { - rpc = pathNormalizer.normalizeRpcPath(apiPath); - } catch (RestconfDocumentedException e) { - return RestconfFuture.failed(e); - } - - return RestconfFuture.of(new OperationsGetResult.Leaf(rpc.inference().modelContext(), rpc.rpc().argument())); + @NonNullByDefault + public RestconfFuture operationsGET(final ServerRequest request, final ApiPath apiPath) { + return operations.httpGET(request, apiPath); } - public @NonNull RestconfFuture operationsPOST(final URI restconfURI, final ApiPath apiPath, - final OperationInputBody body) { + public @NonNull RestconfFuture operationsPOST(final ServerRequest request, final URI restconfURI, + final ApiPath apiPath, final OperationInputBody body) { final Rpc path; try { path = pathNormalizer.normalizeRpcPath(apiPath); @@ -1295,10 +1266,9 @@ public abstract class RestconfStrategy { return RestconfFuture.failed(e); } - final var postPath = new OperationsPostPath(databind, path.inference()); final ContainerNode data; try { - data = body.toContainerNode(postPath); + data = body.toContainerNode(path); } catch (IOException e) { LOG.debug("Error reading input", e); return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(), @@ -1308,7 +1278,8 @@ public abstract class RestconfStrategy { final var type = path.rpc().argument(); final var local = localRpcs.get(type); if (local != null) { - return local.invoke(restconfURI, new OperationInput(databind, postPath.operation(), data)); + return local.invoke(restconfURI, new OperationInput(path, data)) + .transform(output -> outputToInvokeResult(path, output)); } if (rpcService == null) { LOG.debug("RPC invocation is not available"); @@ -1316,13 +1287,13 @@ public abstract class RestconfStrategy { ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED)); } - final var ret = new SettableRestconfFuture(); + final var ret = new SettableRestconfFuture(); Futures.addCallback(rpcService.invokeRpc(type, data), new FutureCallback() { @Override public void onSuccess(final DOMRpcResult response) { final var errors = response.errors(); if (errors.isEmpty()) { - ret.set(new OperationsPostResult(databind, postPath.operation(), 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, @@ -1345,6 +1316,12 @@ public abstract class RestconfStrategy { return ret; } + private static @NonNull InvokeResult outputToInvokeResult(final @NonNull OperationPath path, + final @Nullable ContainerNode value) { + return value == null || value.isEmpty() ? InvokeResult.EMPTY + : new InvokeResult(new OperationOutputBody(path, value)); + } + public @NonNull RestconfFuture resolveSource(final SourceIdentifier source, final Class representation) { final var src = requireNonNull(source); @@ -1403,10 +1380,10 @@ public abstract class RestconfStrategy { ErrorType.APPLICATION, ErrorTag.DATA_MISSING)); } - public final @NonNull RestconfFuture dataPOST(final ApiPath apiPath, - final DataPostBody body, final Map queryParameters) { - if (apiPath.steps().isEmpty()) { - return dataCreatePOST(body.toResource(), queryParameters); + public final @NonNull RestconfFuture dataPOST(final ServerRequest request, + final ApiPath apiPath, final DataPostBody body) { + if (apiPath.isEmpty()) { + return dataCreatePOST(request, body.toResource()); } final InstanceReference path; try { @@ -1416,8 +1393,7 @@ public abstract class RestconfStrategy { } if (path instanceof Data dataPath) { try (var resourceBody = body.toResource()) { - return dataCreatePOST(new DataPostPath(databind, dataPath.inference(), dataPath.instance()), - resourceBody, queryParameters); + return dataCreatePOST(request, dataPath, resourceBody); } } if (path instanceof Action actionPath) { @@ -1430,18 +1406,16 @@ public abstract class RestconfStrategy { return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path)); } - public @NonNull RestconfFuture dataCreatePOST(final ChildBody body, - final Map queryParameters) { - return dataCreatePOST(new DataPostPath(databind, - SchemaInferenceStack.of(databind.modelContext()).toInference(), YangInstanceIdentifier.of()), body, - queryParameters); + public @NonNull RestconfFuture dataCreatePOST(final ServerRequest request, + final ChildBody body) { + return dataCreatePOST(request, new DatabindPath.Data(databind), body); } - private @NonNull RestconfFuture dataCreatePOST(final DataPostPath path, final ChildBody body, - final Map queryParameters) { + private @NonNull RestconfFuture 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)); @@ -1459,11 +1433,11 @@ public abstract class RestconfStrategy { return ret; } - private @NonNull RestconfFuture dataInvokePOST(final Action path, final OperationInputBody body) { - final var inference = path.inference(); + private @NonNull RestconfFuture dataInvokePOST(final @NonNull Action path, + final @NonNull OperationInputBody body) { final ContainerNode input; try { - input = body.toContainerNode(new OperationsPostPath(databind, inference)); + input = body.toContainerNode(path); } catch (IOException e) { LOG.debug("Error reading input", e); return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(), @@ -1474,11 +1448,8 @@ public abstract class RestconfStrategy { return RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing.")); } - final var future = dataInvokePOST(actionService, path, input); - return future.transform(result -> result.getOutput() - .flatMap(output -> output.isEmpty() ? Optional.empty() - : Optional.of(new InvokeOperation(new NormalizedNodePayload(inference, output)))) - .orElse(InvokeOperation.EMPTY)); + return dataInvokePOST(actionService, path, input) + .transform(result -> outputToInvokeResult(path, result.getOutput().orElse(null))); } /**