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