/**
* Inherited from immediate parent
*/
- private final Iterable<Builder> inheritedOne;
+ private final Collection<Builder> inheritedOne;
private final YangInstanceIdentifier nodeId;
private final Collection<Node> nodes;
- private final Map<DataChangeListenerRegistration<?>, Builder> subBuilders = new HashMap<>();
- private final Map<DataChangeListenerRegistration<?>, Builder> oneBuilders = new HashMap<>();
- private final Map<DataChangeListenerRegistration<?>, Builder> baseBuilders = new HashMap<>();
+ private final Map<DataChangeListenerRegistration<?>, Builder> subBuilders;
+ private final Map<DataChangeListenerRegistration<?>, Builder> oneBuilders;
+ private final Map<DataChangeListenerRegistration<?>, Builder> baseBuilders;
private ResolveDataChangeState(final YangInstanceIdentifier nodeId,
- final Iterable<Builder> inheritedSub, final Iterable<Builder> inheritedOne,
+ final Iterable<Builder> inheritedSub, final Collection<Builder> inheritedOne,
final Collection<Node> nodes) {
this.nodeId = Preconditions.checkNotNull(nodeId);
this.nodes = Preconditions.checkNotNull(nodes);
/*
* Collect the nodes which need to be propagated from us to the child.
*/
+ final Map<DataChangeListenerRegistration<?>, Builder> sub = new HashMap<>();
+ final Map<DataChangeListenerRegistration<?>, Builder> one = new HashMap<>();
+ final Map<DataChangeListenerRegistration<?>, Builder> base = new HashMap<>();
for (Node n : nodes) {
for (DataChangeListenerRegistration<?> l : n.getListeners()) {
final Builder b = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE);
switch (l.getScope()) {
case BASE:
- baseBuilders.put(l, b);
+ base.put(l, b);
break;
case ONE:
- oneBuilders.put(l, b);
+ one.put(l, b);
break;
case SUBTREE:
- subBuilders.put(l, b);
+ sub.put(l, b);
break;
}
}
}
+
+ baseBuilders = maybeEmpty(base);
+ oneBuilders = maybeEmpty(one);
+ subBuilders = maybeEmpty(sub);
+ }
+
+ private static <K, V> Map<K, V> maybeEmpty(final Map<K, V> map) {
+ if (map.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ return map;
}
/**
* @return State handle
*/
public ResolveDataChangeState child(final PathArgument childId) {
- return new ResolveDataChangeState(nodeId.node(childId),
- Iterables.concat(inheritedSub, subBuilders.values()),
+ /*
+ * We instantiate a concatenation only when needed:
+ *
+ * 1) If our collection is empty, we reuse the parent's. This is typically the case
+ * for intermediate node, which should be the vast majority.
+ * 2) If the parent's iterable is a Collection and it is empty, reuse our collection.
+ * This is the case for the first node which defines a subtree listener in a
+ * particular subtree.
+ * 3) Concatenate the two collections. This happens when we already have some
+ * subtree listeners and we encounter a node which adds a few more.
+ *
+ * This allows us to lower number of objects allocated and also
+ * speeds up Iterables.isEmpty() in needsProcessing().
+ *
+ * Note that the check for Collection in 2) relies on precisely this logic, which
+ * ensures that we simply cannot see an empty concatenation, but rather start off with
+ * an empty collection, then switch to a non-empty collection and finally switch to
+ * a concatenation. This saves us from instantiating iterators, which a trivial
+ * Iterables.isEmpty() would do as soon as we cross case 3).
+ */
+ final Iterable<Builder> sb;
+ if (!subBuilders.isEmpty()) {
+ if (inheritedSub instanceof Collection && ((Collection<?>) inheritedSub).isEmpty()) {
+ sb = subBuilders.values();
+ } else {
+ sb = Iterables.concat(inheritedSub, subBuilders.values());
+ }
+ } else {
+ sb = inheritedSub;
+ }
+
+ return new ResolveDataChangeState(nodeId.node(childId), sb,
oneBuilders.values(), getListenerChildrenWildcarded(nodes, childId));
}
if (!nodes.isEmpty()) {
return true;
}
- // Have SUBTREE listeners
- if (!Iterables.isEmpty(inheritedSub)) {
- return true;
- }
// Have ONE listeners
- if (!Iterables.isEmpty(inheritedOne)) {
+ if (!inheritedOne.isEmpty()) {
return true;
}
- return false;
+ /*
+ * Have SUBTREE listeners
+ *
+ * This is slightly magical replacement for !Iterables.isEmpty(inheritedSub).
+ * It relies on the logic in child(), which gives us the guarantee that when
+ * inheritedSub is not a Collection, it is guaranteed to be non-empty (which
+ * means we need to process). If it is a collection, we still need to check
+ * it for emptiness.
+ *
+ * Unlike Iterables.isEmpty() this code does not instantiate any temporary
+ * objects and is thus more efficient.
+ */
+ if (inheritedSub instanceof Collection) {
+ return !((Collection<?>) inheritedSub).isEmpty();
+ }
+
+ // Non-Collection => non-empty => have to process
+ return true;
}
/**
private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class);
private final MBeanServerConnection mBeanServerConnection;
- private final ConfigPersisterNotificationListener listener;
+ private final NotificationListener listener;
- public ConfigPersisterNotificationHandler(MBeanServerConnection mBeanServerConnection,
- Persister persisterAggregator) {
+ public ConfigPersisterNotificationHandler(final MBeanServerConnection mBeanServerConnection, final Persister persisterAggregator) {
+ this(mBeanServerConnection, new ConfigPersisterNotificationListener(persisterAggregator));
+ }
+
+ public ConfigPersisterNotificationHandler(final MBeanServerConnection mBeanServerConnection, final NotificationListener notificationListener) {
this.mBeanServerConnection = mBeanServerConnection;
- listener = new ConfigPersisterNotificationListener(persisterAggregator);
+ this.listener = notificationListener;
registerAsJMXListener(mBeanServerConnection, listener);
-
}
- private static void registerAsJMXListener(MBeanServerConnection mBeanServerConnection, ConfigPersisterNotificationListener listener) {
+ private static void registerAsJMXListener(final MBeanServerConnection mBeanServerConnection, final NotificationListener listener) {
logger.trace("Called registerAsJMXListener");
try {
mBeanServerConnection.addNotificationListener(DefaultCommitOperationMXBean.OBJECT_NAME, listener, null, null);
@Override
public synchronized void close() {
// unregister from JMX
- ObjectName on = DefaultCommitOperationMXBean.OBJECT_NAME;
+ final ObjectName on = DefaultCommitOperationMXBean.OBJECT_NAME;
try {
if (mBeanServerConnection.isRegistered(on)) {
mBeanServerConnection.removeNotificationListener(on, listener);
}
- } catch (Exception e) {
+ } catch (final Exception e) {
logger.warn("Unable to unregister {} as listener for {}", listener, on, e);
}
}
private final Persister persisterAggregator;
- ConfigPersisterNotificationListener(Persister persisterAggregator) {
+ ConfigPersisterNotificationListener(final Persister persisterAggregator) {
this.persisterAggregator = persisterAggregator;
}
@Override
- public void handleNotification(Notification notification, Object handback) {
+ public void handleNotification(final Notification notification, final Object handback) {
if (!(notification instanceof NetconfJMXNotification))
return;
if (notification instanceof CommitJMXNotification) {
try {
handleAfterCommitNotification((CommitJMXNotification) notification);
- } catch (Exception e) {
+ } catch (final Exception e) {
// log exceptions from notification Handler here since
// notificationBroadcastSupport logs only DEBUG level
logger.warn("Failed to handle notification {}", notification, e);
persisterAggregator.persistConfig(new CapabilityStrippingConfigSnapshotHolder(notification.getConfigSnapshot(),
notification.getCapabilities()));
logger.trace("Configuration persisted successfully");
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new RuntimeException("Unable to persist configuration snapshot", e);
}
}
public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
if (!persisterWithConfiguration.readOnly){
- logger.debug("Calling {}.persistConfig",persisterWithConfiguration.storage);
- persisterWithConfiguration.storage.persistConfig(holder);
+ logger.debug("Calling {}.persistConfig", persisterWithConfiguration.getStorage());
+ persisterWithConfiguration.getStorage().persistConfig(holder);
}
}
}
--- /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.netconf.persist.impl;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import javax.management.MBeanServerConnection;
+
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.persist.api.Persister;
+
+public class ConfigPersisterNotificationHandlerTest {
+
+ @Mock
+ private MBeanServerConnection mBeanServer;
+ @Mock
+ private Persister notificationListener;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doNothing().when(mBeanServer).addNotificationListener(any(ObjectName.class), any(NotificationListener.class),
+ any(NotificationFilter.class), anyObject());
+ }
+
+ @Test
+ public void testNotificationHandler() throws Exception {
+ doReturn(true).when(mBeanServer).isRegistered(any(ObjectName.class));
+ doThrow(Exception.class).when(mBeanServer).removeNotificationListener(any(ObjectName.class), any(NotificationListener.class));
+
+ final ConfigPersisterNotificationHandler testedHandler = new ConfigPersisterNotificationHandler(mBeanServer, notificationListener);
+ verify(mBeanServer).addNotificationListener(any(ObjectName.class), any(NotificationListener.class),
+ any(NotificationFilter.class), anyObject());
+
+ testedHandler.close();
+ verify(mBeanServer).removeNotificationListener(any(ObjectName.class), any(NotificationListener.class));
+ }
+
+ @Test
+ public void testNotificationHandlerCloseNotRegistered() throws Exception {
+ doReturn(false).when(mBeanServer).isRegistered(any(ObjectName.class));
+
+ final ConfigPersisterNotificationHandler testedHandler = new ConfigPersisterNotificationHandler(mBeanServer, notificationListener);
+
+ testedHandler.close();
+ verify(mBeanServer, times(0)).removeNotificationListener(any(ObjectName.class), any(NotificationListener.class));
+ }
+}
--- /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.netconf.persist.impl;
+
+import java.util.Collections;
+
+import javax.management.Notification;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+import org.opendaylight.controller.config.persist.api.Persister;
+import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification;
+import org.opendaylight.controller.netconf.api.jmx.NetconfJMXNotification;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+
+import com.google.common.collect.Lists;
+
+public class ConfigPersisterNotificationListenerTest {
+
+ @Mock
+ private Persister mockPersister;
+ private PersisterAggregator persisterAggregator;
+
+ @Mock
+ private NetconfJMXNotification unknownNetconfNotif;
+ @Mock
+ private CommitJMXNotification commitNetconfNotif;
+ @Mock
+ private Notification unknownNotif;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Mockito.doNothing().when(mockPersister).persistConfig(Matchers.any(ConfigSnapshotHolder.class));
+ Mockito.doReturn("persister").when(mockPersister).toString();
+ final PersisterAggregator.PersisterWithConfiguration withCfg = new PersisterAggregator.PersisterWithConfiguration(mockPersister, false);
+ persisterAggregator = new PersisterAggregator(Lists.newArrayList(withCfg));
+
+ Mockito.doReturn("netconfUnknownNotification").when(unknownNetconfNotif).toString();
+ Mockito.doReturn("netconfCommitNotification").when(commitNetconfNotif).toString();
+
+ Mockito.doReturn(XmlUtil.readXmlToElement("<config-snapshot/>")).when(commitNetconfNotif).getConfigSnapshot();
+ Mockito.doReturn(Collections.emptySet()).when(commitNetconfNotif).getCapabilities();
+
+ }
+
+ @Test
+ public void testNotificationListenerUnknownNotification() throws Exception {
+ final ConfigPersisterNotificationListener testeListener = new ConfigPersisterNotificationListener(persisterAggregator);
+ testeListener.handleNotification(unknownNotif, null);
+ Mockito.verifyZeroInteractions(mockPersister);
+ }
+
+ @Test
+ public void testNotificationListenerUnknownNetconfNotification() throws Exception {
+ final ConfigPersisterNotificationListener testeListener = new ConfigPersisterNotificationListener(persisterAggregator);
+ try {
+ testeListener.handleNotification(unknownNetconfNotif, null);
+ Assert.fail("Unknown netconf notification should fail");
+ } catch (final IllegalStateException e) {
+ Mockito.verifyZeroInteractions(mockPersister);
+ }
+ }
+
+ @Test
+ public void testNotificationListenerCommitNetconfNotification() throws Exception {
+ final ConfigPersisterNotificationListener testeListener = new ConfigPersisterNotificationListener(persisterAggregator);
+ testeListener.handleNotification(commitNetconfNotif, null);
+ Mockito.verify(mockPersister).persistConfig(Matchers.any(ConfigSnapshotHolder.class));
+ }
+}
package org.opendaylight.controller.netconf.persist.impl;
+import com.google.common.collect.Lists;
+
import org.junit.Test;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.api.Persister;
assertEquals(1, DummyAdapter.props);
}
+ @Test
+ public void testNoopAdapter() throws Exception {
+ final NoOpStorageAdapter noOpStorageAdapter = new NoOpStorageAdapter();
+ final PersisterAggregator persisterAggregator =
+ new PersisterAggregator(Lists.newArrayList(new PersisterWithConfiguration(noOpStorageAdapter, false)));
+
+ noOpStorageAdapter.instantiate(null);
+
+ persisterAggregator.persistConfig(null);
+ persisterAggregator.loadLastConfigs();
+ persisterAggregator.persistConfig(null);
+ persisterAggregator.loadLastConfigs();
+
+ noOpStorageAdapter.close();
+ }
+
@Test
public void testLoadFromPropertyFile() throws Exception {
PersisterAggregator persisterAggregator = PersisterAggregator.createFromProperties(loadFile("test2.properties"));