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