import static java.util.Objects.requireNonNull;
+import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.query.InsertParam;
import org.opendaylight.restconf.api.query.PointParam;
import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
/**
* Parser and holder of query parameters from uriInfo for data and datastore modification operations.
// have a @NonNull PointParam then and there will not be an insert field. We can also ditch toString(),
// as the records will do the right thing.
public final class Insert implements Immutable {
+ @Beta
+ @NonNullByDefault
+ @FunctionalInterface
+ public interface PointParser {
+
+ PathArgument parseValue(String value);
+ }
+
private final @NonNull InsertParam insert;
- private final @Nullable PointParam point;
+ private final @Nullable PathArgument pointArg;
- private Insert(final InsertParam insert, final PointParam point) {
+ private Insert(final InsertParam insert, final PathArgument pointArg) {
this.insert = requireNonNull(insert);
- this.point = point;
+ this.pointArg = pointArg;
}
- public static @Nullable Insert forParams(final @Nullable InsertParam insert, final @Nullable PointParam point) {
+ public static @Nullable Insert forParams(final @Nullable InsertParam insert, final @Nullable PointParam point,
+ final PointParser pointParser) {
if (insert == null) {
if (point != null) {
throw invalidPointIAE();
throw new IllegalArgumentException(
"Insert parameter " + insert.paramValue() + " cannot be used without a Point parameter.");
}
- yield new Insert(insert, point);
+ yield new Insert(insert, pointParser.parseValue(point.value()));
}
case FIRST, LAST -> {
// https://www.rfc-editor.org/rfc/rfc8040#section-4.8.6:
return insert;
}
- public @Nullable PointParam point() {
- return point;
+ public @Nullable PathArgument pointArg() {
+ return pointArg;
}
@Override
public String toString() {
final var helper = MoreObjects.toStringHelper(this).add("insert", insert.paramValue());
- final var local = point;
+ final var local = pointArg;
if (local != null) {
- helper.add("point", local.value());
+ helper.add("point", pointArg);
}
return helper.toString();
}
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@Beta
public final class QueryParams {
InsertParam.uriName, PointParam.uriName,
// Notifications
FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName,
+ // ODL extensions
LeafNodesOnlyParam.uriName, SkipNotificationDataParam.uriName, ChangedLeafNodesOnlyParam.uriName,
ChildNodesOnlyParam.uriName);
return new ReadDataParams(content, depth, fields, withDefaults, prettyPrint);
}
- public static @Nullable Insert parseInsert(final UriInfo uriInfo) {
+ public static @Nullable Insert parseInsert(final EffectiveModelContext modelContext, final UriInfo uriInfo) {
InsertParam insert = null;
PointParam point = null;
}
try {
- return Insert.forParams(insert, point);
+ return Insert.forParams(insert, point,
+ // TODO: instead of a EffectiveModelContext, we should have received
+ // YangInstanceIdentifierDeserializer.Result, from which we can use to seed the parser. This
+ // call-site should not support 'yang-ext:mount' and should just reuse DataSchemaContextTree,
+ // saving a lookup
+ value -> YangInstanceIdentifierDeserializer.create(modelContext, value).path.getLastPathArgument());
} catch (IllegalArgumentException e) {
throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
}
}
private Response putData(final @Nullable String identifier, final UriInfo uriInfo, final ResourceBody body) {
- final var insert = QueryParams.parseInsert(uriInfo);
- final var req = bindResourceRequest(identifier, body);
+ final var localModel = databindProvider.currentContext().modelContext();
+ final var context = ParserIdentifier.toInstanceIdentifier(identifier, localModel, mountPointService);
+ final var insert = QueryParams.parseInsert(context.getSchemaContext(), uriInfo);
+ final var req = bindResourceRequest(context, body);
return switch (
req.strategy().putData(req.path(), req.data(), insert)) {
private Response postData(final Inference inference, final YangInstanceIdentifier parentPath, final ChildBody body,
final UriInfo uriInfo, final @Nullable DOMMountPoint mountPoint) {
- final var insert = QueryParams.parseInsert(uriInfo);
final var modelContext = inference.getEffectiveModelContext();
+ final var insert = QueryParams.parseInsert(modelContext, uriInfo);
final var strategy = getRestconfStrategy(modelContext, mountPoint);
var path = parentPath;
final var payload = body.toPayload(path, inference);
* @param ar {@link AsyncResponse} which needs to be completed
*/
private void plainPatchData(final @Nullable String identifier, final ResourceBody body, final AsyncResponse ar) {
- final var req = bindResourceRequest(identifier, body);
+ final var req = bindResourceRequest(
+ ParserIdentifier.toInstanceIdentifier(identifier, databindProvider.currentContext().modelContext(),
+ mountPointService),
+ body);
final var future = req.strategy().merge(req.path(), req.data());
Futures.addCallback(future, new FutureCallback<>() {
}, MoreExecutors.directExecutor());
}
- private @NonNull ResourceRequest bindResourceRequest(final @Nullable String identifier, final ResourceBody body) {
- final var dataBind = databindProvider.currentContext();
- final var context = ParserIdentifier.toInstanceIdentifier(identifier, dataBind.modelContext(),
- mountPointService);
+ private @NonNull ResourceRequest bindResourceRequest(final InstanceIdentifierContext context,
+ final ResourceBody body) {
final var inference = context.inference();
final var path = context.getInstanceIdentifier();
final var data = body.toNormalizedNode(path, inference, context.getSchemaNode());
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.api.query.ContentParam;
-import org.opendaylight.restconf.api.query.PointParam;
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.patch.PatchStatusContext;
import org.opendaylight.restconf.common.patch.PatchStatusEntity;
import org.opendaylight.restconf.nb.rfc8040.Insert;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
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.ErrorTag;
if (readData == null || readData.isEmpty()) {
yield replaceAndCommit(tx, path, data);
}
- insertWithPointPut(tx, path, data, verifyNotNull(insert.point()), readData, true);
+ insertWithPointPut(tx, path, data, verifyNotNull(insert.pointArg()), readData, true);
yield tx.commit();
}
case AFTER -> {
if (readData == null || readData.isEmpty()) {
yield replaceAndCommit(tx, path, data);
}
- insertWithPointPut(tx, path, data, verifyNotNull(insert.point()), readData, false);
+ insertWithPointPut(tx, path, data, verifyNotNull(insert.pointArg()), readData, false);
yield tx.commit();
}
};
}
private void insertWithPointPut(final RestconfTransaction tx, final YangInstanceIdentifier path,
- final NormalizedNode data, final @NonNull PointParam point, final NormalizedNodeContainer<?> readList,
+ final NormalizedNode data, final @NonNull PathArgument pointArg, final NormalizedNodeContainer<?> readList,
final boolean before) {
tx.remove(path.getParent());
- // FIXME: this should have happened sooner
- final var pointArg = YangInstanceIdentifierDeserializer.create(modelContext, point.value()).path
- .getLastPathArgument();
+
int lastItemPosition = 0;
for (var nodeChild : readList.body()) {
if (nodeChild.name().equals(pointArg)) {
if (!before) {
lastItemPosition++;
}
+
int lastInsertedPosition = 0;
final var emptySubtree = ImmutableNodes.fromInstanceId(modelContext, path.getParent());
tx.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
tx.replace(path, data);
} else {
checkItemDoesNotExists(exists(path), path);
- insertWithPointPost(tx, path, data, verifyNotNull(insert.point()), readData, grandParent, true);
+ insertWithPointPost(tx, path, data, verifyNotNull(insert.pointArg()), readData, grandParent, true);
}
yield tx.commit();
}
tx.replace(path, data);
} else {
checkItemDoesNotExists(exists(path), path);
- insertWithPointPost(tx, path, data, verifyNotNull(insert.point()), readData, grandParent, false);
+ insertWithPointPost(tx, path, data, verifyNotNull(insert.pointArg()), readData, grandParent, false);
}
yield tx.commit();
}
}
private void insertWithPointPost(final RestconfTransaction tx, final YangInstanceIdentifier path,
- final NormalizedNode data, final PointParam point, final NormalizedNodeContainer<?> readList,
+ final NormalizedNode data, final PathArgument pointArg, final NormalizedNodeContainer<?> readList,
final YangInstanceIdentifier grandParentPath, final boolean before) {
tx.remove(grandParentPath);
- // FIXME: this should have happened sooner
- final var pointArg = YangInstanceIdentifierDeserializer.create(modelContext, point.value()).path
- .getLastPathArgument();
+
int lastItemPosition = 0;
for (var nodeChild : readList.body()) {
if (nodeChild.name().equals(pointArg)) {
if (!before) {
lastItemPosition++;
}
+
int lastInsertedPosition = 0;
final var emptySubtree = ImmutableNodes.fromInstanceId(modelContext, grandParentPath);
tx.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
if (lastInsertedPosition == lastItemPosition) {
tx.replace(path, data);
}
- final YangInstanceIdentifier childPath = grandParentPath.node(nodeChild.name());
- tx.replace(childPath, nodeChild);
+ tx.replace(grandParentPath.node(nodeChild.name()), nodeChild);
lastInsertedPosition++;
}
}
import org.opendaylight.restconf.api.query.RestconfQueryParam;
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.nb.rfc8040.legacy.InstanceIdentifierContext;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.yangtools.yang.common.ErrorTag;
*/
@Test
public void optionalParamMultipleTest() {
- final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
+ final var ex = assertThrows(RestconfDocumentedException.class,
() -> QueryParams.optionalParam(ContentParam.uriName, List.of("config", "nonconfig", "all")));
- final List<RestconfError> errors = ex.getErrors();
+ final var errors = ex.getErrors();
assertEquals(1, errors.size());
- final RestconfError error = errors.get(0);
+ final var error = errors.get(0);
assertEquals("Error type is not correct", ErrorType.PROTOCOL, error.getErrorType());
assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, error.getErrorTag());
}
public void checkParametersTypesNegativeTest() {
assertUnknownParam(QueryParams::newNotificationQueryParams);
assertUnknownParam(QueryParams::newReadDataParams);
- assertUnknownParam(QueryParams::parseInsert);
+ assertUnknownParam(uriInfo -> QueryParams.parseInsert(mock(EffectiveModelContext.class), uriInfo));
assertInvalidParam(QueryParams::newNotificationQueryParams, ContentParam.ALL);
assertInvalidParam(QueryParams::newReadDataParams, InsertParam.LAST);
- assertInvalidParam(QueryParams::parseInsert, ContentParam.ALL);
+ assertInvalidParam(
+ uriInfo -> QueryParams.parseInsert(mock(EffectiveModelContext.class), uriInfo),
+ ContentParam.ALL);
}
/**