Eliminate PutDataTransactionUtil
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / PostDataTransactionUtil.java
index e0626193b1002e6020039767ae3f67d7123653d0..810894ba4b30de183e1e29db7de220bf6f39fea2 100644 (file)
@@ -9,32 +9,26 @@ package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
 import com.google.common.util.concurrent.FluentFuture;
 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.Response.Status;
-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.DOMTransactionChain;
-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.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.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.MapNode;
 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.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,7 +37,6 @@ import org.slf4j.LoggerFactory;
  */
 public final class PostDataTransactionUtil {
     private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
-    private static final String POST_TX_TYPE = "POST";
 
     private PostDataTransactionUtil() {
         // Hidden on purpose
@@ -53,24 +46,36 @@ public final class PostDataTransactionUtil {
      * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
      * {@link RestconfStrategy} provided as a parameter.
      *
-     * @param uriInfo       uri info
-     * @param payload       data
+     * @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}
-     * @return {@link Response}
      */
-    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 ListenableFuture<? 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);
-        //This method will close transactionChain if any
-        FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, path);
-        return dataFactory.build();
+    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);
     }
 
     /**
@@ -87,62 +92,58 @@ public final class PostDataTransactionUtil {
     private static ListenableFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
             final NormalizedNode data, final RestconfStrategy strategy, final EffectiveModelContext schemaContext,
             final WriteDataParams params) {
-        final RestconfTransaction transaction = strategy.prepareWriteExecution();
-        final InsertParam insert = params.insert();
+        final var transaction = strategy.prepareWriteExecution();
+        final var insert = params.insert();
         if (insert == null) {
-            makePost(path, data, schemaContext, transaction);
-            return transaction.commit();
+            return makePost(path, data, schemaContext, transaction);
         }
 
-        PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
-        final NormalizedNode readData;
-        switch (insert) {
-            case FIRST:
-                readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
-                if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+        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);
-                    return transaction.commit();
+                } else {
+                    checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+                    transaction.remove(grandParentPath);
+                    transaction.replace(path, data, schemaContext);
+                    transaction.replace(grandParentPath, readData, schemaContext);
                 }
-                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()) {
+                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);
-                    return transaction.commit();
+                } else {
+                    checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+                    insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, true, transaction);
                 }
-                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()) {
+                yield transaction.commit();
+            }
+            case AFTER -> {
+                final var readData = transaction.readList(grandParentPath);
+                if (readData == null || readData.isEmpty()) {
                     transaction.replace(path, data, schemaContext);
-                    return transaction.commit();
+                } else {
+                    checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+                    insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, false, transaction);
                 }
-                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);
-        }
+                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.getParent().getParent();
+        final YangInstanceIdentifier parent = path.coerceParent().coerceParent();
         transaction.remove(parent);
         final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
             .getLastPathArgument();
@@ -169,8 +170,9 @@ public final class PostDataTransactionUtil {
         }
     }
 
-    private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data,
-                                 final EffectiveModelContext schemaContext, final RestconfTransaction transaction) {
+    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) {
@@ -178,31 +180,8 @@ public final class PostDataTransactionUtil {
             transaction.cancel();
             throw e;
         }
-    }
-
-    /**
-     * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
-     *
-     * @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 YangInstanceIdentifier initialPath,
-                                       final EffectiveModelContext schemaContext, final NormalizedNode data) {
-        if (uriInfo == null) {
-            return null;
-        }
 
-        YangInstanceIdentifier path = initialPath;
-        if (data instanceof MapNode mapData) {
-            final var children = mapData.body();
-            if (!children.isEmpty()) {
-                path = path.node(children.iterator().next().name());
-            }
-        }
-
-        return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
+        return transaction.commit();
     }
 
     /**
@@ -214,20 +193,10 @@ public final class PostDataTransactionUtil {
      */
     public static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
                                               final YangInstanceIdentifier path) {
-        final boolean exists;
-        try {
-            exists = existsFuture.get();
-        } catch (ExecutionException e) {
-            throw new RestconfDocumentedException("Failed to access " + path, e);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new RestconfDocumentedException("Interrupted while accessing " + path, e);
-        }
-
-        if (exists) {
+        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);
+            throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
+                path);
         }
     }
 }