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 public 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<>();
61 final DOMDataReadWriteTransaction tx = transactionNode.getTransactionChain().newReadWriteTransaction();
63 for (final PATCHEntity patchEntity : context.getData()) {
64 final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
68 if (errorCounter == 0) {
70 createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
71 patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContextRef);
72 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
73 } catch (final RestconfDocumentedException e) {
74 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
75 false, Lists.newArrayList(e.getErrors())));
81 if (errorCounter == 0) {
83 deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
85 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
86 } catch (final RestconfDocumentedException e) {
87 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
88 false, Lists.newArrayList(e.getErrors())));
94 if (errorCounter == 0) {
96 mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
97 patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContextRef);
98 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
99 } catch (final RestconfDocumentedException e) {
100 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
101 false, Lists.newArrayList(e.getErrors())));
107 if (errorCounter == 0) {
109 replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
110 patchEntity.getTargetNode(), patchEntity.getNode(), schemaContextRef, tx);
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())));
120 if (errorCounter == 0) {
122 removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
124 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
125 } catch (final RestconfDocumentedException e) {
126 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
127 false, Lists.newArrayList(e.getErrors())));
133 editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
134 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
135 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang PATCH operation"))));
141 // if no errors then submit transaction, otherwise cancel
142 if (errorCounter == 0) {
143 final ResponseFactory response = new ResponseFactory();
144 final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
147 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
148 } catch (final RestconfDocumentedException e) {
149 // if errors occurred during transaction commit then patch failed and global errors are reported
150 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
151 Lists.newArrayList(e.getErrors()));
154 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
157 RestConnectorProvider.resetTransactionChainForAdapaters(transactionNode.getTransactionChain());
158 return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
163 * Create data within one transaction, return error if already exists.
164 * @param dataStore Datastore to write data to
165 * @param path Path for data to be created
166 * @param payload Data to be created
167 * @param rWTransaction Transaction
168 * @param schemaContextRef Soft reference for global schema context
170 private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
171 final YangInstanceIdentifier path,
172 final NormalizedNode<?, ?> payload,
173 final DOMDataReadWriteTransaction rWTransaction,
174 final SchemaContextRef schemaContextRef) {
175 LOG.trace("POST {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
176 createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, true);
180 * Check if data exists and remove it within one transaction.
181 * @param dataStore Datastore to delete data from
182 * @param path Path for data to be deleted
183 * @param readWriteTransaction Transaction
185 private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
186 final YangInstanceIdentifier path,
187 final DOMDataReadWriteTransaction readWriteTransaction) {
188 LOG.trace("Delete {} within Restconf PATCH: {}", dataStore.name(), path);
189 checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path);
190 readWriteTransaction.delete(dataStore, path);
194 * Merge data within one transaction.
195 * @param dataStore Datastore to merge data to
196 * @param path Path for data to be merged
197 * @param payload Data to be merged
198 * @param writeTransaction Transaction
199 * @param schemaContextRef Soft reference for global schema context
201 private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
202 final YangInstanceIdentifier path,
203 final NormalizedNode<?, ?> payload,
204 final DOMDataReadWriteTransaction writeTransaction,
205 final SchemaContextRef schemaContextRef) {
206 LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
207 TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
209 // merging is necessary only for lists otherwise we can call put method
210 if (payload instanceof MapNode) {
211 writeTransaction.merge(dataStore, path, payload);
213 writeTransaction.put(dataStore, path, payload);
218 * Do NOT check if data exists and remove it within one transaction.
219 * @param dataStore Datastore to delete data from
220 * @param path Path for data to be deleted
221 * @param writeTransaction Transaction
223 private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
224 final YangInstanceIdentifier path,
225 final DOMDataWriteTransaction writeTransaction) {
226 LOG.trace("Remove {} within Restconf PATCH: {}", dataStore.name(), path);
227 writeTransaction.delete(dataStore, path);
231 * Create data within one transaction, replace if already exists.
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 schemaContextRef Soft reference for global schema context
236 * @param rWTransaction Transaction
238 private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
239 final YangInstanceIdentifier path,
240 final NormalizedNode<?, ?> payload,
241 final SchemaContextRef schemaContextRef,
242 final DOMDataReadWriteTransaction rWTransaction) {
243 LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
244 createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, false);
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 * @param payload Data to be created
251 * @param schemaContext Global schema context
252 * @param path Path for data to be created
253 * @param rWTransaction Transaction
254 * @param dataStore Datastore to write data to
255 * @param errorIfExists Enable checking for existence of data (throws error if already exists)
257 private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
258 final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rWTransaction,
259 final LogicalDatastoreType dataStore, final boolean errorIfExists) {
260 if (payload instanceof MapNode) {
261 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
262 rWTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
263 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
264 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
265 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
268 checkItemDoesNotExistsWithinTransaction(rWTransaction, dataStore, childPath);
271 rWTransaction.put(dataStore, childPath, child);
275 checkItemDoesNotExistsWithinTransaction(rWTransaction, dataStore, path);
278 TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
279 rWTransaction.put(dataStore, path, payload);
284 * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
285 * data does NOT already exists.
286 * @param rWTransaction Transaction
287 * @param store Datastore
288 * @param path Path to be checked
290 public static void checkItemExistsWithinTransaction(final DOMDataReadWriteTransaction rWTransaction,
291 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
292 final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
293 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
295 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
297 if (!response.result) {
298 final String errMsg = "Operation via Restconf was not executed because data does not exist";
299 LOG.trace("{}:{}", errMsg, path);
300 throw new RestconfDocumentedException(
301 "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
306 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
307 * data already exists.
308 * @param rWTransaction Transaction
309 * @param store Datastore
310 * @param path Path to be checked
312 public static void checkItemDoesNotExistsWithinTransaction(final DOMDataReadWriteTransaction rWTransaction,
313 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
314 final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
315 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
317 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
319 if (response.result) {
320 final String errMsg = "Operation via Restconf was not executed because data already exists";
321 LOG.trace("{}:{}", errMsg, path);
322 throw new RestconfDocumentedException(
323 "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);