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.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
34 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
35 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
37 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
38 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher implements DOMStoreTreeChangePublisher {
45 private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMShardTreeChangePublisher.class);
47 private final YangInstanceIdentifier shardPath;
48 private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards;
49 private final DataTree dataTree;
51 protected AbstractDOMShardTreeChangePublisher(final DataTree dataTree,
52 final YangInstanceIdentifier shardPath,
53 final Map<DOMDataTreeIdentifier, ChildShardContext> childShards) {
54 this.dataTree = Preconditions.checkNotNull(dataTree);
55 this.shardPath = Preconditions.checkNotNull(shardPath);
56 this.childShards = Preconditions.checkNotNull(childShards);
60 public <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
63 return setupListenerContext(path, listener);
69 private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
70 // we need to register the listener registration path based on the shards root
71 // we have to strip the shard path from the listener path and then register
72 YangInstanceIdentifier strippedIdentifier = listenerPath;
73 if (!shardPath.isEmpty()) {
74 strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(listenerPath));
77 final DOMDataTreeListenerWithSubshards subshardListener = new DOMDataTreeListenerWithSubshards(dataTree, strippedIdentifier, listener);
78 final AbstractDOMDataTreeChangeListenerRegistration<L> reg = setupContextWithoutSubshards(strippedIdentifier, subshardListener);
80 for (final ChildShardContext maybeAffected : childShards.values()) {
81 if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
82 // consumer has initialDataChangeEvent subshard somewhere on lower level
83 // register to the notification manager with snapshot and forward child notifications to parent
84 LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
85 subshardListener.addSubshard(maybeAffected);
86 } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
87 // bind path is inside subshard
88 // TODO can this happen? seems like in ShardedDOMDataTree we are already registering to the lowest shard possible
89 throw new UnsupportedOperationException("Listener should be registered directly into initialDataChangeEvent subshard");
93 initialDataChangeEvent(listenerPath, listener);
98 private <L extends DOMDataTreeChangeListener> void initialDataChangeEvent(final YangInstanceIdentifier listenerPath, final L listener) {
99 // FIXME Add support for wildcard listeners
100 final Optional<NormalizedNode<?, ?>> preExistingData = dataTree.takeSnapshot().readNode(listenerPath);
101 final DataTreeCandidate initialCandidate;
102 if (preExistingData.isPresent()) {
103 initialCandidate = DataTreeCandidates.fromNormalizedNode(listenerPath, preExistingData.get());
105 initialCandidate = DataTreeCandidates.newDataTreeCandidate(listenerPath,
106 new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument()));
109 listener.onDataTreeChanged(Collections.singleton(initialCandidate));
112 private <L extends DOMDataTreeChangeListener> AbstractDOMDataTreeChangeListenerRegistration<L> setupContextWithoutSubshards(final YangInstanceIdentifier listenerPath, final DOMDataTreeListenerWithSubshards listener) {
113 LOG.debug("Registering root listener at {}", listenerPath);
114 final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node = findNodeFor(listenerPath.getPathArguments());
115 final AbstractDOMDataTreeChangeListenerRegistration<L> registration = new AbstractDOMDataTreeChangeListenerRegistration<L>((L) listener) {
117 protected void removeRegistration() {
119 AbstractDOMShardTreeChangePublisher.this.removeRegistration(node, this);
120 registrationRemoved(this);
123 addRegistration(node, registration);
127 private Iterable<PathArgument> stripShardPath(final YangInstanceIdentifier listenerPath) {
128 if (shardPath.isEmpty()) {
129 return listenerPath.getPathArguments();
132 final List<PathArgument> listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
133 final Iterator<PathArgument> shardIter = shardPath.getPathArguments().iterator();
134 final Iterator<PathArgument> listenerIter = listenerPathArgs.iterator();
136 while (shardIter.hasNext()) {
137 if (shardIter.next().equals(listenerIter.next())) {
138 listenerIter.remove();
144 return listenerPathArgs;
147 private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
149 private final DataTree dataTree;
150 private final YangInstanceIdentifier listenerPath;
151 private final DOMDataTreeChangeListener delegate;
153 private final Map<YangInstanceIdentifier, ListenerRegistration<DOMDataTreeChangeListener>> registrations =
156 DOMDataTreeListenerWithSubshards(final DataTree dataTree,
157 final YangInstanceIdentifier listenerPath,
158 final DOMDataTreeChangeListener delegate) {
159 this.dataTree = Preconditions.checkNotNull(dataTree);
160 this.listenerPath = Preconditions.checkNotNull(listenerPath);
161 this.delegate = Preconditions.checkNotNull(delegate);
165 public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
166 LOG.debug("Received data changed {}", changes.iterator().next());
167 delegate.onDataTreeChanged(changes);
170 void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
171 final List<DataTreeCandidate> newCandidates = changes.stream()
172 .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
173 .collect(Collectors.toList());
174 delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
177 void addSubshard(final ChildShardContext context) {
178 Preconditions.checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
179 "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");
181 final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
182 // since this is going into subshard we want to listen for ALL changes in the subshard
183 registrations.put(context.getPrefix().getRootIdentifier(),
184 listenableShard.registerTreeChangeListener(context.getPrefix().getRootIdentifier(),
185 changes -> onDataTreeChanged(context.getPrefix().getRootIdentifier(), changes)));
189 for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
190 registration.close();
192 registrations.clear();
195 private DataTreeCandidate applyChanges(final Collection<DataTreeCandidate> changes) {
196 final DataTreeModification modification = dataTree.takeSnapshot().newModification();
197 for (final DataTreeCandidate change : changes) {
198 DataTreeCandidates.applyToModification(modification, change);
201 modification.ready();
203 dataTree.validate(modification);
204 } catch (final DataValidationFailedException e) {
205 LOG.error("Validation failed for built modification", e);
206 throw new RuntimeException("Notification validation failed", e);
209 // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
210 DataTreeCandidateNode modifiedChild = dataTree.prepare(modification).getRootNode();
211 for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
212 modifiedChild = modifiedChild.getModifiedChild(pathArgument);
215 if (modifiedChild == null) {
216 modifiedChild = new EmptyDataTreeCandidateNode(listenerPath.getLastPathArgument());
219 return DataTreeCandidates.newDataTreeCandidate(listenerPath, modifiedChild);
223 private static final class EmptyDataTreeCandidateNode implements DataTreeCandidateNode {
225 private final PathArgument identifier;
227 EmptyDataTreeCandidateNode(final PathArgument identifier) {
228 Preconditions.checkNotNull(identifier, "Identifier should not be null");
229 this.identifier = identifier;
234 public PathArgument getIdentifier() {
240 public Collection<DataTreeCandidateNode> getChildNodes() {
241 return Collections.<DataTreeCandidateNode>emptySet();
246 public DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
252 public ModificationType getModificationType() {
253 return ModificationType.UNMODIFIED;
258 public Optional<NormalizedNode<?, ?>> getDataAfter() {
259 return Optional.absent();
264 public Optional<NormalizedNode<?, ?>> getDataBefore() {
265 return Optional.absent();