/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.nodeproperties.internal;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import de.rcenvironment.core.communication.common.IdentifierException;
import de.rcenvironment.core.communication.common.InstanceNodeSessionId;
import de.rcenvironment.core.communication.common.NodeIdentifierTestUtils;
import de.rcenvironment.core.communication.common.NodeIdentifierUtils;
import de.rcenvironment.core.communication.nodeproperties.NodeProperty;
import de.rcenvironment.core.communication.nodeproperties.spi.NodePropertiesChangeListener;
import de.rcenvironment.core.communication.nodeproperties.spi.RawNodePropertiesChangeListener;
import de.rcenvironment.core.communication.spi.NetworkTopologyChangeListener;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.service.MockAdditionalServicesRegistrationService;
/**
* {@link NodePropertiesStateServiceImpl} unit test.
*
* @author Robert Mischke
*/
public class NodePropertiesStateServiceImplTest {
private static final String KEY_A = "keyA";
private static final String KEY_B = "keyB";
private static final String VALUE_A = "valueA";
private static final String VALUE_A2 = "valueA2";
private static final String VALUE_B = "valueB";
private static final String VALUE_B2 = "valueB2";
private static final int ASYNC_EVENTS_WAIT_MSEC = 200;
private NodePropertiesStateServiceImpl service;
private RawNodePropertiesChangeListener serviceRawNodePropertiesChangeListener;
private NetworkTopologyChangeListener serviceNetworkTopologyChangeListener;
private CallbackCollector callbackCollector;
private final InstanceNodeSessionId node1 = NodeIdentifierTestUtils.createTestInstanceNodeSessionId();
private final InstanceNodeSessionId node2 = NodeIdentifierTestUtils.createTestInstanceNodeSessionId();
private final String node1InstanceSessionIdString = node1.getInstanceNodeSessionIdString();
private final String node2InstanceSessionIdString = node2.getInstanceNodeSessionIdString();
private final Set<InstanceNodeSessionId> emptySet;
private final Set<InstanceNodeSessionId> node1Set;
private final Set<InstanceNodeSessionId> node2Set;
private final Set<InstanceNodeSessionId> node12Set;
/**
* Simple holder for the parameters of a NodePropertiesChangeListener event.
*
* @author Robert Mischke
*/
private class CallbackHolder {
public final Collection<? extends NodeProperty> added;
public final Collection<? extends NodeProperty> updated;
public final Collection<? extends NodeProperty> removed;
CallbackHolder(Collection<? extends NodeProperty> addedSet, Collection<? extends NodeProperty> updatedSet,
Collection<? extends NodeProperty> removedSet) {
this.added = addedSet;
this.updated = updatedSet;
this.removed = removedSet;
}
public void verifySetSizes(int aSize, int uSize, int rSize) {
assertEquals(aSize, added.size());
assertEquals(uSize, updated.size());
assertEquals(rSize, removed.size());
}
}
/**
* {@link NodePropertiesChangeListener} implementation that collects callbacks in an internal list.
*
* @author Robert Mischke
*/
private class CallbackCollector implements NodePropertiesChangeListener {
private final List<CallbackHolder> queue = Collections.synchronizedList(new ArrayList<CallbackHolder>());
private final Map<InstanceNodeSessionId, Map<String, String>> propertyMapsByNode =
Collections.synchronizedMap(new HashMap<InstanceNodeSessionId, Map<String, String>>());
public void resetQueue() {
queue.clear();
}
public int getCount() {
return queue.size();
}
public CallbackHolder get(int index) {
return queue.get(index);
}
public Map<String, String> getPropertyMapForNode(InstanceNodeSessionId nodeId) {
return propertyMapsByNode.get(nodeId);
}
@Override
public void onReachableNodePropertiesChanged(Collection<? extends NodeProperty> addedProperties,
Collection<? extends NodeProperty> updatedProperties, Collection<? extends NodeProperty> removedProperties) {
queue.add(new CallbackHolder(addedProperties, updatedProperties, removedProperties));
}
@Override
public void onNodePropertyMapsOfNodesChanged(Map<InstanceNodeSessionId, Map<String, String>> updatedPropertyMaps) {
propertyMapsByNode.putAll(updatedPropertyMaps); // inner maps are immutable
}
}
public NodePropertiesStateServiceImplTest() {
emptySet = new HashSet<InstanceNodeSessionId>();
node1Set = new HashSet<InstanceNodeSessionId>();
node1Set.add(node1);
node2Set = new HashSet<InstanceNodeSessionId>();
node2Set.add(node2);
node12Set = new HashSet<InstanceNodeSessionId>();
node12Set.add(node1);
node12Set.add(node2);
}
/**
* Test setup.
*
* @throws Exception on uncaught exceptions
*/
@Before
public void setUp() throws Exception {
ConcurrencyUtils.getThreadPoolManagement().reset();
service = new NodePropertiesStateServiceImpl();
callbackCollector = new CallbackCollector();
MockAdditionalServicesRegistrationService listenerRegistrationService = new MockAdditionalServicesRegistrationService();
listenerRegistrationService.registerAdditionalServicesProvider(service);
serviceRawNodePropertiesChangeListener =
listenerRegistrationService.getListeners(RawNodePropertiesChangeListener.class).iterator().next();
serviceNetworkTopologyChangeListener =
listenerRegistrationService.getListeners(NetworkTopologyChangeListener.class).iterator().next();
}
/**
* Tests for proper callback on adding a listener to a service with no properties yet.
*
* @throws InterruptedException on interruption
*/
@Test
public void testListenerAdditionOnCleanService() throws InterruptedException {
// subscribe
service.addNodePropertiesChangeListener(callbackCollector);
// verify: there should have been a callback with empty change sets
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
CallbackHolder callbackHolder = callbackCollector.get(0);
assertEquals(0, callbackHolder.added.size());
assertEquals(0, callbackHolder.updated.size());
assertEquals(0, callbackHolder.removed.size());
// check maps
assertEquals(null, callbackCollector.getPropertyMapForNode(node1));
assertEquals(null, callbackCollector.getPropertyMapForNode(node2));
}
/**
* Tests for proper callback on adding a listener to a service with known properties.
*
* @throws InterruptedException on interruption
*/
@Test
public void testListenerAdditionAfterPropertiesKnown() throws InterruptedException {
CallbackHolder currentCallback;
NodeProperty callbackProperty;
// set reachable nodes (node 1 only)
serviceNetworkTopologyChangeListener.onReachableNodesChanged(node1Set, node1Set, emptySet);
// add a property for a reachable node
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node1InstanceSessionIdString, KEY_A, "201", VALUE_A)));
// add an property for an unreachable node
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node2InstanceSessionIdString, KEY_B, "202", VALUE_B)));
// subscribe
service.addNodePropertiesChangeListener(callbackCollector);
// verify callback parameters
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
callbackProperty = currentCallback.added.iterator().next();
assertEquals(node1InstanceSessionIdString, callbackProperty.getInstanceNodeSessionIdString());
assertEquals(KEY_A, callbackProperty.getKey());
assertEquals(VALUE_A, callbackProperty.getValue());
assertEquals(0, currentCallback.updated.size());
assertEquals(0, currentCallback.removed.size());
// check maps
assertEquals(1, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(null, callbackCollector.getPropertyMapForNode(node2));
}
/**
* Tests for proper callbacks on property change events.
*
* @throws InterruptedException on interruption
*/
@Test
public void testListenerCallbacksOnPropertyChanges() throws InterruptedException {
CallbackHolder currentCallback;
NodeProperty callbackProperty;
// set reachable nodes (node 1 only)
serviceNetworkTopologyChangeListener.onReachableNodesChanged(node1Set, node1Set, emptySet);
// subscribe
service.addNodePropertiesChangeListener(callbackCollector);
// verify that the callback happened; content not checked as there is a separate test for that
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
// check maps
assertEquals(null, callbackCollector.getPropertyMapForNode(node1));
assertEquals(null, callbackCollector.getPropertyMapForNode(node2));
callbackCollector.resetQueue();
// add a property
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node1InstanceSessionIdString, KEY_A, "301", VALUE_A)));
// verify callback and parameters
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
assertEquals(1, currentCallback.added.size());
callbackProperty = currentCallback.added.iterator().next();
assertEquals(node1InstanceSessionIdString, callbackProperty.getInstanceNodeSessionIdString());
assertEquals(KEY_A, callbackProperty.getKey());
assertEquals(VALUE_A, callbackProperty.getValue());
assertEquals(0, currentCallback.updated.size());
assertEquals(0, currentCallback.removed.size());
// check maps
assertNotNull(callbackCollector.getPropertyMapForNode(node1));
assertEquals(1, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertEquals(null, callbackCollector.getPropertyMapForNode(node2));
callbackCollector.resetQueue();
// add another property
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node1InstanceSessionIdString, KEY_B, "302", VALUE_B)));
// verify callback and parameters
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
assertEquals(1, currentCallback.added.size());
callbackProperty = currentCallback.added.iterator().next();
assertEquals(node1InstanceSessionIdString, callbackProperty.getInstanceNodeSessionIdString());
assertEquals(KEY_B, callbackProperty.getKey());
assertEquals(VALUE_B, callbackProperty.getValue());
assertEquals(0, currentCallback.updated.size());
assertEquals(0, currentCallback.removed.size());
// check maps
assertNotNull(callbackCollector.getPropertyMapForNode(node1));
assertEquals(2, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertEquals(VALUE_B, callbackCollector.getPropertyMapForNode(node1).get(KEY_B));
assertEquals(null, callbackCollector.getPropertyMapForNode(node2));
callbackCollector.resetQueue();
// update property "keyA"
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node1InstanceSessionIdString, KEY_A, "303", VALUE_A2)));
// verify callback and parameters
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
assertEquals(0, currentCallback.added.size());
assertEquals(1, currentCallback.updated.size());
callbackProperty = currentCallback.updated.iterator().next();
assertEquals(node1InstanceSessionIdString, callbackProperty.getInstanceNodeSessionIdString());
assertEquals(KEY_A, callbackProperty.getKey());
assertEquals(VALUE_A2, callbackProperty.getValue());
assertEquals(0, currentCallback.removed.size());
// check maps
assertNotNull(callbackCollector.getPropertyMapForNode(node1));
assertEquals(2, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A2, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertEquals(VALUE_B, callbackCollector.getPropertyMapForNode(node1).get(KEY_B));
assertEquals(null, callbackCollector.getPropertyMapForNode(node2));
// TODO continue
}
/**
* Tests for proper callbacks on property change events.
*
* @throws InterruptedException on interruption
*/
@Test
public void testListenerCallbacksOnTopologyChanges() throws InterruptedException {
CallbackHolder currentCallback;
NodeProperty currentProperty;
// set reachable nodes (node 1 only)
serviceNetworkTopologyChangeListener.onReachableNodesChanged(node1Set, node1Set, emptySet);
// add a "local" property
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node1InstanceSessionIdString, KEY_A, "401", VALUE_A)));
// "connect" node2
serviceNetworkTopologyChangeListener.onReachableNodesChanged(node12Set, node2Set, emptySet);
// add a "remote" property
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node2InstanceSessionIdString, KEY_B, "402", VALUE_B)));
// subscribe
service.addNodePropertiesChangeListener(callbackCollector);
// verify that the callback happened; content not checked as there is a separate test for that
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
// check maps
assertEquals(1, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertEquals(1, callbackCollector.getPropertyMapForNode(node2).size());
assertEquals(VALUE_B, callbackCollector.getPropertyMapForNode(node2).get(KEY_B));
callbackCollector.resetQueue();
// "disconnect" node2
serviceNetworkTopologyChangeListener.onReachableNodesChanged(node1Set, emptySet, node2Set);
// verify callback and parameters
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
assertEquals(0, currentCallback.added.size());
assertEquals(0, currentCallback.updated.size());
assertEquals(1, currentCallback.removed.size());
verifyPropertyValues(currentCallback.removed.iterator().next(), node2InstanceSessionIdString, KEY_B, VALUE_B);
// check maps
assertEquals(1, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertNull(callbackCollector.getPropertyMapForNode(node2));
callbackCollector.resetQueue();
// create a property change for the disconnected node; this can happen in practice
// as changes are processed asynchronously
serviceRawNodePropertiesChangeListener.onRawNodePropertiesAddedOrModified(createTestProperties(
StringUtils.escapeAndConcat(node2InstanceSessionIdString, KEY_B, "403", VALUE_B2)));
// verify that no change event was fired for the disconnected node
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(0, callbackCollector.getCount());
// check maps; should be unmodified as well
assertEquals(1, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertNull(callbackCollector.getPropertyMapForNode(node2));
callbackCollector.resetQueue();
// with the disconnected node2, unregister and re-register the listener to check the initial callback
service.removeNodePropertiesChangeListener(callbackCollector);
service.addNodePropertiesChangeListener(callbackCollector);
// verify callback and parameters
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
currentCallback.verifySetSizes(1, 0, 0);
currentProperty = currentCallback.added.iterator().next();
verifyPropertyValues(currentProperty, node1InstanceSessionIdString, KEY_A, VALUE_A);
callbackCollector.resetQueue();
// "reconnect" node2
serviceNetworkTopologyChangeListener.onReachableNodesChanged(node12Set, node2Set, emptySet);
// verify callback and parameters; the reconnected property should cause an "added" event
Thread.sleep(ASYNC_EVENTS_WAIT_MSEC);
assertEquals(1, callbackCollector.getCount());
currentCallback = callbackCollector.get(0);
assertEquals(1, currentCallback.added.size());
verifyPropertyValues(currentCallback.added.iterator().next(), node2InstanceSessionIdString, KEY_B, VALUE_B2);
assertEquals(0, currentCallback.updated.size());
assertEquals(0, currentCallback.removed.size());
// check maps
assertEquals(1, callbackCollector.getPropertyMapForNode(node1).size());
assertEquals(VALUE_A, callbackCollector.getPropertyMapForNode(node1).get(KEY_A));
assertEquals(1, callbackCollector.getPropertyMapForNode(node2).size());
assertEquals(VALUE_B2, callbackCollector.getPropertyMapForNode(node2).get(KEY_B));
// TODO continue
}
private void verifyPropertyValues(NodeProperty callbackProperty, String nodeIdString, String key, String value) {
assertEquals(nodeIdString, callbackProperty.getInstanceNodeSessionIdString());
assertEquals(key, callbackProperty.getKey());
assertEquals(value, callbackProperty.getValue());
}
private List<NodeProperty> createTestProperties(String... values) {
final List<NodeProperty> result = new ArrayList<NodeProperty>();
for (String value : values) {
try {
result.add(new NodePropertyImpl(value, NodeIdentifierTestUtils.getTestNodeIdentifierService()));
} catch (IdentifierException e) {
throw NodeIdentifierUtils.wrapIdentifierException(e);
}
}
return result;
}
}