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