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.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.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PatchData;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 public final class PatchDataTransactionUtil {
39 private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
41 private PatchDataTransactionUtil() {
42 throw new UnsupportedOperationException("Util class.");
46 * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} if any inside of object
47 * {@link RestconfStrategy} provided as a parameter.
49 * @param context Patch context to be processed
50 * @param strategy object that perform the actual DS operations
51 * @param schemaContext Global schema context
52 * @return {@link PatchStatusContext}
54 public static PatchStatusContext patchData(final PatchContext context, final RestconfStrategy strategy,
55 final EffectiveModelContext schemaContext) {
56 final List<PatchStatusEntity> editCollection = new ArrayList<>();
57 boolean noError = true;
58 strategy.prepareReadWriteExecution();
60 for (final PatchEntity patchEntity : context.getData()) {
62 switch (patchEntity.getOperation()) {
65 createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
66 patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext);
67 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
68 } catch (final RestconfDocumentedException e) {
69 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
70 false, Lists.newArrayList(e.getErrors())));
76 deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
78 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
79 } catch (final RestconfDocumentedException e) {
80 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
81 false, Lists.newArrayList(e.getErrors())));
87 mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
88 patchEntity.getTargetNode(), patchEntity.getNode(), strategy, schemaContext);
89 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
90 } catch (final RestconfDocumentedException e) {
91 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
92 false, Lists.newArrayList(e.getErrors())));
98 replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
99 patchEntity.getTargetNode(), patchEntity.getNode(), schemaContext, strategy);
100 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
101 } catch (final RestconfDocumentedException e) {
102 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
103 false, Lists.newArrayList(e.getErrors())));
109 removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
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())));
119 editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
120 false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
121 ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
130 // if no errors then submit transaction, otherwise cancel
132 final ResponseFactory response = new ResponseFactory(Status.OK);
133 final FluentFuture<? extends CommitInfo> future = strategy.commit();
136 //This method will close transactionChain if any
137 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, strategy.getTransactionChain());
138 } catch (final RestconfDocumentedException e) {
139 // if errors occurred during transaction commit then patch failed and global errors are reported
140 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
141 Lists.newArrayList(e.getErrors()));
144 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
148 return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
154 * Create data within one transaction, return error if already exists.
156 * @param dataStore Datastore to write data to
157 * @param path Path for data to be created
158 * @param payload Data to be created
159 * @param strategy Object that perform the actual DS operations
160 * @param schemaContext Global schema context
162 private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
163 final YangInstanceIdentifier path,
164 final NormalizedNode<?, ?> payload,
165 final RestconfStrategy strategy,
166 final EffectiveModelContext schemaContext) {
167 LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
168 createData(payload, schemaContext, path, strategy, dataStore, true);
172 * 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 strategy Object that perform the actual DS operations
178 private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
179 final YangInstanceIdentifier path,
180 final RestconfStrategy strategy) {
181 LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path);
182 checkItemExistsWithinTransaction(strategy, dataStore, path);
183 strategy.delete(dataStore, path);
187 * 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 strategy Object that perform the actual DS operations
193 * @param schemaContext Global schema context
195 private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
196 final YangInstanceIdentifier path,
197 final NormalizedNode<?, ?> payload,
198 final RestconfStrategy strategy,
199 final EffectiveModelContext schemaContext) {
200 LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
201 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
202 strategy.merge(dataStore, path, payload);
206 * 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 strategy Object that perform the actual DS operations
212 private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
213 final YangInstanceIdentifier path,
214 final RestconfStrategy strategy) {
215 LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path);
216 strategy.delete(dataStore, path);
220 * Create data within one transaction, replace if already exists.
222 * @param dataStore Datastore to write data to
223 * @param path Path for data to be created
224 * @param payload Data to be created
225 * @param schemaContext Global schema context
226 * @param strategy Object that perform the actual DS operations
228 private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
229 final YangInstanceIdentifier path,
230 final NormalizedNode<?, ?> payload,
231 final EffectiveModelContext schemaContext,
232 final RestconfStrategy strategy) {
233 LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
234 createData(payload, schemaContext, path, strategy, dataStore, false);
238 * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
239 * for existence before created, otherwise they will be overwritten.
241 * @param payload Data to be created
242 * @param schemaContext Global schema context
243 * @param path Path for data to be created
244 * @param strategy Object that perform the actual DS operations
245 * @param dataStore Datastore to write data to
246 * @param errorIfExists Enable checking for existence of data (throws error if already exists)
248 private static void createData(final NormalizedNode<?, ?> payload, final EffectiveModelContext schemaContext,
249 final YangInstanceIdentifier path,
250 final RestconfStrategy strategy,
251 final LogicalDatastoreType dataStore, final boolean errorIfExists) {
252 if (payload instanceof MapNode) {
253 final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
254 strategy.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
255 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
256 for (final MapEntryNode child : ((MapNode) payload).getValue()) {
257 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
260 checkItemDoesNotExistsWithinTransaction(strategy, dataStore, childPath);
264 strategy.create(dataStore, childPath, child);
266 strategy.replace(dataStore, childPath, child);
271 checkItemDoesNotExistsWithinTransaction(strategy, dataStore, path);
274 TransactionUtil.ensureParentsByMerge(path, schemaContext, strategy);
276 strategy.create(dataStore, path, payload);
278 strategy.replace(dataStore, path, payload);
284 * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
285 * data does NOT already exists.
287 * @param strategy Object that perform the actual DS operations
288 * @param store Datastore
289 * @param path Path to be checked
291 public static void checkItemExistsWithinTransaction(final RestconfStrategy strategy,
292 final LogicalDatastoreType store,
293 final YangInstanceIdentifier path) {
294 final FluentFuture<Boolean> future = strategy.exists(store, path);
295 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
297 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
299 if (!response.result) {
300 LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
301 throw new RestconfDocumentedException("Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
307 * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
308 * data already exists.
310 * @param strategy Object that perform the actual DS operations
311 * @param store Datastore
312 * @param path Path to be checked
314 public static void checkItemDoesNotExistsWithinTransaction(final RestconfStrategy strategy,
315 final LogicalDatastoreType store,
316 final YangInstanceIdentifier path) {
317 final FluentFuture<Boolean> future = strategy.exists(store, path);
318 final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
320 FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
322 if (response.result) {
323 LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
324 throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,