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