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.restful.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 org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
17 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
18 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
19 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
21 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
22 import org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation;
23 import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
24 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
25 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
26 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
27 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
28 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
29 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
30 import org.opendaylight.restconf.RestConnectorProvider;
31 import org.opendaylight.restconf.common.references.SchemaContextRef;
32 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
33 import org.opendaylight.restconf.restful.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()) {
64 final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
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();
137 final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
140 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
141 } catch (final RestconfDocumentedException e) {
142 // if errors occurred during transaction commit then patch failed and global errors are reported
143 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
144 Lists.newArrayList(e.getErrors()));
147 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
150 RestConnectorProvider.resetTransactionChainForAdapaters(transactionNode.getTransactionChain());
151 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
157 * Create data within one transaction, return error if already exists.
158 * @param dataStore Datastore to write data to
159 * @param path Path for data to be created
160 * @param payload Data to be created
161 * @param rWTransaction Transaction
162 * @param schemaContextRef Soft reference for global schema context
164 private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
165 final YangInstanceIdentifier path,
166 final NormalizedNode<?, ?> payload,
167 final DOMDataReadWriteTransaction rWTransaction,
168 final SchemaContextRef schemaContextRef) {
169 LOG.trace("POST {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
170 createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, true);
174 * Check if data exists and remove it within one transaction.
175 * @param dataStore Datastore to delete data from
176 * @param path Path for data to be deleted
177 * @param readWriteTransaction Transaction
179 private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
180 final YangInstanceIdentifier path,
181 final DOMDataReadWriteTransaction readWriteTransaction) {
182 LOG.trace("Delete {} within Restconf PATCH: {}", dataStore.name(), path);
183 checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path);
184 readWriteTransaction.delete(dataStore, path);
188 * Merge data within one transaction.
189 * @param dataStore Datastore to merge data to
190 * @param path Path for data to be merged
191 * @param payload Data to be merged
192 * @param writeTransaction Transaction
193 * @param schemaContextRef Soft reference for global schema context
195 private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
196 final YangInstanceIdentifier path,
197 final NormalizedNode<?, ?> payload,
198 final DOMDataReadWriteTransaction writeTransaction,
199 final SchemaContextRef schemaContextRef) {
200 LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
201 TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
203 // merging is necessary only for lists otherwise we can call put method
204 if (payload instanceof MapNode) {
205 writeTransaction.merge(dataStore, path, payload);
207 writeTransaction.put(dataStore, path, payload);
212 * Do NOT check if data exists and remove it within one transaction.
213 * @param dataStore Datastore to delete data from
214 * @param path Path for data to be deleted
215 * @param writeTransaction Transaction
217 private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
218 final YangInstanceIdentifier path,
219 final DOMDataWriteTransaction writeTransaction) {
220 LOG.trace("Remove {} within Restconf PATCH: {}", dataStore.name(), path);
221 writeTransaction.delete(dataStore, path);
225 * Create data within one transaction, replace if already exists.
226 * @param dataStore Datastore to write data to
227 * @param path Path for data to be created
228 * @param payload Data to be created
229 * @param schemaContextRef Soft reference for global schema context
230 * @param rWTransaction Transaction
232 private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
233 final YangInstanceIdentifier path,
234 final NormalizedNode<?, ?> payload,
235 final SchemaContextRef schemaContextRef,
236 final DOMDataReadWriteTransaction rWTransaction) {
237 LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
238 createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, false);
242 * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
243 * for existence before created, otherwise they will be overwritten.
244 * @param payload Data to be created
245 * @param schemaContext Global schema context
246 * @param path Path for data to be created
247 * @param rWTransaction Transaction
248 * @param dataStore Datastore to write data to
249 * @param errorIfExists Enable checking for existence of data (throws error if already exists)
251 private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
252 final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rWTransaction,
253 final LogicalDatastoreType dataStore, final boolean errorIfExists) {
254 if (payload instanceof MapNode) {
255 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
256 rWTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
257 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
258 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
259 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
262 checkItemDoesNotExistsWithinTransaction(rWTransaction, dataStore, childPath);
265 rWTransaction.put(dataStore, childPath, child);
269 checkItemDoesNotExistsWithinTransaction(rWTransaction, dataStore, path);
272 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
273 rWTransaction.put(dataStore, path, payload);
278 * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
279 * data does NOT already exists.
280 * @param rWTransaction Transaction
281 * @param store Datastore
282 * @param path Path to be checked
284 public static void checkItemExistsWithinTransaction(final DOMDataReadWriteTransaction rWTransaction,
285 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
286 final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
287 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
289 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
291 if (!response.result) {
292 final String errMsg = "Operation via Restconf was not executed because data does not exist";
293 LOG.trace("{}:{}", errMsg, path);
294 throw new RestconfDocumentedException(
295 "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
300 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
301 * data already exists.
302 * @param rWTransaction Transaction
303 * @param store Datastore
304 * @param path Path to be checked
306 public static void checkItemDoesNotExistsWithinTransaction(final DOMDataReadWriteTransaction rWTransaction,
307 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
308 final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
309 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
311 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
313 if (response.result) {
314 final String errMsg = "Operation via Restconf was not executed because data already exists";
315 LOG.trace("{}:{}", errMsg, path);
316 throw new RestconfDocumentedException(
317 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);