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