0b350a7667c4608c648ffedfd856813328f5a3c6
[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.TransactionCommitFailedException;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
19 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
20 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
21 import org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation;
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     public 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         int errorCounter = 0;
60         final DOMDataReadWriteTransaction tx = transactionNode.getTransactionChain().newReadWriteTransaction();
61
62         for (final PATCHEntity patchEntity : context.getData()) {
63             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
64
65             switch (operation) {
66                 case CREATE:
67                     if (errorCounter == 0) {
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                             errorCounter++;
76                         }
77                     }
78                     break;
79                 case DELETE:
80                     if (errorCounter == 0) {
81                         try {
82                             deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
83                                     tx);
84                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
85                         } catch (final RestconfDocumentedException e) {
86                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
87                                     false, Lists.newArrayList(e.getErrors())));
88                             errorCounter++;
89                         }
90                     }
91                     break;
92                 case MERGE:
93                     if (errorCounter == 0) {
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                             errorCounter++;
102                         }
103                     }
104                     break;
105                 case REPLACE:
106                     if (errorCounter == 0) {
107                         try {
108                             replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
109                                     patchEntity.getTargetNode(), patchEntity.getNode(), schemaContextRef, tx);
110                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
111                         } catch (final RestconfDocumentedException e) {
112                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
113                                     false, Lists.newArrayList(e.getErrors())));
114                             errorCounter++;
115                         }
116                     }
117                     break;
118                 case REMOVE:
119                     if (errorCounter == 0) {
120                         try {
121                             removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
122                                     tx);
123                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
124                         } catch (final RestconfDocumentedException e) {
125                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
126                                     false, Lists.newArrayList(e.getErrors())));
127                             errorCounter++;
128                         }
129                     }
130                     break;
131                 default:
132                     editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
133                             false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
134                             ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang PATCH operation"))));
135                     errorCounter++;
136                     break;
137             }
138         }
139
140         // if no errors then submit transaction, otherwise cancel
141         if (errorCounter == 0) {
142             final ResponseFactory response = new ResponseFactory();
143             final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
144
145             try {
146                 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
147             } catch (final RestconfDocumentedException e) {
148                 // if errors occurred during transaction commit then patch failed and global errors are reported
149                 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
150                         Lists.newArrayList(e.getErrors()));
151             }
152
153             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
154         } else {
155             tx.cancel();
156             RestConnectorProvider.resetTransactionChainForAdapaters(transactionNode.getTransactionChain());
157             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
158         }
159     }
160
161     /**
162      * Create data within one transaction, return error if already exists.
163      * @param dataStore Datastore to write data to
164      * @param path Path for data to be created
165      * @param payload Data to be created
166      * @param rWTransaction Transaction
167      * @param schemaContextRef Soft reference for global schema context
168      */
169     private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
170                                                     final YangInstanceIdentifier path,
171                                                     final NormalizedNode<?, ?> payload,
172                                                     final DOMDataReadWriteTransaction rWTransaction,
173                                                     final SchemaContextRef schemaContextRef) {
174         LOG.trace("POST {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
175         createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, true);
176     }
177
178     /**
179      * Check if data exists and remove it within one transaction.
180      * @param dataStore Datastore to delete data from
181      * @param path Path for data to be deleted
182      * @param readWriteTransaction Transaction
183      */
184     private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
185                                                     final YangInstanceIdentifier path,
186                                                     final DOMDataReadWriteTransaction readWriteTransaction) {
187         LOG.trace("Delete {} within Restconf PATCH: {}", dataStore.name(), path);
188         TransactionUtil.checkItemExists(readWriteTransaction, dataStore, path, PatchData.PATCH_TX_TYPE);
189         readWriteTransaction.delete(dataStore, path);
190     }
191
192     /**
193      * Merge data within one transaction.
194      * @param dataStore Datastore to merge data to
195      * @param path Path for data to be merged
196      * @param payload Data to be merged
197      * @param writeTransaction Transaction
198      * @param schemaContextRef Soft reference for global schema context
199      */
200     private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
201                                                    final YangInstanceIdentifier path,
202                                                    final NormalizedNode<?, ?> payload,
203                                                    final DOMDataReadWriteTransaction writeTransaction,
204                                                    final SchemaContextRef schemaContextRef) {
205         LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
206         TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
207
208         // merging is necessary only for lists otherwise we can call put method
209         if (payload instanceof MapNode) {
210             writeTransaction.merge(dataStore, path, payload);
211         } else {
212             writeTransaction.put(dataStore, path, payload);
213         }
214     }
215
216     /**
217      * Do NOT check if data exists and remove it within one transaction.
218      * @param dataStore Datastore to delete data from
219      * @param path Path for data to be deleted
220      * @param writeTransaction Transaction
221      */
222     private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
223                                                     final YangInstanceIdentifier path,
224                                                     final DOMDataWriteTransaction writeTransaction) {
225         LOG.trace("Remove {} within Restconf PATCH: {}", dataStore.name(), path);
226         writeTransaction.delete(dataStore, path);
227     }
228
229     /**
230      * Create data within one transaction, replace if already exists.
231      * @param dataStore Datastore to write data to
232      * @param path Path for data to be created
233      * @param payload Data to be created
234      * @param schemaContextRef Soft reference for global schema context
235      * @param rWTransaction Transaction
236      */
237     private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
238                                                      final YangInstanceIdentifier path,
239                                                      final NormalizedNode<?, ?> payload,
240                                                      final SchemaContextRef schemaContextRef,
241                                                      final DOMDataReadWriteTransaction rWTransaction) {
242         LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
243         createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, false);
244     }
245
246     /**
247      * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
248      * for existence before created, otherwise they will be overwritten.
249      * @param payload Data to be created
250      * @param schemaContext Global schema context
251      * @param path Path for data to be created
252      * @param rWTransaction Transaction
253      * @param dataStore Datastore to write data to
254      * @param errorIfExists Enable checking for existence of data (throws error if already exists)
255      */
256     private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
257                                    final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rWTransaction,
258                                    final LogicalDatastoreType dataStore, final boolean errorIfExists) {
259         if (payload instanceof MapNode) {
260             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
261             rWTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
262             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
263             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
264                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
265
266                 if (errorIfExists) {
267                     TransactionUtil.checkItemDoesNotExists(
268                             rWTransaction, dataStore, childPath, PatchData.PATCH_TX_TYPE);
269                 }
270
271                 rWTransaction.put(dataStore, childPath, child);
272             }
273         } else {
274             if (errorIfExists) {
275                 TransactionUtil.checkItemDoesNotExists(
276                         rWTransaction, dataStore, path, PatchData.PATCH_TX_TYPE);
277             }
278
279             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
280             rWTransaction.put(dataStore, path, payload);
281         }
282     }
283 }