2 * Copyright (c) 2015 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.mdsal.dom.spi.store;
10 import java.util.ArrayList;
11 import java.util.IdentityHashMap;
12 import java.util.List;
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;
30 * Abstract base class for {@link DOMStoreTreeChangePublisher} implementations.
32 public abstract class AbstractDOMStoreTreeChangePublisher
33 extends AbstractRegistrationTree<AbstractDOMStoreTreeChangePublisher.RegImpl>
34 implements DOMStoreTreeChangePublisher {
36 * A handle to a registered {@link DOMDataTreeChangeListener}. Implementations of this interface are guaranteed to
37 * use identity-based equality.
40 protected sealed interface Reg permits RegImpl {
42 * Return the underlying listener.
44 * @return the underlying listener
46 DOMDataTreeChangeListener listener();
49 * Check if this handle has not been closed yet.
51 * @return {@code true} if this handle is still open
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.
61 * Implementations must not invoke {@link #close()} nor should otherwise interact with the registration.
64 final class RegImpl extends AbstractObjectRegistration<DOMDataTreeChangeListener> implements Reg {
65 private RegImpl(final DOMDataTreeChangeListener instance) {
70 public DOMDataTreeChangeListener listener() {
75 protected void removeRegistration() {
76 registrationRemoved(this);
80 private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMStoreTreeChangePublisher.class);
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)}.
86 * @param registration the registration to notify
87 * @param changes the list of DataTreeCandidate changes
89 protected abstract void notifyListener(@NonNull Reg registration, @NonNull List<DataTreeCandidate> changes);
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.
97 * @param registration Registration which is being closed
99 protected abstract void registrationRemoved(@NonNull Reg registration);
102 * Process a candidate tree with respect to registered listeners.
104 * @param candidate candidate three which needs to be processed
105 * @return true if at least one listener was notified or false.
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);
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);
119 for (var entry : listenerChanges.entrySet()) {
120 notifyListener(entry.getKey(), entry.getValue());
123 return !listenerChanges.isEmpty();
128 public final Registration registerTreeChangeListener(final YangInstanceIdentifier treeId,
129 final DOMDataTreeChangeListener listener) {
130 // Take the write lock
133 final var reg = new RegImpl(listener);
134 addRegistration(findNodeFor(treeId.getPathArguments()), reg);
137 // Always release the lock
146 * This implementation calls {@link #registerTreeChangeListener(YangInstanceIdentifier, DOMDataTreeChangeListener)},
147 * override if necessary.
150 @Deprecated(since = "13.0.0", forRemoval = true)
151 public Registration registerLegacyTreeChangeListener(final YangInstanceIdentifier treeId,
152 final DOMDataTreeChangeListener listener) {
153 return registerTreeChangeListener(treeId, listener);
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);
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);
168 for (var child : node.getInexactChildren(arg)) {
169 lookupAndNotify(args, offset + 1, child, candidate, listenerChanges);
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);
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);
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);
195 for (var rc : regNode.getInexactChildren(candChild.name())) {
196 notifyNode(path.node(candChild.name()), rc, candChild, listenerChanges);