BUG-5280: centralize ShardSnapshot operations
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / datastore / ShardDataTree.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. 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.controller.cluster.datastore;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import java.util.AbstractMap.SimpleEntry;
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.Map.Entry;
16 import javax.annotation.concurrent.NotThreadSafe;
17 import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
18 import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
19 import org.opendaylight.controller.cluster.datastore.persisted.ShardDataTreeSnapshot;
20 import org.opendaylight.controller.cluster.datastore.persisted.MetadataShardDataTreeSnapshot;
21 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
23 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
24 import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
25 import org.opendaylight.yangtools.concepts.Identifier;
26 import org.opendaylight.yangtools.concepts.ListenerRegistration;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
30 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
31 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
32 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
33 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
35 import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
37 import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Internal shard state, similar to a DOMStore, but optimized for use in the actor system,
44  * e.g. it does not expose public interfaces and assumes it is only ever called from a
45  * single thread.
46  *
47  * This class is not part of the API contract and is subject to change at any time.
48  */
49 @NotThreadSafe
50 public class ShardDataTree extends ShardDataTreeTransactionParent {
51     private static final Logger LOG = LoggerFactory.getLogger(ShardDataTree.class);
52
53     private final Map<LocalHistoryIdentifier, ShardDataTreeTransactionChain> transactionChains = new HashMap<>();
54     private final ShardDataTreeChangeListenerPublisher treeChangeListenerPublisher;
55     private final ShardDataChangeListenerPublisher dataChangeListenerPublisher;
56     private final TipProducingDataTree dataTree;
57     private final String logContext;
58     private SchemaContext schemaContext;
59
60     public ShardDataTree(final SchemaContext schemaContext, final TreeType treeType,
61             final ShardDataTreeChangeListenerPublisher treeChangeListenerPublisher,
62             final ShardDataChangeListenerPublisher dataChangeListenerPublisher, final String logContext) {
63         dataTree = InMemoryDataTreeFactory.getInstance().create(treeType);
64         updateSchemaContext(schemaContext);
65
66         this.treeChangeListenerPublisher = Preconditions.checkNotNull(treeChangeListenerPublisher);
67         this.dataChangeListenerPublisher = Preconditions.checkNotNull(dataChangeListenerPublisher);
68         this.logContext = Preconditions.checkNotNull(logContext);
69     }
70
71     public ShardDataTree(final SchemaContext schemaContext, final TreeType treeType) {
72         this(schemaContext, treeType, new DefaultShardDataTreeChangeListenerPublisher(),
73                 new DefaultShardDataChangeListenerPublisher(), "");
74     }
75
76     public TipProducingDataTree getDataTree() {
77         return dataTree;
78     }
79
80     SchemaContext getSchemaContext() {
81         return schemaContext;
82     }
83
84     void updateSchemaContext(final SchemaContext schemaContext) {
85         dataTree.setSchemaContext(schemaContext);
86         this.schemaContext = Preconditions.checkNotNull(schemaContext);
87     }
88
89     ShardDataTreeSnapshot takeRecoverySnapshot() {
90         return new MetadataShardDataTreeSnapshot(dataTree.takeSnapshot().readNode(YangInstanceIdentifier.EMPTY).get());
91     }
92
93     void applyRecoveryTransaction(final ReadWriteShardDataTreeTransaction transaction) throws DataValidationFailedException {
94         final DataTreeModification snapshot = transaction.getSnapshot();
95         snapshot.ready();
96
97         dataTree.validate(snapshot);
98         dataTree.commit(dataTree.prepare(snapshot));
99     }
100
101     private ShardDataTreeTransactionChain ensureTransactionChain(final LocalHistoryIdentifier localHistoryIdentifier) {
102         ShardDataTreeTransactionChain chain = transactionChains.get(localHistoryIdentifier);
103         if (chain == null) {
104             chain = new ShardDataTreeTransactionChain(localHistoryIdentifier, this);
105             transactionChains.put(localHistoryIdentifier, chain);
106         }
107
108         return chain;
109     }
110
111     ReadOnlyShardDataTreeTransaction newReadOnlyTransaction(final TransactionIdentifier txId) {
112         if (txId.getHistoryId().getHistoryId() == 0) {
113             return new ReadOnlyShardDataTreeTransaction(txId, dataTree.takeSnapshot());
114         }
115
116         return ensureTransactionChain(txId.getHistoryId()).newReadOnlyTransaction(txId);
117     }
118
119     ReadWriteShardDataTreeTransaction newReadWriteTransaction(final TransactionIdentifier txId) {
120         if (txId.getHistoryId().getHistoryId() == 0) {
121             return new ReadWriteShardDataTreeTransaction(ShardDataTree.this, txId, dataTree.takeSnapshot()
122                     .newModification());
123         }
124
125         return ensureTransactionChain(txId.getHistoryId()).newReadWriteTransaction(txId);
126     }
127
128     public void notifyListeners(final DataTreeCandidate candidate) {
129         treeChangeListenerPublisher.publishChanges(candidate, logContext);
130         dataChangeListenerPublisher.publishChanges(candidate, logContext);
131     }
132
133     void notifyOfInitialData(final DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier,
134             NormalizedNode<?, ?>>> listenerReg, final Optional<DataTreeCandidate> currentState) {
135         if (currentState.isPresent()) {
136             ShardDataChangeListenerPublisher localPublisher = dataChangeListenerPublisher.newInstance();
137             localPublisher.registerDataChangeListener(listenerReg.getPath(), listenerReg.getInstance(),
138                     listenerReg.getScope());
139             localPublisher.publishChanges(currentState.get(), logContext);
140         }
141     }
142
143     void notifyOfInitialData(final YangInstanceIdentifier path, final DOMDataTreeChangeListener listener,
144             final Optional<DataTreeCandidate> currentState) {
145         if (currentState.isPresent()) {
146             ShardDataTreeChangeListenerPublisher localPublisher = treeChangeListenerPublisher.newInstance();
147             localPublisher.registerTreeChangeListener(path, listener);
148             localPublisher.publishChanges(currentState.get(), logContext);
149         }
150     }
151
152     void closeAllTransactionChains() {
153         for (ShardDataTreeTransactionChain chain : transactionChains.values()) {
154             chain.close();
155         }
156
157         transactionChains.clear();
158     }
159
160     void closeTransactionChain(final LocalHistoryIdentifier transactionChainId) {
161         final ShardDataTreeTransactionChain chain = transactionChains.remove(transactionChainId);
162         if (chain != null) {
163             chain.close();
164         } else {
165             LOG.debug("{}: Closing non-existent transaction chain {}", logContext, transactionChainId);
166         }
167     }
168
169     Entry<DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>,
170             Optional<DataTreeCandidate>> registerChangeListener(final YangInstanceIdentifier path,
171                     final AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> listener,
172                     final DataChangeScope scope) {
173         final DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> reg =
174                 dataChangeListenerPublisher.registerDataChangeListener(path, listener, scope);
175
176         return new SimpleEntry<>(reg, readCurrentData());
177     }
178
179     private Optional<DataTreeCandidate> readCurrentData() {
180         final Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(YangInstanceIdentifier.EMPTY);
181         return currentState.isPresent() ? Optional.of(DataTreeCandidates.fromNormalizedNode(
182             YangInstanceIdentifier.EMPTY, currentState.get())) : Optional.<DataTreeCandidate>absent();
183     }
184
185     public Entry<ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> registerTreeChangeListener(
186             final YangInstanceIdentifier path, final DOMDataTreeChangeListener listener) {
187         final ListenerRegistration<DOMDataTreeChangeListener> reg = treeChangeListenerPublisher.registerTreeChangeListener(
188                 path, listener);
189
190         return new SimpleEntry<>(reg, readCurrentData());
191     }
192
193     void applyForeignCandidate(final Identifier identifier, final DataTreeCandidate foreign) throws DataValidationFailedException {
194         LOG.debug("{}: Applying foreign transaction {}", logContext, identifier);
195
196         final DataTreeModification mod = dataTree.takeSnapshot().newModification();
197         DataTreeCandidates.applyToModification(mod, foreign);
198         mod.ready();
199
200         LOG.trace("{}: Applying foreign modification {}", logContext, mod);
201         dataTree.validate(mod);
202         final DataTreeCandidate candidate = dataTree.prepare(mod);
203         dataTree.commit(candidate);
204         notifyListeners(candidate);
205     }
206
207     @Override
208     void abortTransaction(final AbstractShardDataTreeTransaction<?> transaction) {
209         // Intentional no-op
210     }
211
212     @Override
213     ShardDataTreeCohort finishTransaction(final ReadWriteShardDataTreeTransaction transaction) {
214         final DataTreeModification snapshot = transaction.getSnapshot();
215         snapshot.ready();
216         return new SimpleShardDataTreeCohort(this, snapshot, transaction.getId());
217     }
218
219     public Optional<NormalizedNode<?, ?>> readNode(final YangInstanceIdentifier path) {
220         return dataTree.takeSnapshot().readNode(path);
221     }
222
223     public DataTreeSnapshot takeSnapshot() {
224         return dataTree.takeSnapshot();
225     }
226
227     public DataTreeModification newModification() {
228         return dataTree.takeSnapshot().newModification();
229     }
230
231     // FIXME: This should be removed, it violates encapsulation
232     public DataTreeCandidate commit(final DataTreeModification modification) throws DataValidationFailedException {
233         modification.ready();
234         dataTree.validate(modification);
235         DataTreeCandidateTip candidate = dataTree.prepare(modification);
236         dataTree.commit(candidate);
237         return candidate;
238     }
239 }