2 * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.MoreExecutors;
14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
15 import java.util.Collection;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.atomic.AtomicInteger;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
20 import org.opendaylight.mdsal.common.api.ReadFailedException;
21 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
22 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
23 import org.opendaylight.restconf.common.errors.RestconfFuture;
24 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
28 final class ExistenceCheck implements FutureCallback<Boolean> {
29 sealed interface Result {
33 record Conflict(@NonNull YangInstanceIdentifier path) implements Result {
40 // - users care only about conflicts
41 // - we could use null, but that is ugly and conflicts with RestconfException, which we want to use
42 private static final class Success implements Result {
43 static final @NonNull Success INSTANCE = new Success();
46 private final SettableRestconfFuture<Result> future;
47 private final AtomicInteger counter;
48 private final @NonNull YangInstanceIdentifier parentPath;
49 private final @NonNull YangInstanceIdentifier path;
50 private final boolean expected;
52 private ExistenceCheck(final SettableRestconfFuture<Result> future, final AtomicInteger counter,
53 final YangInstanceIdentifier parentPath, final YangInstanceIdentifier path, final boolean expected) {
54 this.future = requireNonNull(future);
55 this.counter = requireNonNull(counter);
56 this.parentPath = requireNonNull(parentPath);
57 this.path = requireNonNull(path);
58 this.expected = expected;
61 static RestconfFuture<Result> start(final DOMDataTreeReadOperations tx,
62 final LogicalDatastoreType datastore, final YangInstanceIdentifier parentPath, final boolean expected,
63 final Collection<? extends @NonNull NormalizedNode> children) {
64 final var future = new SettableRestconfFuture<Result>();
65 final var counter = new AtomicInteger(children.size());
67 for (var child : children) {
68 final var path = parentPath.node(child.name());
69 tx.exists(datastore, path).addCallback(new ExistenceCheck(future, counter, parentPath, path, expected),
70 MoreExecutors.directExecutor());
76 public void onSuccess(final Boolean result) {
77 if (expected != result) {
78 // This is okay to race with onFailure(): we either report this or failure, the end result is failure,
79 // though slightly different. This a result of datastore timing, hence there is nothing we can do about it.
80 future.set(new Conflict(path));
81 // Failure is set first, before a parallel success can see counter being 0, hence we win
82 counter.decrementAndGet();
86 final int count = counter.decrementAndGet();
88 // Everything else was a success, we ware done.
89 future.set(Success.INSTANCE);
94 @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
95 public void onFailure(final Throwable throwable) {
96 final var cause = ReadFailedException.MAPPER.apply(throwable instanceof Exception ex ? ex
97 : new ExecutionException(throwable));
98 // We are not decrementing the counter here to ensure onSuccess() does not attempt to set success. Failure paths
99 // are okay -- they differ only in what we report. We rely on SettableFuture's synchronization faculties to
100 // reconcile any conflict with onSuccess() failure path.
101 future.setFailure(new RestconfDocumentedException("Could not determine the existence of path " + parentPath,
102 cause, cause.getErrorList()));