Split transaction lifecycle
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / transactions / MdsalRestconfTransaction.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil.DELETE_TX_TYPE;
13 import static org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil.checkItemDoesNotExists;
14
15 import com.google.common.util.concurrent.FluentFuture;
16 import java.util.Collection;
17 import java.util.Map;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.mdsal.common.api.CommitInfo;
20 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
21 import org.opendaylight.mdsal.common.api.ReadFailedException;
22 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
23 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
24 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
25 import org.opendaylight.restconf.common.errors.RestconfError;
26 import org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil;
27 import org.opendaylight.restconf.nb.rfc8040.rests.utils.TransactionUtil;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
35
36 final class MdsalRestconfTransaction extends RestconfTransaction {
37     private final DOMTransactionChain transactionChain;
38     private DOMDataTreeReadWriteTransaction rwTx;
39
40     MdsalRestconfTransaction(DOMTransactionChain transactionChain) {
41         this.transactionChain = requireNonNull(transactionChain);
42         this.rwTx = transactionChain.newReadWriteTransaction();
43     }
44
45     @Override
46     public void cancel() {
47         if (rwTx != null) {
48             rwTx.cancel();
49             rwTx = null;
50         }
51         transactionChain.close();
52     }
53
54     @Override
55     public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
56         final FluentFuture<Boolean> isExists = verifyNotNull(rwTx).exists(store, path);
57         DeleteDataTransactionUtil.checkItemExists(isExists, path, DELETE_TX_TYPE);
58         rwTx.delete(store, path);
59     }
60
61     @Override
62     public void remove(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
63         verifyNotNull(rwTx).delete(store, path);
64     }
65
66     @Override
67     public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path,
68                       final NormalizedNode<?, ?> data) {
69         verifyNotNull(rwTx).merge(store, path, data);
70     }
71
72     @Override
73     public void create(final LogicalDatastoreType store, final YangInstanceIdentifier path,
74                        final NormalizedNode<?, ?> data, final SchemaContext schemaContext) {
75         if (data instanceof MapNode || data instanceof LeafSetNode) {
76             final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
77             merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(emptySubTree.getIdentifier()),
78                 emptySubTree);
79             TransactionUtil.ensureParentsByMerge(path, schemaContext, this);
80
81             final Collection<? extends NormalizedNode<?, ?>> children =
82                 ((NormalizedNodeContainer<?, ?, ?>) data).getValue();
83             final BatchedExistenceCheck check =
84                 BatchedExistenceCheck.start(transactionChain, LogicalDatastoreType.CONFIGURATION, path, children);
85
86             for (final NormalizedNode<?, ?> child : children) {
87                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
88                 verifyNotNull(rwTx).put(store, childPath, child);
89             }
90             // ... finally collect existence checks and abort the transaction if any of them failed.
91             checkExistence(path, check);
92         } else {
93             final FluentFuture<Boolean> isExists = verifyNotNull(rwTx).exists(store, path);
94             checkItemDoesNotExists(isExists, path);
95             TransactionUtil.ensureParentsByMerge(path, schemaContext, this);
96             verifyNotNull(rwTx).put(store, path, data);
97         }
98     }
99
100     @Override
101     public void replace(final LogicalDatastoreType store, final YangInstanceIdentifier path,
102                         final NormalizedNode<?, ?> data, final SchemaContext schemaContext) {
103         if (data instanceof MapNode || data instanceof LeafSetNode) {
104             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
105             merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(emptySubtree.getIdentifier()),
106                 emptySubtree);
107             TransactionUtil.ensureParentsByMerge(path, schemaContext, this);
108
109             for (final NormalizedNode<?, ?> child : ((NormalizedNodeContainer<?, ?, ?>) data).getValue()) {
110                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
111                 verifyNotNull(rwTx).put(store, childPath, child);
112             }
113         } else {
114             TransactionUtil.ensureParentsByMerge(path, schemaContext, this);
115             verifyNotNull(rwTx).put(store, path, data);
116         }
117     }
118
119     @Override
120     public FluentFuture<? extends @NonNull CommitInfo> commit() {
121         final FluentFuture<? extends @NonNull CommitInfo> ret = verifyNotNull(rwTx).commit();
122         rwTx = null;
123         return ret;
124     }
125
126     private static void checkExistence(final YangInstanceIdentifier path, final BatchedExistenceCheck check) {
127         final Map.Entry<YangInstanceIdentifier, ReadFailedException> failure;
128         try {
129             failure = check.getFailure();
130         } catch (InterruptedException e) {
131             throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
132         }
133
134         if (failure != null) {
135             final ReadFailedException e = failure.getValue();
136             if (e == null) {
137                 throw new RestconfDocumentedException("Data already exists",
138                     RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.DATA_EXISTS, failure.getKey());
139             }
140
141             throw new RestconfDocumentedException(
142                 "Could not determine the existence of path " + failure.getKey(), e, e.getErrorList());
143         }
144     }
145 }