/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.routing.internal.v2;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.communication.api.NodeIdentifierService;
import de.rcenvironment.core.communication.channel.MessageChannelLifecycleListener;
import de.rcenvironment.core.communication.channel.MessageChannelLifecycleListenerAdapter;
import de.rcenvironment.core.communication.common.InstanceNodeSessionId;
import de.rcenvironment.core.communication.configuration.NodeConfigurationService;
import de.rcenvironment.core.communication.nodeproperties.NodePropertiesService;
import de.rcenvironment.core.communication.nodeproperties.NodeProperty;
import de.rcenvironment.core.communication.nodeproperties.spi.RawNodePropertiesChangeListener;
import de.rcenvironment.core.communication.transport.spi.MessageChannel;
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;
/**
* Manager class that creates and publishes the local link state, and keeps track of the link states announced by other nodes in the
* network.
*
* It interacts with the rest of the system by listening for {@link MessageChannel} events (which define the local link state), publishing
* the local link state as a {@link NodeProperty} via {@link NodePropertiesService}, and listening for {@link NodeProperty} change events to
* collect all published link states, including the self-published one for consistency.
*
* @author Robert Mischke
*/
public class DistributedLinkStateManager implements AdditionalServicesProvider {
private static final String LSA_PROPERTY_KEY = "lsa";
private final AsyncOrderedCallbackManager<LinkStateKnowledgeChangeListener> callbackManager =
ConcurrencyUtils.getFactory().createAsyncOrderedCallbackManager(AsyncCallbackExceptionPolicy.LOG_AND_PROCEED);
private NodePropertiesService nodePropertiesService;
private NodeConfigurationService nodeConfigurationService;
private final Map<String, Link> localOutgoingLinks;
private volatile Map<InstanceNodeSessionId, LinkState> linkStateKnowledgeSnapshot;
private volatile LinkState localLinkStateSnapshot;
private InstanceNodeSessionId localNodeId;
private final boolean verboseLogging = DebugSettings.getVerboseLoggingEnabled(getClass());
private final Log log = LogFactory.getLog(getClass());
private boolean localNodeIsRelay;
private NodeIdentifierService nodeIdentifierService;
public DistributedLinkStateManager() {
localOutgoingLinks = new HashMap<String, Link>();
linkStateKnowledgeSnapshot = Collections.unmodifiableMap(new HashMap<InstanceNodeSessionId, LinkState>());
}
/**
* OSGi-DS lifecycle method.
*/
public synchronized void activate() {
localNodeId = nodeConfigurationService.getInstanceNodeSessionId();
localNodeIsRelay = nodeConfigurationService.isRelay();
localLinkStateSnapshot = new LinkState(localOutgoingLinks.values());
setNewLocalLinkState(localLinkStateSnapshot);
if (!localNodeIsRelay) {
// in non-relay mode, publish an empty pseudo link state to make sure no old property circulates in the network
String serializedEmptyLinkState = LinkStateSerializer.serialize(new ArrayList<Link>());
nodePropertiesService.addOrUpdateLocalNodeProperty(LSA_PROPERTY_KEY, serializedEmptyLinkState);
}
}
@Override
public Collection<AdditionalServiceDeclaration> defineAdditionalServices() {
Collection<AdditionalServiceDeclaration> result = new ArrayList<AdditionalServiceDeclaration>();
result.add(new AdditionalServiceDeclaration(RawNodePropertiesChangeListener.class, new RawNodePropertiesChangeListener() {
@Override
public void onRawNodePropertiesAddedOrModified(Collection<? extends NodeProperty> newProperties) {
// forward to main class method
updateOnNodePropertiesAddedOrModified(newProperties);
}
}));
result.add(new AdditionalServiceDeclaration(MessageChannelLifecycleListener.class, new MessageChannelLifecycleListenerAdapter() {
@Override
public void onOutgoingChannelTerminated(MessageChannel connection) {
// forward to main class method
updateOnOutgoingChannelTerminated(connection);
}
@Override
public void onOutgoingChannelEstablished(MessageChannel connection) {
// forward to main class method
updateOnOutgoingChannelEstablished(connection);
}
}));
return result;
}
// linkStateKnowledgeSnapshot is defined as volatile
public Map<InstanceNodeSessionId, LinkState> getCurrentKnowledge() {
return linkStateKnowledgeSnapshot;
}
/**
* OSGi-DS "bind" method; made public for integration testing.
*
* @param newInstance the new service instance to bind
*/
public void bindNodePropertiesService(NodePropertiesService newInstance) {
nodePropertiesService = newInstance;
}
/**
* OSGi-DS "bind" method; made public for integration testing.
*
* @param newInstance the new service instance to bind
*/
public void bindNodeConfigurationService(NodeConfigurationService newInstance) {
this.nodeConfigurationService = newInstance;
this.nodeIdentifierService = nodeConfigurationService.getNodeIdentifierService();
}
/**
* OSGi-DS "bind" method; made public for integration testing.
*
* @param listener the new listener instance to add
*/
public synchronized void addLinkStateKnowledgeChangeListener(LinkStateKnowledgeChangeListener listener) {
// copy reference in synchronized block
final Map<InstanceNodeSessionId, LinkState> currentKnowledgeSnapshotCopy = linkStateKnowledgeSnapshot;
// send initial update
callbackManager.addListenerAndEnqueueCallback(listener, new AsyncCallback<LinkStateKnowledgeChangeListener>() {
@Override
public void performCallback(LinkStateKnowledgeChangeListener listener) {
listener.onLinkStateKnowledgeChanged(currentKnowledgeSnapshotCopy);
}
});
}
/**
* OSGi-DS "unbind" method; made public for integration testing.
*
* @param listener the new listener instance to remove
*/
public void removeLinkStateKnowledgeChangeListener(LinkStateKnowledgeChangeListener listener) {
callbackManager.removeListener(listener);
}
private synchronized void updateOnNodePropertiesAddedOrModified(Collection<? extends NodeProperty> newProperties) {
// only used if a relevant change is detected
Map<InstanceNodeSessionId, LinkState> deltaMap = null;
for (NodeProperty property : newProperties) {
if (property.getKey().equals(LSA_PROPERTY_KEY)) {
String linkStateData = property.getValue();
InstanceNodeSessionId updateSourceInstanceSessionId = property.getInstanceNodeSessionId();
if (localNodeId.isSameInstanceNodeSessionAs(updateSourceInstanceSessionId)) {
// ignore LSA properties for the local node as they can differ from the actual
// local link state in relay mode
continue;
}
// log.debug("Received LSA data for " + nodeId + ": " + linkStateData);
try {
LinkState deserialized = LinkStateSerializer.deserialize(linkStateData);
// log.debug("Parsed LSA: " + deserialized);
// lazy init; also serves as marker that there have been relevant changes
if (deltaMap == null) {
deltaMap = new HashMap<InstanceNodeSessionId, LinkState>();
}
deltaMap.put(updateSourceInstanceSessionId, deserialized);
} catch (IOException e) {
log.error("Ignoring unreadable link state update for node " + updateSourceInstanceSessionId, e);
}
}
}
if (deltaMap != null) {
if (verboseLogging) {
StringBuilder buffer = new StringBuilder();
String locationInfo = "";
if (localNodeId != null) {
locationInfo = " " + localNodeId.toString();
}
buffer.append(StringUtils.format("Detected %d LSA property changes%s: ", deltaMap.size(), locationInfo));
for (Entry<InstanceNodeSessionId, LinkState> entry : deltaMap.entrySet()) {
buffer.append(StringUtils.format("\n %s -> %s", entry.getKey(), entry.getValue().getLinks()));
}
log.debug(buffer.toString());
}
mergeIntoEffectiveLinkStateKnowledge(deltaMap);
}
}
private synchronized void updateOnOutgoingChannelEstablished(MessageChannel connection) {
final String linkId = connection.getChannelId();
final String remoteInstanceNodeSessionIdString = connection.getRemoteNodeInformation().getInstanceNodeSessionIdString();
final Link link = new Link(linkId, remoteInstanceNodeSessionIdString);
localOutgoingLinks.put(linkId, link);
localLinkStateSnapshot = new LinkState(localOutgoingLinks.values());
setNewLocalLinkState(localLinkStateSnapshot);
}
private synchronized void updateOnOutgoingChannelTerminated(MessageChannel connection) {
localOutgoingLinks.remove(connection.getChannelId());
localLinkStateSnapshot = new LinkState(localOutgoingLinks.values());
setNewLocalLinkState(localLinkStateSnapshot);
}
private void setNewLocalLinkState(final LinkState linkState) {
if (localNodeIsRelay) {
// in relay mode, publish the local link state via node properties; the local node
// then consumes its own link state like the ones published by other nodes - misc_ro
String serialized = LinkStateSerializer.serialize(linkState.getLinks());
nodePropertiesService.addOrUpdateLocalNodeProperty(LSA_PROPERTY_KEY, serialized);
}
// regardless of relay or non-relay mode, merge the local link state into the effective link
// state knowledge; to make sure this is not overwritten by the empty pseudo link state
// property in non-relay mode, "received" local property changes must be ignored - misc_ro
Map<InstanceNodeSessionId, LinkState> deltaMap = new HashMap<InstanceNodeSessionId, LinkState>();
deltaMap.put(localNodeId, linkState);
mergeIntoEffectiveLinkStateKnowledge(deltaMap);
// TODO move/merge into "mergeIntoEffectiveLinkStateKnowledge"?
callbackManager.enqueueCallback(new AsyncCallback<LinkStateKnowledgeChangeListener>() {
@Override
public void performCallback(LinkStateKnowledgeChangeListener listener) {
listener.onLocalLinkStateUpdated(linkState);
}
});
}
// NOTE: must be called from synchronized methods only!
private void mergeIntoEffectiveLinkStateKnowledge(Map<InstanceNodeSessionId, LinkState> deltaMap) {
// there have been relevant changes, so replace the current knowledge
Map<InstanceNodeSessionId, LinkState> tempMap = new HashMap<InstanceNodeSessionId, LinkState>(linkStateKnowledgeSnapshot);
tempMap.putAll(deltaMap);
linkStateKnowledgeSnapshot = Collections.unmodifiableMap(tempMap);
// create immutable copy of new knowledge reference and delta map in synchronized block
final Map<InstanceNodeSessionId, LinkState> knowledgeSnapshotCopy = linkStateKnowledgeSnapshot;
final Map<InstanceNodeSessionId, LinkState> deltaMapCopy = Collections.unmodifiableMap(deltaMap);
// trigger callback
callbackManager.enqueueCallback(new AsyncCallback<LinkStateKnowledgeChangeListener>() {
@Override
public void performCallback(LinkStateKnowledgeChangeListener listener) {
listener.onLinkStateKnowledgeChanged(knowledgeSnapshotCopy);
listener.onLinkStatesUpdated(deltaMapCopy);
}
});
}
}