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 java.util.stream.Collectors;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
21 import org.opendaylight.mdsal.common.api.ReadFailedException;
22 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
23 import org.opendaylight.restconf.server.api.DatabindContext;
24 import org.opendaylight.restconf.server.api.ServerError;
25 import org.opendaylight.restconf.server.api.ServerException;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 final class ExistenceCheck implements FutureCallback<Boolean> {
30 sealed interface Result {
34 record Conflict(@NonNull YangInstanceIdentifier path) implements Result {
41 // - users care only about conflicts
42 // - we could use null, but that is ugly and conflicts with RestconfException, which we want to use
43 private static final class Success implements Result {
44 static final @NonNull Success INSTANCE = new Success();
47 private final SettableRestconfFuture<Result> future;
48 private final AtomicInteger counter;
49 private final @NonNull YangInstanceIdentifier parentPath;
50 private final @NonNull YangInstanceIdentifier path;
51 // FIXME: NETCONF-1188: needed for ServerErrorPath propagation
52 // private final @NonNull DatabindContext databind;
53 private final boolean expected;
55 private ExistenceCheck(final DatabindContext databind, final SettableRestconfFuture<Result> future,
56 final AtomicInteger counter, final YangInstanceIdentifier parentPath, final YangInstanceIdentifier path,
57 final boolean expected) {
58 // FIXME: NETCONF-1188: needed for ServerErrorPath propagation
59 // this.databind = requireNonNull(databind);
60 this.future = requireNonNull(future);
61 this.counter = requireNonNull(counter);
62 this.parentPath = requireNonNull(parentPath);
63 this.path = requireNonNull(path);
64 this.expected = expected;
67 static RestconfFuture<Result> start(final DatabindContext databind, final DOMDataTreeReadOperations tx,
68 final LogicalDatastoreType datastore, final YangInstanceIdentifier parentPath, final boolean expected,
69 final Collection<? extends @NonNull NormalizedNode> children) {
70 final var future = new SettableRestconfFuture<Result>();
71 final var counter = new AtomicInteger(children.size());
73 for (var child : children) {
74 final var path = parentPath.node(child.name());
75 tx.exists(datastore, path)
76 .addCallback(new ExistenceCheck(databind, future, counter, parentPath, path, expected),
77 MoreExecutors.directExecutor());
83 public void onSuccess(final Boolean result) {
84 if (expected != result) {
85 // This is okay to race with onFailure(): we either report this or failure, the end result is failure,
86 // though slightly different. This a result of datastore timing, hence there is nothing we can do about it.
87 future.set(new Conflict(path));
88 // Failure is set first, before a parallel success can see counter being 0, hence we win
89 counter.decrementAndGet();
93 final int count = counter.decrementAndGet();
95 // Everything else was a success, we ware done.
96 future.set(Success.INSTANCE);
101 @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
102 public void onFailure(final Throwable throwable) {
103 final var cause = ReadFailedException.MAPPER.apply(throwable instanceof Exception ex ? ex
104 : new ExecutionException(throwable));
105 // We are not decrementing the counter here to ensure onSuccess() does not attempt to set success. Failure paths
106 // are okay -- they differ only in what we report. We rely on SettableFuture's synchronization faculties to
107 // reconcile any conflict with onSuccess() failure path.
108 future.setFailure(new ServerException(cause.getErrorList().stream()
109 .map(ServerError::ofRpcError)
110 .collect(Collectors.toList()), cause, "Could not determine the existence of path %s", parentPath));