/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.internal;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.communication.api.LiveNetworkIdResolutionService;
import de.rcenvironment.core.communication.common.IdentifierException;
import de.rcenvironment.core.communication.common.InstanceNodeSessionId;
import de.rcenvironment.core.communication.common.LogicalNodeId;
import de.rcenvironment.core.communication.common.LogicalNodeSessionId;
import de.rcenvironment.core.communication.common.ResolvableNodeId;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.incubator.DebugSettings;
/**
* Default {@link LiveNetworkIdResolutionService} implementation.
*
* @author Robert Mischke
*/
public class LiveNetworkIdResolutionServiceImpl implements LiveNetworkIdResolutionService {
private final Map<String, InstanceNodeSessionId> instanceNodeSessionIdMap = new HashMap<>();
private String localInstanceNodeIdString;
private String localInstanceNodeSessionIdString;
private final boolean verboseLogging = DebugSettings.getVerboseLoggingEnabled(getClass());
private final Log log = LogFactory.getLog(getClass());
@Override
public InstanceNodeSessionId resolveInstanceNodeIdStringToInstanceNodeSessionId(String input) throws IdentifierException {
final InstanceNodeSessionId result;
synchronized (this) {
result = instanceNodeSessionIdMap.get(input);
}
if (result != null) {
return result;
} else {
throw new IdentifierException("Cannot resolve '" + input
+ "' to a known instance session id; that node has never been reachable, or is not reachable anymore");
}
}
@Override
public LogicalNodeSessionId resolveToLogicalNodeSessionId(ResolvableNodeId id) throws IdentifierException {
final LogicalNodeSessionId result;
result = resolveToLogicalNodeSessionIdInternal(id);
if (verboseLogging) {
log.debug(StringUtils.format("Resolved %s %s to %s", id.getType(), id, result));
}
return result;
}
private LogicalNodeSessionId resolveToLogicalNodeSessionIdInternal(ResolvableNodeId id) throws IdentifierException {
// note: this method always resolves to the default (latest) session id, even if the parameter is already a session id itself
final InstanceNodeSessionId resolvedInstanceNodeSessionId =
resolveInstanceNodeIdStringToInstanceNodeSessionId(id.getInstanceNodeIdString());
switch (id.getType()) {
case INSTANCE_NODE_SESSION_ID:
if (!resolvedInstanceNodeSessionId.isSameInstanceNodeSessionAs((InstanceNodeSessionId) id)) {
log.debug(StringUtils.format("Resolved a given session id %s to the more recent session id %s for the same instance node",
id, resolvedInstanceNodeSessionId));
}
// intentional fall-through (no "break")
case INSTANCE_NODE_ID:
return resolvedInstanceNodeSessionId.convertToDefaultLogicalNodeSessionId();
case LOGICAL_NODE_SESSION_ID:
if (!resolvedInstanceNodeSessionId.getSessionIdPart().equals(((LogicalNodeSessionId) id).getSessionIdPart())) {
log.debug(StringUtils.format("Resolved a given session id %s to the more recent session id %s for the same instance node",
id, resolvedInstanceNodeSessionId));
}
// TODO (p3) >8.0.0 could probably done more efficiently
return ((LogicalNodeSessionId) id).convertToLogicalNodeId().combineWithInstanceNodeSessionId(resolvedInstanceNodeSessionId);
case LOGICAL_NODE_ID:
return ((LogicalNodeId) id).combineWithInstanceNodeSessionId(resolvedInstanceNodeSessionId);
default:
throw new IllegalStateException();
}
}
/**
* Registers the local {@link InstanceNodeSessionId} for its instance and instance session ids. This session id is special because it
* should be protected against any subsequent modification (e.g. by stale information received from other nodes).
*
* @param localINSId the local id to register
*/
public void registerLocalInstanceNodeSessionId(InstanceNodeSessionId localINSId) {
synchronized (this) {
if (localInstanceNodeIdString != null || localInstanceNodeSessionIdString != null) {
throw new IllegalStateException(); // expect exactly one initialization
}
this.localInstanceNodeIdString = localINSId.getInstanceNodeIdString();
this.localInstanceNodeSessionIdString = localINSId.getInstanceNodeSessionIdString();
if (instanceNodeSessionIdMap.put(localInstanceNodeIdString, localINSId) != null) {
throw new IllegalStateException(); // must be the first id to register for this
}
}
}
/**
* Registers a new {@link InstanceNodeSessionId} for its instance and instance session ids. If it replaces a previously set
* {@link InstanceNodeSessionId} for either, log messages are generated.
*
* @param newINSId the new id to register
*/
public void registerInstanceNodeSessionId(InstanceNodeSessionId newINSId) {
final String instanceNodeIdString = newINSId.getInstanceNodeIdString();
final String instanceNodeSessionIdString = newINSId.getInstanceNodeSessionIdString();
final InstanceNodeSessionId existingResolutionForINId;
synchronized (this) {
if (instanceNodeIdString.equals(localInstanceNodeIdString)
&& !instanceNodeSessionIdString.equals(localInstanceNodeSessionIdString)) {
log.debug(StringUtils.format(
"Refused an attempt to replace the local instance's current session id (%s) with another one (%s); "
+ "this may be caused by stale data received from another instance",
localInstanceNodeSessionIdString, instanceNodeSessionIdString));
if (isAlphabeticallyMoreRecentThan(instanceNodeSessionIdString, localInstanceNodeSessionIdString)) {
log.debug(
"The conflicting session id received from the network (see above) is more recent than the local instance's "
+ "session id; this may indicate a duplicate node id within the network, which may result from erronously "
+ "copying and reusing a profile's internal settings");
}
return;
}
existingResolutionForINId = instanceNodeSessionIdMap.get(instanceNodeIdString);
if (existingResolutionForINId == null) {
instanceNodeSessionIdMap.put(instanceNodeIdString, newINSId);
log.debug(StringUtils.format(
"Registered %s as the first known session id for instance node '%s'", newINSId, instanceNodeIdString));
} else {
if (!existingResolutionForINId.equals(newINSId)) {
// a new InstanceNodeSessionId became visible before the old one was unregistered
if (isAlphabeticallyMoreRecentThan(newINSId.getSessionIdPart(), existingResolutionForINId.getSessionIdPart())) {
log.info(StringUtils.format(
"Updated the default instance node session id for instance node '%s' from %s to %s; "
+ "this means that a new session became visible before the old one unregistered from the network "
+ "(e.g. after a crash or network disconnect)",
instanceNodeIdString, existingResolutionForINId, newINSId));
instanceNodeSessionIdMap.put(instanceNodeIdString, newINSId);
} else {
log.debug(StringUtils.format(
"Ignored an outdated session id for instance node '%s' (current: %s, ignored: %s) "
+ "received as part of a remote node's network knowledge",
instanceNodeIdString, existingResolutionForINId, newINSId));
}
}
}
}
}
/**
* Unregisters a node that has disappeared from the known network.
*
* @param oldINSId the node's session id to unregister
*/
public void unregisterInstanceNodeSessionId(InstanceNodeSessionId oldINSId) {
final String instanceNodeIdString = oldINSId.getInstanceNodeIdString();
synchronized (this) {
final InstanceNodeSessionId existingResolutionForINId = instanceNodeSessionIdMap.get(instanceNodeIdString);
if (existingResolutionForINId != null) {
if (existingResolutionForINId.isSameInstanceNodeSessionAs(oldINSId)) {
// the single known session id was unregistered; no known node-to-session resolution anymore
// TODO (p2) potential error case: an outdated id could be registered again after this; keep track of all active ids?
instanceNodeSessionIdMap.remove(instanceNodeIdString);
} else {
// note: this is a very technical message for the INFO log level, but it should be visible to indicate that the previous
// WARNING log message has been resolved
log.debug(StringUtils.format(
"The outdated instance node session id %s for instance node '%s' has been properly unregistered now",
oldINSId, instanceNodeIdString));
}
}
}
}
private boolean isAlphabeticallyMoreRecentThan(final String id1, String id2) {
return id1.compareTo(id2) > 0;
}
}