2 * Copyright (c) 2016 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
9 package org.opendaylight.mdsal.dom.store.inmemory;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
20 import java.util.stream.Collectors;
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
24 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
25 import org.opendaylight.mdsal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
26 import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
27 import org.opendaylight.mdsal.dom.spi.shard.ChildShardContext;
28 import org.opendaylight.mdsal.dom.spi.store.AbstractDOMStoreTreeChangePublisher;
29 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
30 import org.opendaylight.yangtools.concepts.ListenerRegistration;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
38 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
42 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
43 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
44 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
45 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
46 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
50 abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher
51 implements DOMStoreTreeChangePublisher {
53 private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMShardTreeChangePublisher.class);
55 private final YangInstanceIdentifier shardPath;
56 private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards;
57 private final DataTree dataTree;
59 protected AbstractDOMShardTreeChangePublisher(final DataTree dataTree,
60 final YangInstanceIdentifier shardPath,
61 final Map<DOMDataTreeIdentifier, ChildShardContext> childShards) {
62 this.dataTree = Preconditions.checkNotNull(dataTree);
63 this.shardPath = Preconditions.checkNotNull(shardPath);
64 this.childShards = Preconditions.checkNotNull(childShards);
68 public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L>
69 registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
72 return setupListenerContext(path, listener);
78 private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L>
79 setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
80 // we need to register the listener registration path based on the shards root
81 // we have to strip the shard path from the listener path and then register
82 YangInstanceIdentifier strippedIdentifier = listenerPath;
83 if (!shardPath.isEmpty()) {
84 strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(listenerPath));
87 final DOMDataTreeListenerWithSubshards subshardListener =
88 new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
89 final AbstractDOMDataTreeChangeListenerRegistration<L> reg =
90 setupContextWithoutSubshards(strippedIdentifier, subshardListener);
92 for (final ChildShardContext maybeAffected : childShards.values()) {
93 if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
94 // consumer has initialDataChangeEvent subshard somewhere on lower level
95 // register to the notification manager with snapshot and forward child notifications to parent
96 LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
97 subshardListener.addSubshard(maybeAffected);
98 } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
99 // bind path is inside subshard
100 // TODO can this happen? seems like in ShardedDOMDataTree we are
101 // already registering to the lowest shard possible
102 throw new UnsupportedOperationException("Listener should be registered directly "
103 + "into initialDataChangeEvent subshard");
107 initialDataChangeEvent(listenerPath, listener);
112 private <L extends DOMDataTreeChangeListener> void initialDataChangeEvent(
113 final YangInstanceIdentifier listenerPath, final L listener) {
114 // FIXME Add support for wildcard listeners
115 final Optional<NormalizedNode<?, ?>> preExistingData = dataTree.takeSnapshot()
116 .readNode(YangInstanceIdentifier.create(stripShardPath(listenerPath)));
117 final DataTreeCandidate initialCandidate;
119 if (preExistingData.isPresent()) {
120 final NormalizedNode<?, ?> data = preExistingData.get();
121 Preconditions.checkState(data instanceof DataContainerNode,
122 "Expected DataContainer node, but was {}", data.getClass());
123 // if we are listening on root of some shard we still get
124 // empty normalized node, root is always present
125 if (((DataContainerNode<?>) data).getValue().isEmpty()) {
126 initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
127 new EmptyDataTreeCandidateNode(data.getIdentifier()));
129 initialCandidate = DataTreeCandidates.fromNormalizedNode(listenerPath,
130 translateRootShardIdentifierToListenerPath(listenerPath, preExistingData.get()));
133 initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
134 new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument()));
137 listener.onDataTreeChanged(Collections.singleton(initialCandidate));
140 private static NormalizedNode<?, ?> translateRootShardIdentifierToListenerPath(
141 final YangInstanceIdentifier listenerPath, final NormalizedNode<?, ?> node) {
142 if (listenerPath.isEmpty()) {
146 final NormalizedNodeBuilder nodeBuilder;
147 if (node instanceof ContainerNode) {
148 nodeBuilder = ImmutableContainerNodeBuilder.create().withValue(((ContainerNode) node).getValue());
149 } else if (node instanceof MapEntryNode) {
150 nodeBuilder = ImmutableMapEntryNodeBuilder.create().withValue(((MapEntryNode) node).getValue());
152 throw new IllegalArgumentException("Expected ContainerNode or MapEntryNode, but was " + node.getClass());
154 nodeBuilder.withNodeIdentifier(listenerPath.getLastPathArgument());
155 return nodeBuilder.build();
158 private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L>
159 setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath,
160 final DOMDataTreeListenerWithSubshards listener) {
161 LOG.debug("Registering root listener at {}", listenerPath);
162 final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node =
163 findNodeFor(listenerPath.getPathArguments());
164 @SuppressWarnings("unchecked")
165 final AbstractDOMDataTreeChangeListenerRegistration<L> registration =
166 new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
168 protected void removeRegistration() {
170 AbstractDOMShardTreeChangePublisher.this.removeRegistration(node, this);
171 registrationRemoved(this);
174 addRegistration(node, registration);
178 private Iterable<PathArgument> stripShardPath(final YangInstanceIdentifier listenerPath) {
179 if (shardPath.isEmpty()) {
180 return listenerPath.getPathArguments();
183 final List<PathArgument> listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
184 final Iterator<PathArgument> shardIter = shardPath.getPathArguments().iterator();
185 final Iterator<PathArgument> listenerIter = listenerPathArgs.iterator();
187 while (shardIter.hasNext()) {
188 if (shardIter.next().equals(listenerIter.next())) {
189 listenerIter.remove();
195 return listenerPathArgs;
198 private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
200 private final DataTree dataTree;
201 private final YangInstanceIdentifier listenerPath;
202 private final DOMDataTreeChangeListener delegate;
204 private final Map<YangInstanceIdentifier, ListenerRegistration<DOMDataTreeChangeListener>> registrations =
207 DOMDataTreeListenerWithSubshards(final DataTree dataTree,
208 final YangInstanceIdentifier listenerPath,
209 final DOMDataTreeChangeListener delegate) {
210 this.dataTree = Preconditions.checkNotNull(dataTree);
211 this.listenerPath = Preconditions.checkNotNull(listenerPath);
212 this.delegate = Preconditions.checkNotNull(delegate);
216 public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
217 LOG.debug("Received data changed {}", changes);
218 delegate.onDataTreeChanged(changes);
221 void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
222 final List<DataTreeCandidate> newCandidates = changes.stream()
223 .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
224 .collect(Collectors.toList());
225 delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
228 void addSubshard(final ChildShardContext context) {
229 Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
230 "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");
232 final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
233 // since this is going into subshard we want to listen for ALL changes in the subshard
234 registrations.put(context.getPrefix().getRootIdentifier(),
235 listenableShard.registerTreeChangeListener(
236 context.getPrefix().getRootIdentifier(), changes -> onDataTreeChanged(
237 context.getPrefix().getRootIdentifier(), changes)));
241 for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
242 registration.close();
244 registrations.clear();
247 private DataTreeCandidate applyChanges(final Collection<DataTreeCandidate> changes) {
248 final DataTreeModification modification = dataTree.takeSnapshot().newModification();
249 for (final DataTreeCandidate change : changes) {
250 DataTreeCandidates.applyToModification(modification, change);
253 modification.ready();
255 dataTree.validate(modification);
256 } catch (final DataValidationFailedException e) {
257 LOG.error("Validation failed for built modification", e);
258 throw new RuntimeException("Notification validation failed", e);
261 // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
262 DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
263 for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
264 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
267 if (modifiedChild == null) {
268 modifiedChild = new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument());
271 return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
275 private static final class EmptyDataTreeCandidateNode implements DataTreeCandidateNode {
277 private final PathArgument identifier;
279 EmptyDataTreeCandidateNode(final PathArgument identifier) {
280 this.identifier = Preconditions.checkNotNull(identifier, "Identifier should not be null");
285 public PathArgument getIdentifier() {
291 public Collection<DataTreeCandidateNode> getChildNodes() {
292 return Collections.emptySet();
297 public DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
303 public ModificationType getModificationType() {
304 return ModificationType.UNMODIFIED;
309 public Optional<NormalizedNode<?, ?>> getDataAfter() {
310 return Optional.absent();
315 public Optional<NormalizedNode<?, ?>> getDataBefore() {
316 return Optional.absent();