Move CreateOrReplaceResult
[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     /**
42      * Result of a {@code PUT} request as defined in
43      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>. The definition makes it
44      * clear that the logical operation is {@code create-or-replace}.
45      */
46     public enum CreateOrReplaceResult {
47         /**
48          * A new resource has been created.
49          */
50         CREATED,
51         /*
52          * An existing resources has been replaced.
53          */
54         REPLACED;
55     }
56
57     RestconfStrategy() {
58         // Hidden on purpose
59     }
60
61     /**
62      * Look up the appropriate strategy for a particular mount point.
63      *
64      * @param mountPoint Target mount point
65      * @return A strategy, or null if the mount point does not expose a supported interface
66      * @throws NullPointerException if {@code mountPoint} is null
67      */
68     public static Optional<RestconfStrategy> forMountPoint(final DOMMountPoint mountPoint) {
69         final Optional<RestconfStrategy> netconf = mountPoint.getService(NetconfDataTreeService.class)
70             .map(NetconfRestconfStrategy::new);
71         if (netconf.isPresent()) {
72             return netconf;
73         }
74
75         return mountPoint.getService(DOMDataBroker.class).map(MdsalRestconfStrategy::new);
76     }
77
78     /**
79      * Lock the entire datastore.
80      *
81      * @return A {@link RestconfTransaction}. This transaction needs to be either committed or canceled before doing
82      *         anything else.
83      */
84     public abstract RestconfTransaction prepareWriteExecution();
85
86     /**
87      * Read data from the datastore.
88      *
89      * @param store the logical data store which should be modified
90      * @param path the data object path
91      * @return a ListenableFuture containing the result of the read
92      */
93     public abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store,
94         YangInstanceIdentifier path);
95
96     /**
97      * Read data selected using fields from the datastore.
98      *
99      * @param store the logical data store which should be modified
100      * @param path the parent data object path
101      * @param fields paths to selected fields relative to parent path
102      * @return a ListenableFuture containing the result of the read
103      */
104     public abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store,
105             YangInstanceIdentifier path, List<YangInstanceIdentifier> fields);
106
107     /**
108      * Check if data already exists in the datastore.
109      *
110      * @param store the logical data store which should be modified
111      * @param path the data object path
112      * @return a FluentFuture containing the result of the check
113      */
114     public abstract ListenableFuture<Boolean> exists(LogicalDatastoreType store, YangInstanceIdentifier path);
115
116     /**
117      * Delete data from the configuration datastore. If the data does not exist, this operation will fail, as outlined
118      * in <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.7">RFC8040 section 4.7</a>
119      *
120      * @param path Path to delete
121      * @return A {@link RestconfFuture}
122      * @throws NullPointerException if {@code path} is {@code null}
123      */
124     public final @NonNull RestconfFuture<Empty> delete(final YangInstanceIdentifier path) {
125         final var ret = new SettableRestconfFuture<Empty>();
126         delete(ret, requireNonNull(path));
127         return ret;
128     }
129
130     protected abstract void delete(@NonNull SettableRestconfFuture<Empty> future, @NonNull YangInstanceIdentifier path);
131
132     /**
133      * Merge data into the configuration datastore, as outlined in
134      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040 section 4.6.1</a>.
135      *
136      * @param path Path to merge
137      * @param data Data to merge
138      * @param context Corresponding EffectiveModelContext
139      * @return A {@link RestconfFuture}
140      * @throws NullPointerException if any argument is {@code null}
141      */
142     public final @NonNull RestconfFuture<Empty> merge(final YangInstanceIdentifier path, final NormalizedNode data,
143             final EffectiveModelContext context) {
144         final var ret = new SettableRestconfFuture<Empty>();
145         merge(ret, requireNonNull(path), requireNonNull(data), requireNonNull(context));
146         return ret;
147     }
148
149     private void merge(final @NonNull SettableRestconfFuture<Empty> future,
150             final @NonNull YangInstanceIdentifier path, final @NonNull NormalizedNode data,
151             final @NonNull EffectiveModelContext context) {
152         final var tx = prepareWriteExecution();
153         // FIXME: this method should be further specialized to eliminate this call -- it is only needed for MD-SAL
154         TransactionUtil.ensureParentsByMerge(path, context, tx);
155         tx.merge(path, data);
156         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
157             @Override
158             public void onSuccess(final CommitInfo result) {
159                 future.set(Empty.value());
160             }
161
162             @Override
163             public void onFailure(final Throwable cause) {
164                 future.setFailure(TransactionUtil.decodeException(cause, "MERGE", path));
165             }
166         }, MoreExecutors.directExecutor());
167     }
168 }