Deprecate Clustered(DOM)DataTreeChangeListener
[mdsal.git] / dom / mdsal-dom-spi / src / main / java / org / opendaylight / mdsal / dom / spi / store / AbstractDOMStoreTreeChangePublisher.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.mdsal.dom.spi.store;
9
10 import java.util.ArrayList;
11 import java.util.IdentityHashMap;
12 import java.util.List;
13 import java.util.Map;
14 import org.eclipse.jdt.annotation.NonNull;
15 import org.eclipse.jdt.annotation.NonNullByDefault;
16 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
17 import org.opendaylight.mdsal.dom.spi.AbstractRegistrationTree;
18 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
19 import org.opendaylight.yangtools.concepts.Registration;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
22 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
23 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
24 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
25 import org.opendaylight.yangtools.yang.data.tree.spi.DataTreeCandidates;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * Abstract base class for {@link DOMStoreTreeChangePublisher} implementations.
31  */
32 public abstract class AbstractDOMStoreTreeChangePublisher
33         extends AbstractRegistrationTree<AbstractDOMStoreTreeChangePublisher.RegImpl>
34         implements DOMStoreTreeChangePublisher {
35     /**
36      * A handle to a registered {@link DOMDataTreeChangeListener}. Implementations of this interface are guaranteed to
37      * use identity-based equality.
38      */
39     @NonNullByDefault
40     protected sealed interface Reg permits RegImpl {
41         /**
42          * Return the underlying listener.
43          *
44          * @return the underlying listener
45          */
46         DOMDataTreeChangeListener listener();
47
48         /**
49          * Check if this handle has not been closed yet.
50          *
51          * @return {@code true} if this handle is still open
52          */
53         boolean notClosed();
54     }
55
56     /**
57      * Registration handle for a {@link DOMDataTreeChangeListener}. This class is exposed to subclasses only as a
58      * convenience, so they can use its identity-based equality while at the same time having access to the listener.
59      *
60      * <p>
61      * Implementations must not invoke {@link #close()} nor should otherwise interact with the registration.
62      */
63     @NonNullByDefault
64     final class RegImpl extends AbstractObjectRegistration<DOMDataTreeChangeListener> implements Reg {
65         private RegImpl(final DOMDataTreeChangeListener instance) {
66             super(instance);
67         }
68
69         @Override
70         public DOMDataTreeChangeListener listener() {
71             return getInstance();
72         }
73
74         @Override
75         protected void removeRegistration() {
76             registrationRemoved(this);
77         }
78     }
79
80     private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMStoreTreeChangePublisher.class);
81
82     /**
83      * Callback for subclass to notify a specified registration of a list of candidates. This method is guaranteed
84      * to be only called from within {@link #processCandidateTree(DataTreeCandidate)}.
85      *
86      * @param registration the registration to notify
87      * @param changes the list of DataTreeCandidate changes
88      */
89     protected abstract void notifyListener(@NonNull Reg registration, @NonNull List<DataTreeCandidate> changes);
90
91     /**
92      * Callback notifying the subclass that the specified registration is being closed and it's user no longer wishes to
93      * receive notifications. This notification is invoked while the
94      * {@link org.opendaylight.yangtools.concepts.Registration#close()} method is executing. Subclasses can use this
95      * callback to properly remove any delayed notifications pending towards the registration.
96      *
97      * @param registration Registration which is being closed
98      */
99     protected abstract void registrationRemoved(@NonNull Reg registration);
100
101     /**
102      * Process a candidate tree with respect to registered listeners.
103      *
104      * @param candidate candidate three which needs to be processed
105      * @return true if at least one listener was notified or false.
106      */
107     protected final boolean processCandidateTree(final @NonNull DataTreeCandidate candidate) {
108         final var node = candidate.getRootNode();
109         if (node.modificationType() == ModificationType.UNMODIFIED) {
110             LOG.debug("Skipping unmodified candidate {}", candidate);
111             return false;
112         }
113
114         try (var snapshot = takeSnapshot()) {
115             final var toLookup = List.copyOf(candidate.getRootPath().getPathArguments());
116             final var listenerChanges = new IdentityHashMap<Reg, List<DataTreeCandidate>>();
117             lookupAndNotify(toLookup, 0, snapshot.getRootNode(), candidate, listenerChanges);
118
119             for (var entry : listenerChanges.entrySet()) {
120                 notifyListener(entry.getKey(), entry.getValue());
121             }
122
123             return !listenerChanges.isEmpty();
124         }
125     }
126
127     @Override
128     public final Registration registerTreeChangeListener(final YangInstanceIdentifier treeId,
129             final DOMDataTreeChangeListener listener) {
130         // Take the write lock
131         takeLock();
132         try {
133             final var reg = new RegImpl(listener);
134             addRegistration(findNodeFor(treeId.getPathArguments()), reg);
135             return reg;
136         } finally {
137             // Always release the lock
138             releaseLock();
139         }
140     }
141
142     /**
143      * {@inheritDoc}
144      *
145      * <p>
146      * This implementation calls {@link #registerTreeChangeListener(YangInstanceIdentifier, DOMDataTreeChangeListener)},
147      * override if necessary.
148      */
149     @Override
150     @Deprecated(since = "13.0.0", forRemoval = true)
151     public Registration registerLegacyTreeChangeListener(final YangInstanceIdentifier treeId,
152             final DOMDataTreeChangeListener listener) {
153         return registerTreeChangeListener(treeId, listener);
154     }
155
156     private void lookupAndNotify(final List<PathArgument> args, final int offset, final Node<RegImpl> node,
157             final DataTreeCandidate candidate, final Map<Reg, List<DataTreeCandidate>> listenerChanges) {
158         if (args.size() == offset) {
159             notifyNode(candidate.getRootPath(), node, candidate.getRootNode(), listenerChanges);
160             return;
161         }
162
163         final var arg = args.get(offset);
164         final var exactChild = node.getExactChild(arg);
165         if (exactChild != null) {
166             lookupAndNotify(args, offset + 1, exactChild, candidate, listenerChanges);
167         }
168         for (var child : node.getInexactChildren(arg)) {
169             lookupAndNotify(args, offset + 1, child, candidate, listenerChanges);
170         }
171     }
172
173     private void notifyNode(final YangInstanceIdentifier path, final Node<RegImpl> regNode,
174             final DataTreeCandidateNode candNode, final Map<Reg, List<DataTreeCandidate>> listenerChanges) {
175         if (candNode.modificationType() == ModificationType.UNMODIFIED) {
176             LOG.debug("Skipping unmodified candidate {}", path);
177             return;
178         }
179
180         final var regs = regNode.getRegistrations();
181         if (!regs.isEmpty()) {
182             final var dataTreeCandidate = DataTreeCandidates.newDataTreeCandidate(path, candNode);
183             for (var reg : regs) {
184                 listenerChanges.computeIfAbsent(reg, ignored -> new ArrayList<>()).add(dataTreeCandidate);
185             }
186         }
187
188         for (var candChild : candNode.childNodes()) {
189             if (candChild.modificationType() != ModificationType.UNMODIFIED) {
190                 final var regChild = regNode.getExactChild(candChild.name());
191                 if (regChild != null) {
192                     notifyNode(path.node(candChild.name()), regChild, candChild, listenerChanges);
193                 }
194
195                 for (var rc : regNode.getInexactChildren(candChild.name())) {
196                     notifyNode(path.node(candChild.name()), rc, candChild, listenerChanges);
197                 }
198             }
199         }
200     }
201 }