Eliminate FutureCallbackTx 04/107104/2
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 26 Jul 2023 22:05:48 +0000 (00:05 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 26 Jul 2023 22:29:26 +0000 (00:29 +0200)
The FutureCallbackTx.addCallback() is misleading and ResponseFactory
is a useless indirection. Eliminate both in favor of
TransactionUtil.syncCommit().

Change-Id: Iebddeac64b624ceaff71260aa3dc0579596fd454
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PlainPatchDataTransactionUtil.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ResponseFactory.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java

index e109a5ad276bce7bf1d7ac001d8f15f02df16d56..ea9c9975d1b1ad9738ce92ae3a48b83e70d7fbb2 100644 (file)
@@ -7,10 +7,7 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
-import com.google.common.util.concurrent.ListenableFuture;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
@@ -39,10 +36,8 @@ public final class DeleteDataTransactionUtil {
             transaction.cancel();
             throw e;
         }
-        final ListenableFuture<? extends CommitInfo> future = transaction.commit();
-        final ResponseFactory response = new ResponseFactory(Status.NO_CONTENT);
-        //This method will close transactionChain if any
-        FutureCallbackTx.addCallback(future, "DELETE", response, path);
-        return response.build();
+
+        TransactionUtil.syncCommit(transaction.commit(), "DELETE", path);
+        return Response.noContent().build();
     }
 }
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java
deleted file mode 100644 (file)
index 9b31efd..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.base.Throwables;
-import com.google.common.util.concurrent.ListenableFuture;
-import java.util.concurrent.ExecutionException;
-import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
-import org.opendaylight.netconf.api.DocumentedException;
-import org.opendaylight.netconf.api.NetconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.yangtools.yang.common.ErrorTag;
-import org.opendaylight.yangtools.yang.common.ErrorType;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Add callback for future objects and result set to the data factory.
- */
-final class FutureCallbackTx {
-    private static final Logger LOG = LoggerFactory.getLogger(FutureCallbackTx.class);
-
-    private FutureCallbackTx() {
-        // Hidden on purpose
-    }
-
-    /**
-     * Add callback to the future object and close transaction chain.
-     *
-     * @param listenableFuture
-     *             future object
-     * @param txType
-     *             type of operation (READ, POST, PUT, DELETE)
-     * @param dataFactory
-     *             factory setting result
-     * @param path unique identifier of a particular node instance in the data tree
-     * @throws RestconfDocumentedException
-     *             if the Future throws an exception
-     */
-    // FIXME: this is a *synchronous operation* and has to die
-    static void addCallback(final ListenableFuture<? extends CommitInfo> listenableFuture, final String txType,
-            final ResponseFactory dataFactory, final YangInstanceIdentifier path) throws RestconfDocumentedException {
-        try {
-            listenableFuture.get();
-        } catch (InterruptedException e) {
-            dataFactory.setFailureStatus();
-            LOG.warn("Transaction({}) FAILED!", txType, e);
-            throw new RestconfDocumentedException("Transaction failed", e);
-        } catch (ExecutionException e) {
-            dataFactory.setFailureStatus();
-            LOG.warn("Transaction({}) FAILED!", txType, e);
-
-            if (e.getCause() instanceof TransactionCommitFailedException commitFailed) {
-                // If device send some error message we want this message to get to client and not just to throw it away
-                // or override it with new generic message. We search for NetconfDocumentedException that was send from
-                // netconfSB and we create RestconfDocumentedException accordingly.
-                for (Throwable error : Throwables.getCausalChain(commitFailed)) {
-                    if (error instanceof DocumentedException documentedError) {
-                        final ErrorTag errorTag = documentedError.getErrorTag();
-                        if (errorTag.equals(ErrorTag.DATA_EXISTS)) {
-                            LOG.trace("Operation via Restconf was not executed because data at {} already exists",
-                                path);
-                            throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
-                                ErrorTag.DATA_EXISTS, "Data already exists", path));
-                        } else if (errorTag.equals(ErrorTag.DATA_MISSING)) {
-                            LOG.trace("Operation via Restconf was not executed because data at {} does not exist",
-                                path);
-                            throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
-                                ErrorTag.DATA_MISSING, "Data does not exist", path));
-                        }
-                    } else if (error instanceof NetconfDocumentedException documentedError) {
-                        throw new RestconfDocumentedException(documentedError.getMessage(),
-                            documentedError.getErrorType(), documentedError.getErrorTag(), e);
-                    }
-                }
-
-                throw new RestconfDocumentedException("Transaction(" + txType + ") not committed correctly", e);
-            } else {
-                throw new RestconfDocumentedException("Transaction failed", e);
-            }
-        }
-
-        LOG.trace("Transaction({}) SUCCESSFUL", txType);
-    }
-}
index 738f2f41a0aafc25251b4c4a662628d6046c7e27..6aa04311d0e5eefb15a0240ffcdfaf79d071f0b0 100644 (file)
@@ -7,11 +7,8 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
-import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
 import java.util.List;
