/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.connection.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.communication.channel.MessageChannelLifecycleListener;
import de.rcenvironment.core.communication.channel.MessageChannelLifecycleListenerAdapter;
import de.rcenvironment.core.communication.channel.MessageChannelService;
import de.rcenvironment.core.communication.connection.api.ConnectionSetup;
import de.rcenvironment.core.communication.connection.api.ConnectionSetupListener;
import de.rcenvironment.core.communication.connection.api.ConnectionSetupListenerAdapter;
import de.rcenvironment.core.communication.connection.api.ConnectionSetupService;
import de.rcenvironment.core.communication.connection.api.ConnectionSetupState;
import de.rcenvironment.core.communication.connection.api.DisconnectReason;
import de.rcenvironment.core.communication.model.NetworkContactPoint;
import de.rcenvironment.core.communication.transport.spi.MessageChannel;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.service.AdditionalServiceDeclaration;
import de.rcenvironment.core.utils.common.service.AdditionalServicesProvider;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallback;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallbackExceptionPolicy;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncOrderedCallbackManager;
/**
* Default {@link ConnectionSetupService} implementation.
*
* @author Robert Mischke
*/
public class ConnectionSetupServiceImpl implements ConnectionSetupService, AdditionalServicesProvider {
private MessageChannelService messageChannelService;
private final List<ConnectionSetup> setups = new ArrayList<ConnectionSetup>();
// sequential id generator
private final AtomicLong lastSetupId = new AtomicLong();
private final AsyncOrderedCallbackManager<ConnectionSetupListener> callbackManager =
ConcurrencyUtils.getFactory().createAsyncOrderedCallbackManager(AsyncCallbackExceptionPolicy.LOG_AND_CANCEL_LISTENER);
private final Log log = LogFactory.getLog(getClass());
@Override
public Collection<AdditionalServiceDeclaration> defineAdditionalServices() {
List<AdditionalServiceDeclaration> result = new ArrayList<AdditionalServiceDeclaration>();
result.add(new AdditionalServiceDeclaration(MessageChannelLifecycleListener.class, new MessageChannelLifecycleListenerAdapter() {
@Override
public void onOutgoingChannelTerminated(MessageChannel connection) {
handleOutgoingChannelTerminated(connection);
}
}));
return result;
}
@Override
public ConnectionSetup createConnectionSetup(NetworkContactPoint ncp, String displayName, boolean connnectOnStartup) {
// create minimal adapter to delegate/forward state changes to all listeners
ConnectionSetupListenerAdapter stateChangeListenerAdapter = new ConnectionSetupListenerAdapter() {
@Override
public void onStateChanged(final ConnectionSetup setup, final ConnectionSetupState oldState,
final ConnectionSetupState newState) {
callbackManager.enqueueCallback(new AsyncCallback<ConnectionSetupListener>() {
@Override
public void performCallback(ConnectionSetupListener listener) {
listener.onStateChanged(setup, oldState, newState);
}
});
}
@Override
public void onConnectionAttemptFailed(final ConnectionSetup setup, final boolean firstConsecutiveFailure,
final boolean willAutoRetry) {
callbackManager.enqueueCallback(new AsyncCallback<ConnectionSetupListener>() {
@Override
public void performCallback(ConnectionSetupListener listener) {
listener.onConnectionAttemptFailed(setup, firstConsecutiveFailure, willAutoRetry);
}
});
}
@Override
public void onConnectionClosed(final ConnectionSetup setup, final DisconnectReason disconnectReason,
final boolean willAutoRetry) {
callbackManager.enqueueCallback(new AsyncCallback<ConnectionSetupListener>() {
@Override
public void performCallback(ConnectionSetupListener listener) {
listener.onConnectionClosed(setup, disconnectReason, willAutoRetry);
}
});
}
};
long id = lastSetupId.incrementAndGet();
final ConnectionSetupImpl newSetup =
new ConnectionSetupImpl(ncp, displayName, id, connnectOnStartup, messageChannelService, stateChangeListenerAdapter);
final List<ConnectionSetup> snapshotOfCollection;
synchronized (setups) {
setups.add(newSetup);
// create a detached snapshot to guard against race conditions
snapshotOfCollection = createDetachedSnapshotOfCollection();
// trigger callback about new ConnectionSetup
callbackManager.enqueueCallback(new AsyncCallback<ConnectionSetupListener>() {
@Override
public void performCallback(ConnectionSetupListener listener) {
listener.onCollectionChanged(snapshotOfCollection);
listener.onCreated(newSetup);
}
});
}
return newSetup;
}
@Override
public void disposeConnectionSetup(final ConnectionSetup setup) {
setup.signalStopIntent();
final List<ConnectionSetup> snapshotOfCollection;
synchronized (setups) {
boolean wasContained = setups.remove(setup);
if (!wasContained) {
log.warn("Connection setup '" + setup.getDisplayName() + "' was not found in the registry when dispose() was called");
}
// create a detached snapshot to guard against race conditions
snapshotOfCollection = createDetachedSnapshotOfCollection();
callbackManager.enqueueCallback(new AsyncCallback<ConnectionSetupListener>() {
@Override
public void performCallback(ConnectionSetupListener listener) {
listener.onDisposed(setup);
listener.onCollectionChanged(snapshotOfCollection);
}
});
}
}
@Override
public Collection<ConnectionSetup> getAllConnectionSetups() {
synchronized (setups) {
return createDetachedSnapshotOfCollection();
}
}
@Override
public ConnectionSetup getConnectionSetupById(long id) {
// small list -> simple search
for (ConnectionSetup setup : setups) {
if (setup.getId() == id) {
return setup;
}
}
return null;
}
/**
* Registers a {@link ConnectionSetupListener}.
*
* @param listener the new listener
*/
public void addConnectionSetupListener(ConnectionSetupListener listener) {
final List<ConnectionSetup> snapshotOfCollection;
synchronized (setups) {
// create a detached snapshot to guard against race conditions
snapshotOfCollection = createDetachedSnapshotOfCollection();
callbackManager.addListenerAndEnqueueCallback(listener, new AsyncCallback<ConnectionSetupListener>() {
@Override
public void performCallback(ConnectionSetupListener listener) {
// perform initial callback
listener.onCollectionChanged(snapshotOfCollection);
}
});
}
}
/**
* Unregisters a {@link ConnectionSetupListener}.
*
* @param listener the listener to remove
*/
public void removeConnectionSetupListener(ConnectionSetupListener listener) {
callbackManager.removeListener(listener);
}
/**
* OSGi-DS bind method; public for integration test access.
*
* @param newInstance the service to bind
*/
public void bindMessageChannelService(MessageChannelService newInstance) {
messageChannelService = newInstance;
}
private void handleOutgoingChannelTerminated(MessageChannel messageChannel) {
ConnectionSetupImpl matchedSetup = null;
String channelId = messageChannel.getChannelId();
synchronized (setups) {
for (ConnectionSetup setup : setups) {
// note: race conditions are still possible if the channel was established and disconnected right away, although unlikely -
// misc_ro
if (channelId.equals(setup.getLastChannelId())) {
matchedSetup = (ConnectionSetupImpl) setup;
break;
}
}
}
if (messageChannel.getInitiatedByRemote()) {
// consistency check: remote-initiated connections should never have a connection setup
if (matchedSetup != null) {
log.error("Internal consistency error: Remote-initiated message channel had an associated connection setup: " + channelId);
}
return;
}
if (matchedSetup != null) {
matchedSetup.onMessageChannelClosed(messageChannel);
} else {
// unexpected state: self-initiated connection without a connection setup
log.warn("Self-initiated message channel closed, but no associated connection setup found: " + channelId);
// if this actually happens, retrying after a moment should fix it
return;
}
}
private List<ConnectionSetup> createDetachedSnapshotOfCollection() {
// note: should only be called while synchronizing on "setups"
return Collections.unmodifiableList(new ArrayList<ConnectionSetup>(setups));
}
}