/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.common.impl; import java.io.PrintStream; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.api.NodeIdentifierService; import de.rcenvironment.core.communication.common.CommonIdBase; import de.rcenvironment.core.communication.common.IdType; import de.rcenvironment.core.communication.common.IdentifierException; import de.rcenvironment.core.communication.common.InstanceNodeId; 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.model.NodeInformationRegistry; import de.rcenvironment.core.communication.model.internal.NodeInformationRegistryImpl; import de.rcenvironment.core.communication.model.internal.SharedNodeInformationHolderImpl; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.toolkit.utils.common.DefaultTimeSource; import de.rcenvironment.toolkit.utils.common.IdGenerator; import de.rcenvironment.toolkit.utils.common.IdGeneratorType; import de.rcenvironment.toolkit.utils.common.TimeSource; /** * {@link NodeIdentifierService} implementation. * * @author Robert Mischke */ public final class NodeIdentifierServiceImpl implements NodeIdentifierService { private static final int TWO_EXTRA_BYTES_MULTIPLIER = 256; private static final String LOG_PATTERN_SETTING_NAME_ASSOCIATION = "Setting name association '%s' for %s"; private static final String LOG_PATTERN_REPLACING_NAME_ASSOCIATION = "Replacing previous name association '%s' for %s with '%s'"; private static final String LOG_PATTERN_TRIGGER_SUFFIX = " (triggered by setting that name association for %s)"; // note: must be static to prevent collisions even if multiple virtual instances instantiate this service individually private static final AtomicLong sharedSequentialSessionPartIncrement = new AtomicLong(); private final NodeInformationRegistry nodeInformationRegistry = new NodeInformationRegistryImpl(); private final IdGeneratorType idGeneratorType; private final TimeSource timeSource = new DefaultTimeSource(); // used to easily support mock testing later private final Log log = LogFactory.getLog(getClass()); public NodeIdentifierServiceImpl() { this(IdGeneratorType.SECURE); // default to secure id generation } public NodeIdentifierServiceImpl(IdGeneratorType idGeneratorPreference) { this.idGeneratorType = idGeneratorPreference; } @Override public InstanceNodeId generateInstanceNodeId() { final String instanceIdString = createRandomHexString(CommonIdBase.INSTANCE_PART_LENGTH); final String fullIdString = instanceIdString; // collision-free with session ids, even if the session part was empty return new NodeIdentifierImpl(instanceIdString, null, null, fullIdString, nodeInformationRegistry, IdType.INSTANCE_NODE_ID); } @Override public CommonIdBase parseSelectableTypeIdString(String instanceIdString, IdType targetIdType) throws IdentifierException { return new NodeIdentifierImpl(instanceIdString, nodeInformationRegistry, targetIdType); } @Override public InstanceNodeId parseInstanceNodeIdString(String input) throws IdentifierException { return new NodeIdentifierImpl(input, nodeInformationRegistry, IdType.INSTANCE_NODE_ID); } @Override public InstanceNodeSessionId parseInstanceNodeSessionIdString(String input) throws IdentifierException { return new NodeIdentifierImpl(input, nodeInformationRegistry, IdType.INSTANCE_NODE_SESSION_ID); } @Override public LogicalNodeId parseLogicalNodeIdString(String input) throws IdentifierException { return new NodeIdentifierImpl(input, nodeInformationRegistry, IdType.LOGICAL_NODE_ID); } @Override public LogicalNodeSessionId parseLogicalNodeSessionIdString(String input) throws IdentifierException { return new NodeIdentifierImpl(input, nodeInformationRegistry, IdType.LOGICAL_NODE_SESSION_ID); } @Override public InstanceNodeSessionId generateInstanceNodeSessionId(InstanceNodeId instanceId) { final String instanceIdString = instanceId.getInstanceNodeIdString(); // assumed to be validated final String sessionIdPart = createTimestampHexString(CommonIdBase.SESSION_PART_LENGTH); final String fullIdString = StringUtils.format("%s" + CommonIdBase.STRING_FORM_PART_SEPARATOR + CommonIdBase.STRING_FORM_PART_SEPARATOR + "%s", instanceIdString, sessionIdPart); return new NodeIdentifierImpl(instanceIdString, null, sessionIdPart, fullIdString, nodeInformationRegistry, IdType.INSTANCE_NODE_SESSION_ID); } @Override public void associateDisplayName(CommonIdBase id, String newName) { NodeIdentifierImpl idImpl = (NodeIdentifierImpl) id; final IdType idType = idImpl.getType(); final String displayNameSourceId = idImpl.getFullIdString(); switch (idType) { case INSTANCE_NODE_SESSION_ID: case LOGICAL_NODE_SESSION_ID: // associate with instance session if a session part is known associateDisplayNameInternal(idImpl.getInstanceNodeSessionIdString(), newName, displayNameSourceId); // intentional fall-through (no "break") here case INSTANCE_NODE_ID: case LOGICAL_NODE_ID: // always associate with instance id (which is used if no session id is known) associateDisplayNameInternal(idImpl.getInstanceNodeIdString(), newName, displayNameSourceId); break; default: throw new IllegalArgumentException("Internal error: Associating display names for logical node ids is not supported yet"); } } @Override public void printAllNameAssociations(PrintStream output, String introText) { nodeInformationRegistry.printAllNameAssociations(output, introText); } private void associateDisplayNameInternal(final String fullIdString, String newName, final String originalSourceId) { final SharedNodeInformationHolderImpl informationHolder = getMutableNodeInformationHolder(fullIdString); final String oldName = informationHolder.getDisplayName(); if (oldName == null) { if (originalSourceId.equals(fullIdString)) { log.debug(StringUtils.format(LOG_PATTERN_SETTING_NAME_ASSOCIATION, newName, fullIdString)); } else { log.debug(StringUtils .format(LOG_PATTERN_SETTING_NAME_ASSOCIATION + LOG_PATTERN_TRIGGER_SUFFIX, newName, fullIdString, originalSourceId)); } } else { if (!oldName.equals(newName)) { if (originalSourceId.equals(fullIdString)) { log.debug(StringUtils.format( LOG_PATTERN_REPLACING_NAME_ASSOCIATION, oldName, fullIdString, newName)); } else { log.debug(StringUtils .format(LOG_PATTERN_REPLACING_NAME_ASSOCIATION + LOG_PATTERN_TRIGGER_SUFFIX, oldName, fullIdString, newName, originalSourceId)); } } else { // TODO reduce/filter if logged too much log.debug(StringUtils.format("Ignoring request to set the name association '%s' for %s again", newName, fullIdString)); } } informationHolder.setDisplayName(newName); } private SharedNodeInformationHolderImpl getMutableNodeInformationHolder(String key) { return (SharedNodeInformationHolderImpl) nodeInformationRegistry.getNodeInformationHolder(key); } private String createRandomHexString(int length) { return IdGenerator.createRandomHexString(length, idGeneratorType); } private String createTimestampHexString(int totalLength) { // uses timestamp with int32 + 2 extra bytes accuracy final long scaledTimestamp = timeSource.getCurrentTimeMillis() / 4; // approximately *TWO_EXTRA_BYTES_MULTIPLIER/1000 if (scaledTimestamp < 0 || scaledTimestamp > (long) Integer.MAX_VALUE * TWO_EXTRA_BYTES_MULTIPLIER) { throw new IllegalStateException(); } // adjust with running index to ensure unique session ids in unit tests without artificial wait times long adjustedTimestampValue = scaledTimestamp + sharedSequentialSessionPartIncrement.incrementAndGet(); final String hexString = Long.toHexString(adjustedTimestampValue); if (hexString.length() == totalLength) { // default case return hexString; } else { // only relevant for mock time sources, which may return time values close to zero // left pad with zeros for proper lexical comparison return "0000000000".substring(0, totalLength - hexString.length()) + hexString; } } }