import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration;
path = path.node(arg);
}
- PostDataTransactionUtil.postData(path, data, strategy, context, params);
+ strategy.postData(path, data, context, params);
return Response.created(resolveLocation(uriInfo, path, context, data)).build();
}
}
@Override
- public ListenableFuture<Boolean> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
try (var tx = dataBroker.newReadOnlyTransaction()) {
- return tx.exists(store, path);
+ return tx.exists(LogicalDatastoreType.CONFIGURATION, path);
}
}
}
import static com.google.common.base.Verify.verifyNotNull;
import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
-import static org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil.checkItemDoesNotExists;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collection;
BatchedExistenceCheck.start(verifyNotNull(rwTx), CONFIGURATION, path, children);
for (final NormalizedNode child : children) {
- final YangInstanceIdentifier childPath = path.node(child.name());
+ final var childPath = path.node(child.name());
verifyNotNull(rwTx).put(CONFIGURATION, childPath, child);
}
// ... finally collect existence checks and abort the transaction if any of them failed.
checkExistence(path, check);
} else {
- checkItemDoesNotExists(verifyNotNull(rwTx).exists(CONFIGURATION, path), path);
+ RestconfStrategy.checkItemDoesNotExists(verifyNotNull(rwTx).exists(CONFIGURATION, path), path);
TransactionUtil.ensureParentsByMerge(path, schemaContext, this);
verifyNotNull(rwTx).put(CONFIGURATION, path, data);
}
}
@Override
- public ListenableFuture<Boolean> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
- return Futures.transform(remapException(read(store, path)),
+ ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
+ return Futures.transform(remapException(netconfService.getConfig(path)),
optionalNode -> optionalNode != null && optionalNode.isPresent(),
MoreExecutors.directExecutor());
}
import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.api.query.InsertParam;
import org.opendaylight.restconf.api.query.PointParam;
+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.nb.rfc8040.WriteDataParams;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Baseline execution strategy for various RESTCONF operations.
REPLACED;
}
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfStrategy.class);
+
RestconfStrategy() {
// Hidden on purpose
}
YangInstanceIdentifier path, List<YangInstanceIdentifier> fields);
/**
- * Check if data already exists in the datastore.
+ * Check if data already exists in the configuration datastore.
*
- * @param store the logical data store which should be modified
* @param path the data object path
- * @return a FluentFuture containing the result of the check
+ * @return a ListenableFuture containing the result of the check
*/
- public abstract ListenableFuture<Boolean> exists(LogicalDatastoreType store, YangInstanceIdentifier path);
+ // FIXME: this method should be hosted in RestconfTransaction
+ // FIXME: this method should only be needed in MdsalRestconfStrategy
+ abstract ListenableFuture<Boolean> exists(YangInstanceIdentifier path);
/**
* Delete data from the configuration datastore. If the data does not exist, this operation will fail, as outlined
*/
public @NonNull CreateOrReplaceResult putData(final YangInstanceIdentifier path, final NormalizedNode data,
final EffectiveModelContext context, final WriteDataParams params) {
- final var exists = TransactionUtil.syncAccess(exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ final var exists = TransactionUtil.syncAccess(exists(path), path);
final var insert = params.insert();
final ListenableFuture<? extends CommitInfo> commitFuture;
if (insert != null) {
final var parentPath = path.coerceParent();
- PostDataTransactionUtil.checkListAndOrderedType(context, parentPath);
+ checkListAndOrderedType(context, parentPath);
commitFuture = insertAndCommit(path, data, insert, params.point(), parentPath, context);
} else {
commitFuture = replaceAndCommit(prepareWriteExecution(), path, data, context);
tx.replace(path, data, context);
return tx.commit();
}
+
+ private static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
+ final YangInstanceIdentifier path) {
+ final var dataSchemaNode = DataSchemaContextTree.from(ctx).findChild(path).orElseThrow().dataSchemaNode();
+
+ final String message;
+ if (dataSchemaNode instanceof ListSchemaNode listSchema) {
+ if (listSchema.isUserOrdered()) {
+ return listSchema;
+ }
+ message = "Insert parameter can be used only with ordered-by user list.";
+ } else if (dataSchemaNode instanceof LeafListSchemaNode leafListSchema) {
+ if (leafListSchema.isUserOrdered()) {
+ return leafListSchema;
+ }
+ message = "Insert parameter can be used only with ordered-by user leaf-list.";
+ } else {
+ message = "Insert parameter can be used only with list or leaf-list";
+ }
+ throw new RestconfDocumentedException(message, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+ }
+
+ /**
+ * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
+ * {@link RestconfStrategy} provided as a parameter.
+ *
+ * @param path path
+ * @param data data
+ * @param schemaContext reference to actual {@link EffectiveModelContext}
+ * @param params {@link WriteDataParams}
+ */
+ public final void postData(final YangInstanceIdentifier path, final NormalizedNode data,
+ final EffectiveModelContext schemaContext, final WriteDataParams params) {
+ TransactionUtil.syncCommit(submitData(path, data, schemaContext, params), "POST", path);
+ }
+
+ private ListenableFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
+ final NormalizedNode data, final EffectiveModelContext schemaContext, final WriteDataParams params) {
+ final var transaction = prepareWriteExecution();
+ final var insert = params.insert();
+ if (insert == null) {
+ return makePost(path, data, schemaContext, transaction);
+ }
+
+ final var parentPath = path.coerceParent();
+ checkListAndOrderedType(schemaContext, parentPath);
+ final var grandParentPath = parentPath.coerceParent();
+
+ return switch (insert) {
+ case FIRST -> {
+ final var readData = transaction.readList(grandParentPath);
+ if (readData == null || readData.isEmpty()) {
+ transaction.replace(path, data, schemaContext);
+ } else {
+ checkItemDoesNotExists(exists(path), path);
+ transaction.remove(grandParentPath);
+ transaction.replace(path, data, schemaContext);
+ transaction.replace(grandParentPath, readData, schemaContext);
+ }
+ yield transaction.commit();
+ }
+ case LAST -> makePost(path, data, schemaContext, transaction);
+ case BEFORE -> {
+ final var readData = transaction.readList(grandParentPath);
+ if (readData == null || readData.isEmpty()) {
+ transaction.replace(path, data, schemaContext);
+ } else {
+ checkItemDoesNotExists(exists(path), path);
+ insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, true, transaction);
+ }
+ yield transaction.commit();
+ }
+ case AFTER -> {
+ final var readData = transaction.readList(grandParentPath);
+ if (readData == null || readData.isEmpty()) {
+ transaction.replace(path, data, schemaContext);
+ } else {
+ checkItemDoesNotExists(exists(path), path);
+ insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, false, transaction);
+ }
+ yield transaction.commit();
+ }
+ };
+ }
+
+ private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
+ final EffectiveModelContext schemaContext, final PointParam point,
+ final NormalizedNodeContainer<?> readList, final boolean before, final RestconfTransaction transaction) {
+ final YangInstanceIdentifier parent = path.coerceParent().coerceParent();
+ transaction.remove(parent);
+ final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
+ .getLastPathArgument();
+ int lastItemPosition = 0;
+ for (var nodeChild : readList.body()) {
+ if (nodeChild.name().equals(pointArg)) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final var emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
+ transaction.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
+ for (var nodeChild : readList.body()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ transaction.replace(path, data, schemaContext);
+ }
+ final YangInstanceIdentifier childPath = parent.node(nodeChild.name());
+ transaction.replace(childPath, nodeChild, schemaContext);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static ListenableFuture<? extends CommitInfo> makePost(final YangInstanceIdentifier path,
+ final NormalizedNode data, final EffectiveModelContext schemaContext,
+ final RestconfTransaction transaction) {
+ try {
+ transaction.create(path, data, schemaContext);
+ } catch (RestconfDocumentedException e) {
+ // close transaction if any and pass exception further
+ transaction.cancel();
+ throw e;
+ }
+
+ return transaction.commit();
+ }
+
+ /**
+ * Check if items do NOT already exists at specified {@code path}.
+ *
+ * @param existsFuture if checked data exists
+ * @param path Path to be checked
+ * @throws RestconfDocumentedException if data already exists.
+ */
+ static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
+ final YangInstanceIdentifier path) {
+ if (TransactionUtil.syncAccess(existsFuture, path)) {
+ LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
+ throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
+ path);
+ }
+ }
}
+++ /dev/null
-/*
- * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.nb.rfc8040.rests.utils;
-
-import com.google.common.util.concurrent.FluentFuture;
-import com.google.common.util.concurrent.ListenableFuture;
-import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
-import org.opendaylight.restconf.api.query.PointParam;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
-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.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Util class to post data to DS.
- */
-public final class PostDataTransactionUtil {
- private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
-
- private PostDataTransactionUtil() {
- // Hidden on purpose
- }
-
- /**
- * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
- * {@link RestconfStrategy} provided as a parameter.
- *
- * @param path path
- * @param data data
- * @param strategy Object that perform the actual DS operations
- * @param schemaContext reference to actual {@link EffectiveModelContext}
- * @param params {@link WriteDataParams}
- */
- public static void postData(final YangInstanceIdentifier path, final NormalizedNode data,
- final RestconfStrategy strategy, final EffectiveModelContext schemaContext, final WriteDataParams params) {
- TransactionUtil.syncCommit(submitData(path, data, strategy, schemaContext, params), "POST", path);
- }
-
- public static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
- final YangInstanceIdentifier path) {
- final var dataSchemaNode = DataSchemaContextTree.from(ctx).findChild(path).orElseThrow().dataSchemaNode();
-
- final String message;
- if (dataSchemaNode instanceof ListSchemaNode listSchema) {
- if (listSchema.isUserOrdered()) {
- return listSchema;
- }
- message = "Insert parameter can be used only with ordered-by user list.";
- } else if (dataSchemaNode instanceof LeafListSchemaNode leafListSchema) {
- if (leafListSchema.isUserOrdered()) {
- return leafListSchema;
- }
- message = "Insert parameter can be used only with ordered-by user leaf-list.";
- } else {
- message = "Insert parameter can be used only with list or leaf-list";
- }
- throw new RestconfDocumentedException(message, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
-
- /**
- * Post data by type.
- *
- * @param path path
- * @param data data
- * @param strategy object that perform the actual DS operations
- * @param schemaContext schema context of data
- * @param point query parameter
- * @param insert query parameter
- * @return {@link FluentFuture}
- */
- private static ListenableFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
- final NormalizedNode data, final RestconfStrategy strategy, final EffectiveModelContext schemaContext,
- final WriteDataParams params) {
- final var transaction = strategy.prepareWriteExecution();
- final var insert = params.insert();
- if (insert == null) {
- return makePost(path, data, schemaContext, transaction);
- }
-
- final var parentPath = path.coerceParent();
- checkListAndOrderedType(schemaContext, parentPath);
- final var grandParentPath = parentPath.coerceParent();
-
- return switch (insert) {
- case FIRST -> {
- final var readData = transaction.readList(grandParentPath);
- if (readData == null || readData.isEmpty()) {
- transaction.replace(path, data, schemaContext);
- } else {
- checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
- transaction.remove(grandParentPath);
- transaction.replace(path, data, schemaContext);
- transaction.replace(grandParentPath, readData, schemaContext);
- }
- yield transaction.commit();
- }
- case LAST -> makePost(path, data, schemaContext, transaction);
- case BEFORE -> {
- final var readData = transaction.readList(grandParentPath);
- if (readData == null || readData.isEmpty()) {
- transaction.replace(path, data, schemaContext);
- } else {
- checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
- insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, true, transaction);
- }
- yield transaction.commit();
- }
- case AFTER -> {
- final var readData = transaction.readList(grandParentPath);
- if (readData == null || readData.isEmpty()) {
- transaction.replace(path, data, schemaContext);
- } else {
- checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
- insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, false, transaction);
- }
- yield transaction.commit();
- }
- };
- }
-
- private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
- final EffectiveModelContext schemaContext, final PointParam point,
- final NormalizedNodeContainer<?> readList, final boolean before,
- final RestconfTransaction transaction) {
- final YangInstanceIdentifier parent = path.coerceParent().coerceParent();
- transaction.remove(parent);
- final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
- .getLastPathArgument();
- int lastItemPosition = 0;
- for (var nodeChild : readList.body()) {
- if (nodeChild.name().equals(pointArg)) {
- break;
- }
- lastItemPosition++;
- }
- if (!before) {
- lastItemPosition++;
- }
- int lastInsertedPosition = 0;
- final var emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
- transaction.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
- for (var nodeChild : readList.body()) {
- if (lastInsertedPosition == lastItemPosition) {
- transaction.replace(path, data, schemaContext);
- }
- final YangInstanceIdentifier childPath = parent.node(nodeChild.name());
- transaction.replace(childPath, nodeChild, schemaContext);
- lastInsertedPosition++;
- }
- }
-
- private static ListenableFuture<? extends CommitInfo> makePost(final YangInstanceIdentifier path,
- final NormalizedNode data, final EffectiveModelContext schemaContext,
- final RestconfTransaction transaction) {
- try {
- transaction.create(path, data, schemaContext);
- } catch (RestconfDocumentedException e) {
- // close transaction if any and pass exception further
- transaction.cancel();
- throw e;
- }
-
- return transaction.commit();
- }
-
- /**
- * Check if items do NOT already exists at specified {@code path}.
- *
- * @param existsFuture if checked data exists
- * @param path Path to be checked
- * @throws RestconfDocumentedException if data already exists.
- */
- public static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
- final YangInstanceIdentifier path) {
- if (TransactionUtil.syncAccess(existsFuture, path)) {
- LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
- throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
- path);
- }
- }
-}
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.yang.common.ErrorTag;
@Test
public final void testPostContainerData() {
- PostDataTransactionUtil.postData(JUKEBOX_IID, EMPTY_JUKEBOX, testPostContainerDataStrategy(), JUKEBOX_SCHEMA,
+ testPostContainerDataStrategy().postData(JUKEBOX_IID, EMPTY_JUKEBOX, JUKEBOX_SCHEMA,
WriteDataParams.empty());
}
@Test
public final void testPostListData() {
- PostDataTransactionUtil.postData(PLAYLIST_IID, PLAYLIST,
- testPostListDataStrategy(BAND_ENTRY, PLAYLIST_IID.node(BAND_ENTRY.name())), JUKEBOX_SCHEMA,
- WriteDataParams.empty());
+ testPostListDataStrategy(BAND_ENTRY, PLAYLIST_IID.node(BAND_ENTRY.name())).postData(PLAYLIST_IID, PLAYLIST,
+ JUKEBOX_SCHEMA, WriteDataParams.empty());
}
abstract @NonNull RestconfStrategy testPostListDataStrategy(MapEntryNode entryNode, YangInstanceIdentifier node);
final var domException = new DOMException((short) 414, "Post request failed");
RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
- () -> PostDataTransactionUtil.postData(JUKEBOX_IID, EMPTY_JUKEBOX,
- testPostDataFailStrategy(domException), JUKEBOX_SCHEMA, WriteDataParams.empty()));
+ () -> testPostDataFailStrategy(domException).postData(JUKEBOX_IID, EMPTY_JUKEBOX, JUKEBOX_SCHEMA,
+ WriteDataParams.empty()));
assertEquals(1, ex.getErrors().size());
assertThat(ex.getErrors().get(0).getErrorInfo(), containsString(domException.getMessage()));
}