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