/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.management.internal;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Version;
import de.rcenvironment.core.communication.channel.MessageChannelService;
import de.rcenvironment.core.communication.channel.ServerContactPoint;
import de.rcenvironment.core.communication.common.CommunicationException;
import de.rcenvironment.core.communication.configuration.CommunicationConfiguration;
import de.rcenvironment.core.communication.configuration.NodeConfigurationService;
import de.rcenvironment.core.communication.connection.api.ConnectionSetup;
import de.rcenvironment.core.communication.connection.api.ConnectionSetupService;
import de.rcenvironment.core.communication.management.CommunicationManagementService;
import de.rcenvironment.core.communication.messaging.MessageEndpointHandler;
import de.rcenvironment.core.communication.messaging.internal.HealthCheckNetworkRequestHandler;
import de.rcenvironment.core.communication.messaging.internal.MessageEndpointHandlerImpl;
import de.rcenvironment.core.communication.messaging.internal.RPCNetworkRequestHandler;
import de.rcenvironment.core.communication.model.InitialNodeInformation;
import de.rcenvironment.core.communication.model.NetworkContactPoint;
import de.rcenvironment.core.communication.nodeproperties.NodePropertiesService;
import de.rcenvironment.core.communication.nodeproperties.NodePropertyConstants;
import de.rcenvironment.core.communication.protocol.ProtocolConstants;
import de.rcenvironment.core.communication.routing.NetworkRoutingService;
import de.rcenvironment.core.communication.rpc.spi.RemoteServiceCallHandlerService;
import de.rcenvironment.core.communication.transport.spi.AbstractMessageChannel;
import de.rcenvironment.core.communication.transport.spi.MessageChannel;
import de.rcenvironment.core.configuration.CommandLineArguments;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.VersionUtils;
import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription;
/**
* Default {@link CommunicationManagementService} implementation.
*
* @author Robert Mischke
*/
public class CommunicationManagementServiceImpl implements CommunicationManagementService {
/**
* The delay between announcing the shutdown to all neighbors, and actually shutting down.
*/
private static final int DELAY_AFTER_SHUTDOWN_ANNOUNCE_MSEC = 200;
private MessageChannelService connectionService;
private NetworkRoutingService networkRoutingService;
private InitialNodeInformation ownNodeInformation;
private NodeConfigurationService nodeConfigurationService;
private List<ServerContactPoint> initializedServerContactPoints = new ArrayList<ServerContactPoint>();
private ScheduledFuture<?> connectionHealthCheckTaskHandle;
private RemoteServiceCallHandlerService serviceCallHandler;
private NodePropertiesService nodePropertiesService;
private ConnectionSetupService connectionSetupService;
private long sessionStartTimeMsec;
private boolean autoStartNetworkOnActivation = true; // disabled by integration tests
private boolean started;
private final Log log = LogFactory.getLog(getClass());
@Override
public synchronized void startUpNetwork() {
sessionStartTimeMsec = System.currentTimeMillis();
// add to local metadata
Map<String, String> localMetadata = createLocalMetadataContribution();
nodePropertiesService.addOrUpdateLocalNodeProperties(localMetadata);
// start server contact points
log.debug("Starting server contact points");
for (NetworkContactPoint ncp : nodeConfigurationService.getServerContactPoints()) {
// log.debug(StringUtils.format("Virtual instance '%s': Starting server at %s",
// ownNodeInformation.getLogName(), ncp));
try {
synchronized (initializedServerContactPoints) {
ServerContactPoint newSCP = connectionService.startServer(ncp);
initializedServerContactPoints.add(newSCP);
}
} catch (CommunicationException e) {
log.warn("Error while starting server at " + ncp, e);
}
}
// FIXME temporary fix until connection retry (or similar) is implemented;
// without this, simultaneous startup of instance groups will usually fail,
// because some instances will try to connect before others have fully started
try {
Thread.sleep(nodeConfigurationService.getDelayBeforeStartupConnectAttempts());
} catch (InterruptedException e1) {
log.error("Interrupted while waiting during startup; not connecting to neighbors", e1);
return;
}
connectionService.setShutdownFlag(false);
// trigger connections to initial peers
log.debug("Starting preconfigured connections");
for (final NetworkContactPoint ncp : nodeConfigurationService.getInitialNetworkContactPoints()) {
// TODO add custom display name when available; move string reconstruction into NCP
final String displayName = StringUtils.format("%s:%s", ncp.getHost(), ncp.getPort());
boolean connectOnStartup = !"false".equals(ncp.getAttributes().get("connectOnStartup"));
ConnectionSetup setup = connectionSetupService.createConnectionSetup(ncp, displayName, connectOnStartup);
log.debug(StringUtils.format("Loaded pre-configured network connection \"%s\" (Settings: %s)",
setup.getDisplayName(), ncp.getAttributes()));
if (setup.getConnnectOnStartup()) {
setup.signalStartIntent();
}
}
connectionHealthCheckTaskHandle = ConcurrencyUtils.getAsyncTaskService().scheduleAtFixedRate(new Runnable() {
@Override
@TaskDescription("Communication Layer: Connection health check (trigger task)")
public void run() {
try {
connectionService.triggerHealthCheckForAllChannels();
} catch (RuntimeException e) {
log.error("Uncaught exception during connection health check", e);
}
}
}, CommunicationConfiguration.CONNECTION_HEALTH_CHECK_INTERVAL_MSEC);
started = true;
}
@Override
@Deprecated
public MessageChannel connectToRuntimePeer(NetworkContactPoint ncp) throws CommunicationException {
Future<MessageChannel> future = connectionService.connect(ncp, true);
try {
return future.get();
} catch (ExecutionException e) {
throw new CommunicationException(e);
} catch (InterruptedException e) {
throw new CommunicationException(e);
}
}
@Override
@Deprecated
public void asyncConnectToNetworkPeer(final NetworkContactPoint ncp) {
ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() {
@Override
@TaskDescription("Communication Layer: Connect to remote node (trigger task)")
public void run() {
try {
log.debug("Initiating asynchronous connection to " + ncp);
connectToRuntimePeer(ncp);
} catch (CommunicationException e) {
log.warn("Failed to contact initial peer at NCP " + ncp, e);
}
}
});
}
@Override
public synchronized void shutDownNetwork() {
if (!started) {
log.debug("Network layer was not started, ignoring request to shut down");
return;
}
started = false;
connectionService.setShutdownFlag(true);
connectionHealthCheckTaskHandle.cancel(true);
// workaround for old tests that assume a network message on shutdown
// TODO rework to proper solution
nodePropertiesService.addOrUpdateLocalNodeProperty("state", "shutting down");
// FIXME dirty hack until the shutdown LSA broadcast waits for a response or timeout itself;
// without this, the asynchronous sending might not happen before the connections are closed
// TODO wait for confirmations from all neighbors (with a short timeout) instead?
try {
Thread.sleep(DELAY_AFTER_SHUTDOWN_ANNOUNCE_MSEC);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting", e);
}
// close outgoing connections
connectionService.closeAllOutgoingChannels();
// shut down server contact points
synchronized (initializedServerContactPoints) {
for (ServerContactPoint scp : initializedServerContactPoints) {
// log.debug(StringUtils.format("Virtual instance '%s': Stopping server at %s",
// ownNodeInformation.getLogName(), ncp));
scp.shutDown();
}
initializedServerContactPoints.clear();
}
}
@Override
public void simulateUncleanShutdown() {
// simulate crash of outgoing channels
for (MessageChannel channel : connectionService.getAllOutgoingChannels()) {
((AbstractMessageChannel) channel).setSimulatingBreakdown(true);
}
connectionService.closeAllOutgoingChannels();
// simulate crash of server contact points
synchronized (initializedServerContactPoints) {
for (ServerContactPoint scp : initializedServerContactPoints) {
scp.setSimulatingBreakdown(true);
}
initializedServerContactPoints.clear();
}
}
/**
* OSGi-DS bind method; public for integration test access.
*
* @param newService the service to bind
*/
public void bindMessageChannelService(MessageChannelService newService) {
// do not allow rebinding for now
if (connectionService != null) {
throw new IllegalStateException();
}
connectionService = newService;
}
/**
* OSGi-DS bind method; public for integration test access.
*
* @param newService the service to bind
*/
public void bindNetworkRoutingService(NetworkRoutingService newService) {
// do not allow rebinding for now
if (networkRoutingService != null) {
throw new IllegalStateException();
}
networkRoutingService = newService;
}
/**
* OSGi-DS bind method; public for integration test access.
*
* @param newService the service to bind
*/
public void bindNodeConfigurationService(NodeConfigurationService newService) {
// do not allow rebinding for now
if (this.nodeConfigurationService != null) {
throw new IllegalStateException();
}
this.nodeConfigurationService = newService;
}
/**
* Define the {@link RemoteServiceCallHandlerService} implementation to use for incoming RPC calls; made public for integration testing.
*
* @param newInstance the {@link RemoteServiceCallHandlerService} to use
*/
public void bindServiceCallHandler(RemoteServiceCallHandlerService newInstance) {
serviceCallHandler = newInstance;
}
/**
* OSGi-DS bind method; public for integration test access.
*
* @param newInstance the new service instance to bind
*/
public void bindNodePropertiesService(NodePropertiesService newInstance) {
this.nodePropertiesService = newInstance;
}
/**
* OSGi-DS bind method; public for integration test access.
*
* @param newInstance the new service instance to bind
*/
public void bindConnectionSetupService(ConnectionSetupService newInstance) {
this.connectionSetupService = newInstance;
}
/**
* OSGi-DS lifecycle method.
*/
public void activate() {
ownNodeInformation = nodeConfigurationService.getInitialNodeInformation();
MessageEndpointHandler messageEndpointHandler = new MessageEndpointHandlerImpl(nodeConfigurationService.getNodeIdentifierService());
messageEndpointHandler.registerRequestHandler(ProtocolConstants.VALUE_MESSAGE_TYPE_RPC, new RPCNetworkRequestHandler(
serviceCallHandler));
messageEndpointHandler.registerRequestHandler(ProtocolConstants.VALUE_MESSAGE_TYPE_HEALTH_CHECK,
new HealthCheckNetworkRequestHandler());
connectionService.setMessageEndpointHandler(messageEndpointHandler);
// register LSA protocol handler
// messageEndpointHandler.registerRequestHandlers(networkRoutingService.getProtocolManager().getNetworkRequestHandlers());
// register metadata protocol handler
messageEndpointHandler.registerRequestHandlers(nodePropertiesService.getNetworkRequestHandlers());
// "autoStartNetworkOnActivation" is true by default; only disabled in test code
if (autoStartNetworkOnActivation && !CommandLineArguments.isDoNotStartNetworkRequested()) {
ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() {
@Override
@TaskDescription("Communication Layer: Main startup")
public void run() {
startUpNetwork();
}
});
} else {
log.debug("Network startup is disabled");
}
}
/**
* OSGi-DS lifecycle method.
*/
public void deactivate() {}
/**
* Allows unit or integration tests to prevent {@link #startUpNetwork()} from being called automatically as part of the
* {@link #activate()} method.
*
* @param autoStartNetworkOnActivation the new value; default is "true"
*/
public void setAutoStartNetworkOnActivation(boolean autoStartNetworkOnActivation) {
this.autoStartNetworkOnActivation = autoStartNetworkOnActivation;
}
private Map<String, String> createLocalMetadataContribution() {
Map<String, String> localData = new HashMap<String, String>();
localData.put(NodePropertyConstants.KEY_NODE_ID, ownNodeInformation.getInstanceNodeSessionIdString());
localData.put(NodePropertyConstants.KEY_DISPLAY_NAME, ownNodeInformation.getDisplayName());
localData.put(NodePropertyConstants.KEY_SESSION_START_TIME, Long.toString(sessionStartTimeMsec));
// TODO @5.0: review: provide options to disable? - misc_ro
localData.put("debug.sessionStartInfo",
DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(sessionStartTimeMsec))); // temporary
Version coreVersion = VersionUtils.getVersionOfCoreBundles();
if (coreVersion != null) {
localData.put("debug.coreVersion", coreVersion.toString());
} else {
localData.put("debug.coreVersion", "<unknown>");
}
localData.put("debug.osInfo", StringUtils.format("%s (%s/%s)",
System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")));
localData.put("debug.isRelay", Boolean.toString(nodeConfigurationService.isRelay()));
if (nodeConfigurationService.getLocationCoordinates() != null) {
localData.put("coordinates",
"[" + nodeConfigurationService.getLocationCoordinates()[0] + "," + nodeConfigurationService.getLocationCoordinates()[1]
+ "]");
}
localData.put("locationName", nodeConfigurationService.getLocationName());
localData.put("contact", nodeConfigurationService.getInstanceContact());
localData.put("additionalInformation", nodeConfigurationService.getInstanceAdditionalInformation());
return localData;
}
}