2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
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.DOMDataTreeReadOperations;
19 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
20 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
21 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
22 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
23 import org.opendaylight.restconf.common.errors.RestconfError;
24 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
25 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
26 import org.opendaylight.restconf.common.patch.PatchContext;
27 import org.opendaylight.restconf.common.patch.PatchEntity;
28 import org.opendaylight.restconf.common.patch.PatchStatusContext;
29 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
30 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
31 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
32 import org.opendaylight.restconf.nb.rfc8040.rests.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;
42 public final class PatchDataTransactionUtil {
43 private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
45 private PatchDataTransactionUtil() {
46 throw new UnsupportedOperationException("Util class.");
50 * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} inside of object
51 * {@link TransactionVarsWrapper} provided as a parameter.
52 * @param context Patch context to be processed
53 * @param transactionNode Wrapper for transaction
54 * @param schemaContextRef Soft reference for global schema context
55 * @return {@link PatchStatusContext}
57 public static PatchStatusContext patchData(final PatchContext context, final TransactionVarsWrapper transactionNode,
58 final SchemaContextRef schemaContextRef) {
59 final List<PatchStatusEntity> editCollection = new ArrayList<>();
60 boolean noError = true;
61 final DOMTransactionChain transactionChain = transactionNode.getTransactionChain();
62 final DOMDataTreeReadWriteTransaction tx = transactionChain.newReadWriteTransaction();
64 for (final PatchEntity patchEntity : context.getData()) {
66 switch (patchEntity.getOperation()) {
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())));
80 deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
82 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
83 } catch (final RestconfDocumentedException e) {
84 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
85 false, Lists.newArrayList(e.getErrors())));
91 mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
92 patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContextRef);
93 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
94 } catch (final RestconfDocumentedException e) {
95 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
96 false, Lists.newArrayList(e.getErrors())));
102 replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
103 patchEntity.getTargetNode(), patchEntity.getNode(), schemaContextRef, tx);
104 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
105 } catch (final RestconfDocumentedException e) {
106 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
107 false, Lists.newArrayList(e.getErrors())));
113 removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
115 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
116 } catch (final RestconfDocumentedException e) {
117 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
118 false, Lists.newArrayList(e.getErrors())));
123 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
124 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
125 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
134 // if no errors then submit transaction, otherwise cancel
136 final ResponseFactory response = new ResponseFactory(Status.OK);
137 final FluentFuture<? extends CommitInfo> future = tx.commit();
140 //This method will close transactionChain
141 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, transactionChain);
142 } catch (final RestconfDocumentedException e) {
143 // if errors occurred during transaction commit then patch failed and global errors are reported
144 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
145 Lists.newArrayList(e.getErrors()));
148 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
151 transactionChain.close();
152 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
158 * Create data within one transaction, return error if already exists.
159 * @param dataStore Datastore to write data to
160 * @param path Path for data to be created
161 * @param payload Data to be created
162 * @param rwTransaction Transaction
163 * @param schemaContextRef Soft reference for global schema context
165 private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
166 final YangInstanceIdentifier path,
167 final NormalizedNode<?, ?> payload,
168 final DOMDataTreeReadWriteTransaction rwTransaction,
169 final SchemaContextRef schemaContextRef) {
170 LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
171 createData(payload, schemaContextRef.get(), path, rwTransaction, dataStore, true);
175 * Check if data exists and remove it within one transaction.
176 * @param dataStore Datastore to delete data from
177 * @param path Path for data to be deleted
178 * @param readWriteTransaction Transaction
180 private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
181 final YangInstanceIdentifier path,
182 final DOMDataTreeReadWriteTransaction readWriteTransaction) {
183 LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path);
184 checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path);
185 readWriteTransaction.delete(dataStore, path);
189 * Merge data within one transaction.
190 * @param dataStore Datastore to merge data to
191 * @param path Path for data to be merged
192 * @param payload Data to be merged
193 * @param writeTransaction Transaction
194 * @param schemaContextRef Soft reference for global schema context
196 private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
197 final YangInstanceIdentifier path,
198 final NormalizedNode<?, ?> payload,
199 final DOMDataTreeWriteTransaction writeTransaction,
200 final SchemaContextRef schemaContextRef) {
201 LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
202 TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
203 writeTransaction.merge(dataStore, path, payload);
207 * Do NOT check if data exists and remove it within one transaction.
208 * @param dataStore Datastore to delete data from
209 * @param path Path for data to be deleted
210 * @param writeTransaction Transaction
212 private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
213 final YangInstanceIdentifier path,
214 final DOMDataTreeWriteTransaction writeTransaction) {
215 LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path);
216 writeTransaction.delete(dataStore, path);
220 * Create data within one transaction, replace if already exists.
221 * @param dataStore Datastore to write data to
222 * @param path Path for data to be created
223 * @param payload Data to be created
224 * @param schemaContextRef Soft reference for global schema context
225 * @param rwTransaction Transaction
227 private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
228 final YangInstanceIdentifier path,
229 final NormalizedNode<?, ?> payload,
230 final SchemaContextRef schemaContextRef,
231 final DOMDataTreeReadWriteTransaction rwTransaction) {
232 LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
233 createData(payload, schemaContextRef.get(), path, rwTransaction, dataStore, false);
237 * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
238 * for existence before created, otherwise they will be overwritten.
239 * @param payload Data to be created
240 * @param schemaContext Global schema context
241 * @param path Path for data to be created
242 * @param rwTransaction Transaction
243 * @param dataStore Datastore to write data to
244 * @param errorIfExists Enable checking for existence of data (throws error if already exists)
246 private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
247 final YangInstanceIdentifier path,
248 final DOMDataTreeReadWriteTransaction rwTransaction,
249 final LogicalDatastoreType dataStore, final boolean errorIfExists) {
250 if (payload instanceof MapNode) {
251 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
252 rwTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
253 TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
254 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
255 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
258 checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, childPath);
261 rwTransaction.put(dataStore, childPath, child);
265 checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, path);
268 TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
269 rwTransaction.put(dataStore, path, payload);
274 * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
275 * data does NOT already exists.
276 * @param rwTransaction Transaction
277 * @param store Datastore
278 * @param path Path to be checked
280 public static void checkItemExistsWithinTransaction(final DOMDataTreeReadOperations rwTransaction,
281 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
282 final FluentFuture<Boolean> future = rwTransaction.exists(store, path);
283 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
285 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
287 if (!response.result) {
288 LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
289 throw new RestconfDocumentedException("Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
295 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
296 * data already exists.
297 * @param rwTransaction Transaction
298 * @param store Datastore
299 * @param path Path to be checked
301 public static void checkItemDoesNotExistsWithinTransaction(final DOMDataTreeReadOperations rwTransaction,
302 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
303 final FluentFuture<Boolean> future = rwTransaction.exists(store, path);
304 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
306 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
308 if (response.result) {
309 LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
310 throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,