Eliminate PlainPatchDataTransactionUtil
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / RestconfStrategy.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, 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.Futures;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import java.util.List;
17 import java.util.Optional;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.mdsal.common.api.CommitInfo;
20 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
21 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
22 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
23 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
24 import org.opendaylight.restconf.common.errors.RestconfFuture;
25 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
26 import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
27 import org.opendaylight.yangtools.yang.common.Empty;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
31
32 /**
33  * Baseline execution strategy for various RESTCONF operations.
34  *
35  * @see NetconfRestconfStrategy
36  * @see MdsalRestconfStrategy
37  */
38 // FIXME: it seems the first three operations deal with lifecycle of a transaction, while others invoke various
39 //        operations. This should be handled through proper allocation indirection.
40 public abstract class RestconfStrategy {
41     RestconfStrategy() {
42         // Hidden on purpose
43     }
44
45     /**
46      * Look up the appropriate strategy for a particular mount point.
47      *
48      * @param mountPoint Target mount point
49      * @return A strategy, or null if the mount point does not expose a supported interface
50      * @throws NullPointerException if {@code mountPoint} is null
51      */
52     public static Optional<RestconfStrategy> forMountPoint(final DOMMountPoint mountPoint) {
53         final Optional<RestconfStrategy> netconf = mountPoint.getService(NetconfDataTreeService.class)
54             .map(NetconfRestconfStrategy::new);
55         if (netconf.isPresent()) {
56             return netconf;
57         }
58
59         return mountPoint.getService(DOMDataBroker.class).map(MdsalRestconfStrategy::new);
60     }
61
62     /**
63      * Lock the entire datastore.
64      *
65      * @return A {@link RestconfTransaction}. This transaction needs to be either committed or canceled before doing
66      *         anything else.
67      */
68     public abstract RestconfTransaction prepareWriteExecution();
69
70     /**
71      * Read data from the datastore.
72      *
73      * @param store the logical data store which should be modified
74      * @param path the data object path
75      * @return a ListenableFuture containing the result of the read
76      */
77     public abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store,
78         YangInstanceIdentifier path);
79
80     /**
81      * Read data selected using fields from the datastore.
82      *
83      * @param store the logical data store which should be modified
84      * @param path the parent data object path
85      * @param fields paths to selected fields relative to parent path
86      * @return a ListenableFuture containing the result of the read
87      */
88     public abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store,
89             YangInstanceIdentifier path, List<YangInstanceIdentifier> fields);
90
91     /**
92      * Check if data already exists in the datastore.
93      *
94      * @param store the logical data store which should be modified
95      * @param path the data object path
96      * @return a FluentFuture containing the result of the check
97      */
98     public abstract ListenableFuture<Boolean> exists(LogicalDatastoreType store, YangInstanceIdentifier path);
99
100     /**
101      * Delete data from the configuration datastore. If the data does not exist, this operation will fail, as outlined
102      * in <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.7">RFC8040 section 4.7</a>
103      *
104      * @param path Path to delete
105      * @return A {@link RestconfFuture}
106      * @throws NullPointerException if {@code path} is {@code null}
107      */
108     public final @NonNull RestconfFuture<Empty> delete(final YangInstanceIdentifier path) {
109         final var ret = new SettableRestconfFuture<Empty>();
110         delete(ret, requireNonNull(path));
111         return ret;
112     }
113
114     protected abstract void delete(@NonNull SettableRestconfFuture<Empty> future, @NonNull YangInstanceIdentifier path);
115
116     /**
117      * Merge data into the configuration datastore, as outlined in
118      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040 section 4.6.1</a>.
119      *
120      * @param path Path to merge
121      * @param data Data to merge
122      * @param context Corresponding EffectiveModelContext
123      * @return A {@link RestconfFuture}
124      * @throws NullPointerException if any argument is {@code null}
125      */
126     public final @NonNull RestconfFuture<Empty> merge(final YangInstanceIdentifier path, final NormalizedNode data,
127             final EffectiveModelContext context) {
128         final var ret = new SettableRestconfFuture<Empty>();
129         merge(ret, requireNonNull(path), requireNonNull(data), requireNonNull(context));
130         return ret;
131     }
132
133     private void merge(final @NonNull SettableRestconfFuture<Empty> future,
134             final @NonNull YangInstanceIdentifier path, final @NonNull NormalizedNode data,
135             final @NonNull EffectiveModelContext context) {
136         final var tx = prepareWriteExecution();
137         // FIXME: this method should be further specialized to eliminate this call -- it is only needed for MD-SAL
138         TransactionUtil.ensureParentsByMerge(path, context, tx);
139         tx.merge(path, data);
140         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
141             @Override
142             public void onSuccess(final CommitInfo result) {
143                 future.set(Empty.value());
144             }
145
146             @Override
147             public void onFailure(final Throwable cause) {
148                 future.setFailure(TransactionUtil.decodeException(cause, "MERGE", path));
149             }
150         }, MoreExecutors.directExecutor());
151     }
152 }