Eliminate InstanceIdentifierContext
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / NetconfRestconfStrategy.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.collect.ImmutableMap;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import com.google.common.util.concurrent.SettableFuture;
18 import java.util.List;
19 import java.util.Optional;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.opendaylight.mdsal.common.api.CommitInfo;
23 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
24 import org.opendaylight.mdsal.common.api.ReadFailedException;
25 import org.opendaylight.mdsal.dom.api.DOMActionService;
26 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
27 import org.opendaylight.mdsal.dom.api.DOMRpcService;
28 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
29 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
30 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
31 import org.opendaylight.restconf.api.query.ContentParam;
32 import org.opendaylight.restconf.api.query.WithDefaultsParam;
33 import org.opendaylight.restconf.common.errors.RestconfFuture;
34 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
35 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
36 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
37 import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
38 import org.opendaylight.restconf.server.api.DataGetParams;
39 import org.opendaylight.restconf.server.api.DatabindContext;
40 import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
41 import org.opendaylight.yangtools.yang.common.Empty;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44
45 /**
46  * Implementation of RESTCONF operations on top of a raw NETCONF backend.
47  *
48  * @see NetconfDataTreeService
49  */
50 public final class NetconfRestconfStrategy extends RestconfStrategy {
51     private final NetconfDataTreeService netconfService;
52
53     public NetconfRestconfStrategy(final DatabindContext databind, final NetconfDataTreeService netconfService,
54             final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
55             final @Nullable DOMYangTextSourceProvider sourceProvider,
56             final @Nullable DOMMountPointService mountPointService) {
57         super(databind, ImmutableMap.of(), rpcService, actionService, sourceProvider, mountPointService);
58         this.netconfService = requireNonNull(netconfService);
59     }
60
61     @Override
62     RestconfTransaction prepareWriteExecution() {
63         return new NetconfRestconfTransaction(modelContext(), netconfService);
64     }
65
66     @Override
67     void delete(final SettableRestconfFuture<Empty> future, final YangInstanceIdentifier path) {
68         final var tx = prepareWriteExecution();
69         tx.delete(path);
70         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
71             @Override
72             public void onSuccess(final CommitInfo result) {
73                 future.set(Empty.value());
74             }
75
76             @Override
77             public void onFailure(final Throwable cause) {
78                 future.setFailure(TransactionUtil.decodeException(cause, "DELETE", path, modelContext()));
79             }
80         }, MoreExecutors.directExecutor());
81     }
82
83     @Override
84     RestconfFuture<NormalizedNodePayload> dataGET(final DataPath path, final DataGetParams params) {
85         final var inference = path.inference();
86         final var fields = params.fields();
87         final List<YangInstanceIdentifier> fieldPaths;
88         if (fields != null) {
89             final var tmp = NetconfFieldsTranslator.translate(inference.getEffectiveModelContext(), path.schema(),
90                 fields);
91             fieldPaths = tmp == null || tmp.isEmpty() ? null : tmp;
92         } else {
93             fieldPaths = null;
94         }
95
96         final NormalizedNode node;
97         if (fieldPaths != null) {
98             node = readData(params.content(), path.instance(), params.withDefaults(), fieldPaths);
99         } else {
100             node = readData(params.content(), path.instance(), params.withDefaults());
101         }
102         return completeDataGET(inference, QueryParameters.of(params), node);
103     }
104
105     @Override
106     ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
107             final YangInstanceIdentifier path) {
108         return switch (store) {
109             case CONFIGURATION -> netconfService.getConfig(path);
110             case OPERATIONAL -> netconfService.get(path);
111         };
112     }
113
114     private ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
115             final YangInstanceIdentifier path, final List<YangInstanceIdentifier> fields) {
116         return switch (store) {
117             case CONFIGURATION -> netconfService.getConfig(path, fields);
118             case OPERATIONAL -> netconfService.get(path, fields);
119         };
120     }
121
122     /**
123      * Read specific type of data from data store via transaction with specified subtrees that should only be read.
124      * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
125      *
126      * @param content  type of data to read (config, state, all)
127      * @param path     the parent path to read
128      * @param withDefa value of with-defaults parameter
129      * @param fields   paths to selected subtrees which should be read, relative to to the parent path
130      * @return {@link NormalizedNode}
131      */
132     // FIXME: NETCONF-1155: this method should asynchronous
133     public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
134             final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
135             final @NonNull List<YangInstanceIdentifier> fields) {
136         return switch (content) {
137             case ALL -> {
138                 // PREPARE STATE DATA NODE
139                 final var stateDataNode = readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
140                 // PREPARE CONFIG DATA NODE
141                 final var configDataNode = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
142
143                 yield mergeConfigAndSTateDataIfNeeded(stateDataNode, withDefa == null ? configDataNode
144                     : prepareDataByParamWithDef(configDataNode, path, withDefa.mode()));
145             }
146             case CONFIG -> {
147                 final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
148                 yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode());
149             }
150             case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
151         };
152     }
153
154     /**
155      * Read specific type of data {@link LogicalDatastoreType} via transaction in {@link RestconfStrategy} with
156      * specified subtrees that should only be read.
157      *
158      * @param store                 datastore type
159      * @param path                  parent path to selected fields
160      * @param closeTransactionChain if it is set to {@code true}, after transaction it will close transactionChain
161      *                              in {@link RestconfStrategy} if any
162      * @param fields                paths to selected subtrees which should be read, relative to to the parent path
163      * @return {@link NormalizedNode}
164      */
165     private @Nullable NormalizedNode readDataViaTransaction(final @NonNull LogicalDatastoreType store,
166             final @NonNull YangInstanceIdentifier path, final @NonNull List<YangInstanceIdentifier> fields) {
167         return TransactionUtil.syncAccess(read(store, path, fields), path).orElse(null);
168     }
169
170     @Override
171     ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
172         return Futures.transform(remapException(netconfService.getConfig(path)),
173             optionalNode -> optionalNode != null && optionalNode.isPresent(),
174             MoreExecutors.directExecutor());
175     }
176
177     private static <T> ListenableFuture<T> remapException(final ListenableFuture<T> input) {
178         final var ret = SettableFuture.<T>create();
179         Futures.addCallback(input, new FutureCallback<T>() {
180             @Override
181             public void onSuccess(final T result) {
182                 ret.set(result);
183             }
184
185             @Override
186             public void onFailure(final Throwable cause) {
187                 ret.setException(cause instanceof ReadFailedException ? cause
188                     : new ReadFailedException("NETCONF operation failed", cause));
189             }
190         }, MoreExecutors.directExecutor());
191         return ret;
192     }
193 }