/*
* 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.List;
import java.util.Map;
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;
/**
* Registry for received node property information.
*
* IMPORTANT: This class performs no synchronization; this is expected to be done by the caller.
*
* @author Robert Mischke
*/
public class NodePropertiesRegistry {
private final Map<CompositeNodePropertyKey, NodePropertyImpl> knowledgeMap;
private final Log log = LogFactory.getLog(getClass());
public NodePropertiesRegistry() {
this.knowledgeMap = new HashMap<CompositeNodePropertyKey, NodePropertyImpl>();
}
/**
* Returns the full property map for a single node. Modifications to the map do not cause any side effects.
*
* @param nodeId the id of the target node
* @return the property map
*/
public Map<String, String> getNodeProperties(InstanceNodeSessionId nodeId) {
String nodeIdString = nodeId.getInstanceNodeSessionIdString();
Map<String, String> result = new HashMap<String, String>();
for (NodePropertyImpl entry : knowledgeMap.values()) {
CompositeNodePropertyKey key = entry.getCompositeKey();
if (key.getInstanceNodeSessionIdString().equals(nodeIdString)) {
result.put(key.getDataKey(), entry.getValue());
}
}
return result;
}
/**
* Returns a single property for a single node.
*
* @param nodeId the id of the target node
* @param dataKey the property key to look up
* @return the value for the given key, or null if it does not exist
*/
public NodeProperty getNodeProperty(InstanceNodeSessionId nodeId, String dataKey) {
CompositeNodePropertyKey ckey = new CompositeNodePropertyKey(nodeId.getInstanceNodeSessionIdString(), dataKey);
return knowledgeMap.get(ckey);
}
/**
* Returns a single property value for a single node.
*
* @param nodeId the id of the target node
* @param dataKey the property key to look up
* @return the value for the given key, or null if it does not exist
*/
public String getNodePropertyValue(InstanceNodeSessionId nodeId, String dataKey) {
NodeProperty property = getNodeProperty(nodeId, dataKey);
if (property != null) {
return property.getValue();
} else {
return null;
}
}
/**
* Returns the full property map for all known nodes.
*
* @return the map of property maps as returned by {@link #getNodeProperties(InstanceNodeSessionId)}
*/
public Map<InstanceNodeSessionId, Map<String, String>> getAllNodeProperties() {
Map<InstanceNodeSessionId, Map<String, String>> result = new HashMap<InstanceNodeSessionId, Map<String, String>>();
for (NodePropertyImpl entry : knowledgeMap.values()) {
CompositeNodePropertyKey key = entry.getCompositeKey();
InstanceNodeSessionId nodeId = entry.getInstanceNodeSessionId();
Map<String, String> nodeMap = result.get(nodeId);
if (!result.containsKey(nodeId)) {
nodeMap = new HashMap<String, String>();
result.put(nodeId, nodeMap);
}
nodeMap.put(key.getDataKey(), entry.getValue());
}
return result;
}
/**
* Returns the full property map for all given nodes.
*
* @param nodeIds the ids of the relevant nodes
* @return the map of property maps as returned by {@link #getNodeProperties(InstanceNodeSessionId)}
*/
public Map<InstanceNodeSessionId, Map<String, String>> getAllNodeProperties(Collection<InstanceNodeSessionId> nodeIds) {
Map<InstanceNodeSessionId, Map<String, String>> result = new HashMap<InstanceNodeSessionId, Map<String, String>>();
for (InstanceNodeSessionId nodeId : nodeIds) {
result.put(nodeId, getNodeProperties(nodeId));
}
return result;
}
/**
* Merges the given {@link NodePropertyImpl}s into the registry state. No timestamp checking is performed; it is assumed that all given
* entries are up-to-date.
*
* @param update the entries to merge
*/
public void mergeUnchecked(Collection<NodePropertyImpl> update) {
for (NodePropertyImpl entry : update) {
knowledgeMap.put(entry.getCompositeKey(), entry);
}
}
/**
* Updates the registry state with those {@link NodePropertyImpl}s that are newer than the data already present, and returns this set of
* entries.
*
* @param update the entries to merge selectively
* @return the subset of the given entries that caused a change to the registry state
*/
public Collection<NodePropertyImpl> mergeAndGetEffectiveSubset(Collection<NodePropertyImpl> update) {
List<NodePropertyImpl> effectiveSubset = new ArrayList<NodePropertyImpl>();
for (NodePropertyImpl entry : update) {
CompositeNodePropertyKey ckey = entry.getCompositeKey();
NodePropertyImpl existing = knowledgeMap.get(ckey);
if (existing == null || existing.getSequenceNo() < entry.getSequenceNo()) {
knowledgeMap.put(ckey, entry);
effectiveSubset.add(entry);
}
}
return effectiveSubset;
}
public Collection<NodePropertyImpl> getDetachedCopyOfEntries() {
return Collections.unmodifiableCollection(new ArrayList<NodePropertyImpl>(knowledgeMap.values()));
}
/**
* Returns the part of the registry state that is newer than or not present in the given entries.
*
* This method is used to synchronize registries: Registry A sends its complete state to B, B merges this information and sends the
* complementing set to A, which A merges into its state. After this exchange, both registries hold the same data.
*
* @param input the entries representing the known state of the sender
* @return the set of entries that newer than or not present in the given input
*/
public Collection<NodePropertyImpl> getComplementingKnowledge(Collection<NodePropertyImpl> input) {
// create set to avoid O(n*m) search over input keys
Map<CompositeNodePropertyKey, NodePropertyImpl> inputMap = new HashMap<CompositeNodePropertyKey, NodePropertyImpl>();
for (NodePropertyImpl entry : input) {
CompositeNodePropertyKey ckey = entry.getCompositeKey();
NodeProperty existing = inputMap.get(ckey);
if (existing == null) {
// typical case: one entry per key
inputMap.put(ckey, entry);
} else {
// log warning and fall back to full knowledge response
log.warn("Received node property update with more than one entry for key "
+ ckey + "; falling back to full knowledge response");
return getDetachedCopyOfEntries();
}
}
// construct response
Collection<NodePropertyImpl> response = new ArrayList<NodePropertyImpl>();
for (NodePropertyImpl ownEntry : knowledgeMap.values()) {
CompositeNodePropertyKey ckey = ownEntry.getCompositeKey();
NodePropertyImpl correspondingInput = inputMap.get(ckey);
if (correspondingInput == null || correspondingInput.getSequenceNo() < ownEntry.getSequenceNo()) {
// missing or older -> add own entry to response
response.add(ownEntry);
}
}
return Collections.unmodifiableCollection(response);
}
public int getEntryCount() {
return knowledgeMap.size();
}
}