/*******************************************************************************
* Copyright (c) 2014, 2015 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.core.internal.channelmanager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IPeer;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IStreams;
import org.eclipse.tcf.te.runtime.callback.Callback;
import org.eclipse.tcf.te.runtime.events.EventManager;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
import org.eclipse.tcf.te.runtime.properties.PropertiesContainer;
import org.eclipse.tcf.te.runtime.stepper.interfaces.IStepAttributes;
import org.eclipse.tcf.te.runtime.stepper.interfaces.IStepContext;
import org.eclipse.tcf.te.runtime.stepper.interfaces.IStepperOperationService;
import org.eclipse.tcf.te.runtime.stepper.job.StepperJob;
import org.eclipse.tcf.te.runtime.stepper.utils.StepperHelper;
import org.eclipse.tcf.te.tcf.core.activator.CoreBundleActivator;
import org.eclipse.tcf.te.tcf.core.events.ChannelEvent;
import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
import org.eclipse.tcf.te.tcf.core.interfaces.IStepperServiceOperations;
import org.eclipse.tcf.te.tcf.core.interfaces.steps.ITcfStepAttributes;
import org.eclipse.tcf.te.tcf.core.interfaces.tracing.ITraceIds;
import org.eclipse.tcf.te.tcf.core.internal.channelmanager.steps.ShutdownValueAddStep;
import org.eclipse.tcf.te.tcf.core.nls.Messages;
import org.eclipse.tcf.te.tcf.core.va.ValueAddManager;
import org.eclipse.tcf.te.tcf.core.va.interfaces.IValueAdd;
/**
* Channel manager implementation.
*/
public class ChannelManager extends PlatformObject implements IChannelManager {
// The map of reference counters per channel
/* default */ final Map<IChannel, AtomicInteger> refCounters = new HashMap<IChannel, AtomicInteger>();
// The map of channels per peer id
/* default */ final Map<String, IChannel> channels = new HashMap<String, IChannel>();
// The map of pending open channel callback's per peer id
/* default */ final Map<String, List<DoneOpenChannel>> pendingDones = new HashMap<String, List<DoneOpenChannel>>();
// The list of channels opened via "forceNew" flag (needed to handle the close channel correctly)
/* default */ final List<IChannel> forcedChannels = new ArrayList<IChannel>();
// The map of flags used for opening a forced channel per channel
/* default */ final Map<IChannel, Map<String, Boolean>> forcedChannelFlags = new HashMap<IChannel, Map<String, Boolean>>();
// The map of stream listener proxies per channel
/* default */ final Map<IChannel, List<StreamListenerProxy>> streamProxies = new HashMap<IChannel, List<StreamListenerProxy>>();
// The map of scheduled "open channel" stepper jobs per peer id
/* default */ final Map<String, StepperJob> pendingOpenChannel = new HashMap<String, StepperJob>();
// The map of scheduled "close channel" stepper jobs per channel
/* default */ final Map<IChannel, StepperJob> pendingCloseChannel = new HashMap<IChannel, StepperJob>();
// The map of channel to peer associations
/* default */ final Map<IChannel, IPeer> c2p = new HashMap<IChannel, IPeer>();
/**
* Constructor
*/
public ChannelManager() {
super();
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#openChannel(org.eclipse.tcf.protocol.IPeer, java.util.Map, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel)
*/
@Override
public void openChannel(final IPeer peer, final Map<String, Boolean> flags, final DoneOpenChannel done) {
openChannel(peer, flags, done, null);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#openChannel(org.eclipse.tcf.protocol.IPeer, java.util.Map, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void openChannel(final IPeer peer, final Map<String, Boolean> flags, final DoneOpenChannel done, final IProgressMonitor monitor) {
Assert.isNotNull(peer);
Assert.isNotNull(done);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(1, ITraceIds.TRACE_CHANNEL_MANAGER)) {
try {
throw new Throwable();
} catch (Throwable e) {
CoreBundleActivator.getTraceHandler().trace("ChannelManager#openChannel called from:", //$NON-NLS-1$
1, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
e.printStackTrace();
}
}
// The client done callback must be called within the TCF event dispatch thread
final DoneOpenChannel internalDone = new DoneOpenChannel() {
@Override
public void doneOpenChannel(final Throwable error, final IChannel channel) {
// Invoke the client done callback
Runnable runnable = new Runnable() {
@Override
public void run() {
done.doneOpenChannel(error, channel);
}
};
if (Protocol.isDispatchThread()) runnable.run();
else Protocol.invokeLater(runnable);
}
};
// The channel instance to return
IChannel channel = null;
// Get the peer id
final String id = peer.getID();
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_message, id, flags),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// First thing to determine is if to open a new channel or the shared
// channel can be used, if there is a shared channel at all.
boolean forceNew = flags != null && flags.containsKey(IChannelManager.FLAG_FORCE_NEW) ? flags.get(IChannelManager.FLAG_FORCE_NEW).booleanValue() : false;
final boolean noValueAdd = flags != null && flags.containsKey(IChannelManager.FLAG_NO_VALUE_ADD) ? flags.get(IChannelManager.FLAG_NO_VALUE_ADD).booleanValue() : false;
final boolean noPathMap = flags != null && flags.containsKey(IChannelManager.FLAG_NO_PATH_MAP) ? flags.get(IChannelManager.FLAG_NO_PATH_MAP).booleanValue() : false;
// If noValueAdd == true or noPathMap == true -> forceNew has to be true as well
if (noValueAdd || noPathMap) forceNew = true;
final boolean finForceNew = forceNew;
// Query the shared channel if not forced to open a new channel
if (!forceNew) channel = channels.get(id);
// If a shared channel is available, check if the shared channel can be used
if (channel != null) {
// If the channel is still open, it's all done and the channel can be returned right away
if (channel.getState() == IChannel.STATE_OPEN) {
// Increase the reference count
AtomicInteger counter = refCounters.get(channel);
if (counter == null) {
counter = new AtomicInteger(0);
refCounters.put(channel, counter);
}
counter.incrementAndGet();
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_reuse_message, id, counter.toString()),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Invoke the channel open done callback
internalDone.doneOpenChannel(null, channel);
}
// If the channel is opening, wait for the channel to become fully opened.
// Add the done open channel callback to the list of pending callback's.
else if (channel.getState() == IChannel.STATE_OPENING) {
List<DoneOpenChannel> dones = pendingDones.get(id);
if (dones == null) {
dones = new ArrayList<DoneOpenChannel>();
pendingDones.put(id, dones);
}
Assert.isNotNull(dones);
dones.add(internalDone);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_pending_message, id, "0x" + Integer.toHexString(internalDone.hashCode())), //$NON-NLS-1$
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
}
else {
// Channel is not in open state -> drop the instance
channels.remove(id);
refCounters.remove(channel);
c2p.remove(channel);
channel = null;
}
}
// Channel not available -> open a new one
if (channel == null) {
// Check if there is a pending "open channel" stepper job
StepperJob job = !forceNew ? pendingOpenChannel.get(id) : null;
if (job == null) {
// No pending "open channel" stepper job -> schedule one and initiate opening the channel
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_new_message, id),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Create the data properties container passed to the "open channel" steps
final IPropertiesContainer data = new PropertiesContainer();
// Set the flags to be passed to the "open channel" steps
data.setProperty(IChannelManager.FLAG_FORCE_NEW, forceNew);
data.setProperty(IChannelManager.FLAG_NO_VALUE_ADD, noValueAdd);
data.setProperty(IChannelManager.FLAG_NO_PATH_MAP, noPathMap);
// No recent action history persistence
data.setProperty(IStepAttributes.PROP_SKIP_LAST_RUN_HISTORY, true);
// Create the callback to be invoked once the "open channel" stepper job is completed
final ICallback callback = new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
// Check for error
if (status.getSeverity() == IStatus.ERROR) {
// Extract the failure cause
Throwable error = status.getException();
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_failed_message, id, error),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Job is done -> remove it from the list of pending jobs (shared channels only)
if (!finForceNew) pendingOpenChannel.remove(id);
// Invoke the primary "open channel" done callback
internalDone.doneOpenChannel(error, null);
// Invoke pending callback's
List<DoneOpenChannel> pending = pendingDones.remove(id);
if (pending != null && !pending.isEmpty()) {
for (DoneOpenChannel d : pending) {
d.doneOpenChannel(error, null);
}
}
} else {
// Get the channel
IChannel channel = (IChannel)data.getProperty(ITcfStepAttributes.ATTR_CHANNEL);
Assert.isNotNull(channel);
Assert.isTrue(channel.getState() == IChannel.STATE_OPEN);
// Store the channel
if (!finForceNew) channels.put(id, channel);
if (!finForceNew) refCounters.put(channel, new AtomicInteger(1));
if (finForceNew) forcedChannels.add(channel);
if (finForceNew) forcedChannelFlags.put(channel, flags);
// Remember for which peer the channel got opened.
c2p.put(channel, peer);
// Job is done -> remove it from the list of pending jobs (shared channels only)
if (!finForceNew) pendingOpenChannel.remove(id);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_success_message, id),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Invoke the primary "open channel" done callback
internalDone.doneOpenChannel(null, channel);
// Invoke pending callback's (shared channels only)
if (!finForceNew) {
List<DoneOpenChannel> pending = pendingDones.remove(id);
if (pending != null && !pending.isEmpty()) {
for (DoneOpenChannel d : pending) {
d.doneOpenChannel(null, channel);
}
}
}
}
}
};
// Get the stepper operation service
IStepperOperationService stepperOperationService = StepperHelper.getService(peer, IStepperServiceOperations.OPEN_CHANNEL);
// Schedule the "open channel" stepper job
IStepContext stepContext = stepperOperationService.getStepContext(peer, IStepperServiceOperations.OPEN_CHANNEL);
String stepGroupId = stepperOperationService.getStepGroupId(peer, IStepperServiceOperations.OPEN_CHANNEL);
if (stepGroupId != null && stepContext != null) {
String name = stepperOperationService.getStepGroupName(peer, IStepperServiceOperations.OPEN_CHANNEL);
boolean isCancelable = stepperOperationService.isCancelable(peer, IStepperServiceOperations.OPEN_CHANNEL);
job = new StepperJob(name != null ? name : "", stepContext, stepperOperationService.getStepGroupData(peer, IStepperServiceOperations.OPEN_CHANNEL, data), stepGroupId, IStepperServiceOperations.OPEN_CHANNEL, isCancelable, true); //$NON-NLS-1$
job.setJobCallback(callback);
job.markStatusHandled();
if (monitor != null) {
final StepperJob finalJob = job;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
finalJob.run(monitor);
}
}, job.getName());
thread.start();
}
else {
job.schedule();
}
}
// Remember the "open channel" stepper job until finished (shared channels only)
if (job != null && !forceNew) {
pendingOpenChannel.put(id, job);
}
} else {
// There is a pending "open channel" stepper job -> add the open channel callback to the list
List<DoneOpenChannel> dones = pendingDones.get(id);
if (dones == null) {
dones = new ArrayList<DoneOpenChannel>();
pendingDones.put(id, dones);
}
Assert.isNotNull(dones);
dones.add(internalDone);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_openChannel_pending_message, id, "0x" + Integer.toHexString(internalDone.hashCode())), //$NON-NLS-1$
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
}
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#getChannel(org.eclipse.tcf.protocol.IPeer)
*/
@Override
public IChannel getChannel(final IPeer peer) {
final AtomicReference<IChannel> channel = new AtomicReference<IChannel>();
Runnable runnable = new Runnable() {
@Override
public void run() {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
channel.set(internalGetChannel(peer));
}
};
if (Protocol.isDispatchThread()) runnable.run();
else Protocol.invokeAndWait(runnable);
return channel.get();
}
/**
* Returns the shared channel instance for the given peer.
* <p>
* <b>Note:</b> This method must be invoked at the TCF dispatch thread.
*
* @param peer The peer. Must not be <code>null</code>.
* @return The channel instance or <code>null</code>.
*/
public IChannel internalGetChannel(IPeer peer) {
Assert.isNotNull(peer);
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
// Get the peer id
String id = peer.getID();
// Get the channel
IChannel channel = channels.get(id);
if (channel != null && !(channel.getState() == IChannel.STATE_OPEN || channel.getState() == IChannel.STATE_OPENING)) {
// Channel is not in open state -> drop the instance
channels.remove(id);
refCounters.remove(channel);
c2p.remove(channel);
channel = null;
}
return channel;
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#closeChannel(org.eclipse.tcf.protocol.IChannel)
*/
@Override
public void closeChannel(final IChannel channel) {
closeChannel(channel, null);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#closeChannel(org.eclipse.tcf.protocol.IChannel)
*/
@Override
public void closeChannel(final IChannel channel, final IProgressMonitor monitor) {
Runnable runnable = new Runnable() {
@Override
public void run() {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
internalCloseChannel(channel, monitor);
}
};
if (Protocol.isDispatchThread()) runnable.run();
else Protocol.invokeLater(runnable);
}
/**
* Closes the given channel.
* <p>
* If the given channel is a reference counted channel, the channel will be closed if the reference counter
* reaches 0. For non reference counted channels, the channel is closed immediately.
* <p>
* <b>Note:</b> This method must be invoked at the TCF dispatch thread.
*
* @param channel The channel. Must not be <code>null</code>.
*/
/* default */ void internalCloseChannel(final IChannel channel, final IProgressMonitor monitor) {
Assert.isNotNull(channel);
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
// Determine the peer for the channel to close
IPeer p = c2p.get(channel);
final IPeer peer = p != null ? p : channel.getRemotePeer();
// Get the id of the remote peer
final String id = peer.getID();
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_closeChannel_message, id),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Determine if the given channel is a reference counted channel
final boolean isRefCounted = !forcedChannels.contains(channel);
// Get the reference counter (if the channel is a reference counted channel)
AtomicInteger counter = isRefCounted ? refCounters.get(channel) : null;
// If the counter is null or get 0 after the decrement, close the channel
if (counter == null || counter.decrementAndGet() == 0) {
// Check if there is a pending "close channel" stepper job
StepperJob job = pendingCloseChannel.get(channel);
if (job == null) {
// No pending "close channel" stepper job -> schedule one and initiate closing the channel
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_closeChannel_close_message, id),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Create the data properties container passed to the "close channel" steps
final IPropertiesContainer data = new PropertiesContainer();
// Set the channel to close
data.setProperty(ITcfStepAttributes.ATTR_CHANNEL, channel);
// No recent action history persistence
data.setProperty(IStepAttributes.PROP_SKIP_LAST_RUN_HISTORY, true);
// Determine if the value-add's can be shutdown or must stay alive.
// In case the channel to close is not reference counted, but this is a reference
// counted channel to the same peer, and that channel is still open, the
// value-adds must stay alive.
if (!isRefCounted) {
IChannel shared = channels.get(id);
if (shared != null && (shared.getState() == IChannel.STATE_OPEN || shared.getState() == IChannel.STATE_OPENING)) {
data.setProperty(ShutdownValueAddStep.PROP_SKIP_SHUTDOWN_STEP, true);
}
} else {
// The channel is reference counted, that means it is a shared channel
// and normally it will shutdown the value-adds if closed. However, we
// can have not reference counted channels to the same target that is
// using value-add's. In this case we also have to skip shutting down
// the value-add.
for (IChannel c : forcedChannels) {
if (id.equals(c.getRemotePeer().getID())) {
Map<String, Boolean> flags = forcedChannelFlags.get(c);
boolean noValueAdd = flags != null && flags.containsKey(IChannelManager.FLAG_NO_VALUE_ADD) ? flags.get(IChannelManager.FLAG_NO_VALUE_ADD).booleanValue() : false;
if (!noValueAdd) {
data.setProperty(ShutdownValueAddStep.PROP_SKIP_SHUTDOWN_STEP, true);
break;
}
}
}
}
// Create the callback to be invoked once the "close channel" stepper job is completed
final ICallback callback = new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
// Check for error
if (status.getSeverity() == IStatus.ERROR) {
// Extract the failure cause
Throwable error = status.getException();
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_closeChannel_failed_message, id, error),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
// Job is done -> remove it from the list of pending jobs
pendingCloseChannel.remove(channel);
} else {
// Job is done -> remove it from the list of pending jobs
pendingCloseChannel.remove(channel);
// Clean the reference counter and the channel map
if (isRefCounted) channels.remove(id);
if (isRefCounted) refCounters.remove(channel);
if (!isRefCounted) forcedChannels.remove(channel);
if (!isRefCounted) forcedChannelFlags.remove(channel);
// Remove the channel / peer association
c2p.remove(channel);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_closeChannel_closed_message, id),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
}
// Log closed channels
ChannelEvent event = new ChannelEvent(ChannelManager.this, channel, ChannelEvent.TYPE_CLOSE, null);
EventManager.getInstance().fireEvent(event);
// If there is no remaining shared or private channel to the target,
// also close the log writer which is shared by all channels to the same target.
Runnable runnable = new Runnable() {
@Override
public void run() {
boolean closeWriter = internalGetChannel(peer) == null;
if (closeWriter) {
for (IChannel c : forcedChannels) {
if (id.equals(c.getRemotePeer().getID()) && c.getState() != IChannel.STATE_CLOSED) {
closeWriter = false;
break;
}
}
if (closeWriter) {
ChannelEvent event = new ChannelEvent(ChannelManager.this, channel, ChannelEvent.TYPE_CLOSE_WRITER, null);
EventManager.getInstance().fireEvent(event);
}
}
}
};
if (Protocol.isDispatchThread()) runnable.run();
else Protocol.invokeLater(runnable);
}
};
// Get the stepper operation service
IStepperOperationService stepperOperationService = StepperHelper.getService(peer, IStepperServiceOperations.CLOSE_CHANNEL);
// Schedule the "close channel" stepper job
IStepContext stepContext = stepperOperationService.getStepContext(peer, IStepperServiceOperations.CLOSE_CHANNEL);
String stepGroupId = stepperOperationService.getStepGroupId(peer, IStepperServiceOperations.CLOSE_CHANNEL);
if (stepGroupId != null && stepContext != null) {
String name = stepperOperationService.getStepGroupName(peer, IStepperServiceOperations.CLOSE_CHANNEL);
boolean isCancelable = stepperOperationService.isCancelable(peer, IStepperServiceOperations.CLOSE_CHANNEL);
job = new StepperJob(name != null ? name : "", stepContext, stepperOperationService.getStepGroupData(peer, IStepperServiceOperations.CLOSE_CHANNEL, data), stepGroupId, IStepperServiceOperations.CLOSE_CHANNEL, isCancelable, true); //$NON-NLS-1$
job.setJobCallback(callback);
job.markStatusHandled();
if (monitor != null) {
final StepperJob finalJob = job;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
finalJob.run(monitor);
}
}, "Close channel to " + job.getName()); //$NON-NLS-1$
thread.start();
}
else {
job.schedule();
}
}
// Remember the "close channel" stepper job until finished
if (job != null) {
pendingCloseChannel.put(channel, job);
}
} else {
// There is a pending "close channel" stepper job
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_closeChannel_pending_message, id),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
}
} else {
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ChannelManager_closeChannel_inuse_message, id, counter.toString()),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, ChannelManager.this);
}
}
// Clean up the list of forced channels. Remove all channels already been closed.
ListIterator<IChannel> iter = forcedChannels.listIterator();
while (iter.hasNext()) {
IChannel c = iter.next();
if (c.getState() == IChannel.STATE_CLOSED) {
iter.remove();
forcedChannelFlags.remove(c);
c2p.remove(c);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#shutdown(org.eclipse.tcf.protocol.IPeer, boolean)
*/
@Override
public void shutdown(final IPeer peer, boolean wait) {
Runnable runnable = new Runnable() {
@Override
public void run() {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
internalShutdown(peer);
}
};
if (Protocol.isDispatchThread()) runnable.run();
else if (wait) Protocol.invokeAndWait(runnable);
else Protocol.invokeLater(runnable);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#shutdown(org.eclipse.tcf.protocol.IPeer)
*/
@Override
public void shutdown(final IPeer peer) {
shutdown(peer, false);
}
/**
* Shutdown the communication to the given peer, no matter of the current
* reference count. A possible associated value-add is shutdown as well.
*
* @param peer The peer. Must not be <code>null</code>.
*/
/* default */ void internalShutdown(IPeer peer) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(peer);
// Get the peer id
String id = peer.getID();
// First, close all channels that are not reference counted
ListIterator<IChannel> iter = forcedChannels.listIterator();
while (iter.hasNext()) {
IChannel c = iter.next();
if (id.equals(c.getRemotePeer().getID())) {
c.close();
iter.remove();
forcedChannelFlags.remove(c);
}
}
// Get the channel
IChannel channel = internalGetChannel(peer);
if (channel != null) {
// Reset the reference count (will force a channel close)
refCounters.remove(channel);
channels.remove(channel.getRemotePeer().getID());
c2p.remove(channel);
// Close the channel
channel.close();
// Log closed channels
ChannelEvent event = new ChannelEvent(ChannelManager.this, channel, ChannelEvent.TYPE_CLOSE, null);
EventManager.getInstance().fireEvent(event);
event = new ChannelEvent(ChannelManager.this, channel, ChannelEvent.TYPE_CLOSE_WRITER, null);
EventManager.getInstance().fireEvent(event);
}
try {
IValueAdd[] valueAdds = ValueAddManager.getInstance().getValueAdd(peer);
if (valueAdds != null) {
for (IValueAdd valueAdd : valueAdds) {
valueAdd.shutdown(peer.getID(), new Callback());
}
}
}
catch (Exception e) {
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#closeAll(boolean)
*/
@Override
public void closeAll(boolean wait) {
if (wait) Assert.isTrue(!Protocol.isDispatchThread());
Runnable runnable = new Runnable() {
@Override
public void run() {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
internalCloseAll();
}
};
if (Protocol.isDispatchThread()) runnable.run();
else if (wait) Protocol.invokeAndWait(runnable);
else Protocol.invokeLater(runnable);
}
/**
* Close all open channel, no matter of the current reference count.
* <p>
* <b>Note:</b> This method must be invoked at the TCF dispatch thread.
*/
/* default */ void internalCloseAll() {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
IChannel[] openChannels = channels.values().toArray(new IChannel[channels.values().size()]);
refCounters.clear();
channels.clear();
for (IChannel channel : openChannels) internalCloseChannel(channel, null);
c2p.clear();
}
// ----- Streams handling -----
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#subscribeStream(org.eclipse.tcf.protocol.IChannel, java.lang.String, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.IStreamsListener, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneSubscribeStream)
*/
@Override
public void subscribeStream(final IChannel channel, final String streamType, final IStreamsListener listener, final DoneSubscribeStream done) {
Assert.isNotNull(channel);
Assert.isNotNull(streamType);
Assert.isNotNull(listener);
Assert.isNotNull(done);
if (channel.getState() != IChannel.STATE_OPEN) {
done.doneSubscribeStream(new Exception(Messages.ChannelManager_stream_closed_message));
return;
}
StreamListenerProxy proxy = null;
// Get all the streams listener proxy instance for the given channel
List<StreamListenerProxy> proxies = streamProxies.get(channel);
// Loop the proxies and find the one for the given stream type
if (proxies != null) {
for (StreamListenerProxy candidate : proxies) {
if (streamType.equals(candidate.getStreamType())) {
proxy = candidate;
break;
}
}
}
// If the proxy already exist, add the listener to the proxy and return immediately
if (proxy != null) {
proxy.addListener(listener);
done.doneSubscribeStream(null);
} else {
// No proxy yet -> subscribe to the stream type for real and register the proxy
proxy = new StreamListenerProxy(channel, streamType);
if (proxies == null) {
proxies = new ArrayList<StreamListenerProxy>();
streamProxies.put(channel, proxies);
}
proxies.add(proxy);
proxy.addListener(listener);
IStreams service = channel.getRemoteService(IStreams.class);
if (service != null) {
final StreamListenerProxy finProxy = proxy;
final List<StreamListenerProxy> finProxies = proxies;
// Subscribe to the stream type
service.subscribe(streamType, proxy, new IStreams.DoneSubscribe() {
@Override
public void doneSubscribe(IToken token, Exception error) {
if (error != null) {
finProxy.removeListener(listener);
if (finProxy.isEmpty()) finProxies.remove(finProxy);
if (finProxies.isEmpty()) streamProxies.remove(channel);
}
done.doneSubscribeStream(error);
}
});
} else {
proxy.removeListener(listener);
if (proxy.isEmpty()) proxies.remove(proxy);
if (proxies.isEmpty()) streamProxies.remove(channel);
done.doneSubscribeStream(new Exception(Messages.ChannelManager_stream_missing_service_message));
}
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager#unsubscribeStream(org.eclipse.tcf.protocol.IChannel, java.lang.String, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.IStreamsListener, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneUnsubscribeStream)
*/
@Override
public void unsubscribeStream(final IChannel channel, final String streamType, final IStreamsListener listener, final DoneUnsubscribeStream done) {
Assert.isNotNull(channel);
Assert.isNotNull(streamType);
Assert.isNotNull(listener);
Assert.isNotNull(done);
if (channel.getState() != IChannel.STATE_OPEN) {
done.doneUnsubscribeStream(new Exception(Messages.ChannelManager_stream_closed_message));
return;
}
StreamListenerProxy proxy = null;
// Get all the streams listener proxy instance for the given channel
List<StreamListenerProxy> proxies = streamProxies.get(channel);
// Loop the proxies and find the one for the given stream type
if (proxies != null) {
for (StreamListenerProxy candidate : proxies) {
if (streamType.equals(candidate.getStreamType())) {
proxy = candidate;
break;
}
}
}
if (proxy != null) {
// Remove the listener from the proxy
proxy.removeListener(listener);
// Are there remaining proxied listeners for this stream type?
if (proxy.isEmpty()) {
// Remove from proxy list
proxies.remove(proxy);
if (proxies.isEmpty()) streamProxies.remove(channel);
// Unregister the stream type
IStreams service = channel.getRemoteService(IStreams.class);
if (service != null) {
// Unsubscribe
service.unsubscribe(streamType, proxy, new IStreams.DoneUnsubscribe() {
@Override
public void doneUnsubscribe(IToken token, Exception error) {
done.doneUnsubscribeStream(error);
}
});
} else {
done.doneUnsubscribeStream(new Exception(Messages.ChannelManager_stream_missing_service_message));
}
} else {
done.doneUnsubscribeStream(null);
}
} else {
done.doneUnsubscribeStream(null);
}
}
}