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