import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.fromInstanceId;
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;
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;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
-import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
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.WithDefaultsParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
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.databind.ChildBody;
-import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
-import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
-import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
-import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
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.utils.parser.YangInstanceIdentifierSerializer;
+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.DataPostPath;
+import org.opendaylight.restconf.server.api.DataPostBody;
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.OperationsGetResult;
-import org.opendaylight.restconf.server.api.OperationsPostPath;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
+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.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.DataPath;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer.InstanceReference;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Rpc;
+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.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;
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;
import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeContainerBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
+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.repo.api.SchemaSourceRepresentation;
-import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
-import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
-import org.opendaylight.yangtools.yang.model.repo.api.YinTextSchemaSource;
-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;
// operations. This should be handled through proper allocation indirection.
public abstract class RestconfStrategy {
@NonNullByDefault
- public record StrategyAndPath(RestconfStrategy strategy, DataPath path) {
+ public record StrategyAndPath(RestconfStrategy strategy, Data path) {
public StrategyAndPath {
requireNonNull(strategy);
requireNonNull(path);
private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
private final @NonNull ApiPathNormalizer pathNormalizer;
private final @NonNull DatabindContext databind;
- private final DOMYangTextSourceProvider sourceProvider;
+ private final YangTextSourceExtension sourceProvider;
private final DOMMountPointService mountPointService;
private final DOMActionService actionService;
private final DOMRpcService rpcService;
+ private final HttpGetResource operations;
RestconfStrategy(final DatabindContext databind, final ImmutableMap<QName, RpcImplementation> localRpcs,
final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
- final DOMYangTextSourceProvider sourceProvider, final @Nullable DOMMountPointService mountPointService) {
+ final @Nullable YangTextSourceExtension sourceProvider,
+ final @Nullable DOMMountPointService mountPointService) {
this.databind = requireNonNull(databind);
this.localRpcs = requireNonNull(localRpcs);
this.rpcService = rpcService;
this.sourceProvider = sourceProvider;
this.mountPointService = mountPointService;
pathNormalizer = new ApiPathNormalizer(databind);
+ operations = new OperationsResource(pathNormalizer);
}
public final @NonNull StrategyAndPath resolveStrategyPath(final ApiPath path) {
final var rpcService = mountPoint.getService(DOMRpcService.class).orElse(null);
final var actionService = mountPoint.getService(DOMActionService.class).orElse(null);
final var sourceProvider = mountPoint.getService(DOMSchemaService.class)
- .flatMap(schema -> Optional.ofNullable(schema.getExtensions().getInstance(DOMYangTextSourceProvider.class)))
+ .flatMap(schema -> Optional.ofNullable(schema.extension(YangTextSourceExtension.class)))
.orElse(null);
final var netconfService = mountPoint.getService(NetconfDataTreeService.class);
}
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) {
- final DataPath path;
+ public @NonNull RestconfFuture<DataPutResult> dataPUT(final ServerRequest request, final ApiPath apiPath,
+ final ResourceBody body) {
+ final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
} catch (RestconfDocumentedException e) {
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);
}
}
int lastInsertedPosition = 0;
- final var emptySubtree = ImmutableNodes.fromInstanceId(modelContext(), path.getParent());
+ final var emptySubtree = fromInstanceId(modelContext(), path.getParent());
tx.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
for (var nodeChild : readList.body()) {
if (lastInsertedPosition == lastItemPosition) {
tx.replace(childPath, nodeChild);
lastInsertedPosition++;
}
+
+ // In case we are inserting after last element
+ if (!before) {
+ if (lastInsertedPosition == lastItemPosition) {
+ tx.replace(path, data);
+ }
+ }
}
private static ListenableFuture<? extends CommitInfo> replaceAndCommit(final RestconfTransaction tx,
* @param insert {@link Insert}
* @return A {@link RestconfFuture}
*/
- public final @NonNull RestconfFuture<CreateResource> postData(final YangInstanceIdentifier path,
+ public final @NonNull RestconfFuture<CreateResourceResult> postData(final YangInstanceIdentifier path,
final NormalizedNode data, final @Nullable Insert insert) {
final ListenableFuture<? extends CommitInfo> future;
if (insert != null) {
- final var parentPath = path.coerceParent();
- checkListAndOrderedType(parentPath);
- future = insertAndCommitPost(path, data, insert, parentPath);
+ checkListAndOrderedType(path);
+ future = insertAndCommitPost(path, data, insert);
} else {
future = createAndCommit(prepareWriteExecution(), path, data);
}
- final var ret = new SettableRestconfFuture<CreateResource>();
+ final var ret = new SettableRestconfFuture<CreateResourceResult>();
Futures.addCallback(future, new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(final CommitInfo result) {
- ret.set(new CreateResource(new YangInstanceIdentifierSerializer(databind).serializePath(
+ ret.set(new CreateResourceResult(new ApiPathCanonizer(databind).dataToApiPath(
data instanceof MapNode mapData && !mapData.isEmpty()
? path.node(mapData.body().iterator().next().name()) : path)));
}
}
private ListenableFuture<? extends CommitInfo> insertAndCommitPost(final YangInstanceIdentifier path,
- final NormalizedNode data, final @NonNull Insert insert, final YangInstanceIdentifier parent) {
- final var grandParent = parent.coerceParent();
+ final NormalizedNode data, final @NonNull Insert insert) {
final var tx = prepareWriteExecution();
return switch (insert.insert()) {
case FIRST -> {
- final var readData = tx.readList(grandParent);
+ final var readData = tx.readList(path);
if (readData == null || readData.isEmpty()) {
tx.replace(path, data);
} else {
- checkItemDoesNotExists(exists(path), path);
- tx.remove(grandParent);
+ checkListDataDoesNotExist(path, data);
+ tx.remove(path);
tx.replace(path, data);
- tx.replace(grandParent, readData);
+ tx.replace(path, readData);
}
yield tx.commit();
}
case LAST -> createAndCommit(tx, path, data);
case BEFORE -> {
- final var readData = tx.readList(grandParent);
+ final var readData = tx.readList(path);
if (readData == null || readData.isEmpty()) {
tx.replace(path, data);
} else {
- checkItemDoesNotExists(exists(path), path);
- insertWithPointPost(tx, path, data, verifyNotNull(insert.pointArg()), readData, grandParent, true);
+ checkListDataDoesNotExist(path, data);
+ insertWithPointPost(tx, path, data, verifyNotNull(insert.pointArg()), readData, true);
}
yield tx.commit();
}
case AFTER -> {
- final var readData = tx.readList(grandParent);
+ final var readData = tx.readList(path);
if (readData == null || readData.isEmpty()) {
tx.replace(path, data);
} else {
- checkItemDoesNotExists(exists(path), path);
- insertWithPointPost(tx, path, data, verifyNotNull(insert.pointArg()), readData, grandParent, false);
+ checkListDataDoesNotExist(path, data);
+ insertWithPointPost(tx, path, data, verifyNotNull(insert.pointArg()), readData, false);
}
yield tx.commit();
}
* @throws NullPointerException if any argument is {@code null}
*/
public final @NonNull RestconfFuture<DataPatchResult> dataPATCH(final ApiPath apiPath, final ResourceBody body) {
- final DataPath path;
+ final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
} catch (RestconfDocumentedException 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);
}
return merge(path.instance(), data);
}
- public final @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath apiPath, final PatchBody body) {
- final DataPath path;
+ public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath, final PatchBody body) {
+ final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
} catch (RestconfDocumentedException e) {
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(),
* @param patch Patch context to be processed
* @return {@link PatchStatusContext}
*/
- public final @NonNull RestconfFuture<PatchStatusContext> patchData(final PatchContext patch) {
+ @VisibleForTesting
+ public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
final var editCollection = new ArrayList<PatchStatusEntity>();
final var tx = prepareWriteExecution();
}
}
- final var ret = new SettableRestconfFuture<PatchStatusContext>();
+ final var ret = new SettableRestconfFuture<DataYangPatchResult>();
// We have errors
if (!noError) {
tx.cancel();
- ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false, null));
+ 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 PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), true,
- null));
+ 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 PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false,
- TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors()));
+ ret.set(new DataYangPatchResult(
+ new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false,
+ TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors())));
}
}, MoreExecutors.directExecutor());
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 YangInstanceIdentifier grandParentPath, final boolean before) {
- tx.remove(grandParentPath);
+ final boolean before) {
+ tx.remove(path);
int lastItemPosition = 0;
for (var nodeChild : readList.body()) {
}
int lastInsertedPosition = 0;
- final var emptySubtree = ImmutableNodes.fromInstanceId(modelContext(), grandParentPath);
- tx.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
for (var nodeChild : readList.body()) {
if (lastInsertedPosition == lastItemPosition) {
tx.replace(path, data);
}
- tx.replace(grandParentPath.node(nodeChild.name()), nodeChild);
+ tx.replace(path.node(nodeChild.name()), nodeChild);
lastInsertedPosition++;
}
+
+ // In case we are inserting after last element
+ if (!before) {
+ if (lastInsertedPosition == lastItemPosition) {
+ tx.replace(path, data);
+ }
+ }
}
private static ListenableFuture<? extends CommitInfo> createAndCommit(final RestconfTransaction tx,
return tx.commit();
}
+ /**
+ * Check if child items do NOT already exists in List at specified {@code path}.
+ *
+ * @param data Data to be checked
+ * @param path Path to be checked
+ * @throws RestconfDocumentedException if data already exists.
+ */
+ private void checkListDataDoesNotExist(final YangInstanceIdentifier path, final NormalizedNode data) {
+ if (data instanceof NormalizedNodeContainer<?> dataNode) {
+ for (final var node : dataNode.body()) {
+ checkItemDoesNotExists(exists(path.node(node.name())), path.node(node.name()));
+ }
+ } else {
+ throw new RestconfDocumentedException("Unexpected node type: " + data.getClass().getName());
+ }
+ }
+
/**
* Check if items do NOT already exists at specified {@code path}.
*
* @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) {
- final DataPath path;
+ public final RestconfFuture<Empty> dataDELETE(final ServerRequest request, final ApiPath apiPath) {
+ final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
} catch (RestconfDocumentedException e) {
// 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) {
- final DataPath path;
+ 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(DataPath 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()));
}
/**
* @return {@link NormalizedNode}
*/
// FIXME: NETCONF-1155: this method should asynchronous
- public final @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+ @VisibleForTesting
+ final @Nullable NormalizedNode readData(final @NonNull ContentParam content,
final @NonNull YangInstanceIdentifier path, final WithDefaultsParam defaultsMode) {
return switch (content) {
case ALL -> {
// FIXME: we have this readily available in InstanceIdentifierContext
final var ctxNode = databind.schemaTree().findChild(path).orElseThrow();
if (readData instanceof ContainerNode container) {
- final var builder = Builders.containerBuilder().withNodeIdentifier(container.name());
+ final var builder = ImmutableNodes.newContainerBuilder().withNodeIdentifier(container.name());
buildCont(builder, container.body(), ctxNode, trim);
return builder.build();
} else if (readData instanceof MapEntryNode mapEntry) {
throw new IllegalStateException("Input " + mapEntry + " does not match " + ctxNode);
}
- final var builder = Builders.mapEntryBuilder().withNodeIdentifier(mapEntry.name());
+ final var builder = ImmutableNodes.newMapEntryBuilder().withNodeIdentifier(mapEntry.name());
buildMapEntryBuilder(builder, mapEntry.body(), ctxNode, trim, listSchema.getKeyDefinition());
return builder.build();
} else {
private static void appendContainer(final DataContainerNodeBuilder<?, ?> builder, final ContainerNode container,
final DataSchemaContext ctxNode, final boolean trim) {
- final var childBuilder = Builders.containerBuilder().withNodeIdentifier(container.name());
+ final var childBuilder = ImmutableNodes.newContainerBuilder().withNodeIdentifier(container.name());
buildCont(childBuilder, container.body(), ctxNode, trim);
builder.withChild(childBuilder.build());
}
}
final var childBuilder = switch (map.ordering()) {
- case SYSTEM -> Builders.mapBuilder();
- case USER -> Builders.orderedMapBuilder();
+ case SYSTEM -> ImmutableNodes.newSystemMapBuilder();
+ case USER -> ImmutableNodes.newUserMapBuilder();
};
buildList(childBuilder.withNodeIdentifier(map.name()), map.body(), childCtx, trim,
listSchema.getKeyDefinition());
final List<@NonNull QName> keys) {
for (var entry : entries) {
final var childCtx = getChildContext(ctxNode, entry);
- final var mapEntryBuilder = Builders.mapEntryBuilder().withNodeIdentifier(entry.name());
+ final var mapEntryBuilder = ImmutableNodes.newMapEntryBuilder().withNodeIdentifier(entry.name());
buildMapEntryBuilder(mapEntryBuilder, entry.body(), childCtx, trim, keys);
builder.withChild(mapEntryBuilder.build());
}
static final NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
final NormalizedNode configDataNode) {
- // if no data exists
- if (stateDataNode == null && configDataNode == null) {
- return null;
- }
-
- // return config data
if (stateDataNode == null) {
+ // No state, return config
return configDataNode;
}
-
- // return state data
if (configDataNode == null) {
+ // No config, return state
return stateDataNode;
}
-
- // merge data from config and state
+ // merge config and state
return mergeStateAndConfigData(stateDataNode, configDataNode);
}
*/
private static @NonNull NormalizedNode prepareRpcData(final @NonNull NormalizedNode configDataNode,
final @NonNull NormalizedNode stateDataNode) {
- final var mapEntryBuilder = Builders.mapEntryBuilder()
+ final var mapEntryBuilder = ImmutableNodes.newMapEntryBuilder()
.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.name());
// MAP CONFIG DATA
// MAP STATE DATA
mapRpcDataNode(stateDataNode, mapEntryBuilder);
- return Builders.mapBuilder()
+ return ImmutableNodes.newSystemMapBuilder()
.withNodeIdentifier(NodeIdentifier.create(configDataNode.name().getNodeType()))
.addChild(mapEntryBuilder.build())
.build();
private static @NonNull NormalizedNode prepareData(final @NonNull NormalizedNode configDataNode,
final @NonNull NormalizedNode stateDataNode) {
if (configDataNode instanceof UserMapNode configMap) {
- final var builder = Builders.orderedMapBuilder().withNodeIdentifier(configMap.name());
+ final var builder = ImmutableNodes.newUserMapBuilder().withNodeIdentifier(configMap.name());
mapValueToBuilder(configMap.body(), ((UserMapNode) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof SystemMapNode configMap) {
- final var builder = Builders.mapBuilder().withNodeIdentifier(configMap.name());
+ final var builder = ImmutableNodes.newSystemMapBuilder().withNodeIdentifier(configMap.name());
mapValueToBuilder(configMap.body(), ((SystemMapNode) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof MapEntryNode configEntry) {
- final var builder = Builders.mapEntryBuilder().withNodeIdentifier(configEntry.name());
+ final var builder = ImmutableNodes.newMapEntryBuilder().withNodeIdentifier(configEntry.name());
mapValueToBuilder(configEntry.body(), ((MapEntryNode) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof ContainerNode configContaienr) {
- final var builder = Builders.containerBuilder().withNodeIdentifier(configContaienr.name());
+ final var builder = ImmutableNodes.newContainerBuilder().withNodeIdentifier(configContaienr.name());
mapValueToBuilder(configContaienr.body(), ((ContainerNode) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof ChoiceNode configChoice) {
- final var builder = Builders.choiceBuilder().withNodeIdentifier(configChoice.name());
+ final var builder = ImmutableNodes.newChoiceBuilder().withNodeIdentifier(configChoice.name());
mapValueToBuilder(configChoice.body(), ((ChoiceNode) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof LeafNode configLeaf) {
return configLeaf;
} else if (configDataNode instanceof UserLeafSetNode) {
final var configLeafSet = (UserLeafSetNode<Object>) configDataNode;
- final var builder = Builders.<Object>orderedLeafSetBuilder().withNodeIdentifier(configLeafSet.name());
+ final var builder = ImmutableNodes.<Object>newUserLeafSetBuilder().withNodeIdentifier(configLeafSet.name());
mapValueToBuilder(configLeafSet.body(), ((UserLeafSetNode<Object>) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof SystemLeafSetNode) {
final var configLeafSet = (SystemLeafSetNode<Object>) configDataNode;
- final var builder = Builders.<Object>leafSetBuilder().withNodeIdentifier(configLeafSet.name());
+ final var builder = ImmutableNodes.<Object>newSystemLeafSetBuilder()
+ .withNodeIdentifier(configLeafSet.name());
mapValueToBuilder(configLeafSet.body(), ((SystemLeafSetNode<Object>) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof LeafSetEntryNode<?> configEntry) {
// config trumps oper
return configEntry;
} else if (configDataNode instanceof UnkeyedListNode configList) {
- final var builder = Builders.unkeyedListBuilder().withNodeIdentifier(configList.name());
+ final var builder = ImmutableNodes.newUnkeyedListBuilder().withNodeIdentifier(configList.name());
mapValueToBuilder(configList.body(), ((UnkeyedListNode) stateDataNode).body(), builder);
return builder.build();
} else if (configDataNode instanceof UnkeyedListEntryNode configEntry) {
- final var builder = Builders.unkeyedListEntryBuilder().withNodeIdentifier(configEntry.name());
+ final var builder = ImmutableNodes.newUnkeyedListEntryBuilder().withNodeIdentifier(configEntry.name());
mapValueToBuilder(configEntry.body(), ((UnkeyedListEntryNode) stateDataNode).body(), builder);
return builder.build();
} else {
y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
}
- public @NonNull RestconfFuture<OperationsGetResult> 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<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
- 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.getNamespace(), ignored -> new HashMap<>())
- .put(namespace.getRevision().orElse(null), rpcNames);
- }
- }
-
- // Now pick the latest revision for each namespace
- final var rpcs = ImmutableSetMultimap.<QNameModule, QName>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.create(entry.getKey(), row.getKey()), row.getValue()));
- }
- return RestconfFuture.of(new OperationsGetResult.Container(modelContext, rpcs.build()));
+ @NonNullByDefault
+ public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request) {
+ return operations.httpGET(request);
}
- public @NonNull RestconfFuture<OperationsGetResult> 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().getEffectiveModelContext(), rpc.rpc().argument()));
+ @NonNullByDefault
+ public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request, final ApiPath apiPath) {
+ return operations.httpGET(request, apiPath);
}
- public @NonNull RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
- final OperationInputBody body) {
- final OperationPath.Rpc path;
+ 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);
} catch (RestconfDocumentedException e) {
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(),
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");
ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED));
}
- final var ret = new SettableRestconfFuture<OperationsPostResult>();
+ final var ret = new SettableRestconfFuture<InvokeResult>();
Futures.addCallback(rpcService.invokeRpc(type, data), new FutureCallback<DOMRpcResult>() {
@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,
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<CharSource> resolveSource(final SourceIdentifier source,
- final Class<? extends SchemaSourceRepresentation> representation) {
+ final Class<? extends SourceRepresentation> representation) {
final var src = requireNonNull(source);
- if (YangTextSchemaSource.class.isAssignableFrom(representation)) {
+ if (YangTextSource.class.isAssignableFrom(representation)) {
if (sourceProvider != null) {
final var ret = new SettableRestconfFuture<CharSource>();
- Futures.addCallback(sourceProvider.getSource(src), new FutureCallback<YangTextSchemaSource>() {
+ Futures.addCallback(sourceProvider.getYangTexttSource(src), new FutureCallback<>() {
@Override
- public void onSuccess(final YangTextSchemaSource result) {
+ public void onSuccess(final YangTextSource result) {
ret.set(result);
}
}
return exportSource(modelContext(), src, YangCharSource::new, YangCharSource::new);
}
- if (YinTextSchemaSource.class.isAssignableFrom(representation)) {
+ if (YinTextSource.class.isAssignableFrom(representation)) {
return exportSource(modelContext(), src, YinCharSource.OfModule::new, YinCharSource.OfSubmodule::new);
}
return RestconfFuture.failed(new RestconfDocumentedException(
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 {
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- if (path instanceof DataPath dataPath) {
+ 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 OperationPath.Action actionPath) {
+ if (path instanceof Action actionPath) {
try (var inputBody = body.toOperationInput()) {
return dataInvokePOST(actionPath, inputBody);
}
return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path));
}
- public @NonNull RestconfFuture<CreateResource> dataCreatePOST(final ChildBody body,
- final Map<String, String> queryParameters) {
- return dataCreatePOST(new DataPostPath(databind,
- SchemaInferenceStack.of(databind.modelContext()).toInference(), YangInstanceIdentifier.of()), body,
- queryParameters);
+ public @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final ServerRequest request,
+ final ChildBody body) {
+ return dataCreatePOST(request, new DatabindPath.Data(databind), body);
}
- private @NonNull RestconfFuture<CreateResource> dataCreatePOST(final DataPostPath 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));
return ret;
}
- private @NonNull RestconfFuture<InvokeOperation> dataInvokePOST(final OperationPath.Action path,
- final OperationInputBody body) {
- final var inference = path.inference();
+ private @NonNull RestconfFuture<InvokeResult> 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(),
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)));
}
/**
* @return {@link DOMActionResult}
*/
private static RestconfFuture<DOMActionResult> dataInvokePOST(final DOMActionService actionService,
- final OperationPath.Action path, final @NonNull ContainerNode input) {
+ final Action path, final @NonNull ContainerNode input) {
final var ret = new SettableRestconfFuture<DOMActionResult>();
Futures.addCallback(actionService.invokeAction(
path.inference().toSchemaInferenceStack().toSchemaNodeIdentifier(),
- new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, path.instance()), input),
+ DOMDataTreeIdentifier.of(LogicalDatastoreType.OPERATIONAL, path.instance()), input),
new FutureCallback<DOMActionResult>() {
@Override
public void onSuccess(final DOMActionResult result) {