Inline checkItemExistsWithinTransaction()
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / 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 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
9
10 import com.google.common.collect.ImmutableList;
11 import com.google.common.collect.Lists;
12 import com.google.common.util.concurrent.FluentFuture;
13 import java.util.ArrayList;
14 import java.util.List;
15 import javax.ws.rs.core.Response.Status;
16 import org.opendaylight.mdsal.common.api.CommitInfo;
17 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
18 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.common.errors.RestconfError;
21 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
22 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
23 import org.opendaylight.restconf.common.patch.PatchContext;
24 import org.opendaylight.restconf.common.patch.PatchEntity;
25 import org.opendaylight.restconf.common.patch.PatchStatusContext;
26 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
27 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
28 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PatchData;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 public final class PatchDataTransactionUtil {
39     private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
40
41     private PatchDataTransactionUtil() {
42         throw new UnsupportedOperationException("Util class.");
43     }
44
45     /**
46      * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} if any inside of object
47      * {@link RestconfStrategy} provided as a parameter.
48      *
49      * @param context       Patch context to be processed
50      * @param strategy      object that perform the actual DS operations
51      * @param schemaContext Global schema context
52      * @return {@link PatchStatusContext}
53      */
54     public static PatchStatusContext patchData(final PatchContext context, final RestconfStrategy strategy,
55                                                final EffectiveModelContext schemaContext) {
56         final List<PatchStatusEntity> editCollection = new ArrayList<>();
57         boolean noError = true;
58         strategy.prepareReadWriteExecution();
59
60         for (final PatchEntity patchEntity : context.getData()) {
61             if (noError) {
62                 switch (patchEntity.getOperation()) {
63                     case CREATE:
64                         try {
65                             createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
66                                     patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext);
67                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
68                         } catch (final RestconfDocumentedException e) {
69                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
70                                     false, Lists.newArrayList(e.getErrors())));
71                             noError = false;
72                         }
73                         break;
74                     case DELETE:
75                         try {
76                             deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
77                                     strategy);
78                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
79                         } catch (final RestconfDocumentedException e) {
80                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
81                                     false, Lists.newArrayList(e.getErrors())));
82                             noError = false;
83                         }
84                         break;
85                     case MERGE:
86                         try {
87                             mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
88                                     patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext);
89                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
90                         } catch (final RestconfDocumentedException e) {
91                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
92                                     false, Lists.newArrayList(e.getErrors())));
93                             noError = false;
94                         }
95                         break;
96                     case REPLACE:
97                         try {
98                             replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
99                                     patchEntity.getTargetNode(), patchEntity.getNode(), schemaContext, strategy);
100                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
101                         } catch (final RestconfDocumentedException e) {
102                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
103                                     false, Lists.newArrayList(e.getErrors())));
104                             noError = false;
105                         }
106                         break;
107                     case REMOVE:
108                         try {
109                             removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
110                                     strategy);
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                             noError = false;
116                         }
117                         break;
118                     default:
119                         editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
120                                 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
121                                 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
122                         noError = false;
123                         break;
124                 }
125             } else {
126                 break;
127             }
128         }
129
130         // if no errors then submit transaction, otherwise cancel
131         if (noError) {
132             final ResponseFactory response = new ResponseFactory(Status.OK);
133             final FluentFuture<? extends CommitInfo> future = strategy.commit();
134
135             try {
136                 //This method will close transactionChain if any
137                 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, strategy.getTransactionChain());
138             } catch (final RestconfDocumentedException e) {
139                 // if errors occurred during transaction commit then patch failed and global errors are reported
140                 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
141                         Lists.newArrayList(e.getErrors()));
142             }
143
144             return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
145                     true, null);
146         } else {
147             strategy.cancel();
148             return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
149                     false, null);
150         }
151     }
152
153     /**
154      * Create data within one transaction, return error if already exists.
155      *
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 strategy      Object that perform the actual DS operations
160      * @param schemaContext Global schema context
161      */
162     private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
163                                                     final YangInstanceIdentifier path,
164                                                     final NormalizedNode<?, ?> payload,
165                                                     final RestconfStrategy strategy,
166                                                     final EffectiveModelContext schemaContext) {
167         LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
168         createData(payload, schemaContext, path, strategy, dataStore, true);
169     }
170
171     /**
172      * Check if data exists and remove it within one transaction.
173      *
174      * @param dataStore            Datastore to delete data from
175      * @param path                 Path for data to be deleted
176      * @param strategy             Object that perform the actual DS operations
177      */
178     private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
179                                                     final YangInstanceIdentifier path,
180                                                     final RestconfStrategy strategy) {
181         LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path);
182         final FluentFuture<Boolean> future = strategy.exists(dataStore, path);
183         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
184
185         FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
186
187         if (!response.result) {
188             LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
189             throw new RestconfDocumentedException("Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
190                     path);
191         }
192
193         strategy.delete(dataStore, path);
194     }
195
196     /**
197      * Merge data within one transaction.
198      *
199      * @param dataStore     Datastore to merge data to
200      * @param path          Path for data to be merged
201      * @param payload       Data to be merged
202      * @param strategy      Object that perform the actual DS operations
203      * @param schemaContext Global schema context
204      */
205     private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
206                                                    final YangInstanceIdentifier path,
207                                                    final NormalizedNode<?, ?> payload,
208                                                    final RestconfStrategy strategy,
209                                                    final EffectiveModelContext schemaContext) {
210         LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
211         TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
212         strategy.merge(dataStore, path, payload);
213     }
214
215     /**
216      * Do NOT check if data exists and remove it within one transaction.
217      *
218      * @param dataStore        Datastore to delete data from
219      * @param path             Path for data to be deleted
220      * @param strategy         Object that perform the actual DS operations
221      */
222     private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
223                                                     final YangInstanceIdentifier path,
224                                                     final RestconfStrategy strategy) {
225         LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path);
226         strategy.delete(dataStore, path);
227     }
228
229     /**
230      * Create data within one transaction, replace if already exists.
231      *
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 schemaContext Global schema context
236      * @param strategy      Object that perform the actual DS operations
237      */
238     private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
239                                                      final YangInstanceIdentifier path,
240                                                      final NormalizedNode<?, ?> payload,
241                                                      final EffectiveModelContext schemaContext,
242                                                      final RestconfStrategy strategy) {
243         LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
244         createData(payload, schemaContext, path, strategy, 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      *
251      * @param payload       Data to be created
252      * @param schemaContext Global schema context
253      * @param path          Path for data to be created
254      * @param strategy      Object that perform the actual DS operations
255      * @param dataStore     Datastore to write data to
256      * @param errorIfExists Enable checking for existence of data (throws error if already exists)
257      */
258     private static void createData(final NormalizedNode<?, ?> payload, final EffectiveModelContext schemaContext,
259                                    final YangInstanceIdentifier path,
260                                    final RestconfStrategy strategy,
261                                    final LogicalDatastoreType dataStore, final boolean errorIfExists) {
262         if (payload instanceof MapNode) {
263             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
264             strategy.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
265             TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
266             for (final MapEntryNode child : ((MapNode) payload).getValue()) {
267                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
268
269                 if (errorIfExists) {
270                     checkItemDoesNotExistsWithinTransaction(strategy, dataStore, childPath);
271                 }
272
273                 if (errorIfExists) {
274                     strategy.create(dataStore, childPath, child);
275                 } else {
276                     strategy.replace(dataStore, childPath, child);
277                 }
278             }
279         } else {
280             if (errorIfExists) {
281                 checkItemDoesNotExistsWithinTransaction(strategy, dataStore, path);
282             }
283
284             TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
285             if (errorIfExists) {
286                 strategy.create(dataStore, path, payload);
287             } else {
288                 strategy.replace(dataStore, path, payload);
289             }
290         }
291     }
292
293     /**
294      * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
295      * data already exists.
296      *
297      * @param strategy      Object that perform the actual DS operations
298      * @param store         Datastore
299      * @param path          Path to be checked
300      */
301     public static void checkItemDoesNotExistsWithinTransaction(final RestconfStrategy strategy,
302                                                                final LogicalDatastoreType store,
303                                                                final YangInstanceIdentifier path) {
304         final FluentFuture<Boolean> future = strategy.exists(store, path);
305         final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
306
307         FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
308
309         if (response.result) {
310             LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
311             throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
312                     path);
313         }
314     }
315 }