09cdf6a6e2284ef6bbe5f7cb6ef2601c8da321db
[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.common.references.SchemaContextRef;
30 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
31 import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant.PatchData;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 public final class PatchDataTransactionUtil {
42     private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
43
44     public PatchDataTransactionUtil() {
45         throw new UnsupportedOperationException("Util class.");
46     }
47
48     /**
49      * Process edit operations of one {@link PATCHContext}.
50      * @param context Patch context to be processed
51      * @param transactionNode Wrapper for transaction
52      * @param schemaContextRef Soft reference for global schema context
53      * @return {@link PATCHStatusContext}
54      */
55     public static PATCHStatusContext patchData(final PATCHContext context, final TransactionVarsWrapper transactionNode,
56                                                final SchemaContextRef schemaContextRef) {
57         final List<PATCHStatusEntity> editCollection = new ArrayList<>();
58         int errorCounter = 0;
59
60         for (final PATCHEntity patchEntity : context.getData()) {
61             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
62
63             switch (operation) {
64                 case CREATE:
65                     if (errorCounter == 0) {
66                         try {
67                             createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
68                                     patchEntity.getTargetNode(), patchEntity.getNode(),
69                                     transactionNode.getTransaction(), schemaContextRef);
70                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
71                         } catch (final RestconfDocumentedException e) {
72                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
73                                     false, Lists.newArrayList(e.getErrors())));
74                             errorCounter++;
75                         }
76                     }
77                     break;
78                 case DELETE:
79                     if (errorCounter == 0) {
80                         try {
81                             deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
82                                     patchEntity.getTargetNode(), transactionNode.getTransaction());
83                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
84                         } catch (final RestconfDocumentedException e) {
85                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
86                                     false, Lists.newArrayList(e.getErrors())));
87                             errorCounter++;
88                         }
89                     }
90                     break;
91                 case MERGE:
92                     if (errorCounter == 0) {
93                         try {
94                             mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
95                                     patchEntity.getTargetNode(), patchEntity.getNode(), transactionNode.getTransaction(),
96                                     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,
110                                     transactionNode.getTransaction());
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,
123                                     patchEntity.getTargetNode(), transactionNode.getTransaction());
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 = transactionNode
145                     .getTransaction().submit();
146
147             try {
148                 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
149             } catch (final RestconfDocumentedException e) {
150                 // if errors occurred during transaction commit then patch failed and global errors are reported
151                 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
152                         Lists.newArrayList(e.getErrors()));
153             }
154
155             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
156         } else {
157             transactionNode.getTransaction().cancel();
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         TransactionUtil.checkItemExists(readWriteTransaction, dataStore, path, PatchData.PATCH_TX_TYPE);
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                     TransactionUtil.checkItemDoesNotExists(
269                             rWTransaction, dataStore, childPath, PatchData.PATCH_TX_TYPE);
270                 }
271
272                 rWTransaction.put(dataStore, childPath, child);
273             }
274         } else {
275             if (errorIfExists) {
276                 TransactionUtil.checkItemDoesNotExists(
277                         rWTransaction, dataStore, path, PatchData.PATCH_TX_TYPE);
278             }
279
280             TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
281             rWTransaction.put(dataStore, path, payload);
282         }
283     }
284 }