Rework body formatting wiring
[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 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;
27
28 final class ExistenceCheck implements FutureCallback<Boolean> {
29     sealed interface Result {
30         // Nothing else
31     }
32
33     record Conflict(@NonNull YangInstanceIdentifier path) implements Result {
34         Conflict {
35             requireNonNull(path);
36         }
37     }
38
39     // Hidden on purpose:
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();
44     }
45
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;
51
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;
59     }
60
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());
66
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());
71         }
72         return future;
73     }
74
75     @Override
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();
83             return;
84         }
85
86         final int count = counter.decrementAndGet();
87         if (count == 0) {
88             // Everything else was a success, we ware done.
89             future.set(Success.INSTANCE);
90         }
91     }
92
93     @Override
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()));
103     }
104 }