Bug 6947 / Bug 6948 - implement point and insert query params
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / restconf / restful / utils / PostDataTransactionUtil.java
index 2d9c246ac256af6300f4f44436d792e1fb23f91c..23ce4f406a0f3d13a175f88ab0219adc5832b217 100644 (file)
@@ -8,34 +8,31 @@
 package org.opendaylight.restconf.restful.utils;
 
 import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.ListenableFuture;
 import java.net.URI;
-import java.util.concurrent.ExecutionException;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.restconf.common.references.SchemaContextRef;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
-import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
 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.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.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -63,19 +60,19 @@ public final class PostDataTransactionUtil {
      *            - wrapper for transaction data
      * @param schemaContextRef
      *            - reference to actual {@link SchemaContext}
+     * @param point
+     * @param insert
      * @return {@link CheckedFuture}
      */
     public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
-            final TransactionVarsWrapper transactionNode, final SchemaContextRef schemaContextRef) {
+            final TransactionVarsWrapper transactionNode, final SchemaContextRef schemaContextRef, final String insert,
+            final String point) {
         final CheckedFuture<Void, TransactionCommitFailedException> future = submitData(
                 payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(),
-                transactionNode, schemaContextRef.get());
+                transactionNode, schemaContextRef.get(), insert, point);
         final URI location = PostDataTransactionUtil.resolveLocation(uriInfo, transactionNode, schemaContextRef);
-        final ResponseFactory dataFactory = new ResponseFactory(
-                ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode),
-                location);
-        FutureCallbackTx.addCallback(future, transactionNode.getTransaction(),
-                RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory);
+        final ResponseFactory dataFactory = new ResponseFactory(null, location);
+        FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory);
         return dataFactory.build();
     }
 
@@ -90,76 +87,223 @@ public final class PostDataTransactionUtil {
      *            - wrapper for data to transaction
      * @param schemaContext
      *            - schema context of data
+     * @param point
+     *            - query parameter
+     * @param insert
+     *            - query parameter
      * @return {@link CheckedFuture}
      */
     private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
             final NormalizedNode<?, ?> data, final TransactionVarsWrapper transactionNode,
-            final SchemaContext schemaContext) {
-        final DOMDataReadWriteTransaction transaction = transactionNode.getTransaction();
-        final NormalizedNode<?, ?> node = ImmutableNodes.fromInstanceId(schemaContext, path);
-        transaction.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(node.getIdentifier()), node);
-        TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
+            final SchemaContext schemaContext, final String insert, final String point) {
+        final DOMTransactionChain domTransactionChain = transactionNode.getTransactionChain();
+        final DOMDataReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
+        if (insert == null) {
+            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+            return newReadWriteTransaction.submit();
+        } else {
+            final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
+            switch (insert) {
+                case "first":
+                    if (schemaNode instanceof ListSchemaNode) {
+                        final NormalizedNode<?, ?> readData =
+                                PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+                                        schemaNode);
+                        final OrderedMapNode readList = (OrderedMapNode) readData;
+                        if ((readList == null) || readList.getValue().isEmpty()) {
+                            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        } else {
+                            newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
+                                    path.getParent().getParent());
+                            simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
+                                    schemaContext, domTransactionChain);
+                            makePost(path, readData, schemaContext, domTransactionChain,
+                                    newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        }
+                    } else {
+                        final NormalizedNode<?, ?> readData =
+                                PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain, schemaNode);
 
-        if (data instanceof MapNode) {
-            for (final MapEntryNode child : ((MapNode) data).getValue()) {
-                putChild(child, transaction, path);
-            }
-        } else if (data instanceof AugmentationNode) {
-            for (final DataContainerChild<? extends PathArgument, ?> child : ((AugmentationNode) data).getValue()) {
-                putChild(child, transaction, path);
-            }
-        } else if (data instanceof ChoiceNode) {
-            for (final DataContainerChild<? extends PathArgument, ?> child : ((ChoiceNode) data).getValue()) {
-                putChild(child, transaction, path);
+                        final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+                        if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+                            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        } else {
+                            newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
+                                    path.getParent().getParent());
+                            simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
+                                    schemaContext, domTransactionChain);
+                            makePost(path, readData, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        }
+                    }
+                case "last":
+                    makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                    return newReadWriteTransaction.submit();
+                case "before":
+                    if (schemaNode instanceof ListSchemaNode) {
+                        final NormalizedNode<?, ?> readData =
+                                PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+                                        schemaNode);
+                        final OrderedMapNode readList = (OrderedMapNode) readData;
+                        if ((readList == null) || readList.getValue().isEmpty()) {
+                            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        } else {
+                            insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
+                                    data, schemaContext, point, readList, true, domTransactionChain);
+                            return newReadWriteTransaction.submit();
+                        }
+                    } else {
+                        final NormalizedNode<?, ?> readData =
+                                PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+                                        schemaNode);
+
+                        final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+                        if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+                            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        } else {
+                            insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+                                    path, data, schemaContext, point, readLeafList, true, domTransactionChain);
+                            return newReadWriteTransaction.submit();
+                        }
+                    }
+                case "after":
+                    if (schemaNode instanceof ListSchemaNode) {
+                        final NormalizedNode<?, ?> readData =
+                                PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+                                        schemaNode);
+                        final OrderedMapNode readList = (OrderedMapNode) readData;
+                        if ((readList == null) || readList.getValue().isEmpty()) {
+                            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        } else {
+                            insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
+                                    data, schemaContext, point, readList, false, domTransactionChain);
+                            return newReadWriteTransaction.submit();
+                        }
+                    } else {
+                        final NormalizedNode<?, ?> readData =
+                                PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+                                        schemaNode);
+
+                        final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+                        if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+                            makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+                            return newReadWriteTransaction.submit();
+                        } else {
+                            insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+                                    path, data, schemaContext, point, readLeafList, true, domTransactionChain);
+                            return newReadWriteTransaction.submit();
+                        }
+                    }
+                default:
+                    throw new RestconfDocumentedException(
+                            "Used bad value of insert parameter. Possible values are first, last, before or after, "
+                                    + "but was: " + insert);
             }