-import javax.ws.rs.core.Response.Status;
-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.common.errors.RestconfDocumentedException;
@@ -119,12 +116,9 @@ public final class PatchDataTransactionUtil {
 
         // if no errors then submit transaction, otherwise cancel
         if (noError) {
-            final ResponseFactory response = new ResponseFactory(Status.OK);
-            final ListenableFuture<? extends CommitInfo> future = transaction.commit();
-
             try {
-                FutureCallbackTx.addCallback(future, PATCH_TX_TYPE, response, null);
-            } catch (final RestconfDocumentedException e) {
+                TransactionUtil.syncCommit(transaction.commit(), PATCH_TX_TYPE, null);
+            } catch (RestconfDocumentedException e) {
                 // if errors occurred during transaction commit then patch failed and global errors are reported
                 return new PatchStatusContext(context.getPatchId(), List.copyOf(editCollection), false, e.getErrors());
             }
index 8b4b372afa510111a371b96bf72865fce0dd48fc..178b0351fd894718f5f02fb543b70bdbec3defda 100644 (file)
@@ -9,7 +9,6 @@
 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
@@ -53,11 +52,7 @@ public final class PlainPatchDataTransactionUtil {
             throw new IllegalArgumentException(e);
         }
 
-        final var future = transaction.commit();
-        final ResponseFactory response = new ResponseFactory(Status.OK);
-
-        // closes transactionChain if any, may throw
-        FutureCallbackTx.addCallback(future, PatchDataTransactionUtil.PATCH_TX_TYPE, response, path);
-        return response.build();
+        TransactionUtil.syncCommit(transaction.commit(), PatchDataTransactionUtil.PATCH_TX_TYPE, path);
+        return Response.ok().build();
     }
 }
index 009a20e8766e2a76f99f7bfb6d438bd0cce22161..cff96ea2348a27977035d33323de6d8551a8d799 100644 (file)
@@ -12,7 +12,6 @@ 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;
@@ -62,11 +61,8 @@ public final class PostDataTransactionUtil {
      */
     public static Response postData(final UriInfo uriInfo, final YangInstanceIdentifier path, final NormalizedNode data,
             final RestconfStrategy strategy, final EffectiveModelContext schemaContext, final WriteDataParams params) {
-        final ListenableFuture<? extends CommitInfo> future = submitData(path, data, strategy, schemaContext, params);
-        final URI location = resolveLocation(uriInfo, path, schemaContext, data);
-        final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
-        FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, path);
-        return dataFactory.build();
+        TransactionUtil.syncCommit(submitData(path, data, strategy, schemaContext, params), POST_TX_TYPE, path);
+        return Response.created(resolveLocation(uriInfo, path, schemaContext, data)).build();
     }
 
     /**
index d9d37f5a50f7d87abcc9d1f6d51e7b3161f8e663..6c617cf55d629a5aae8fca08b82945c6e8dd1369 100644 (file)
@@ -68,13 +68,9 @@ public final class PutDataTransactionUtil {
             throw new RestconfDocumentedException("Interrupted while accessing " + path, e);
         }
 
-        final ResponseFactory responseFactory =
-            new ResponseFactory(exists ? Status.NO_CONTENT : Status.CREATED);
-        final ListenableFuture<? extends CommitInfo> submitData = submitData(path, schemaContext, strategy, data,
-            params);
-        //This method will close transactionChain if any
-        FutureCallbackTx.addCallback(submitData, PUT_TX_TYPE, responseFactory, path);
-        return responseFactory.build();
+        TransactionUtil.syncCommit(submitData(path, schemaContext, strategy, data, params), PUT_TX_TYPE, path);
+        // TODO: Status.CREATED implies a location...
+        return exists ? Response.noContent().build() : Response.status(Status.CREATED).build();
     }
 
     /**
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ResponseFactory.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ResponseFactory.java
deleted file mode 100644 (file)
index b653b88..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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 java.net.URI;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
-import javax.ws.rs.core.Response.Status;
-import org.eclipse.jdt.annotation.NonNull;
-
-final class ResponseFactory {
-    private final ResponseBuilder responseBuilder;
-
-    private boolean statusFail = false;
-
-    ResponseFactory(final Status status) {
-        responseBuilder = Response.status(status);
-    }
-
-    ResponseFactory location(final URI location) {
-        responseBuilder.location(location);
-        return this;
-    }
-
-    void setFailureStatus() {
-        statusFail = true;
-    }
-
-    public @NonNull Response build() {
-        if (statusFail) {
-            responseBuilder.status(Status.INTERNAL_SERVER_ERROR);
-        }
-        return responseBuilder.build();
-    }
-}
index 0a87cf9e479be4f6233781b82e08033524ce5306..5d63bcb1630c90b3ba3bfc9a814e084b64adbd77 100644 (file)
@@ -7,18 +7,34 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
+import com.google.common.base.Throwables;
+import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.netconf.api.DocumentedException;
+import org.opendaylight.netconf.api.NetconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
+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.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 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 for common methods of transactions.
  */
 public final class TransactionUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(TransactionUtil.class);
