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