Clean up {Post,Put}DataTransactionUtil.submitData()
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / PostDataTransactionUtil.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. 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.utils;
9
10 import com.google.common.util.concurrent.FluentFuture;
11 import com.google.common.util.concurrent.ListenableFuture;
12 import java.net.URI;
13 import javax.ws.rs.core.Response;
14 import javax.ws.rs.core.UriInfo;
15 import org.opendaylight.mdsal.common.api.CommitInfo;
16 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
17 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
18 import org.opendaylight.restconf.api.query.PointParam;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
21 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
22 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
23 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
24 import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
25 import org.opendaylight.yangtools.yang.common.ErrorTag;
26 import org.opendaylight.yangtools.yang.common.ErrorType;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
32 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
33 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * Util class to post data to DS.
39  */
40 public final class PostDataTransactionUtil {
41     private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
42     private static final String POST_TX_TYPE = "POST";
43
44     private PostDataTransactionUtil() {
45         // Hidden on purpose
46     }
47
48     /**
49      * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
50      * {@link RestconfStrategy} provided as a parameter.
51      *
52      * @param uriInfo       uri info
53      * @param path          path
54      * @param data          data
55      * @param strategy      Object that perform the actual DS operations
56      * @param schemaContext reference to actual {@link EffectiveModelContext}
57      * @param params        {@link WriteDataParams}
58      * @return {@link Response}
59      */
60     public static Response postData(final UriInfo uriInfo, final YangInstanceIdentifier path, final NormalizedNode data,
61             final RestconfStrategy strategy, final EffectiveModelContext schemaContext, final WriteDataParams params) {
62         TransactionUtil.syncCommit(submitData(path, data, strategy, schemaContext, params), POST_TX_TYPE, path);
63         return Response.created(resolveLocation(uriInfo, path, schemaContext, data)).build();
64     }
65
66     /**
67      * Post data by type.
68      *
69      * @param path          path
70      * @param data          data
71      * @param strategy      object that perform the actual DS operations
72      * @param schemaContext schema context of data
73      * @param point         query parameter
74      * @param insert        query parameter
75      * @return {@link FluentFuture}
76      */
77     private static ListenableFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
78             final NormalizedNode data, final RestconfStrategy strategy, final EffectiveModelContext schemaContext,
79             final WriteDataParams params) {
80         final var transaction = strategy.prepareWriteExecution();
81         final var insert = params.insert();
82         if (insert == null) {
83             return makePost(path, data, schemaContext, transaction);
84         }
85
86         final var parentPath = path.coerceParent();
87         PutDataTransactionUtil.checkListAndOrderedType(schemaContext, parentPath);
88         final var grandParentPath = parentPath.coerceParent();
89
90         return switch (insert) {
91             case FIRST -> {
92                 final var readData = PutDataTransactionUtil.readList(strategy, grandParentPath);
93                 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
94                     transaction.replace(path, data, schemaContext);
95                     yield transaction.commit();
96                 }
97                 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
98                 transaction.remove(grandParentPath);
99                 transaction.replace(path, data, schemaContext);
100                 transaction.replace(grandParentPath, readData, schemaContext);
101                 yield transaction.commit();
102             }
103             case LAST -> makePost(path, data, schemaContext, transaction);
104             case BEFORE -> {
105                 final var readData = PutDataTransactionUtil.readList(strategy, grandParentPath);
106                 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
107                     transaction.replace(path, data, schemaContext);
108                     yield transaction.commit();
109                 }
110                 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
111                 insertWithPointPost(path, data, schemaContext, params.getPoint(),
112                     (NormalizedNodeContainer<?>) readData, true, transaction);
113                 yield transaction.commit();
114             }
115             case AFTER -> {
116                 final var readData = PutDataTransactionUtil.readList(strategy, grandParentPath);
117                 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
118                     transaction.replace(path, data, schemaContext);
119                     yield transaction.commit();
120                 }
121                 checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
122                 insertWithPointPost(path, data, schemaContext, params.getPoint(),
123                     (NormalizedNodeContainer<?>) readData, false, transaction);
124                 yield transaction.commit();
125             }
126         };
127     }
128
129     private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
130                                             final EffectiveModelContext schemaContext, final PointParam point,
131                                             final NormalizedNodeContainer<?> readList, final boolean before,
132                                             final RestconfTransaction transaction) {
133         final YangInstanceIdentifier parent = path.coerceParent().coerceParent();
134         transaction.remove(parent);
135         final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
136             .getLastPathArgument();
137         int lastItemPosition = 0;
138         for (var nodeChild : readList.body()) {
139             if (nodeChild.name().equals(pointArg)) {
140                 break;
141             }
142             lastItemPosition++;
143         }
144         if (!before) {
145             lastItemPosition++;
146         }
147         int lastInsertedPosition = 0;
148         final var emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, parent);
149         transaction.merge(YangInstanceIdentifier.of(emptySubtree.name()), emptySubtree);
150         for (var nodeChild : readList.body()) {
151             if (lastInsertedPosition == lastItemPosition) {
152                 transaction.replace(path, data, schemaContext);
153             }
154             final YangInstanceIdentifier childPath = parent.node(nodeChild.name());
155             transaction.replace(childPath, nodeChild, schemaContext);
156             lastInsertedPosition++;
157         }
158     }
159
160     private static ListenableFuture<? extends CommitInfo> makePost(final YangInstanceIdentifier path,
161             final NormalizedNode data, final EffectiveModelContext schemaContext,
162             final RestconfTransaction transaction) {
163         try {
164             transaction.create(path, data, schemaContext);
165         } catch (RestconfDocumentedException e) {
166             // close transaction if any and pass exception further
167             transaction.cancel();
168             throw e;
169         }
170
171         return transaction.commit();
172     }
173
174     /**
175      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
176      *
177      * @param uriInfo       uri info
178      * @param initialPath   data path
179      * @param schemaContext reference to {@link SchemaContext}
180      * @return {@link URI}
181      */
182     private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
183                                        final EffectiveModelContext schemaContext, final NormalizedNode data) {
184         if (uriInfo == null) {
185             return null;
186         }
187
188         YangInstanceIdentifier path = initialPath;
189         if (data instanceof MapNode mapData) {
190             final var children = mapData.body();
191             if (!children.isEmpty()) {
192                 path = path.node(children.iterator().next().name());
193             }
194         }
195
196         return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
197     }
198
199     /**
200      * Check if items do NOT already exists at specified {@code path}.
201      *
202      * @param existsFuture if checked data exists
203      * @param path         Path to be checked
204      * @throws RestconfDocumentedException if data already exists.
205      */
206     public static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
207                                               final YangInstanceIdentifier path) {
208         if (TransactionUtil.syncAccess(existsFuture, path)) {
209             LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
210             throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
211                 path);
212         }
213     }
214 }