private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(final Feature feature) throws Exception, InterruptedException {
LinkedHashSet<FeatureConfigSnapshotHolder> configs = new LinkedHashSet<FeatureConfigSnapshotHolder>();
if(isInstalled(feature)) {
- ChildAwareFeatureWrapper wrappedFeature = new ChildAwareFeatureWrapper(feature,featuresService);
- configs = wrappedFeature.getFeatureConfigSnapshotHolders();
- if(!configs.isEmpty()) {
- configs = pushConfig(configs);
- feature2configs.putAll(feature, configs);
+ // FIXME Workaround for BUG-2836, features service returns null for feature: standard-condition-webconsole_0_0_0, 3.0.1
+ if(featuresService.getFeature(feature.getName(), feature.getVersion()) == null) {
+ LOG.warn("Feature: {}, {} is missing from features service. Skipping", feature.getName(), feature.getVersion());
+ } else {
+ ChildAwareFeatureWrapper wrappedFeature = new ChildAwareFeatureWrapper(feature, featuresService);
+ configs = wrappedFeature.getFeatureConfigSnapshotHolders();
+ if (!configs.isEmpty()) {
+ configs = pushConfig(configs);
+ feature2configs.putAll(feature, configs);
+ }
}
}
return configs;
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.spi;
+
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
+import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
+
+/**
+ * Abstract implementation of a ListenerRegistration constrained to subclasses
+ * of {@link DOMDataTreeChangeListener}.
+ *
+ * @param <T> type of listener
+ */
+public abstract class AbstractDOMDataTreeChangeListenerRegistration<T extends DOMDataTreeChangeListener> extends AbstractListenerRegistration<T> {
+ protected AbstractDOMDataTreeChangeListenerRegistration(final T listener) {
+ super(listener);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.spi;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+
+/**
+ * An abstract tree of registrations. Allows a read-only snapshot to be taken.
+ *
+ * @param <T> Type of registered object
+ */
+public abstract class AbstractRegistrationTree<T> {
+ private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
+ private final RegistrationTreeNode<T> rootNode = new RegistrationTreeNode<>(null, null);
+
+ protected AbstractRegistrationTree() {
+
+ }
+
+ /**
+ * Acquire the read-write lock. This should be done before invoking {@link #findNodeFor(Iterable)}.
+ */
+ protected final void takeLock() {
+ rwLock.writeLock().lock();
+ }
+
+ /**
+ * Release the read-write lock. This should be done after invocation of {@link #findNodeFor(Iterable)}
+ * and modification of the returned node. Note that callers should do so in a finally block.
+ */
+ protected final void releaseLock() {
+ rwLock.writeLock().unlock();
+ }
+
+ /**
+ * Find an existing, or allocate a fresh, node for a particular path. Must be called with the
+ * read-write lock held.
+ *
+ * @param path Path to find a node for
+ * @return A registration node for the specified path
+ */
+ @Nonnull protected final RegistrationTreeNode<T> findNodeFor(@Nonnull final Iterable<PathArgument> path) {
+ RegistrationTreeNode<T> walkNode = rootNode;
+ for (final PathArgument arg : path) {
+ walkNode = walkNode.ensureChild(arg);
+ }
+
+ return walkNode;
+ }
+
+ /**
+ * Add a registration to a particular node. The node must have been returned via {@link #findNodeFor(Iterable)}
+ * and the lock must still be held.
+ *
+ * @param node Tree node
+ * @param registration Registration instance
+ */
+ protected final void addRegistration(@Nonnull final RegistrationTreeNode<T> node, @Nonnull final T registration) {
+ node.addRegistration(registration);
+ }
+
+ /**
+ * Remove a registration from a particular node. This method must not be called while the read-write lock
+ * is held.
+ *
+ * @param node Tree node
+ * @param registration Registration instance
+ */
+ protected final void removeRegistration(@Nonnull final RegistrationTreeNode<T> node, @Nonnull final T registration) {
+ // Take the write lock
+ rwLock.writeLock().lock();
+ try {
+ node.removeRegistration(registration);
+ } finally {
+ // Always release the lock
+ rwLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Obtain a tree snapshot. This snapshot ensures a consistent view of
+ * registrations. The snapshot should be closed as soon as it is not required,
+ * because each unclosed instance blocks modification of this tree.
+ *
+ * @return A snapshot instance.
+ */
+ @Nonnull public final RegistrationTreeSnapshot<T> takeSnapshot() {
+ final RegistrationTreeSnapshot<T> ret = new RegistrationTreeSnapshot<>(rwLock.readLock(), rootNode);
+ rwLock.readLock().lock();
+ return ret;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.spi;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a single node within the registration tree. Note that the data returned from
+ * and instance of this class is guaranteed to have any relevance or consistency
+ * only as long as the {@link RegistrationTreeSnapshot} instance through which it is reached
+ * remains unclosed.
+ *
+ * @param <T> registration type
+ * @author Robert Varga
+ */
+public final class RegistrationTreeNode<T> implements Identifiable<PathArgument> {
+ private static final Logger LOG = LoggerFactory.getLogger(RegistrationTreeNode.class);
+
+ private final Map<PathArgument, RegistrationTreeNode<T>> children = new HashMap<>();
+ private final Collection<T> registrations = new ArrayList<>(2);
+ private final Collection<T> publicRegistrations = Collections.unmodifiableCollection(registrations);
+ private final Reference<RegistrationTreeNode<T>> parent;
+ private final PathArgument identifier;
+
+ RegistrationTreeNode(final RegistrationTreeNode<T> parent, final PathArgument identifier) {
+ this.parent = new WeakReference<>(parent);
+ this.identifier = identifier;
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ return identifier;
+ }
+
+ /**
+ * Return the child matching a {@link PathArgument} specification.
+ *
+ * @param arg Child identifier
+ * @return Child matching exactly, or null.
+ */
+ public RegistrationTreeNode<T> getExactChild(@Nonnull final PathArgument arg) {
+ return children.get(Preconditions.checkNotNull(arg));
+ }
+
+ /**
+ * Return a collection children which match a {@link PathArgument} specification inexactly.
+ * This explicitly excludes the child returned by {@link #getExactChild(PathArgument)}.
+ *
+ * @param arg Child identifier
+ * @return Collection of children, guaranteed to be non-null.
+ */
+ public @Nonnull Collection<RegistrationTreeNode<T>> getInexactChildren(@Nonnull final PathArgument arg) {
+ Preconditions.checkNotNull(arg);
+ if (arg instanceof NodeWithValue || arg instanceof NodeIdentifierWithPredicates) {
+ /*
+ * TODO: This just all-or-nothing wildcards, which we have historically supported. Given
+ * that the argument is supposed to have all the elements filled out, we could support
+ * partial wildcards by iterating over the registrations and matching the maps for
+ * partial matches.
+ */
+ final RegistrationTreeNode<T> child = children.get(new NodeIdentifier(arg.getNodeType()));
+ if (child == null) {
+ return Collections.emptyList();
+ } else {
+ return Collections.singletonList(child);
+ }
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ public Collection<T> getRegistrations() {
+ return publicRegistrations;
+ }
+
+ RegistrationTreeNode<T> ensureChild(@Nonnull final PathArgument child) {
+ RegistrationTreeNode<T> potential = children.get(Preconditions.checkNotNull(child));
+ if (potential == null) {
+ potential = new RegistrationTreeNode<T>(this, child);
+ children.put(child, potential);
+ }
+ return potential;
+ }
+
+ void addRegistration(@Nonnull final T registration) {
+ registrations.add(Preconditions.checkNotNull(registration));
+ LOG.debug("Registration {} added", registration);
+ }
+
+ void removeRegistration(@Nonnull final T registration) {
+ registrations.remove(Preconditions.checkNotNull(registration));
+ LOG.debug("Registration {} removed", registration);
+
+ // We have been called with the write-lock held, so we can perform some cleanup.
+ removeThisIfUnused();
+ }
+
+ private void removeThisIfUnused() {
+ final RegistrationTreeNode<T> p = parent.get();
+ if (p != null && registrations.isEmpty() && children.isEmpty()) {
+ p.removeChild(identifier);
+ }
+ }
+
+ private void removeChild(final PathArgument arg) {
+ children.remove(arg);
+ removeThisIfUnused();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("identifier", identifier)
+ .add("registrations", registrations.size())
+ .add("children", children.size()).toString();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.spi;
+
+import com.google.common.base.Preconditions;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * A stable read-only snapshot of a {@link AbstractRegistrationTree}.
+ *
+ * @author Robert Varga
+ */
+public final class RegistrationTreeSnapshot<T> implements AutoCloseable {
+ @SuppressWarnings("rawtypes")
+ private static final AtomicIntegerFieldUpdater<RegistrationTreeSnapshot> CLOSED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(RegistrationTreeSnapshot.class, "closed");
+ private final RegistrationTreeNode<T> node;
+ private final Lock lock;
+
+ // Used via CLOSED_UPDATER
+ @SuppressWarnings("unused")
+ private volatile int closed = 0;
+
+ RegistrationTreeSnapshot(final Lock lock, final RegistrationTreeNode<T> node) {
+ this.lock = Preconditions.checkNotNull(lock);
+ this.node = Preconditions.checkNotNull(node);
+ }
+
+ public RegistrationTreeNode<T> getRootNode() {
+ return node;
+ }
+
+ @Override
+ public void close() {
+ if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
+ lock.unlock();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.sal.core.spi;
+
+import java.util.concurrent.Future;
+import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
+import org.opendaylight.controller.sal.core.api.BrokerService;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+
+public abstract class ForwardingConsumerSession implements ConsumerSession {
+
+ protected abstract ConsumerSession delegate();
+
+ @Override
+ public void close() {
+ delegate().close();
+ }
+
+ @Override
+ public <T extends BrokerService> T getService(Class<T> arg0) {
+ return delegate().getService(arg0);
+ }
+
+ @Override
+ public boolean isClosed() {
+ return delegate().isClosed();
+ }
+
+ @Override
+ @Deprecated
+ public Future<RpcResult<CompositeNode>> rpc(QName arg0, CompositeNode arg1) {
+ return delegate().rpc(arg0, arg1);
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.sal.core.spi;
+
+import java.util.Set;
+import java.util.concurrent.Future;
+import org.opendaylight.controller.sal.core.api.Broker.ProviderSession;
+import org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration;
+import org.opendaylight.controller.sal.core.api.Broker.RpcRegistration;
+import org.opendaylight.controller.sal.core.api.BrokerService;
+import org.opendaylight.controller.sal.core.api.RpcImplementation;
+import org.opendaylight.controller.sal.core.api.RpcRegistrationListener;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+
+public abstract class ForwardingProviderSession implements ProviderSession {
+
+
+ protected abstract ProviderSession delegate();
+
+ @Override
+ @Deprecated
+ public RoutedRpcRegistration addMountedRpcImplementation(QName arg0, RpcImplementation arg1) {
+ return delegate().addMountedRpcImplementation(arg0, arg1);
+ }
+
+ @Override
+ @Deprecated
+ public RoutedRpcRegistration addRoutedRpcImplementation(QName arg0, RpcImplementation arg1) {
+ return delegate().addRoutedRpcImplementation(arg0, arg1);
+ }
+
+ @Override
+ @Deprecated
+ public RpcRegistration addRpcImplementation(QName arg0, RpcImplementation arg1)
+ throws IllegalArgumentException {
+ return delegate().addRpcImplementation(arg0, arg1);
+ }
+
+ @Deprecated
+ @Override
+ public ListenerRegistration<RpcRegistrationListener> addRpcRegistrationListener(
+ RpcRegistrationListener arg0) {
+ return delegate().addRpcRegistrationListener(arg0);
+ }
+
+ @Override
+ public void close() {
+ delegate().close();
+ }
+
+ @Override
+ public <T extends BrokerService> T getService(Class<T> arg0) {
+ return delegate().getService(arg0);
+ }
+
+ @Override
+ public Set<QName> getSupportedRpcs() {
+ return delegate().getSupportedRpcs();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return delegate().isClosed();
+ }
+
+ @Override
+ public Future<RpcResult<CompositeNode>> rpc(QName arg0, CompositeNode arg1) {
+ return delegate().rpc(arg0, arg1);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.sal.core.spi.data;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
+import org.opendaylight.controller.md.sal.dom.spi.AbstractRegistrationTree;
+import org.opendaylight.controller.md.sal.dom.spi.RegistrationTreeNode;
+import org.opendaylight.controller.md.sal.dom.spi.RegistrationTreeSnapshot;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract base class for {@link DOMStoreTreeChangePublisher} implementations.
+ */
+public abstract class AbstractDOMStoreTreeChangePublisher extends AbstractRegistrationTree<AbstractDOMDataTreeChangeListenerRegistration<?>> implements DOMStoreTreeChangePublisher {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractDOMStoreTreeChangePublisher.class);
+
+ /**
+ * Callback for subclass to notify specified registrations of a candidate at a specified path. This method is guaranteed
+ * to be only called from within {@link #processCandidateTree(DataTreeCandidate)}.
+ *
+ * @param registrations Registrations which are affected by the candidate node
+ * @param path Path of changed candidate node. Guaranteed to match the path specified by the registration
+ * @param node Candidate node
+ */
+ protected abstract void notifyListeners(@Nonnull Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> registrations, @Nonnull YangInstanceIdentifier path, @Nonnull DataTreeCandidateNode node);
+
+ /**
+ * Callback notifying the subclass that the specified registration is being closed and it's user no longer
+ * wishes to receive notifications. This notification is invoked while the {@link ListenerRegistration#close()}
+ * method is executing. Subclasses can use this callback to properly remove any delayed notifications pending
+ * towards the registration.
+ *
+ * @param registration Registration which is being closed
+ */
+ protected abstract void registrationRemoved(@Nonnull AbstractDOMDataTreeChangeListenerRegistration<?> registration);
+
+ /**
+ * Process a candidate tree with respect to registered listeners.
+ *
+ * @param candidate candidate three which needs to be processed
+ */
+ protected final void processCandidateTree(@Nonnull final DataTreeCandidate candidate) {
+ final DataTreeCandidateNode node = candidate.getRootNode();
+ if (node.getModificationType() == ModificationType.UNMODIFIED) {
+ LOG.debug("Skipping unmodified candidate {}", candidate);
+ return;
+ }
+
+ try (final RegistrationTreeSnapshot<AbstractDOMDataTreeChangeListenerRegistration<?>> snapshot = takeSnapshot()) {
+ final List<PathArgument> toLookup = ImmutableList.copyOf(candidate.getRootPath().getPathArguments());
+ lookupAndNotify(toLookup, 0, snapshot.getRootNode(), candidate);
+ }
+ }
+
+ @Override
+ public final <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier treeId, final L listener) {
+ // Take the write lock
+ takeLock();
+ try {
+ final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node = findNodeFor(treeId.getPathArguments());
+ final AbstractDOMDataTreeChangeListenerRegistration<L> reg = new AbstractDOMDataTreeChangeListenerRegistration<L>(listener) {
+ @Override
+ protected void removeRegistration() {
+ AbstractDOMStoreTreeChangePublisher.this.removeRegistration(node, this);
+ registrationRemoved(this);
+ }
+ };
+
+ addRegistration(node, reg);
+ return reg;
+ } finally {
+ // Always release the lock
+ releaseLock();
+ }
+ }
+
+ private void lookupAndNotify(final List<PathArgument> args, final int offset, final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node, final DataTreeCandidate candidate) {
+ if (args.size() != offset) {
+ final PathArgument arg = args.get(offset);
+
+ final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> exactChild = node.getExactChild(arg);
+ if (exactChild != null) {
+ lookupAndNotify(args, offset + 1, exactChild, candidate);
+ }
+
+ for (RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> c : node.getInexactChildren(arg)) {
+ lookupAndNotify(args, offset + 1, c, candidate);
+ }
+ } else {
+ notifyNode(candidate.getRootPath(), node, candidate.getRootNode());
+ }
+ }
+
+ private void notifyNode(final YangInstanceIdentifier path, final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> regNode, final DataTreeCandidateNode candNode) {
+ if (candNode.getModificationType() == ModificationType.UNMODIFIED) {
+ LOG.debug("Skipping unmodified candidate {}", path);
+ return;
+ }
+
+ final Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> regs = regNode.getRegistrations();
+ if (!regs.isEmpty()) {
+ notifyListeners(regs, path, candNode);
+ }
+
+ for (DataTreeCandidateNode candChild : candNode.getChildNodes()) {
+ if (candChild.getModificationType() != ModificationType.UNMODIFIED) {
+ final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> regChild = regNode.getExactChild(candChild.getIdentifier());
+ if (regChild != null) {
+ notifyNode(path.node(candChild.getIdentifier()), regChild, candChild);
+ }
+
+ for (RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> rc : regNode.getInexactChildren(candChild.getIdentifier())) {
+ notifyNode(path.node(candChild.getIdentifier()), rc, candChild);
+ }
+ }
+ }
+ }
+}
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.controller.md.sal.dom.store.impl.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
import org.opendaylight.controller.sal.core.spi.data.DOMStore;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransactionChain;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreTreeChangePublisher;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
import org.opendaylight.yangtools.concepts.Identifiable;
* to implement {@link DOMStore} contract.
*
*/
-public class InMemoryDOMDataStore extends TransactionReadyPrototype implements DOMStore, Identifiable<String>, SchemaContextListener, AutoCloseable {
+public class InMemoryDOMDataStore extends TransactionReadyPrototype implements DOMStore, Identifiable<String>, SchemaContextListener, AutoCloseable, DOMStoreTreeChangePublisher {
private static final Logger LOG = LoggerFactory.getLogger(InMemoryDOMDataStore.class);
private static final ListenableFuture<Void> SUCCESSFUL_FUTURE = Futures.immediateFuture(null);
private static final ListenableFuture<Boolean> CAN_COMMIT_FUTURE = Futures.immediateFuture(Boolean.TRUE);
private final AtomicLong txCounter = new AtomicLong(0);
private final QueuedNotificationManager<DataChangeListenerRegistration<?>, DOMImmutableDataChangeEvent> dataChangeListenerNotificationManager;
+ private final InMemoryDOMStoreTreeChangePublisher changePublisher;
private final ExecutorService dataChangeListenerExecutor;
private final boolean debugTransactions;
private final String name;
new QueuedNotificationManager<>(this.dataChangeListenerExecutor,
DCL_NOTIFICATION_MGR_INVOKER, maxDataChangeListenerQueueSize,
"DataChangeListenerQueueMgr");
+ changePublisher = new InMemoryDOMStoreTreeChangePublisher(this.dataChangeListenerExecutor, maxDataChangeListenerQueueSize);
}
public void setCloseable(final AutoCloseable closeable) {
};
}
+ @Override
+ public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerTreeChangeListener(final YangInstanceIdentifier treeId, final L listener) {
+ return changePublisher.registerTreeChangeListener(treeId, listener);
+ }
+
@Override
protected void transactionAborted(final SnapshotBackedWriteTransaction tx) {
LOG.debug("Tx: {} is closed.", tx.getIdentifier());
*/
synchronized (InMemoryDOMDataStore.this) {
dataTree.commit(candidate);
+ changePublisher.publishChange(candidate);
listenerResolver.resolve(dataChangeListenerNotificationManager);
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
+import org.opendaylight.controller.sal.core.spi.data.AbstractDOMStoreTreeChangePublisher;
+import org.opendaylight.yangtools.util.concurrent.QueuedNotificationManager;
+import org.opendaylight.yangtools.util.concurrent.QueuedNotificationManager.Invoker;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class InMemoryDOMStoreTreeChangePublisher extends AbstractDOMStoreTreeChangePublisher {
+ private static final Invoker<AbstractDOMDataTreeChangeListenerRegistration<?>, DataTreeCandidate> MANAGER_INVOKER =
+ new Invoker<AbstractDOMDataTreeChangeListenerRegistration<?>, DataTreeCandidate>() {
+ @Override
+ public void invokeListener(final AbstractDOMDataTreeChangeListenerRegistration<?> listener, final DataTreeCandidate notification) {
+ // FIXME: this is inefficient, as we could grab the entire queue for the listener and post it
+ final DOMDataTreeChangeListener inst = listener.getInstance();
+ if (inst != null) {
+ inst.onDataTreeChanged(Collections.singletonList(notification));
+ }
+ }
+ };
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryDOMStoreTreeChangePublisher.class);
+ private final QueuedNotificationManager<AbstractDOMDataTreeChangeListenerRegistration<?>, DataTreeCandidate> notificationManager;
+
+ InMemoryDOMStoreTreeChangePublisher(final ExecutorService listenerExecutor, final int maxQueueSize) {
+ notificationManager = new QueuedNotificationManager<>(listenerExecutor, MANAGER_INVOKER, maxQueueSize, "DataTreeChangeListenerQueueMgr");
+ }
+
+ @Override
+ protected void notifyListeners(final Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> registrations, final YangInstanceIdentifier path, final DataTreeCandidateNode node) {
+ final DataTreeCandidate candidate = new SimpleDataTreeCandidate(path, node);
+
+ for (AbstractDOMDataTreeChangeListenerRegistration<?> reg : registrations) {
+ LOG.debug("Enqueueing candidate {} to registration {}", candidate, registrations);
+ notificationManager.submitNotification(reg, candidate);
+ }
+ }
+
+ @Override
+ protected synchronized void registrationRemoved(final AbstractDOMDataTreeChangeListenerRegistration<?> registration) {
+ LOG.debug("Closing registration {}", registration);
+
+ // FIXME: remove the queue for this registration and make sure we clear it
+ }
+
+ synchronized void publishChange(@Nonnull final DataTreeCandidate candidate) {
+ // Runs synchronized with registrationRemoved()
+ processCandidateTree(candidate);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+
+final class SimpleDataTreeCandidate implements DataTreeCandidate {
+ private final YangInstanceIdentifier rootPath;
+ private final DataTreeCandidateNode rootNode;
+
+ SimpleDataTreeCandidate(final YangInstanceIdentifier rootPath, final DataTreeCandidateNode rootNode) {
+ this.rootPath = Preconditions.checkNotNull(rootPath);
+ this.rootNode = Preconditions.checkNotNull(rootNode);
+ }
+
+ @Override
+ public DataTreeCandidateNode getRootNode() {
+ return rootNode;
+ }
+
+ @Override
+ public YangInstanceIdentifier getRootPath() {
+ return rootPath;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("rootPath", rootPath).add("rootNode", rootNode).toString();
+ }
+}
\ No newline at end of file
--- /dev/null
+/**
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl.tree;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
+import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+abstract class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> implements DataChangeListenerRegistration<T> {
+ public DataChangeListenerRegistrationImpl(final T listener) {
+ super(listener);
+ }
+}
\ No newline at end of file
package org.opendaylight.controller.md.sal.dom.store.impl.tree;
import com.google.common.base.Optional;
-import java.lang.ref.Reference;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+import com.google.common.base.Preconditions;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import org.opendaylight.controller.md.sal.dom.spi.RegistrationTreeNode;
import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.DataChangeListenerRegistrationImpl;
import org.opendaylight.yangtools.concepts.Identifiable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* This is a single node within the listener tree. Note that the data returned from
* @author Robert Varga
*/
public class ListenerNode implements StoreTreeNode<ListenerNode>, Identifiable<PathArgument> {
+ final RegistrationTreeNode<DataChangeListenerRegistration<?>> delegate;
- private static final Logger LOG = LoggerFactory.getLogger(ListenerNode.class);
-
- private final Collection<DataChangeListenerRegistration<?>> listeners = new ArrayList<>();
- private final Map<PathArgument, ListenerNode> children = new HashMap<>();
- private final PathArgument identifier;
- private final Reference<ListenerNode> parent;
-
- ListenerNode(final ListenerNode parent, final PathArgument identifier) {
- this.parent = new WeakReference<>(parent);
- this.identifier = identifier;
+ ListenerNode(final RegistrationTreeNode<DataChangeListenerRegistration<?>> delegate) {
+ this.delegate = Preconditions.checkNotNull(delegate);
}
@Override
public PathArgument getIdentifier() {
- return identifier;
+ return delegate.getIdentifier();
}
@Override
public Optional<ListenerNode> getChild(final PathArgument child) {
- return Optional.fromNullable(children.get(child));
+ final RegistrationTreeNode<DataChangeListenerRegistration<?>> c = delegate.getExactChild(child);
+ if (c == null) {
+ return Optional.absent();
+ }
+
+ return Optional.of(new ListenerNode(c));
}
/**
* @return the list of current listeners
*/
public Collection<DataChangeListenerRegistration<?>> getListeners() {
- return listeners;
+ return delegate.getRegistrations();
}
- ListenerNode ensureChild(final PathArgument child) {
- ListenerNode potential = children.get(child);
- if (potential == null) {
- potential = new ListenerNode(this, child);
- children.put(child, potential);
- }
- return potential;
- }
-
- void addListener(final DataChangeListenerRegistration<?> listener) {
- listeners.add(listener);
- LOG.debug("Listener {} registered", listener);
- }
-
- void removeListener(final DataChangeListenerRegistrationImpl<?> listener) {
- listeners.remove(listener);
- LOG.debug("Listener {} unregistered", listener);
-
- // We have been called with the write-lock held, so we can perform some cleanup.
- removeThisIfUnused();
- }
-
- private void removeThisIfUnused() {
- final ListenerNode p = parent.get();
- if (p != null && listeners.isEmpty() && children.isEmpty()) {
- p.removeChild(identifier);
- }
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
}
- private void removeChild(final PathArgument arg) {
- children.remove(arg);
- removeThisIfUnused();
+ @Override
+ public boolean equals(final Object obj) {
+ return delegate.equals(obj);
}
@Override
public String toString() {
- return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]";
+ return delegate.toString();
}
}
*/
package org.opendaylight.controller.md.sal.dom.store.impl.tree;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.spi.AbstractRegistrationTree;
+import org.opendaylight.controller.md.sal.dom.spi.RegistrationTreeNode;
import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
-import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* A set of listeners organized as a tree by node to which they listen. This class
*
* @author Robert Varga
*/
-public final class ListenerTree {
- private static final Logger LOG = LoggerFactory.getLogger(ListenerTree.class);
- private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
- private final ListenerNode rootNode = new ListenerNode(null, null);
-
+public final class ListenerTree extends AbstractRegistrationTree<DataChangeListenerRegistration<?>> {
private ListenerTree() {
// Private to disallow direct instantiation
}
final L listener, final DataChangeScope scope) {
// Take the write lock
- rwLock.writeLock().lock();
-
+ takeLock();
try {
- ListenerNode walkNode = rootNode;
- for (final PathArgument arg : path.getPathArguments()) {
- walkNode = walkNode.ensureChild(arg);
- }
-
- final ListenerNode node = walkNode;
+ final RegistrationTreeNode<DataChangeListenerRegistration<?>> node = findNodeFor(path.getPathArguments());
DataChangeListenerRegistration<L> reg = new DataChangeListenerRegistrationImpl<L>(listener) {
@Override
public DataChangeScope getScope() {
* While this does not directly violate the ListenerRegistration
* contract, it is probably not going to be liked by the users.
*/
-
- // Take the write lock
- ListenerTree.this.rwLock.writeLock().lock();
- try {
- node.removeListener(this);
- } finally {
- // Always release the lock
- ListenerTree.this.rwLock.writeLock().unlock();
- }
+ ListenerTree.this.removeRegistration(node, this);
}
};
- node.addListener(reg);
+ addRegistration(node, reg);
return reg;
} finally {
// Always release the lock
- rwLock.writeLock().unlock();
+ releaseLock();
}
}
* external user exist, make the Walker a phantom reference, which
* will cleanup the lock if not told to do so.
*/
- final ListenerWalker ret = new ListenerWalker(rwLock.readLock(), rootNode);
- rwLock.readLock().lock();
- return ret;
- }
-
- abstract static class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> //
- implements DataChangeListenerRegistration<T> {
- public DataChangeListenerRegistrationImpl(final T listener) {
- super(listener);
- }
+ return new ListenerWalker(takeSnapshot());
}
}
package org.opendaylight.controller.md.sal.dom.store.impl.tree;
import com.google.common.base.Preconditions;
-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import java.util.concurrent.locks.Lock;
+import org.opendaylight.controller.md.sal.dom.spi.RegistrationTreeSnapshot;
+import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
/**
* A walking context, pretty much equivalent to an iterator, but it
* @author Robert Varga
*/
public class ListenerWalker implements AutoCloseable {
- private static final AtomicIntegerFieldUpdater<ListenerWalker> CLOSED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ListenerWalker.class, "closed");
- private final Lock lock;
- private final ListenerNode node;
+ private final RegistrationTreeSnapshot<DataChangeListenerRegistration<?>> delegate;
- // Used via CLOSED_UPDATER
- @SuppressWarnings("unused")
- private volatile int closed = 0;
-
- ListenerWalker(final Lock lock, final ListenerNode node) {
- this.lock = Preconditions.checkNotNull(lock);
- this.node = Preconditions.checkNotNull(node);
+ ListenerWalker(final RegistrationTreeSnapshot<DataChangeListenerRegistration<?>> delegate) {
+ this.delegate = Preconditions.checkNotNull(delegate);
}
public ListenerNode getRootNode() {
- return node;
+ return new ListenerNode(delegate.getRootNode());
}
@Override
public void close() {
- if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
- lock.unlock();
- }
+ delegate.close();
}
}
\ No newline at end of file