Reduce exception guard
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / PutDataTransactionUtil.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.util.Optional;
12 import javax.ws.rs.core.Response;
13 import javax.ws.rs.core.Response.Status;
14 import org.opendaylight.mdsal.common.api.CommitInfo;
15 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
16 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
17 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.nb.rfc8040.InsertParam;
20 import org.opendaylight.restconf.nb.rfc8040.PointParam;
21 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
22 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
23 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
24 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
25 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
26 import org.opendaylight.yangtools.yang.common.ErrorTag;
27 import org.opendaylight.yangtools.yang.common.ErrorType;
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.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
32 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
33 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
34 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
36 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39
40 /**
41  * Util class for put data to DS.
42  *
43  */
44 public final class PutDataTransactionUtil {
45     private static final String PUT_TX_TYPE = "PUT";
46
47     private PutDataTransactionUtil() {
48     }
49
50     /**
51      * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} if any
52      * inside of object {@link RestconfStrategy} provided as a parameter if any.
53      *
54      * @param payload       data to put
55      * @param schemaContext reference to {@link EffectiveModelContext}
56      * @param strategy      object that perform the actual DS operations
57      * @param params        {@link WriteDataParams}
58      * @return {@link Response}
59      */
60     public static Response putData(final NormalizedNodePayload payload, final EffectiveModelContext schemaContext,
61                                    final RestconfStrategy strategy, final WriteDataParams params) {
62         final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
63
64         final FluentFuture<Boolean> existsFuture = strategy.exists(LogicalDatastoreType.CONFIGURATION, path);
65         final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
66         FutureCallbackTx.addCallback(existsFuture, PUT_TX_TYPE, existsResponse);
67
68         final ResponseFactory responseFactory =
69             new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED);
70         final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext, strategy,
71             payload.getData(), params);
72         //This method will close transactionChain if any
73         FutureCallbackTx.addCallback(submitData, PUT_TX_TYPE, responseFactory, path);
74         return responseFactory.build();
75     }
76
77     /**
78      * Put data to DS.
79      *
80      * @param path          path of data
81      * @param schemaContext {@link SchemaContext}
82      * @param strategy      object that perform the actual DS operations
83      * @param data          data
84      * @param params        {@link WriteDataParams}
85      * @return {@link FluentFuture}
86      */
87     private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
88                                                                  final EffectiveModelContext schemaContext,
89                                                                  final RestconfStrategy strategy,
90                                                                  final NormalizedNode data,
91                                                                  final WriteDataParams params) {
92         final RestconfTransaction transaction = strategy.prepareWriteExecution();
93         final InsertParam insert = params.insert();
94         if (insert == null) {
95             return makePut(path, schemaContext, transaction, data);
96         }
97
98         checkListAndOrderedType(schemaContext, path);
99         final NormalizedNode readData;
100         switch (insert) {
101             case FIRST:
102                 readData = readList(strategy, path.getParent());
103                 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
104                     return makePut(path, schemaContext, transaction, data);
105                 }
106                 transaction.remove(path.getParent());
107                 transaction.replace(path, data, schemaContext);
108                 transaction.replace(path.getParent(), readData, schemaContext);
109                 return transaction.commit();
110             case LAST:
111                 return makePut(path, schemaContext, transaction, data);
112             case BEFORE:
113                 readData = readList(strategy, path.getParent());
114                 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
115                     return makePut(path, schemaContext, transaction, data);
116                 }
117                 insertWithPointPut(transaction, path, data, schemaContext, params.getPoint(),
118                     (NormalizedNodeContainer<?>) readData, true);
119                 return transaction.commit();
120             case AFTER:
121                 readData = readList(strategy, path.getParent());
122                 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
123                     return makePut(path, schemaContext, transaction, data);
124                 }
125                 insertWithPointPut(transaction, path, data, schemaContext, params.getPoint(),
126                     (NormalizedNodeContainer<?>) readData, false);
127                 return transaction.commit();
128             default:
129                 throw new RestconfDocumentedException(
130                         "Used bad value of insert parameter. Possible values are first, last, before or after, "
131                                 + "but was: " + insert, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
132         }
133     }
134
135     // FIXME: this method is only called from a context where we are modifying data. This should be part of strategy,
136     //        requiring an already-open transaction. It also must return a future, so it can be properly composed.
137     static NormalizedNode readList(final RestconfStrategy strategy, final YangInstanceIdentifier path) {
138         return ReadDataTransactionUtil.readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path);
139     }
140
141     private static void insertWithPointPut(final RestconfTransaction transaction,
142                                            final YangInstanceIdentifier path,
143                                            final NormalizedNode data,
144                                            final EffectiveModelContext schemaContext, final PointParam point,
145                                            final NormalizedNodeContainer<?> readList, final boolean before) {
146         transaction.remove(path.getParent());
147         final InstanceIdentifierContext instanceIdentifier =
148             // FIXME: Point should be able to give us this method
149             ParserIdentifier.toInstanceIdentifier(point.value(), schemaContext, Optional.empty());
150         int lastItemPosition = 0;
151         for (final NormalizedNode nodeChild : readList.body()) {
152             if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
153                 break;
154             }
155             lastItemPosition++;
156         }
157         if (!before) {
158             lastItemPosition++;
159         }
160         int lastInsertedPosition = 0;
161         final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
162         transaction.merge(YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
163         for (final NormalizedNode nodeChild : readList.body()) {
164             if (lastInsertedPosition == lastItemPosition) {
165                 transaction.replace(path, data, schemaContext);
166             }
167             final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
168             transaction.replace(childPath, nodeChild, schemaContext);
169             lastInsertedPosition++;
170         }
171     }
172
173     private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
174                                                               final EffectiveModelContext schemaContext,
175                                                               final RestconfTransaction transaction,
176                                                               final NormalizedNode data) {
177         transaction.replace(path, data, schemaContext);
178         return transaction.commit();
179     }
180
181     public static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
182             final YangInstanceIdentifier path) {
183         final YangInstanceIdentifier parent = path.getParent();
184         final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).findChild(parent).orElseThrow();
185         final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
186
187         if (dataSchemaNode instanceof ListSchemaNode) {
188             if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
189                 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
190                         ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
191             }
192             return dataSchemaNode;
193         }
194         if (dataSchemaNode instanceof LeafListSchemaNode) {
195             if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
196                 throw new RestconfDocumentedException(
197                         "Insert parameter can be used only with ordered-by user leaf-list.",
198                         ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
199             }
200             return dataSchemaNode;
201         }
202         throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
203                 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
204     }
205 }