/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.nodeproperties.internal; 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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; 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.communication.spi.NetworkTopologyChangeListenerAdapter; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.service.AdditionalServiceDeclaration; import de.rcenvironment.core.utils.common.service.AdditionalServicesProvider; import de.rcenvironment.core.utils.incubator.DebugSettings; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallback; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallbackExceptionPolicy; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncOrderedCallbackManager; import de.rcenvironment.toolkit.utils.common.AutoCreationMap; /** * A service that listens for low-level {@link RawNodePropertiesChangeListener} and {@link NetworkTopologyChangeListener} events, and * converts them into higher-level {@link NodePropertiesChangeListener} events. * * @author Robert Mischke */ public class NodePropertiesStateServiceImpl implements AdditionalServicesProvider { private static final Set<NodeProperty> IMMUTABLE_EMPTY_PROPERTY_SET = Collections.unmodifiableSet(new HashSet<NodeProperty>()); private final AutoCreationMap<InstanceNodeSessionId, Map<String, NodeProperty>> propertyObjectMapsByNode = new AutoCreationMap<InstanceNodeSessionId, Map<String, NodeProperty>>() { protected Map<String, NodeProperty> createNewEntry(InstanceNodeSessionId key) { return new HashMap<String, NodeProperty>(); }; }; private final Map<InstanceNodeSessionId, Map<String, String>> immutableValueMapsOfNodes = new HashMap<InstanceNodeSessionId, Map<String, String>>(); private final Map<InstanceNodeSessionId, Map<String, String>> immutableValueMapsOfReachableNodes = new HashMap<InstanceNodeSessionId, Map<String, String>>(); private Set<InstanceNodeSessionId> reachableNodes = new HashSet<InstanceNodeSessionId>(); private final Set<NodeProperty> reachableProperties = new HashSet<NodeProperty>(); private final AsyncOrderedCallbackManager<NodePropertiesChangeListener> callbackManager; private final boolean verboseLogging = DebugSettings.getVerboseLoggingEnabled(getClass()); private final Log log = LogFactory.getLog(getClass()); public NodePropertiesStateServiceImpl() { callbackManager = ConcurrencyUtils.getFactory().createAsyncOrderedCallbackManager(AsyncCallbackExceptionPolicy.LOG_AND_CANCEL_LISTENER); } @Override public Collection<AdditionalServiceDeclaration> defineAdditionalServices() { final List<AdditionalServiceDeclaration> result = new ArrayList<AdditionalServiceDeclaration>(); result.add(new AdditionalServiceDeclaration(RawNodePropertiesChangeListener.class, new RawNodePropertiesChangeListener() { @Override public void onRawNodePropertiesAddedOrModified(Collection<? extends NodeProperty> newProperties) { updateOnRawPropertiesAddedOrModified(newProperties); } })); result.add(new AdditionalServiceDeclaration(NetworkTopologyChangeListener.class, new NetworkTopologyChangeListenerAdapter() { @Override public void onReachableNodesChanged(Set<InstanceNodeSessionId> newReachableNodes, Set<InstanceNodeSessionId> addedNodes, Set<InstanceNodeSessionId> removedNodes) { NodePropertiesStateServiceImpl.this.updateOnReachableNodesChanged(newReachableNodes, addedNodes, removedNodes); } })); return result; } /** * Registers a new {@link NodePropertiesChangeListener}. A {@link NodePropertiesChangeListener#onReachableNodePropertiesChanged()} * callback will be triggered immediately, with all currently connected/reachable {@link NodeProperty}s as "added" properties. * * @param listener the new listener */ public synchronized void addNodePropertiesChangeListener(NodePropertiesChangeListener listener) { // create copies in synchronized block block final Set<NodeProperty> reachablePropertiesCopy = new HashSet<NodeProperty>(reachableProperties); final Map<InstanceNodeSessionId, Map<String, String>> valueMapsDeltaCopy = Collections.unmodifiableMap(immutableValueMapsOfReachableNodes); // send initial callback callbackManager.addListenerAndEnqueueCallback(listener, new AsyncCallback<NodePropertiesChangeListener>() { @Override public void performCallback(NodePropertiesChangeListener listener) { listener.onReachableNodePropertiesChanged(reachablePropertiesCopy, IMMUTABLE_EMPTY_PROPERTY_SET, IMMUTABLE_EMPTY_PROPERTY_SET); listener.onNodePropertyMapsOfNodesChanged(valueMapsDeltaCopy); } }); } /** * Unregisters a {@link NodePropertiesChangeListener}. * * @param listener the listener to remove */ public void removeNodePropertiesChangeListener(NodePropertiesChangeListener listener) { callbackManager.removeListener(listener); } private synchronized void updateOnRawPropertiesAddedOrModified(Collection<? extends NodeProperty> newOrUpdatedProperties) { Set<NodeProperty> addedProperties = new HashSet<NodeProperty>(); Set<NodeProperty> updatedProperties = new HashSet<NodeProperty>(); Set<NodeProperty> removedProperties = new HashSet<NodeProperty>(); Set<InstanceNodeSessionId> valueMapsToUpdate = new HashSet<InstanceNodeSessionId>(); for (NodeProperty property : newOrUpdatedProperties) { final InstanceNodeSessionId nodeId = property.getInstanceNodeSessionId(); final Map<String, NodeProperty> propertyObjectMap = propertyObjectMapsByNode.get(nodeId); boolean isReachableNode = reachableNodes.contains(nodeId); valueMapsToUpdate.add(nodeId); if (property.getValue() != null) { // register in by-node map NodeProperty replacedPropertyInstance = propertyObjectMap.put(property.getKey(), property); boolean wasPresent = replacedPropertyInstance != null; if (isReachableNode) { reachableProperties.add(property); if (wasPresent) { updatedProperties.add(property); } else { addedProperties.add(property); } } } else { // remove from by-node map propertyObjectMap.remove(property.getKey()); if (isReachableNode) { removedProperties.add(property); reachableProperties.remove(property); } propertyObjectMap.remove(property.getKey()); } } if (!addedProperties.isEmpty() || !updatedProperties.isEmpty() || !removedProperties.isEmpty()) { final Set<NodeProperty> addedPropertiesCopy = Collections.unmodifiableSet(addedProperties); final Set<NodeProperty> updatedPropertiesCopy = Collections.unmodifiableSet(updatedProperties); final Set<NodeProperty> removedPropertiesCopy = Collections.unmodifiableSet(removedProperties); if (verboseLogging) { log.debug(StringUtils.format("Reporting node property state change: %d properties added, %d updated, %d removed", addedPropertiesCopy.size(), updatedPropertiesCopy.size(), removedPropertiesCopy.size())); } callbackManager.enqueueCallback(new AsyncCallback<NodePropertiesChangeListener>() { @Override public void performCallback(NodePropertiesChangeListener listener) { listener.onReachableNodePropertiesChanged(addedPropertiesCopy, updatedPropertiesCopy, removedPropertiesCopy); } }); } Map<InstanceNodeSessionId, Map<String, String>> valueMapsDelta = new HashMap<InstanceNodeSessionId, Map<String, String>>(); for (InstanceNodeSessionId nodeId : valueMapsToUpdate) { // construct new value map for each modified node Map<String, String> valueMap = createImmutableValueMapForNode(nodeId); if (reachableNodes.contains(nodeId)) { // add to "delta" map if this node is reachable valueMapsDelta.put(nodeId, valueMap); // add to state-keeping map of *reachable* nodes immutableValueMapsOfReachableNodes.put(nodeId, valueMap); } // add to state-keeping map of *all* nodes immutableValueMapsOfNodes.put(nodeId, valueMap); } // TODO check: can this be safely merged with the other callback (regarding asynchronous topology changes)? - misc_ro if (!valueMapsDelta.isEmpty()) { final Map<InstanceNodeSessionId, Map<String, String>> valueMapsDeltaCopy = Collections.unmodifiableMap(valueMapsDelta); callbackManager.enqueueCallback(new AsyncCallback<NodePropertiesChangeListener>() { @Override public void performCallback(NodePropertiesChangeListener listener) { listener.onNodePropertyMapsOfNodesChanged(valueMapsDeltaCopy); } }); } } private synchronized void updateOnReachableNodesChanged(Set<InstanceNodeSessionId> newReachableNodes, Set<InstanceNodeSessionId> addedNodes, Set<InstanceNodeSessionId> removedNodes) { Map<InstanceNodeSessionId, Map<String, String>> valueMapsDelta = new HashMap<InstanceNodeSessionId, Map<String, String>>(); // find now-disconnected properties List<NodeProperty> disconnectedProperties = new ArrayList<NodeProperty>(); for (InstanceNodeSessionId nodeId : removedNodes) { Map<String, NodeProperty> nodePropertyMap = propertyObjectMapsByNode.get(nodeId); if (nodePropertyMap != null) { disconnectedProperties.addAll(nodePropertyMap.values()); } valueMapsDelta.put(nodeId, null); // adapt (reduce) map of reachable node value maps immutableValueMapsOfReachableNodes.remove(nodeId); } // find reconnected properties List<NodeProperty> reconnectedProperties = new ArrayList<NodeProperty>(); for (InstanceNodeSessionId nodeId : addedNodes) { Map<String, NodeProperty> nodePropertyMap = propertyObjectMapsByNode.get(nodeId); if (nodePropertyMap != null) { reconnectedProperties.addAll(nodePropertyMap.values()); } Map<String, String> immutableValueMap = immutableValueMapsOfNodes.get(nodeId); valueMapsDelta.put(nodeId, immutableValueMap); // adapt map of reachable node value maps immutableValueMapsOfReachableNodes.put(nodeId, immutableValueMap); } // if (!disconnectedProperties.isEmpty()) { // final Collection<NodeProperty> disconnectedPropertiesCopy = Collections.unmodifiableCollection(disconnectedProperties); // callbackManager.enqueueCallback(new AsyncCallback<NodePropertiesChangeListener>() { // // @Override // public void performCallback(NodePropertiesChangeListener listener) { // listener.onNodePropertiesDisconnected(disconnectedPropertiesCopy); // } // }); // } // if (!reconnectedProperties.isEmpty()) { // final Collection<NodeProperty> reconnectedPropertiesCopy = Collections.unmodifiableCollection(reconnectedProperties); // callbackManager.enqueueCallback(new AsyncCallback<NodePropertiesChangeListener>() { // // @Override // public void performCallback(NodePropertiesChangeListener listener) { // listener.onNodePropertiesReconnected(reconnectedPropertiesCopy); // } // }); // } if (!disconnectedProperties.isEmpty() || !reconnectedProperties.isEmpty()) { if (verboseLogging) { log.debug(StringUtils.format( "Reporting node property state change after topology change: %d properties disconnected, %d reconnected", disconnectedProperties.size(), reconnectedProperties.size())); } final Collection<NodeProperty> disconnectedPropertiesCopy = Collections.unmodifiableCollection(disconnectedProperties); final Collection<NodeProperty> reconnectedPropertiesCopy = Collections.unmodifiableCollection(reconnectedProperties); final Collection<NodeProperty> updatedPropertiesDummy = Collections.unmodifiableCollection(new ArrayList<NodeProperty>()); callbackManager.enqueueCallback(new AsyncCallback<NodePropertiesChangeListener>() { @Override public void performCallback(NodePropertiesChangeListener listener) { listener.onReachableNodePropertiesChanged(reconnectedPropertiesCopy, updatedPropertiesDummy, disconnectedPropertiesCopy); } }); } // TODO check: can this be safely merged with the other callback (regarding asynchronous topology changes)? - misc_ro if (!valueMapsDelta.isEmpty()) { final Map<InstanceNodeSessionId, Map<String, String>> valueMapsDeltaCopy = Collections.unmodifiableMap(valueMapsDelta); callbackManager.enqueueCallback(new AsyncCallback<NodePropertiesChangeListener>() { @Override public void performCallback(NodePropertiesChangeListener listener) { listener.onNodePropertyMapsOfNodesChanged(valueMapsDeltaCopy); } }); } reachableProperties.addAll(reconnectedProperties); reachableProperties.removeAll(disconnectedProperties); reachableNodes = newReachableNodes; } private Map<String, String> createImmutableValueMapForNode(InstanceNodeSessionId nodeId) { final Map<String, NodeProperty> propertyObjectMap = propertyObjectMapsByNode.get(nodeId); Map<String, String> valueMap = new HashMap<String, String>(); for (NodeProperty property : propertyObjectMap.values()) { valueMap.put(property.getKey(), property.getValue()); } // make immutable return Collections.unmodifiableMap(valueMap); } }