import org.opendaylight.restconf.common.patch.PatchContext;
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;
import org.opendaylight.restconf.server.api.DataPostBody;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPutResult;
-import org.opendaylight.restconf.server.api.DataYangPatchParams;
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.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;
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;
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;
}
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",
}, 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);
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));
return merge(path.instance(), data);
}
- public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath,
- final Map<String, String> queryParameters, final PatchBody body) {
- final DataYangPatchParams params;
- try {
- params = DataYangPatchParams.ofQueryParameters(queryParameters);
- } catch (IllegalArgumentException e) {
- return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
- }
-
+ public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath, final PatchBody body) {
final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
}
- return patchData(params, patch);
+ return patchData(patch);
}
/**
* @return {@link PatchStatusContext}
*/
@VisibleForTesting
- public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final DataYangPatchParams params,
- final PatchContext patch) {
+ public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
final var editCollection = new ArrayList<PatchStatusEntity>();
final var tx = prepareWriteExecution();
// We have errors
if (!noError) {
tx.cancel();
- ret.set(new DataYangPatchResult(params,
+ ret.set(new DataYangPatchResult(
new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false, null)));
return ret;
}
Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(final CommitInfo result) {
- ret.set(new DataYangPatchResult(params,
+ ret.set(new DataYangPatchResult(
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(params,
+ ret.set(new DataYangPatchResult(
new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false,
TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors())));
}
* @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);
// 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()));
}
/**
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);
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);
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");
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,
}
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,
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 {
}
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
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));
}
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);
}
return dataInvokePOST(actionService, path, input)
- .transform(result -> outputToInvokeResult(path, params, result.getOutput().orElse(null)));
+ .transform(result -> outputToInvokeResult(path, result.getOutput().orElse(null)));
}
/**