import com.google.common.util.concurrent.FluentFuture;
import java.net.URI;
+import java.util.Collection;
import java.util.Optional;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
-import org.opendaylight.restconf.common.context.NormalizedNodeContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
-import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.restconf.nb.rfc8040.InsertParam;
+import org.opendaylight.restconf.nb.rfc8040.PointParam;
+import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+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.IdentifierCodec;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+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.LeafSetEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
-import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+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 static final String POST_TX_TYPE = "POST";
+
private PostDataTransactionUtil() {
- throw new UnsupportedOperationException("Util class.");
+ // Hidden on purpose
}
/**
- * Check mount point and prepare variables for post data.
- *
- * @param uriInfo
+ * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
+ * {@link RestconfStrategy} provided as a parameter.
*
- * @param payload
- * data
- * @param transactionNode
- * wrapper for transaction data
- * @param schemaContextRef
- * reference to actual {@link SchemaContext}
- * @param point
- * point
- * @param insert
- * insert
+ * @param uriInfo uri info
+ * @param payload data
+ * @param strategy Object that perform the actual DS operations
+ * @param schemaContext reference to actual {@link EffectiveModelContext}
+ * @param params {@link WriteDataParams}
* @return {@link Response}
*/
- public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
- final TransactionVarsWrapper transactionNode, final SchemaContextRef schemaContextRef, final String insert,
- final String point) {
- final FluentFuture<? extends CommitInfo> future = submitData(
- payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(),
- transactionNode, schemaContextRef.get(), insert, point);
- final URI location = PostDataTransactionUtil.resolveLocation(uriInfo, transactionNode, schemaContextRef);
+ public static Response postData(final UriInfo uriInfo, final NormalizedNodePayload payload,
+ final RestconfStrategy strategy,
+ final EffectiveModelContext schemaContext, final WriteDataParams params) {
+ final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
+ final FluentFuture<? extends CommitInfo> future = submitData(path, payload.getData(),
+ strategy, schemaContext, params);
+ final URI location = resolveLocation(uriInfo, path, schemaContext, payload.getData());
final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
- FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory);
+ //This method will close transactionChain if any
+ FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, path);
return dataFactory.build();
}
/**
* Post data by type.
*
- * @param path
- * path
- * @param data
- * data
- * @param transactionNode
- * wrapper for data to transaction
- * @param schemaContext
- * schema context of data
- * @param point
- * query parameter
- * @param insert
- * query parameter
+ * @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 FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
- final NormalizedNode<?, ?> data, final TransactionVarsWrapper transactionNode,
- final SchemaContext schemaContext, final String insert, final String point) {
- final DOMTransactionChain domTransactionChain = transactionNode.getTransactionChain();
- final DOMDataTreeReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
+ final NormalizedNode data,
+ final RestconfStrategy strategy,
+ final EffectiveModelContext schemaContext,
+ final WriteDataParams params) {
+ final RestconfTransaction transaction = strategy.prepareWriteExecution();
+ final InsertParam insert = params.insert();
if (insert == null) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
- switch (insert) {
- case "first":
- if (schemaNode instanceof ListSchemaNode) {
- final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
- schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
- final OrderedMapNode readList = (OrderedMapNode) readData;
- if (readList == null || readList.getValue().isEmpty()) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
- path.getParent().getParent());
- simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
- schemaContext, transactionNode.getTransactionChainHandler());
- makePost(path, readData, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- }
- } else {
- final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
- schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
-
- final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
- if (readLeafList == null || readLeafList.getValue().isEmpty()) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
- path.getParent().getParent());
- simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
- schemaContext, transactionNode.getTransactionChainHandler());
- makePost(path, readData, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- }
- }
- case "last":
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- case "before":
- if (schemaNode instanceof ListSchemaNode) {
- final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
- schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
- final OrderedMapNode readList = (OrderedMapNode) readData;
- if (readList == null || readList.getValue().isEmpty()) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
- data, schemaContext, point, readList, true,
- transactionNode.getTransactionChainHandler());
- return newReadWriteTransaction.commit();
- }
- } else {
- final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
- schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
-
- final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
- if (readLeafList == null || readLeafList.getValue().isEmpty()) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
- path, data, schemaContext, point, readLeafList, true,
- transactionNode.getTransactionChainHandler());
- return newReadWriteTransaction.commit();
- }
- }
- case "after":
- if (schemaNode instanceof ListSchemaNode) {
- final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
- schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
- final OrderedMapNode readList = (OrderedMapNode) readData;
- if (readList == null || readList.getValue().isEmpty()) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
- data, schemaContext, point, readList, false,
- transactionNode.getTransactionChainHandler());
- return newReadWriteTransaction.commit();
- }
- } else {
- final NormalizedNode<?, ?> readData = PutDataTransactionUtil.readList(path.getParent(),
- schemaContext, transactionNode.getTransactionChainHandler(), schemaNode);
-
- final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
- if (readLeafList == null || readLeafList.getValue().isEmpty()) {
- makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(),
- newReadWriteTransaction);
- return newReadWriteTransaction.commit();
- } else {
- insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
- path, data, schemaContext, point, readLeafList, true,
- transactionNode.getTransactionChainHandler());
- return newReadWriteTransaction.commit();
- }
- }
- default:
- throw new RestconfDocumentedException(
- "Used bad value of insert parameter. Possible values are first, last, before or after, "
- + "but was: " + insert);
- }
+ makePost(path, data, schemaContext, transaction);
+ return transaction.commit();
}
- }
- private static void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
- final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
- final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
- final boolean before, final TransactionChainHandler transactionChainHandler) {
- rwTransaction.delete(datastore, path.getParent().getParent());
- final InstanceIdentifierContext<?> instanceIdentifier =
- ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
- int lastItemPosition = 0;
- for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
- if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
- break;
- }
- lastItemPosition++;
- }
- if (!before) {
- lastItemPosition++;
- }
- int lastInsertedPosition = 0;
- final NormalizedNode<?, ?> emptySubtree =
- ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
- rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
- for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
- if (lastInsertedPosition == lastItemPosition) {
- TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
- rwTransaction.put(datastore, path, payload);
- }
- final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
- TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, childPath,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
- rwTransaction.put(datastore, childPath, nodeChild);
- lastInsertedPosition++;
+ PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
+ final NormalizedNode readData;
+ switch (insert) {
+ case FIRST:
+ readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
+ if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+ transaction.replace(path, data, schemaContext);
+ return transaction.commit();
+ }
+ checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ transaction.remove(path.getParent().getParent());
+ transaction.replace(path, data, schemaContext);
+ transaction.replace(path.getParent().getParent(), readData, schemaContext);
+ return transaction.commit();
+ case LAST:
+ makePost(path, data, schemaContext, transaction);
+ return transaction.commit();
+ case BEFORE:
+ readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
+ if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+ transaction.replace(path, data, schemaContext);
+ return transaction.commit();
+ }
+ checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ insertWithPointPost(path, data, schemaContext, params.getPoint(),
+ (NormalizedNodeContainer<?>) readData, true, transaction);
+ return transaction.commit();
+ case AFTER:
+ readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
+ if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+ transaction.replace(path, data, schemaContext);
+ return transaction.commit();
+ }
+ checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ insertWithPointPost(path, data, schemaContext, params.getPoint(),
+ (NormalizedNodeContainer<?>) readData, false, transaction);
+ return transaction.commit();
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
+ + insert, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
}
}
- private static void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
- final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
- final SchemaContext schemaContext, final String point, final MapNode readList, final boolean before,
- final TransactionChainHandler transactionChainHandler) {
- rwTransaction.delete(datastore, path.getParent().getParent());
- final InstanceIdentifierContext<?> instanceIdentifier =
- ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty());
+ 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.getParent().getParent();
+ transaction.remove(parent);
+ final InstanceIdentifierContext instanceIdentifier =
+ // FIXME: Point should be able to give us this method
+ ParserIdentifier.toInstanceIdentifier(point.value(), schemaContext, Optional.empty());
int lastItemPosition = 0;
- for (final MapEntryNode mapEntryNode : readList.getValue()) {
- if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ for (final NormalizedNode nodeChild : readList.body()) {
+ if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
break;
}
lastItemPosition++;
lastItemPosition++;
}
int lastInsertedPosition = 0;
- final NormalizedNode<?, ?> emptySubtree =
- ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
- rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
- for (final MapEntryNode mapEntryNode : readList.getValue()) {
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
+ transaction.merge(YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final NormalizedNode nodeChild : readList.body()) {
if (lastInsertedPosition == lastItemPosition) {
- TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
- rwTransaction.put(datastore, path, payload);
+ transaction.replace(path, data, schemaContext);
}
- final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
- TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, childPath,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
- rwTransaction.put(datastore, childPath, mapEntryNode);
+ final YangInstanceIdentifier childPath = parent.node(nodeChild.getIdentifier());
+ transaction.replace(childPath, nodeChild, schemaContext);
lastInsertedPosition++;
}
}
- private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
- final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler,
- final DOMDataTreeReadWriteTransaction transaction) {
- if (data instanceof MapNode) {
- boolean merge = false;
- for (final MapEntryNode child : ((MapNode) data).getValue()) {
- final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
- TransactionUtil.checkItemDoesNotExists(
- transactionChainHandler, transaction, LogicalDatastoreType.CONFIGURATION, childPath,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
- if (!merge) {
- merge = true;
- TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
- final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
- transaction.merge(LogicalDatastoreType.CONFIGURATION,
- YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
- }
- transaction.put(LogicalDatastoreType.CONFIGURATION, childPath, child);
- }
- } else {
- TransactionUtil.checkItemDoesNotExists(
- transactionChainHandler, transaction, LogicalDatastoreType.CONFIGURATION, path,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
-
- TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
- transaction.put(LogicalDatastoreType.CONFIGURATION, path, data);
+ private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data,
+ final SchemaContext 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;
}
}
/**
* Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
*
- * @param uriInfo
- * uri info
- * @param transactionNode
- * wrapper for data of transaction
- * @param schemaContextRef
- * reference to {@link SchemaContext}
+ * @param uriInfo uri info
+ * @param initialPath data path
+ * @param schemaContext reference to {@link SchemaContext}
* @return {@link URI}
*/
- private static URI resolveLocation(final UriInfo uriInfo, final TransactionVarsWrapper transactionNode,
- final SchemaContextRef schemaContextRef) {
+ private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
+ final EffectiveModelContext schemaContext, final NormalizedNode data) {
if (uriInfo == null) {
return null;
}
- final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
- uriBuilder.path("data");
- uriBuilder.path(ParserIdentifier
- .stringFromYangInstanceIdentifier(transactionNode.getInstanceIdentifier().getInstanceIdentifier(),
- schemaContextRef.get()));
+ YangInstanceIdentifier path = initialPath;
+ if (data instanceof MapNode) {
+ final Collection<MapEntryNode> children = ((MapNode) data).body();
+ if (!children.isEmpty()) {
+ path = path.node(children.iterator().next().getIdentifier());
+ }
+ }
- return uriBuilder.build();
+ return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
}
- private static void simplePost(final DOMDataTreeReadWriteTransaction rwTransaction,
- final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
- final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler) {
- TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path,
- RestconfDataServiceConstant.PostData.POST_TX_TYPE);
- TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
- rwTransaction.put(datastore, path, payload);
+ /**
+ * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+ * data already exists.
+ *
+ * @param isExistsFuture if checked data exists
+ * @param path Path to be checked
+ */
+ public static void checkItemDoesNotExists(final FluentFuture<Boolean> isExistsFuture,
+ final YangInstanceIdentifier path) {
+ final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+ FutureCallbackTx.addCallback(isExistsFuture, POST_TX_TYPE, response);
+
+ if (response.result) {
+ 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);
+ }
}
}