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
9 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
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 javax.ws.rs.core.Response.Status;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
19 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
21 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
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.RestConnectorProvider;
31 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
32 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
33 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PatchData;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
39 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 public final class PatchDataTransactionUtil {
44 private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
46 private PatchDataTransactionUtil() {
47 throw new UnsupportedOperationException("Util class.");
51 * Process edit operations of one {@link PatchContext}.
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 DOMDataReadWriteTransaction tx = transactionNode.getTransactionChain().newReadWriteTransaction();
63 for (final PatchEntity patchEntity : context.getData()) {
65 switch (patchEntity.getOperation()) {
68 createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
69 patchEntity.getTargetNode(), patchEntity.getNode(), tx, 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())));
79 deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
81 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
82 } catch (final RestconfDocumentedException e) {
83 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
84 false, Lists.newArrayList(e.getErrors())));
90 mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
91 patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContextRef);
92 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
93 } catch (final RestconfDocumentedException e) {
94 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
95 false, Lists.newArrayList(e.getErrors())));
101 replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
102 patchEntity.getTargetNode(), patchEntity.getNode(), schemaContextRef, tx);
103 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
104 } catch (final RestconfDocumentedException e) {
105 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
106 false, Lists.newArrayList(e.getErrors())));
112 removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
114 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
115 } catch (final RestconfDocumentedException e) {
116 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
117 false, Lists.newArrayList(e.getErrors())));
122 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
123 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
124 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
133 // if no errors then submit transaction, otherwise cancel
135 final ResponseFactory response = new ResponseFactory(Status.OK);
136 final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
139 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
140 } catch (final RestconfDocumentedException e) {
141 // if errors occurred during transaction commit then patch failed and global errors are reported
142 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
143 Lists.newArrayList(e.getErrors()));
146 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
149 RestConnectorProvider.resetTransactionChainForAdapaters(transactionNode.getTransactionChain());
150 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
156 * Create data within one transaction, return error if already exists.
157 * @param dataStore Datastore to write data to
158 * @param path Path for data to be created
159 * @param payload Data to be created
160 * @param rWTransaction Transaction
161 * @param schemaContextRef Soft reference for global schema context
163 private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
164 final YangInstanceIdentifier path,
165 final NormalizedNode<?, ?> payload,
166 final DOMDataReadWriteTransaction rwTransaction,
167 final SchemaContextRef schemaContextRef) {
168 LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
169 createData(payload, schemaContextRef.get(), path, rwTransaction, dataStore, true);
173 * Check if data exists and remove it within one transaction.
174 * @param dataStore Datastore to delete data from
175 * @param path Path for data to be deleted
176 * @param readWriteTransaction Transaction
178 private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
179 final YangInstanceIdentifier path,
180 final DOMDataReadWriteTransaction readWriteTransaction) {
181 LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path);
182 checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path);
183 readWriteTransaction.delete(dataStore, path);
187 * Merge data within one transaction.
188 * @param dataStore Datastore to merge data to
189 * @param path Path for data to be merged
190 * @param payload Data to be merged
191 * @param writeTransaction Transaction
192 * @param schemaContextRef Soft reference for global schema context
194 private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
195 final YangInstanceIdentifier path,
196 final NormalizedNode<?, ?> payload,
197 final DOMDataReadWriteTransaction writeTransaction,
198 final SchemaContextRef schemaContextRef) {
199 LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
200 TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
201 writeTransaction.merge(dataStore, path, payload);
205 * Do NOT check if data exists and remove it within one transaction.
206 * @param dataStore Datastore to delete data from
207 * @param path Path for data to be deleted
208 * @param writeTransaction Transaction
210 private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
211 final YangInstanceIdentifier path,
212 final DOMDataWriteTransaction writeTransaction) {
213 LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path);
214 writeTransaction.delete(dataStore, path);
218 * Create data within one transaction, replace if already exists.
219 * @param dataStore Datastore to write data to
220 * @param path Path for data to be created
221 * @param payload Data to be created
222 * @param schemaContextRef Soft reference for global schema context
223 * @param rwTransaction Transaction
225 private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
226 final YangInstanceIdentifier path,
227 final NormalizedNode<?, ?> payload,
228 final SchemaContextRef schemaContextRef,
229 final DOMDataReadWriteTransaction rwTransaction) {
230 LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
231 createData(payload, schemaContextRef.get(), path, rwTransaction, dataStore, false);
235 * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
236 * for existence before created, otherwise they will be overwritten.
237 * @param payload Data to be created
238 * @param schemaContext Global schema context
239 * @param path Path for data to be created
240 * @param rwTransaction Transaction
241 * @param dataStore Datastore to write data to
242 * @param errorIfExists Enable checking for existence of data (throws error if already exists)
244 private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
245 final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rwTransaction,
246 final LogicalDatastoreType dataStore, final boolean errorIfExists) {
247 if (payload instanceof MapNode) {
248 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
249 rwTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
250 TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
251 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
252 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
255 checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, childPath);
258 rwTransaction.put(dataStore, childPath, child);
262 checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, path);
265 TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
266 rwTransaction.put(dataStore, path, payload);
271 * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
272 * data does NOT already exists.
273 * @param rwTransaction Transaction
274 * @param store Datastore
275 * @param path Path to be checked
277 public static void checkItemExistsWithinTransaction(final DOMDataReadWriteTransaction rwTransaction,
278 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
279 final CheckedFuture<Boolean, ReadFailedException> future = rwTransaction.exists(store, path);
280 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
282 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
284 if (!response.result) {
285 final String errMsg = "Operation via Restconf was not executed because data does not exist";
286 LOG.trace("{}:{}", errMsg, path);
287 throw new RestconfDocumentedException(
288 "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
293 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
294 * data already exists.
295 * @param rwTransaction Transaction
296 * @param store Datastore
297 * @param path Path to be checked
299 public static void checkItemDoesNotExistsWithinTransaction(final DOMDataReadWriteTransaction rwTransaction,
300 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
301 final CheckedFuture<Boolean, ReadFailedException> future = rwTransaction.exists(store, path);
302 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
304 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
306 if (response.result) {
307 final String errMsg = "Operation via Restconf was not executed because data already exists";
308 LOG.trace("{}:{}", errMsg, path);
309 throw new RestconfDocumentedException(
310 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);