/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.transport.spi; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.channel.MessageChannelState; import de.rcenvironment.core.communication.channel.ServerContactPoint; import de.rcenvironment.core.communication.common.CommunicationException; import de.rcenvironment.core.communication.model.InitialNodeInformation; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Abstract base class for the {@link MessageChannel} implementations of network transports. * * @author Robert Mischke */ public abstract class AbstractMessageChannel implements MessageChannel { protected ServerContactPoint associatedSCP; private volatile MessageChannelState state = MessageChannelState.CONNECTING; private volatile boolean simulatingBreakdown = false; private InitialNodeInformation remoteNodeInformation; private boolean initiatedByRemote = false; private String associatedMirrorChannelId; private String connectionId; private boolean closedBecauseMirrorChannelClosed = false; @Override public MessageChannelState getState() { return state; // volatile } @Override public InitialNodeInformation getRemoteNodeInformation() { return remoteNodeInformation; } @Override public void setRemoteNodeInformation(InitialNodeInformation nodeInformation) { this.remoteNodeInformation = nodeInformation; } @Override public void setAssociatedSCP(ServerContactPoint networkContactPoint) { this.associatedSCP = networkContactPoint; } @Override public boolean getInitiatedByRemote() { return initiatedByRemote; } @Override public void setInitiatedByRemote(boolean value) { this.initiatedByRemote = value; } @Override public String getAssociatedMirrorChannelId() { return associatedMirrorChannelId; } @Override public boolean isClosedBecauseMirrorChannelClosed() { return closedBecauseMirrorChannelClosed; } @Override public void setAssociatedMirrorChannelId(String initiatingChannelId) { this.associatedMirrorChannelId = initiatingChannelId; } @Override public void markAsClosedBecauseMirrorChannelClosed() { closedBecauseMirrorChannelClosed = true; } @Override public String getChannelId() { return connectionId; } @Override public void setChannelId(String id) { if (connectionId != null) { throw new IllegalArgumentException("Duplicate id assignment"); } connectionId = id; } @Override public synchronized void markAsEstablished() { MessageChannelState oldState = state; if (oldState != MessageChannelState.CONNECTING) { throw new IllegalStateException(oldState.toString()); } state = MessageChannelState.ESTABLISHED; } @Override public boolean isReadyToUse() { return state == MessageChannelState.ESTABLISHED; } @Override public final synchronized boolean close() { MessageChannelState oldState = state; switch (oldState) { case ESTABLISHED: // standard case state = MessageChannelState.CLOSED; asyncFireOnClosedOrBroken(); return true; case MARKED_AS_BROKEN: // actively closed after already marked as broken; ignore return false; // another call already marked this channel as broken, so it was already unregistered case CLOSED: // duplicate call; ignore return false; default: // should not happen; indicates consistency error throw new IllegalStateException(oldState.toString()); } } @Override public synchronized boolean markAsBroken() { MessageChannelState oldState = state; switch (oldState) { case CONNECTING: // can occur on initial handshake; log warning and proceed LogFactory.getLog(getClass()).warn("Channel " + getChannelId() + " marked as broken while in state " + oldState.toString()); return true; case ESTABLISHED: // standard case state = MessageChannelState.MARKED_AS_BROKEN; asyncFireOnClosedOrBroken(); return true; case MARKED_AS_BROKEN: // duplicate call; ignore return false; case CLOSED: // as closing a connection can cause follow-up errors, they should be ignored and not change the channel's state - misc_ro return false; // another call already closed this channel, so it was already unregistered default: // should not happen; indicates consistency error throw new IllegalStateException(oldState.toString()); } } private void asyncFireOnClosedOrBroken() { ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() { @Override @TaskDescription("Communication Layer: Asynchronous handling of connection breakdown") public void run() { onClosedOrBroken(); } }); } @Override public String toString() { // TODO improve String suffix = ""; if (simulatingBreakdown) { suffix = "; simulating breakdown"; } return StringUtils.format("Channel %s (%s%s)", connectionId, state, suffix); } protected abstract void onClosedOrBroken(); // for integration testing public void setSimulatingBreakdown(boolean simulatingBreakdown) { this.simulatingBreakdown = simulatingBreakdown; } // for integration testing public boolean isSimulatingBreakdown() { return simulatingBreakdown; } /** * Throws a {@link CommunicationException} with a proper end-user description if the given protocol version strings do not match. * * @param remoteProtocolVersion the received version string (may be null) * @param expectedProtocolVersion the local version string * @throws CommunicationException on a version mismatch */ protected final void failOnIncompatibleVersions(String remoteProtocolVersion, String expectedProtocolVersion) throws CommunicationException { if (remoteProtocolVersion == null) { throw new CommunicationException( "The remote instance sent a response, but it did not contain a proper version code. " + "You are probably trying to connect to an instance running an incompatible version of the software."); } if (!remoteProtocolVersion.equals(expectedProtocolVersion)) { throw new CommunicationException( "The remote instance is running a software version that is not compatible with the local instance: " + "The remote version is '" + remoteProtocolVersion + "' and the local version is '" + expectedProtocolVersion + "'."); } } }