/*
* 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 + "'.");
}
}
}