Bug 4883 - implement query parameter - filter
[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     private 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         boolean noError = true;
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             if (noError) {
66                 switch (operation) {
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();
137             final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
138
139             try {
140                 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
141             } catch (final RestconfDocumentedException e) {
142                 // if errors occurred during transaction commit then patch failed and global errors are reported
143                 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
144                         Lists.newArrayList(e.getErrors()));
145             }
146
147             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
148         } else {
149             tx.cancel();
150             RestConnectorProvider.resetTransactionChainForAdapaters(transactionNode.getTransactionChain());
151             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
152                     false, null);
153         }
154     }
155
156     /**
157      * Create data within one transaction, return error if already exists.
158      * @param dataStore Datastore to write data to
159      * @param path Path for data to be created
160      * @param payload Data to be created
161      * @param rWTransaction Transaction
162      * @param schemaContextRef Soft reference for global schema context
163      */
164     private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
165                                                     final YangInstanceIdentifier path,
166                                                     final NormalizedNode<?, ?> payload,
167                                                     final DOMDataReadWriteTransaction rWTransaction,
168                                                     final SchemaContextRef schemaContextRef) {
169         LOG.trace("POST {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
170         createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, true);
171     }
172
173     /**
174      * Check if data exists and remove it within one transaction.
175      * @param dataStore Datastore to delete data from
176      * @param path Path for data to be deleted
177      * @param readWriteTransaction Transaction
178      */
179     private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
180                                                     final YangInstanceIdentifier path,
181                                                     final DOMDataReadWriteTransaction readWriteTransaction) {
182         LOG.trace("Delete {} within Restconf PATCH: {}", dataStore.name(), path);
183         checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path);
184         readWriteTransaction.delete(dataStore, path);
185     }
186
187     /**
188      * Merge data within one transaction.
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 writeTransaction Transaction
193      * @param schemaContextRef Soft reference for global schema context
194      */
195     private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
196                                                    final YangInstanceIdentifier path,
197                                                    final NormalizedNode<?, ?> payload,
198                                                    final DOMDataReadWriteTransaction writeTransaction,
199                                                    final SchemaContextRef schemaContextRef) {
200         LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
201         TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
202
203         // merging is necessary only for lists otherwise we can call put method
204         if (payload instanceof MapNode) {
205             writeTransaction.merge(dataStore, path, payload);
206         } else {
207             writeTransaction.put(dataStore, path, payload);
208         }
209     }
210
211     /**
212      * Do NOT check if data exists and remove it within one transaction.
213      * @param dataStore Datastore to delete data from
214      * @param path Path for data to be deleted
215      * @param writeTransaction Transaction
216      */
217     private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
218                                                     final YangInstanceIdentifier path,
219                                                     final DOMDataWriteTransaction writeTransaction) {
220         LOG.trace("Remove {} within Restconf PATCH: {}", dataStore.name(), path);
221         writeTransaction.delete(dataStore, path);
222     }
223
224     /**
225      * Create data within one transaction, replace if already exists.
226      * @param dataStore Datastore to write data to
227      * @param path Path for data to be created
228      * @param payload Data to be created
229      * @param schemaContextRef Soft reference for global schema context
230      * @param rWTransaction Transaction
231      */
232     private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
233                                                      final YangInstanceIdentifier path,
234                                                      final NormalizedNode<?, ?> payload,
235                                                      final SchemaContextRef schemaContextRef,
236                                                      final DOMDataReadWriteTransaction rWTransaction) {
237         LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
238         createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, false);
239     }
240
241     /**
242      * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
243      * for existence before created, otherwise they will be overwritten.
244      * @param payload Data to be created
245      * @param schemaContext Global schema context
246      * @param path Path for data to be created
247      * @param rWTransaction Transaction
248      * @param dataStore Datastore to write data to
249      * @param errorIfExists Enable checking for existence of data (throws error if already exists)
250      */
251     private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
252                                    final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rWTransaction,
253                                    final LogicalDatastoreType dataStore, final boolean errorIfExists) {
254         if (payload instanceof MapNode) {
255             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
256             rWTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
257             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
258             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
259                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
260
261                 if (errorIfExists) {
262                     checkItemDoesNotExistsWithinTransaction(rWTransaction, dataStore, childPath);
263                 }
264
265                 rWTransaction.put(dataStore, childPath, child);
266             }
267         } else {
268             if (errorIfExists) {
269                 checkItemDoesNotExistsWithinTransaction(rWTransaction, dataStore, path);
270             }
271
272             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
273             rWTransaction.put(dataStore, path, payload);
274         }
275     }
276
277     /**
278      * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
279      * data does NOT already exists.
280      * @param rWTransaction Transaction
281      * @param store Datastore
282      * @param path Path to be checked
283      */
284     public static void checkItemExistsWithinTransaction(final DOMDataReadWriteTransaction rWTransaction,
285                                                         final LogicalDatastoreType store, final YangInstanceIdentifier path) {
286         final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
287         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
288
289         FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
290
291         if (!response.result) {
292             final String errMsg = "Operation via Restconf was not executed because data does not exist";
293             LOG.trace("{}:{}", errMsg, path);
294             throw new RestconfDocumentedException(
295                     "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
296         }
297     }
298
299     /**
300      * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
301      * data already exists.
302      * @param rWTransaction Transaction
303      * @param store Datastore
304      * @param path Path to be checked
305      */
306     public static void checkItemDoesNotExistsWithinTransaction(final DOMDataReadWriteTransaction rWTransaction,
307                                                                final LogicalDatastoreType store, final YangInstanceIdentifier path) {
308         final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
309         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
310
311         FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
312
313         if (response.result) {
314             final String errMsg = "Operation via Restconf was not executed because data already exists";
315             LOG.trace("{}:{}", errMsg, path);
316             throw new RestconfDocumentedException(
317                     "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
318         }
319     }
320 }