*/
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;
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();
}
}
+++ /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.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);
- }
-}
*/
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;
// 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());
}
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;
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();
}
}
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;
*/
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();
}
/**
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();
}
/**
+++ /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 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();
- }
-}
*/
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
}
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);
+ }
}