import static java.util.Objects.requireNonNull;
import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.SettableFuture;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.common.api.ReadFailedException;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfFuture;
+import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
// Nothing else
}
- record Conflict(@NonNull YangInstanceIdentifier path, @Nullable ReadFailedException cause) implements Result {
+ record Conflict(@NonNull YangInstanceIdentifier path) implements Result {
Conflict {
requireNonNull(path);
}
private static final AtomicIntegerFieldUpdater<ExistenceCheck> UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ExistenceCheck.class, "outstanding");
- private final SettableFuture<@NonNull Result> future = SettableFuture.create();
+ private final SettableRestconfFuture<Result> future = new SettableRestconfFuture<>();
@SuppressWarnings("unused")
private volatile int outstanding;
outstanding = total;
}
- static ListenableFuture<@NonNull Result> start(final DOMDataTreeReadOperations tx,
+ static RestconfFuture<Result> start(final DOMDataTreeReadOperations tx,
final LogicalDatastoreType datastore, final YangInstanceIdentifier parentPath, final boolean expected,
final Collection<? extends NormalizedNode> children) {
final var ret = new ExistenceCheck(children.size());
@Override
@SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
public void onFailure(final Throwable throwable) {
- ret.complete(path, ReadFailedException.MAPPER.apply(
- throwable instanceof Exception ex ? ex : new ExecutionException(throwable)));
+ ret.complete(parentPath, ReadFailedException.MAPPER.apply(
+ throwable instanceof Exception ex ? ex : new ExecutionException(throwable)));
}
}, MoreExecutors.directExecutor());
}
private void complete(final YangInstanceIdentifier path, final boolean expected, final boolean present) {
final int count = UPDATER.decrementAndGet(this);
if (expected != present) {
- future.set(new Conflict(path, null));
+ future.set(new Conflict(path));
} else if (count == 0) {
future.set(Success.INSTANCE);
}
private void complete(final YangInstanceIdentifier path, final ReadFailedException cause) {
UPDATER.decrementAndGet(this);
- future.set(new Conflict(path, cause));
+ future.setFailure(new RestconfDocumentedException("Could not determine the existence of path " + path, cause,
+ cause.getErrorList()));
}
}
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Optional;
-import java.util.concurrent.ExecutionException;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.ExistenceCheck.Conflict;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.ExistenceCheck.Result;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
ensureParentsByMerge(path);
final var children = ((DistinctNodeContainer<?, ?>) data).body();
+
+ // Fire off an existence check
final var check = ExistenceCheck.start(verifyNotNull(rwTx), CONFIGURATION, path, false, children);
+ // ... and perform any put() operations, which happen-after existence check
for (var child : children) {
final var childPath = path.node(child.name());
verifyNotNull(rwTx).put(CONFIGURATION, childPath, child);
}
+
// ... finally collect existence checks and abort the transaction if any of them failed.
- checkExistence(path, check);
+ if (check.getOrThrow() instanceof Conflict) {
+ throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
+ path);
+ }
} else {
RestconfStrategy.checkItemDoesNotExists(verifyNotNull(rwTx).exists(CONFIGURATION, path), path);
ensureParentsByMerge(path);
ListenableFuture<Optional<NormalizedNode>> read(final YangInstanceIdentifier path) {
return verifyNotNull(rwTx).read(CONFIGURATION, path);
}
-
- private static void checkExistence(final YangInstanceIdentifier path,
- final ListenableFuture<@NonNull Result> future) {
- final Result result;
- try {
- result = future.get();
- } catch (ExecutionException e) {
- // This should never happen
- throw new IllegalStateException(e);
- } catch (InterruptedException e) {
- throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
- }
-
- if (result instanceof Conflict conflict) {
- final var cause = conflict.cause();
- if (cause == null) {
- throw new RestconfDocumentedException("Data already exists",
- ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
- }
- throw new RestconfDocumentedException("Could not determine the existence of path " + conflict.path(), cause,
- cause.getErrorList());
- }
- }
}