Eliminate unnecessary blocking checks
[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.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 public final class PatchDataTransactionUtil {
35     private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
36     // FIXME: why is this used from other contexts?
37     static final String PATCH_TX_TYPE = "Patch";
38
39     private PatchDataTransactionUtil() {
40         throw new UnsupportedOperationException("Util class.");
41     }
42
43     /**
44      * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} if any inside of object
45      * {@link RestconfStrategy} provided as a parameter.
46      *
47      * @param context       Patch context to be processed
48      * @param strategy      object that perform the actual DS operations
49      * @param schemaContext Global schema context
50      * @return {@link PatchStatusContext}
51      */
52     public static PatchStatusContext patchData(final PatchContext context, final RestconfStrategy strategy,
53                                                final EffectiveModelContext schemaContext) {
54         final List<PatchStatusEntity> editCollection = new ArrayList<>();
55         boolean noError = true;
56         strategy.prepareReadWriteExecution();
57
58         for (final PatchEntity patchEntity : context.getData()) {
59             if (noError) {
60                 switch (patchEntity.getOperation()) {
61                     case CREATE:
62                         try {
63                             createDataWithinTransaction(patchEntity.getTargetNode(), patchEntity.getNode(),
64                                 schemaContext, strategy);
65                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
66                         } catch (final RestconfDocumentedException e) {
67                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
68                                     false, Lists.newArrayList(e.getErrors())));
69                             noError = false;
70                         }
71                         break;
72                     case DELETE:
73                         try {
74                             deleteDataWithinTransaction(patchEntity.getTargetNode(), strategy);
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 MERGE:
83                         try {
84                             mergeDataWithinTransaction(patchEntity.getTargetNode(), patchEntity.getNode(),
85                                 schemaContext, strategy);
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 REPLACE:
94                         try {
95                             replaceDataWithinTransaction(patchEntity.getTargetNode(), patchEntity.getNode(),
96                                 schemaContext, strategy);
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 REMOVE:
105                         try {
106                             removeDataWithinTransaction(patchEntity.getTargetNode(), strategy);
107                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
108                         } catch (final RestconfDocumentedException e) {
109                             editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
110                                     false, Lists.newArrayList(e.getErrors())));
111                             noError = false;
112                         }
113                         break;
114                     default:
115                         editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
116                                 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
117                                 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
118                         noError = false;
119                         break;
120                 }
121             } else {
122                 break;
123             }
124         }
125
126         // if no errors then submit transaction, otherwise cancel
127         if (noError) {
128             final ResponseFactory response = new ResponseFactory(Status.OK);
129             final FluentFuture<? extends CommitInfo> future = strategy.commit();
130
131             try {
132                 //This method will close transactionChain if any
133                 FutureCallbackTx.addCallback(future, PATCH_TX_TYPE, response, strategy.getTransactionChain());
134             } catch (final RestconfDocumentedException e) {
135                 // if errors occurred during transaction commit then patch failed and global errors are reported
136                 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
137                         Lists.newArrayList(e.getErrors()));
138             }
139
140             return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
141         } else {
142             strategy.cancel();
143             return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
144         }
145     }
146
147     /**
148      * Create data within one transaction, return error if already exists.
149      *
150      * @param path          Path for data to be created
151      * @param payload       Data to be created
152      * @param strategy      Object that perform the actual DS operations
153      */
154     private static void createDataWithinTransaction(final YangInstanceIdentifier path,
155                                                     final NormalizedNode<?, ?> payload,
156                                                     final EffectiveModelContext schemaContext,
157                                                     final RestconfStrategy strategy) {
158         LOG.trace("POST {} within Restconf Patch: {} with payload {}", LogicalDatastoreType.CONFIGURATION.name(),
159             path, payload);
160         createData(payload, path, strategy, schemaContext, true);
161     }
162
163     /**
164      * Remove data within one transaction.
165      *
166      * @param path     Path for data to be deleted
167      * @param strategy Object that perform the actual DS operations
168      */
169     private static void deleteDataWithinTransaction(final YangInstanceIdentifier path,
170                                                     final RestconfStrategy strategy) {
171         LOG.trace("Delete {} within Restconf Patch: {}", LogicalDatastoreType.CONFIGURATION.name(), path);
172         strategy.delete(LogicalDatastoreType.CONFIGURATION, path);
173     }
174
175     /**
176      * Merge data within one transaction.
177      *
178      * @param path     Path for data to be merged
179      * @param payload  Data to be merged
180      * @param strategy Object that perform the actual DS operations
181      */
182     private static void mergeDataWithinTransaction(final YangInstanceIdentifier path,
183                                                    final NormalizedNode<?, ?> payload,
184                                                    final EffectiveModelContext schemaContext,
185                                                    final RestconfStrategy strategy) {
186         LOG.trace("Merge {} within Restconf Patch: {} with payload {}", LogicalDatastoreType.CONFIGURATION.name(),
187             path, payload);
188         TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
189         strategy.merge(LogicalDatastoreType.CONFIGURATION, path, payload);
190     }
191
192     /**
193      * Do NOT check if data exists and remove it within one transaction.
194      *
195      * @param path     Path for data to be deleted
196      * @param strategy Object that perform the actual DS operations
197      */
198     private static void removeDataWithinTransaction(final YangInstanceIdentifier path,
199                                                     final RestconfStrategy strategy) {
200         LOG.trace("Remove {} within Restconf Patch: {}", LogicalDatastoreType.CONFIGURATION.name(), path);
201         strategy.remove(LogicalDatastoreType.CONFIGURATION, path);
202     }
203
204     /**
205      * Create data within one transaction, replace if already exists.
206      *
207      * @param path          Path for data to be created
208      * @param payload       Data to be created
209      * @param strategy      Object that perform the actual DS operations
210      */
211     private static void replaceDataWithinTransaction(final YangInstanceIdentifier path,
212                                                      final NormalizedNode<?, ?> payload,
213                                                      final EffectiveModelContext schemaContext,
214                                                      final RestconfStrategy strategy) {
215         LOG.trace("PUT {} within Restconf Patch: {} with payload {}",
216             LogicalDatastoreType.CONFIGURATION.name(), path, payload);
217         createData(payload, path, strategy, schemaContext, false);
218     }
219
220     /**
221      * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
222      * for existence before created, otherwise they will be overwritten.
223      *
224      * @param data          Data to be created
225      * @param path          Path for data to be created
226      * @param strategy      Object that perform the actual DS operations
227      * @param errorIfExists Enable checking for existence of data (throws error if already exists)
228      */
229     private static void createData(final NormalizedNode<?, ?> data,
230                                    final YangInstanceIdentifier path,
231                                    final RestconfStrategy strategy,
232                                    final EffectiveModelContext schemaContext,
233                                    final boolean errorIfExists) {
234         if (errorIfExists) {
235             strategy.create(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
236         } else {
237             strategy.replace(LogicalDatastoreType.CONFIGURATION, path, data, schemaContext);
238         }
239     }
240 }