-        } else if (data instanceof LeafSetNode<?>) {
-            for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) data).getValue()) {
-                putChild(child, transaction, path);
+        }
+    }
+
+    private static void insertWithPointLeafListPost(final DOMDataReadWriteTransaction rWTransaction,
+            final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+            final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
+            final boolean before, final DOMTransactionChain domTransactionChain) {
+        rWTransaction.delete(datastore, path.getParent().getParent());
+        final InstanceIdentifierContext<?> instanceIdentifier =
+                ControllerContext.getInstance().toInstanceIdentifier(point);
+        int p = 0;
+        for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
+            if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+                break;
             }
-        } else if (data instanceof ContainerNode) {
-            for (final DataContainerChild<? extends PathArgument, ?> child : ((ContainerNode) data).getValue()) {
-                putChild(child, transaction, path);
+            p++;
+        }
+        if (!before) {
+            p++;
+        }
+        int h = 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 (h == p) {
+                TransactionUtil.checkItemDoesNotExists(domTransactionChain, rWTransaction, datastore, path,
+                        RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+                rWTransaction.put(datastore, path, payload);
             }
+            final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
+            TransactionUtil.checkItemDoesNotExists(domTransactionChain, rWTransaction, datastore, childPath,
+                    RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+            rWTransaction.put(datastore, childPath, nodeChild);
+            h++;
         }
-        return transaction.submit();
     }
 
-    /**
-     * Prepare data for submit
-     *
-     * @param child
-     *            - data
-     * @param readWriteTx
-     *            - transaction
-     * @param path
-     *            - path to data
-     */
-    private static void putChild(final NormalizedNode<?, ?> child, final DOMDataReadWriteTransaction readWriteTx,
-            final YangInstanceIdentifier path) {
-        final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
-        checkItemDesNotExits(childPath, readWriteTx);
-        readWriteTx.put(LogicalDatastoreType.CONFIGURATION, childPath, child);
+    private static void insertWithPointListPost(final DOMDataReadWriteTransaction rWTransaction,
+            final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+            final SchemaContext schemaContext, final String point, final MapNode readList, final boolean before,
+            final DOMTransactionChain domTransactionChain) {
+        rWTransaction.delete(datastore, path.getParent().getParent());
+        final InstanceIdentifierContext<?> instanceIdentifier =
+                ControllerContext.getInstance().toInstanceIdentifier(point);
+        int p = 0;
+        for (final MapEntryNode mapEntryNode : readList.getValue()) {
+            if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+                break;
+            }
+            p++;
+        }
+        if (!before) {
+            p++;
+        }
+        int h = 0;
+        final NormalizedNode<?, ?> emptySubtree =
+                ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
+        rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+        for (final MapEntryNode mapEntryNode : readList.getValue()) {
+            if (h == p) {
+                TransactionUtil.checkItemDoesNotExists(domTransactionChain, rWTransaction, datastore, path,
+                        RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+                rWTransaction.put(datastore, path, payload);
+            }
+            final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
+            TransactionUtil.checkItemDoesNotExists(domTransactionChain, rWTransaction, datastore, childPath,
+                    RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+            rWTransaction.put(datastore, childPath, mapEntryNode);
+            h++;
+        }
     }
 
-    /**
-     * Check if data posted to create doesn't exits.
-     *
-     * @param path
-     *            - path to data
-     * @param readWriteTx
-     *            - read write transaction
-     */
-    private static void checkItemDesNotExits(final YangInstanceIdentifier path,
-            final DOMDataReadWriteTransaction readWriteTx) {
-        final ListenableFuture<Boolean> existData = readWriteTx.exists(LogicalDatastoreType.CONFIGURATION, path);
-        try {
-            if (existData.get()) {
-                readWriteTx.cancel();
-                throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
-                        ErrorTag.DATA_EXISTS);
+    private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
+            final SchemaContext schemaContext, final DOMTransactionChain transactionChain,
+            final DOMDataReadWriteTransaction transaction) {
+        if (data instanceof MapNode) {
+            boolean merge = false;
+            for (final MapEntryNode child : ((MapNode) data).getValue()) {
+                final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+                TransactionUtil.checkItemDoesNotExists(
+                        transactionChain, 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);
             }
-        } catch (InterruptedException | ExecutionException e) {
-            LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
+        } else {
+            TransactionUtil.checkItemDoesNotExists(
+                    transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, path,
+                    RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+
+            TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
+            transaction.put(LogicalDatastoreType.CONFIGURATION, path, data);
         }
     }
 
@@ -187,5 +331,13 @@ public final class PostDataTransactionUtil {
 
         return uriBuilder.build();
     }
-}
 
+    private static void simplePost(final DOMDataReadWriteTransaction rWTransaction,
+            final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+            final SchemaContext schemaContext, final DOMTransactionChain transactionChain) {
+        TransactionUtil.checkItemDoesNotExists(transactionChain, rWTransaction, datastore, path,
+                RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+        TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
+        rWTransaction.put(datastore, path, payload);
+    }
+}