Merge "BUG-770: NumberFormatException for input string on switch OFPT_HELLO"
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / md / sal / dom / store / impl / InMemoryDOMDataStore.java
1 /*
2  * Copyright (c) 2014 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.md.sal.dom.store.impl;
9
10 import static com.google.common.base.Preconditions.checkNotNull;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import java.util.Collections;
14 import java.util.concurrent.Callable;
15 import java.util.concurrent.atomic.AtomicLong;
16
17 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
18 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
19 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataPreconditionFailedException;
20 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTree;
21 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeCandidate;
22 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeModification;
23 import org.opendaylight.controller.md.sal.dom.store.impl.tree.DataTreeSnapshot;
24 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
25 import org.opendaylight.controller.md.sal.dom.store.impl.tree.data.InMemoryDataTreeFactory;
26 import org.opendaylight.controller.sal.core.spi.data.DOMStore;
27 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
28 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
29 import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
30 import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction;
31 import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
32 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
33 import org.opendaylight.yangtools.concepts.Identifiable;
34 import org.opendaylight.yangtools.concepts.ListenerRegistration;
35 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.common.base.Objects;
43 import com.google.common.base.Objects.ToStringHelper;
44 import com.google.common.base.Optional;
45 import com.google.common.base.Preconditions;
46 import com.google.common.util.concurrent.Futures;
47 import com.google.common.util.concurrent.ListenableFuture;
48 import com.google.common.util.concurrent.ListeningExecutorService;
49
50 public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, SchemaContextListener {
51     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDOMDataStore.class);
52     private final DataTree dataTree = InMemoryDataTreeFactory.getInstance().create();
53     private final ListenerTree listenerTree = ListenerTree.create();
54     private final AtomicLong txCounter = new AtomicLong(0);
55     private final ListeningExecutorService executor;
56     private final String name;
57
58     public InMemoryDOMDataStore(final String name, final ListeningExecutorService executor) {
59         this.name = Preconditions.checkNotNull(name);
60         this.executor = Preconditions.checkNotNull(executor);
61     }
62
63     @Override
64     public final String getIdentifier() {
65         return name;
66     }
67
68     @Override
69     public DOMStoreReadTransaction newReadOnlyTransaction() {
70         return new SnapshotBackedReadTransaction(nextIdentifier(), dataTree.takeSnapshot());
71     }
72
73     @Override
74     public DOMStoreReadWriteTransaction newReadWriteTransaction() {
75         return new SnapshotBackedReadWriteTransaction(nextIdentifier(), dataTree.takeSnapshot(), this);
76     }
77
78     @Override
79     public DOMStoreWriteTransaction newWriteOnlyTransaction() {
80         return new SnapshotBackedWriteTransaction(nextIdentifier(), dataTree.takeSnapshot(), this);
81     }
82
83     @Override
84     public synchronized void onGlobalContextUpdated(final SchemaContext ctx) {
85         dataTree.setSchemaContext(ctx);
86     }
87
88     @Override
89     public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> ListenerRegistration<L> registerChangeListener(
90             final InstanceIdentifier path, final L listener, final DataChangeScope scope) {
91
92         /*
93          * Make sure commit is not occurring right now. Listener has to be
94          * registered and its state capture enqueued at a consistent point.
95          *
96          * FIXME: improve this to read-write lock, such that multiple listener
97          * registrations can occur simultaneously
98          */
99         final DataChangeListenerRegistration<L> reg;
100         synchronized (this) {
101             LOG.debug("{}: Registering data change listener {} for {}", name, listener, path);
102
103             reg = listenerTree.registerDataChangeListener(path, listener, scope);
104
105             Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(path);
106             if (currentState.isPresent()) {
107                 final NormalizedNode<?, ?> data = currentState.get();
108
109                 final DOMImmutableDataChangeEvent event = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE) //
110                         .setAfter(data) //
111                         .addCreated(path, data) //
112                         .build();
113                 executor.submit(new ChangeListenerNotifyTask(Collections.singletonList(reg), event));
114             }
115         }
116
117         return new AbstractListenerRegistration<L>(listener) {
118             @Override
119             protected void removeRegistration() {
120                 synchronized (InMemoryDOMDataStore.this) {
121                     reg.close();
122                 }
123             }
124         };
125     }
126
127     private synchronized DOMStoreThreePhaseCommitCohort submit(final SnapshotBackedWriteTransaction writeTx) {
128         LOG.debug("Tx: {} is submitted. Modifications: {}", writeTx.getIdentifier(), writeTx.getMutatedView());
129         return new ThreePhaseCommitImpl(writeTx);
130     }
131
132     private Object nextIdentifier() {
133         return name + "-" + txCounter.getAndIncrement();
134     }
135
136     private static abstract class AbstractDOMStoreTransaction implements DOMStoreTransaction {
137         private final Object identifier;
138
139         protected AbstractDOMStoreTransaction(final Object identifier) {
140             this.identifier = identifier;
141         }
142
143         @Override
144         public final Object getIdentifier() {
145             return identifier;
146         }
147
148         @Override
149         public final String toString() {
150             return addToStringAttributes(Objects.toStringHelper(this)).toString();
151         }
152
153         /**
154          * Add class-specific toString attributes.
155          *
156          * @param toStringHelper
157          *            ToStringHelper instance
158          * @return ToStringHelper instance which was passed in
159          */
160         protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
161             return toStringHelper.add("id", identifier);
162         }
163     }
164
165     private static final class SnapshotBackedReadTransaction extends AbstractDOMStoreTransaction implements
166     DOMStoreReadTransaction {
167         private DataTreeSnapshot stableSnapshot;
168
169         public SnapshotBackedReadTransaction(final Object identifier, final DataTreeSnapshot snapshot) {
170             super(identifier);
171             this.stableSnapshot = Preconditions.checkNotNull(snapshot);
172             LOG.debug("ReadOnly Tx: {} allocated with snapshot {}", identifier, snapshot);
173         }
174
175         @Override
176         public void close() {
177             LOG.debug("Store transaction: {} : Closed", getIdentifier());
178             stableSnapshot = null;
179         }
180
181         @Override
182         public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
183             checkNotNull(path, "Path must not be null.");
184             checkState(stableSnapshot != null, "Transaction is closed");
185             return Futures.immediateFuture(stableSnapshot.readNode(path));
186         }
187     }
188
189     private static class SnapshotBackedWriteTransaction extends AbstractDOMStoreTransaction implements
190     DOMStoreWriteTransaction {
191         private DataTreeModification mutableTree;
192         private InMemoryDOMDataStore store;
193         private boolean ready = false;
194
195         public SnapshotBackedWriteTransaction(final Object identifier, final DataTreeSnapshot snapshot,
196                 final InMemoryDOMDataStore store) {
197             super(identifier);
198             mutableTree = snapshot.newModification();
199             this.store = store;
200             LOG.debug("Write Tx: {} allocated with snapshot {}", identifier, snapshot);
201         }
202
203         @Override
204         public void close() {
205             LOG.debug("Store transaction: {} : Closed", getIdentifier());
206             this.mutableTree = null;
207             this.store = null;
208         }
209
210         @Override
211         public void write(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
212             checkNotReady();
213             try {
214                 LOG.trace("Tx: {} Write: {}:{}", getIdentifier(), path, data);
215                 mutableTree.write(path, data);
216                 // FIXME: Add checked exception
217             } catch (Exception e) {
218                 LOG.error("Tx: {}, failed to write {}:{} in {}", getIdentifier(), path, data, mutableTree, e);
219             }
220         }
221
222         @Override
223         public void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
224             checkNotReady();
225             try {
226                 LOG.trace("Tx: {} Merge: {}:{}", getIdentifier(), path, data);
227                 mutableTree.merge(path, data);
228                 // FIXME: Add checked exception
229             } catch (Exception e) {
230                 LOG.error("Tx: {}, failed to write {}:{} in {}", getIdentifier(), path, data, mutableTree, e);
231             }
232         }
233
234         @Override
235         public void delete(final InstanceIdentifier path) {
236             checkNotReady();
237             try {
238                 LOG.trace("Tx: {} Delete: {}", getIdentifier(), path);
239                 mutableTree.delete(path);
240                 // FIXME: Add checked exception
241             } catch (Exception e) {
242                 LOG.error("Tx: {}, failed to delete {} in {}", getIdentifier(), path, mutableTree, e);
243             }
244         }
245
246         protected final boolean isReady() {
247             return ready;
248         }
249
250         protected final void checkNotReady() {
251             checkState(!ready, "Transaction %s is ready. No further modifications allowed.", getIdentifier());
252         }
253
254         @Override
255         public synchronized DOMStoreThreePhaseCommitCohort ready() {
256             checkState(!ready, "Transaction %s is already ready.", getIdentifier());
257             ready = true;
258
259             LOG.debug("Store transaction: {} : Ready", getIdentifier());
260             mutableTree.seal();
261             return store.submit(this);
262         }
263
264         protected DataTreeModification getMutatedView() {
265             return mutableTree;
266         }
267
268         @Override
269         protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
270             return toStringHelper.add("ready", isReady());
271         }
272     }
273
274     private static class SnapshotBackedReadWriteTransaction extends SnapshotBackedWriteTransaction implements
275     DOMStoreReadWriteTransaction {
276
277         protected SnapshotBackedReadWriteTransaction(final Object identifier, final DataTreeSnapshot snapshot,
278                 final InMemoryDOMDataStore store) {
279             super(identifier, snapshot, store);
280         }
281
282         @Override
283         public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
284             LOG.trace("Tx: {} Read: {}", getIdentifier(), path);
285             try {
286                 return Futures.immediateFuture(getMutatedView().readNode(path));
287             } catch (Exception e) {
288                 LOG.error("Tx: {} Failed Read of {}", getIdentifier(), path, e);
289                 throw e;
290             }
291         }
292     }
293
294     private class ThreePhaseCommitImpl implements DOMStoreThreePhaseCommitCohort {
295
296         private final SnapshotBackedWriteTransaction transaction;
297         private final DataTreeModification modification;
298
299         private ResolveDataChangeEventsTask listenerResolver;
300         private DataTreeCandidate candidate;
301
302         public ThreePhaseCommitImpl(final SnapshotBackedWriteTransaction writeTransaction) {
303             this.transaction = writeTransaction;
304             this.modification = transaction.getMutatedView();
305         }
306
307         @Override
308         public ListenableFuture<Boolean> canCommit() {
309             return executor.submit(new Callable<Boolean>() {
310                 @Override
311                 public Boolean call() {
312                     try {
313                         dataTree.validate(modification);
314                         LOG.debug("Store Transaction: {} can be committed", transaction.getIdentifier());
315                         return true;
316                     } catch (DataPreconditionFailedException e) {
317                         LOG.warn("Store Tx: {} Data Precondition failed for {}.",transaction.getIdentifier(),e.getPath(),e);
318                         return false;
319                     }
320                 }
321             });
322         }
323
324         @Override
325         public ListenableFuture<Void> preCommit() {
326             return executor.submit(new Callable<Void>() {
327                 @Override
328                 public Void call() {
329                     candidate = dataTree.prepare(modification);
330                     listenerResolver = ResolveDataChangeEventsTask.create(candidate, listenerTree);
331                     return null;
332                 }
333             });
334         }
335
336         @Override
337         public ListenableFuture<Void> abort() {
338             candidate = null;
339             return Futures.immediateFuture(null);
340         }
341
342         @Override
343         public ListenableFuture<Void> commit() {
344             checkState(candidate != null, "Proposed subtree must be computed");
345
346             /*
347              * The commit has to occur atomically with regard to listener
348              * registrations.
349              */
350             synchronized (this) {
351                 dataTree.commit(candidate);
352
353                 for (ChangeListenerNotifyTask task : listenerResolver.call()) {
354                     LOG.trace("Scheduling invocation of listeners: {}", task);
355                     executor.submit(task);
356                 }
357             }
358
359             return Futures.immediateFuture(null);
360         }
361     }
362 }