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.store.AbstractDOMStoreTreeChangePublisher;
28 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
29 import org.opendaylight.yangtools.concepts.ListenerRegistration;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
37 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
38 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
42 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
43 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
44 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
45 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
49 abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher implements DOMStoreTreeChangePublisher {
51 private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMShardTreeChangePublisher.class);
53 private final YangInstanceIdentifier shardPath;
54 private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards;
55 private final DataTree dataTree;
57 protected AbstractDOMShardTreeChangePublisher(final DataTree dataTree,
58 final YangInstanceIdentifier shardPath,
59 final Map<DOMDataTreeIdentifier, ChildShardContext> childShards) {
60 this.dataTree = Preconditions.checkNotNull(dataTree);
61 this.shardPath = Preconditions.checkNotNull(shardPath);
62 this.childShards = Preconditions.checkNotNull(childShards);
66 public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
69 return setupListenerContext(path, listener);
75 private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
76 // we need to register the listener registration path based on the shards root
77 // we have to strip the shard path from the listener path and then register
78 YangInstanceIdentifier strippedIdentifier = listenerPath;
79 if (!shardPath.isEmpty()) {
80 strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(listenerPath));
83 final DOMDataTreeListenerWithSubshards subshardListener = new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
84 final AbstractDOMDataTreeChangeListenerRegistration<L> reg = setupContextWithoutSubshards(strippedIdentifier, subshardListener);
86 for (final ChildShardContext maybeAffected : childShards.values()) {
87 if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
88 // consumer has initialDataChangeEvent subshard somewhere on lower level
89 // register to the notification manager with snapshot and forward child notifications to parent
90 LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
91 subshardListener.addSubshard(maybeAffected);
92 } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
93 // bind path is inside subshard
94 // TODO can this happen? seems like in ShardedDOMDataTree we are already registering to the lowest shard possible
95 throw new UnsupportedOperationException("Listener should be registered directly into initialDataChangeEvent subshard");
99 initialDataChangeEvent(listenerPath, listener);
104 private <L extends DOMDataTreeChangeListener> void initialDataChangeEvent(final YangInstanceIdentifier listenerPath, final L listener) {
105 // FIXME Add support for wildcard listeners
106 final Optional<NormalizedNode<?, ?>> preExistingData = dataTree.takeSnapshot()
107 .readNode(YangInstanceIdentifier.create(stripShardPath(listenerPath)));
108 final DataTreeCandidate initialCandidate;
110 if (preExistingData.isPresent()) {
111 final NormalizedNode<?, ?> data = preExistingData.get();
112 Preconditions.checkState(data instanceof DataContainerNode,
113 "Expected DataContainer node, but was {}", data.getClass());
114 // if we are listening on root of some shard we still get
115 // empty normalized node, root is always present
116 if (((DataContainerNode) data).getValue().isEmpty()) {
117 initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
118 new EmptyDataTreeCandidateNode(data.getIdentifier()));
120 initialCandidate = DataTreeCandidates.fromNormalizedNode(listenerPath,
121 translateRootShardIdentifierToListenerPath(listenerPath, preExistingData.get()));
124 initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
125 new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument()));
128 listener.onDataTreeChanged(Collections.singleton(initialCandidate));
131 private NormalizedNode<?, ?> translateRootShardIdentifierToListenerPath(final YangInstanceIdentifier listenerPath,
132 final NormalizedNode<?, ?> node) {
133 if (listenerPath.isEmpty()) {
137 final NormalizedNodeBuilder nodeBuilder;
138 if (node instanceof ContainerNode) {
139 nodeBuilder = ImmutableContainerNodeBuilder.create().withValue(((ContainerNode) node).getValue());
140 } else if (node instanceof MapEntryNode) {
141 nodeBuilder = ImmutableMapEntryNodeBuilder.create().withValue(((MapEntryNode) node).getValue());
143 throw new IllegalArgumentException("Expected ContainerNode or MapEntryNode, but was " + node.getClass());
145 nodeBuilder.withNodeIdentifier(listenerPath.getLastPathArgument());
146 return nodeBuilder.build();
149 private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath, final DOMDataTreeListenerWithSubshards listener) {
150 LOG.debug("Registering root listener at {}", listenerPath);
151 final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node = findNodeFor(listenerPath.getPathArguments());
152 final AbstractDOMDataTreeChangeListenerRegistration<L> registration = new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
154 protected void removeRegistration() {
156 AbstractDOMShardTreeChangePublisher.this.removeRegistration(node, this);
157 registrationRemoved(this);
160 addRegistration(node, registration);
164 private Iterable<PathArgument> stripShardPath(final YangInstanceIdentifier listenerPath) {
165 if (shardPath.isEmpty()) {
166 return listenerPath.getPathArguments();
169 final List<PathArgument> listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
170 final Iterator<PathArgument> shardIter = shardPath.getPathArguments().iterator();
171 final Iterator<PathArgument> listenerIter = listenerPathArgs.iterator();
173 while (shardIter.hasNext()) {
174 if (shardIter.next().equals(listenerIter.next())) {
175 listenerIter.remove();
181 return listenerPathArgs;
184 private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
186 private final DataTree dataTree;
187 private final YangInstanceIdentifier listenerPath;
188 private final DOMDataTreeChangeListener delegate;
190 private final Map<YangInstanceIdentifier, ListenerRegistration<DOMDataTreeChangeListener>> registrations =
193 DOMDataTreeListenerWithSubshards(final DataTree dataTree,
194 final YangInstanceIdentifier listenerPath,
195 final DOMDataTreeChangeListener delegate) {
196 this.dataTree = Preconditions.checkNotNull(dataTree);
197 this.listenerPath = Preconditions.checkNotNull(listenerPath);
198 this.delegate = Preconditions.checkNotNull(delegate);
202 public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
203 LOG.debug("Received data changed {}", changes.iterator().next());
204 delegate.onDataTreeChanged(changes);
207 void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
208 final List<DataTreeCandidate> newCandidates = changes.stream()
209 .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
210 .collect(Collectors.toList());
211 delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
214 void addSubshard(final ChildShardContext context) {
215 Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
216 "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");
218 final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
219 // since this is going into subshard we want to listen for ALL changes in the subshard
220 registrations.put(context.getPrefix().getRootIdentifier(),
221 listenableShard.registerTreeChangeListener(context.getPrefix().getRootIdentifier(),
222 changes -> onDataTreeChanged(context.getPrefix().getRootIdentifier(), changes)));
226 for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
227 registration.close();
229 registrations.clear();
232 private DataTreeCandidate applyChanges(final Collection<DataTreeCandidate> changes) {
233 final DataTreeModification modification = dataTree.takeSnapshot().newModification();
234 for (final DataTreeCandidate change : changes) {
235 DataTreeCandidates.applyToModification(modification, change);
238 modification.ready();
240 dataTree.validate(modification);
241 } catch (final DataValidationFailedException e) {
242 LOG.error("Validation failed for built modification", e);
243 throw new RuntimeException("Notification validation failed", e);
246 // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
247 DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
248 for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
249 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
252 if (modifiedChild == null) {
253 modifiedChild = new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument());
256 return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
260 private static final class EmptyDataTreeCandidateNode implements DataTreeCandidateNode {
262 private final PathArgument identifier;
264 EmptyDataTreeCandidateNode(final PathArgument identifier) {
265 Preconditions.checkNotNull(identifier, "Identifier should not be null");
266 this.identifier = identifier;
271 public PathArgument getIdentifier() {
277 public Collection<DataTreeCandidateNode> getChildNodes() {
278 return Collections.<DataTreeCandidateNode>emptySet();
283 public DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
289 public ModificationType getModificationType() {
290 return ModificationType.UNMODIFIED;
295 public Optional<NormalizedNode<?, ?>> getDataAfter() {
296 return Optional.absent();
301 public Optional<NormalizedNode<?, ?>> getDataBefore() {
302 return Optional.absent();