Create NetconfDataTreeService with base and additional operations for netconf
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / PatchDataTransactionUtil.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.collect.ImmutableList;
11 import com.google.common.collect.Lists;
12 import com.google.common.util.concurrent.FluentFuture;
13 import java.util.ArrayList;
14 import java.util.List;
15 import javax.ws.rs.core.Response.Status;
16 import org.opendaylight.mdsal.common.api.CommitInfo;
17 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
18 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.common.errors.RestconfError;
21 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
22 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
23 import org.opendaylight.restconf.common.patch.PatchContext;
24 import org.opendaylight.restconf.common.patch.PatchEntity;
25 import org.opendaylight.restconf.common.patch.PatchStatusContext;
26 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
27 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
28 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PatchData;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 public final class PatchDataTransactionUtil {
39     private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
40
41     private PatchDataTransactionUtil() {
42         throw new UnsupportedOperationException("Util class.");
43     }
44
45     /**
46      * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} if any inside of object
47      * {@link RestconfStrategy} provided as a parameter.
48      *
49      * @param context       Patch context to be processed
50      * @param strategy      object that perform the actual DS operations
51      * @param schemaContext Global schema context
52      * @return {@link PatchStatusContext}
53      */
54     public static PatchStatusContext patchData(final PatchContext context, final RestconfStrategy strategy,
55                                                final EffectiveModelContext schemaContext) {
56         final List<PatchStatusEntity> editCollection = new ArrayList<>();
57         boolean noError = true;
58         strategy.prepareReadWriteExecution();
59
60         for (final PatchEntity patchEntity : context.getData()) {
61             if (noError) {
62                 switch (patchEntity.getOperation()) {
63                     case CREATE:
64                         try {
65                             createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
66                                     patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext);
67                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
68                         } catch (final RestconfDocumentedException e) {
69                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
70                                     false, Lists.newArrayList(e.getErrors())));
71                             noError = false;
72                         }
73                         break;
74                     case DELETE:
75                         try {
76                             deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
77                                     strategy);
78                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
79                         } catch (final RestconfDocumentedException e) {
80                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
81                                     false, Lists.newArrayList(e.getErrors())));
82                             noError = false;
83                         }
84                         break;
85                     case MERGE:
86                         try {
87                             mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
88                                     patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext);
89                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
90                         } catch (final RestconfDocumentedException e) {
91                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
92                                     false, Lists.newArrayList(e.getErrors())));
93                             noError = false;
94                         }
95                         break;
96                     case REPLACE:
97                         try {
98                             replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
99                                     patchEntity.getTargetNode(), patchEntity.getNode(), schemaContext, strategy);
100                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
101                         } catch (final RestconfDocumentedException e) {
102                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
103                                     false, Lists.newArrayList(e.getErrors())));
104                             noError = false;
105                         }
106                         break;
107                     case REMOVE:
108                         try {
109                             removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
110                                     strategy);
111                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
112                         } catch (final RestconfDocumentedException e) {
113                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
114                                     false, Lists.newArrayList(e.getErrors())));
115                             noError = false;
116                         }
117                         break;
118                     default:
119                         editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
120                                 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
121                                 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
122                         noError = false;
123                         break;
124                 }
125             } else {
126                 break;
127             }
128         }
129
130         // if no errors then submit transaction, otherwise cancel
131         if (noError) {
132             final ResponseFactory response = new ResponseFactory(Status.OK);
133             final FluentFuture<? extends CommitInfo> future = strategy.commit();
134
135             try {
136                 //This method will close transactionChain if any
137                 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, strategy.getTransactionChain());
138             } catch (final RestconfDocumentedException e) {
139                 // if errors occurred during transaction commit then patch failed and global errors are reported
140                 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
141                         Lists.newArrayList(e.getErrors()));
142             }
143
144             return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
145                     true, null);
146         } else {
147             strategy.cancel();
148             return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
149                     false, null);
150         }
151     }
152
153     /**
154      * Create data within one transaction, return error if already exists.
155      *
156      * @param dataStore     Datastore to write data to
157      * @param path          Path for data to be created
158      * @param payload       Data to be created
159      * @param strategy      Object that perform the actual DS operations
160      * @param schemaContext Global schema context
161      */
162     private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
163                                                     final YangInstanceIdentifier path,
164                                                     final NormalizedNode<?, ?> payload,
165                                                     final RestconfStrategy strategy,
166                                                     final EffectiveModelContext schemaContext) {
167         LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
168         createData(payload, schemaContext, path, strategy, dataStore, true);
169     }
170
171     /**
172      * Check if data exists and remove it within one transaction.
173      *
174      * @param dataStore            Datastore to delete data from
175      * @param path                 Path for data to be deleted
176      * @param strategy             Object that perform the actual DS operations
177      */
178     private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
179                                                     final YangInstanceIdentifier path,
180                                                     final RestconfStrategy strategy) {
181         LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path);
182         checkItemExistsWithinTransaction(strategy, dataStore, path);
183         strategy.delete(dataStore, path);
184     }
185
186     /**
187      * Merge data within one transaction.
188      *
189      * @param dataStore     Datastore to merge data to
190      * @param path          Path for data to be merged
191      * @param payload       Data to be merged
192      * @param strategy      Object that perform the actual DS operations
193      * @param schemaContext Global schema context
194      */
195     private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
196                                                    final YangInstanceIdentifier path,
197                                                    final NormalizedNode<?, ?> payload,
198                                                    final RestconfStrategy strategy,
199                                                    final EffectiveModelContext schemaContext) {
200         LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
201         TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
202         strategy.merge(dataStore, path, payload);
203     }
204
205     /**
206      * Do NOT check if data exists and remove it within one transaction.
207      *
208      * @param dataStore        Datastore to delete data from
209      * @param path             Path for data to be deleted
210      * @param strategy         Object that perform the actual DS operations
211      */
212     private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
213                                                     final YangInstanceIdentifier path,
214                                                     final RestconfStrategy strategy) {
215         LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path);
216         strategy.delete(dataStore, path);
217     }
218
219     /**
220      * Create data within one transaction, replace if already exists.
221      *
222      * @param dataStore     Datastore to write data to
223      * @param path          Path for data to be created
224      * @param payload       Data to be created
225      * @param schemaContext Global schema context
226      * @param strategy      Object that perform the actual DS operations
227      */
228     private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
229                                                      final YangInstanceIdentifier path,
230                                                      final NormalizedNode<?, ?> payload,
231                                                      final EffectiveModelContext schemaContext,
232                                                      final RestconfStrategy strategy) {
233         LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
234         createData(payload, schemaContext, path, strategy, dataStore, false);
235     }
236
237     /**
238      * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
239      * for existence before created, otherwise they will be overwritten.
240      *
241      * @param payload       Data to be created
242      * @param schemaContext Global schema context
243      * @param path          Path for data to be created
244      * @param strategy      Object that perform the actual DS operations
245      * @param dataStore     Datastore to write data to
246      * @param errorIfExists Enable checking for existence of data (throws error if already exists)
247      */
248     private static void createData(final NormalizedNode<?, ?> payload, final EffectiveModelContext schemaContext,
249                                    final YangInstanceIdentifier path,
250                                    final RestconfStrategy strategy,
251                                    final LogicalDatastoreType dataStore, final boolean errorIfExists) {
252         if (payload instanceof MapNode) {
253             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
254             strategy.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
255             TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
256             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
257                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
258
259                 if (errorIfExists) {
260                     checkItemDoesNotExistsWithinTransaction(strategy, dataStore, childPath);
261                 }
262
263                 if (errorIfExists) {
264                     strategy.create(dataStore, childPath, child);
265                 } else {
266                     strategy.replace(dataStore, childPath, child);
267                 }
268             }
269         } else {
270             if (errorIfExists) {
271                 checkItemDoesNotExistsWithinTransaction(strategy, dataStore, path);
272             }
273
274             TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
275             if (errorIfExists) {
276                 strategy.create(dataStore, path, payload);
277             } else {
278                 strategy.replace(dataStore, path, payload);
279             }
280         }
281     }
282
283     /**
284      * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
285      * data does NOT already exists.
286      *
287      * @param strategy      Object that perform the actual DS operations
288      * @param store         Datastore
289      * @param path          Path to be checked
290      */
291     public static void checkItemExistsWithinTransaction(final RestconfStrategy strategy,
292                                                         final LogicalDatastoreType store,
293                                                         final YangInstanceIdentifier path) {
294         final FluentFuture<Boolean> future = strategy.exists(store, path);
295         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
296
297         FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
298
299         if (!response.result) {
300             LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
301             throw new RestconfDocumentedException("Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
302                     path);
303         }
304     }
305
306     /**
307      * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
308      * data already exists.
309      *
310      * @param strategy      Object that perform the actual DS operations
311      * @param store         Datastore
312      * @param path          Path to be checked
313      */
314     public static void checkItemDoesNotExistsWithinTransaction(final RestconfStrategy strategy,
315                                                                final LogicalDatastoreType store,
316                                                                final YangInstanceIdentifier path) {
317         final FluentFuture<Boolean> future = strategy.exists(store, path);
318         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
319
320         FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
321
322         if (response.result) {
323             LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
324             throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
325                     path);
326         }
327     }
328 }