OpenApi add POST request to device root
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / ExistenceCheck.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
9
10 import static java.util.Objects.requireNonNull;
11
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;
28
29 final class ExistenceCheck implements FutureCallback<Boolean> {
30     sealed interface Result {
31         // Nothing else
32     }
33
34     record Conflict(@NonNull YangInstanceIdentifier path) implements Result {
35         Conflict {
36             requireNonNull(path);
37         }
38     }
39
40     // Hidden on purpose:
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();
45     }
46
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;
54
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;
65     }
66
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());
72
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());
78         }
79         return future;
80     }
81
82     @Override
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();
90             return;
91         }
92
93         final int count = counter.decrementAndGet();
94         if (count == 0) {
95             // Everything else was a success, we ware done.
96             future.set(Success.INSTANCE);
97         }
98     }
99
100     @Override
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));
111     }
112 }