+
     private TransactionUtil() {
         // Hidden on purpose
     }
@@ -58,4 +74,56 @@ public final class TransactionUtil {
         transaction.merge(rootNormalizedPath,
             ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.of(normalizedPathWithoutChildArgs)));
     }
+
+    /**
+     * Synchronize commit future, translating any failure to a {@link RestconfDocumentedException}.
+     *
+     * @param future Commit future
+     * @param txType Transaction type name
+     * @param path Modified path
+     * @throws RestconfDocumentedException if commit fails
+     */
+    public static void syncCommit(final ListenableFuture<? extends CommitInfo> future, final String txType,
+            final YangInstanceIdentifier path) {
+        try {
+            future.get();
+        } catch (InterruptedException e) {
+            LOG.warn("Transaction({}) FAILED!", txType, e);
+            throw new RestconfDocumentedException("Transaction failed", e);
+        } catch (ExecutionException e) {
+            LOG.warn("Transaction({}) FAILED!", txType, e);
+            throw decodeException(e, txType, path);
+        }
+        LOG.trace("Transaction({}) SUCCESSFUL", txType);
+    }
+
+    private static @NonNull RestconfDocumentedException decodeException(final ExecutionException ex,
+            final String txType, final YangInstanceIdentifier path) {
+        if (ex.getCause() instanceof TransactionCommitFailedException commitFailed) {
+            // If device send some error message we want this message to get to client and not just to throw it away
+            // or override it with new generic message. We search for NetconfDocumentedException that was send from
+            // netconfSB and we create RestconfDocumentedException accordingly.
+            for (var error : Throwables.getCausalChain(commitFailed)) {
+                if (error instanceof DocumentedException documentedError) {
+                    final ErrorTag errorTag = documentedError.getErrorTag();
+                    if (errorTag.equals(ErrorTag.DATA_EXISTS)) {
+                        LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
+                        return new RestconfDocumentedException(ex, new RestconfError(ErrorType.PROTOCOL,
+                            ErrorTag.DATA_EXISTS, "Data already exists", path));
+                    } else if (errorTag.equals(ErrorTag.DATA_MISSING)) {
+                        LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
+                        return new RestconfDocumentedException(ex, new RestconfError(ErrorType.PROTOCOL,
+                            ErrorTag.DATA_MISSING, "Data does not exist", path));
+                    }
+                } else if (error instanceof NetconfDocumentedException netconfError) {
+                    return new RestconfDocumentedException(netconfError.getMessage(), netconfError.getErrorType(),
+                        netconfError.getErrorTag(), ex);
+                }
+            }
+
+            return new RestconfDocumentedException("Transaction(" + txType + ") not committed correctly", ex);
+        }
+
+        return new RestconfDocumentedException("Transaction(" + txType + ") failed", ex);
+    }
 }